diff --git a/autotests/libs/monitornotificationtest.cpp b/autotests/libs/monitornotificationtest.cpp index 0e5ed423a..5f8871824 100644 --- a/autotests/libs/monitornotificationtest.cpp +++ b/autotests/libs/monitornotificationtest.cpp @@ -1,319 +1,319 @@ /* Copyright (c) 2011 Stephen Kelly 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 "monitor.h" #include "fakeserverdata.h" #include "fakesession.h" #include "inspectablemonitor.h" #include "inspectablechangerecorder.h" #include "akonaditestfake_export.h" #include #include using namespace Akonadi; class MonitorNotificationTest : public QObject { Q_OBJECT public: MonitorNotificationTest(QObject *parent = nullptr) : QObject(parent) { m_sessionName = "MonitorNotificationTest fake session"; m_fakeSession = new FakeSession(m_sessionName, FakeSession::EndJobsImmediately); m_fakeSession->setAsDefaultSession(); } ~MonitorNotificationTest() { delete m_fakeSession; } private Q_SLOTS: void testSingleMessage(); void testFillPipeline(); void testMonitor(); void testSingleMessage_data(); void testFillPipeline_data(); void testMonitor_data(); private: template void testSingleMessage_impl(MonitorImpl *monitor, FakeCollectionCache *collectionCache, FakeItemCache *itemCache); template void testFillPipeline_impl(MonitorImpl *monitor, FakeCollectionCache *collectionCache, FakeItemCache *itemCache); template void testMonitor_impl(MonitorImpl *monitor, FakeCollectionCache *collectionCache, FakeItemCache *itemCache); private: FakeSession *m_fakeSession = nullptr; QByteArray m_sessionName; }; void MonitorNotificationTest::testSingleMessage_data() { QTest::addColumn("useChangeRecorder"); QTest::newRow("useChangeRecorder") << true; QTest::newRow("useMonitor") << false; } void MonitorNotificationTest::testSingleMessage() { QFETCH(bool, useChangeRecorder); FakeCollectionCache *collectionCache = new FakeCollectionCache(m_fakeSession); FakeItemCache *itemCache = new FakeItemCache(m_fakeSession); FakeMonitorDependeciesFactory *depsFactory = new FakeMonitorDependeciesFactory(itemCache, collectionCache); if (!useChangeRecorder) { testSingleMessage_impl(new InspectableMonitor(depsFactory, this), collectionCache, itemCache); } else { InspectableChangeRecorder *changeRecorder = new InspectableChangeRecorder(depsFactory, this); changeRecorder->setChangeRecordingEnabled(false); testSingleMessage_impl(changeRecorder, collectionCache, itemCache); } } template void MonitorNotificationTest::testSingleMessage_impl(MonitorImpl *monitor, FakeCollectionCache *collectionCache, FakeItemCache *itemCache) { Q_UNUSED(itemCache) // Workaround for the QTimer::singleShot() in fake monitors to happen QTest::qWait(10); monitor->setSession(m_fakeSession); monitor->fetchCollection(true); Protocol::ChangeNotificationList list; Collection parent(1); Collection added(2); auto msg = Protocol::CollectionChangeNotificationPtr::create(); msg->setParentCollection(parent.id()); msg->setOperation(Protocol::CollectionChangeNotification::Add); - msg->setCollection(Protocol::FetchCollectionsResponsePtr::create(added.id())); + msg->setCollection(Protocol::FetchCollectionsResponse(added.id())); // With notification payloads most requests by-pass the pipeline as the // notification already contains everything. To force pipelineing we set // the internal metadata (normally set by ChangeRecorder) msg->addMetadata("FETCH_COLLECTION"); QHash data; data.insert(parent.id(), parent); data.insert(added.id(), added); // Pending notifications remains empty because we don't fill the pipeline with one message. QVERIFY(monitor->pipeline().isEmpty()); QVERIFY(monitor->pendingNotifications().isEmpty()); monitor->notificationConnection()->emitNotify(msg); QTRY_COMPARE(monitor->pipeline().size(), 1); QVERIFY(monitor->pendingNotifications().isEmpty()); collectionCache->setData(data); collectionCache->emitDataAvailable(); QVERIFY(monitor->pipeline().isEmpty()); QVERIFY(monitor->pendingNotifications().isEmpty()); } void MonitorNotificationTest::testFillPipeline_data() { QTest::addColumn("useChangeRecorder"); QTest::newRow("useChangeRecorder") << true; QTest::newRow("useMonitor") << false; } void MonitorNotificationTest::testFillPipeline() { QFETCH(bool, useChangeRecorder); FakeCollectionCache *collectionCache = new FakeCollectionCache(m_fakeSession); FakeItemCache *itemCache = new FakeItemCache(m_fakeSession); FakeMonitorDependeciesFactory *depsFactory = new FakeMonitorDependeciesFactory(itemCache, collectionCache); if (!useChangeRecorder) { testFillPipeline_impl(new InspectableMonitor(depsFactory, this), collectionCache, itemCache); } else { InspectableChangeRecorder *changeRecorder = new InspectableChangeRecorder(depsFactory, this); changeRecorder->setChangeRecordingEnabled(false); testFillPipeline_impl(changeRecorder, collectionCache, itemCache); } } template void MonitorNotificationTest::testFillPipeline_impl(MonitorImpl *monitor, FakeCollectionCache *collectionCache, FakeItemCache *itemCache) { Q_UNUSED(itemCache) monitor->setSession(m_fakeSession); monitor->fetchCollection(true); Protocol::ChangeNotificationList list; QHash data; int i = 1; while (i < 40) { Collection parent(i++); Collection added(i++); auto msg = Protocol::CollectionChangeNotificationPtr::create(); msg->setParentCollection(parent.id()); msg->setOperation(Protocol::CollectionChangeNotification::Add); - msg->setCollection(Protocol::FetchCollectionsResponsePtr::create(added.id())); + msg->setCollection(Protocol::FetchCollectionsResponse(added.id())); msg->addMetadata("FETCH_COLLECTION"); data.insert(parent.id(), parent); data.insert(added.id(), added); list << msg; } QVERIFY(monitor->pipeline().isEmpty()); QVERIFY(monitor->pendingNotifications().isEmpty()); Q_FOREACH (const Protocol::ChangeNotificationPtr &ntf, list) { monitor->notificationConnection()->emitNotify(ntf); } QTRY_COMPARE(monitor->pipeline().size(), 5); QCOMPARE(monitor->pendingNotifications().size(), 15); collectionCache->setData(data); collectionCache->emitDataAvailable(); QVERIFY(monitor->pipeline().isEmpty()); QVERIFY(monitor->pendingNotifications().isEmpty()); } void MonitorNotificationTest::testMonitor_data() { QTest::addColumn("useChangeRecorder"); QTest::newRow("useChangeRecorder") << true; QTest::newRow("useMonitor") << false; } void MonitorNotificationTest::testMonitor() { QFETCH(bool, useChangeRecorder); FakeCollectionCache *collectionCache = new FakeCollectionCache(m_fakeSession); FakeItemCache *itemCache = new FakeItemCache(m_fakeSession); FakeMonitorDependeciesFactory *depsFactory = new FakeMonitorDependeciesFactory(itemCache, collectionCache); if (!useChangeRecorder) { testMonitor_impl(new InspectableMonitor(depsFactory, this), collectionCache, itemCache); } else { InspectableChangeRecorder *changeRecorder = new InspectableChangeRecorder(depsFactory, this); changeRecorder->setChangeRecordingEnabled(false); testMonitor_impl(changeRecorder, collectionCache, itemCache); } } template void MonitorNotificationTest::testMonitor_impl(MonitorImpl *monitor, FakeCollectionCache *collectionCache, FakeItemCache *itemCache) { Q_UNUSED(itemCache) monitor->setSession(m_fakeSession); monitor->fetchCollection(true); Protocol::ChangeNotificationList list; Collection col2(2); col2.setParentCollection(Collection::root()); collectionCache->insert(col2); int i = 4; while (i < 8) { Collection added(i++); auto msg = Protocol::CollectionChangeNotificationPtr::create(); msg->setParentCollection(i % 2 ? 2 : added.id() - 1); msg->setOperation(Protocol::CollectionChangeNotification::Add); - msg->setCollection(Protocol::FetchCollectionsResponsePtr::create(added.id())); + msg->setCollection(Protocol::FetchCollectionsResponse(added.id())); msg->addMetadata("FETCH_COLLECTION"); list << msg; } QVERIFY(monitor->pipeline().isEmpty()); QVERIFY(monitor->pendingNotifications().isEmpty()); Collection col4(4); col4.setParentCollection(col2); Collection col6(6); col6.setParentCollection(col2); collectionCache->insert(col4); collectionCache->insert(col6); qRegisterMetaType(); QSignalSpy collectionAddedSpy(monitor, SIGNAL(collectionAdded(Akonadi::Collection,Akonadi::Collection))); collectionCache->emitDataAvailable(); QTRY_VERIFY(monitor->pipeline().isEmpty()); QVERIFY(monitor->pendingNotifications().isEmpty()); Q_FOREACH (const Protocol::ChangeNotificationPtr &ntf, list) { monitor->notificationConnection()->emitNotify(ntf); } // Collection 6 is not notified, because Collection 5 has held up the pipeline QTRY_COMPARE(collectionAddedSpy.size(), 1); QCOMPARE((int)collectionAddedSpy.takeFirst().first().value().id(), 4); QCOMPARE(monitor->pipeline().size(), 3); QCOMPARE(monitor->pendingNotifications().size(), 0); Collection col7(7); col7.setParentCollection(col6); collectionCache->insert(col7); collectionCache->emitDataAvailable(); // Collection 5 is still holding the pipeline QCOMPARE(collectionAddedSpy.size(), 0); QCOMPARE(monitor->pipeline().size(), 3); QCOMPARE(monitor->pendingNotifications().size(), 0); Collection col5(5); col5.setParentCollection(col4); collectionCache->insert(col5); collectionCache->emitDataAvailable(); // Collection 5 is in cache, pipeline is flushed QCOMPARE(collectionAddedSpy.size(), 3); QCOMPARE(monitor->pipeline().size(), 0); QCOMPARE(monitor->pendingNotifications().size(), 0); } QTEST_MAIN(MonitorNotificationTest) #include "monitornotificationtest.moc" diff --git a/autotests/private/notificationmessagetest.cpp b/autotests/private/notificationmessagetest.cpp index bcfd3ab24..c622a459a 100644 --- a/autotests/private/notificationmessagetest.cpp +++ b/autotests/private/notificationmessagetest.cpp @@ -1,110 +1,110 @@ /* Copyright (c) 2007 Volker Krause 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 "notificationmessagetest.h" #include #include #include QTEST_APPLESS_MAIN(NotificationMessageTest) using namespace Akonadi; using namespace Akonadi::Protocol; void NotificationMessageTest::testCompress() { ChangeNotificationList list; - auto collection = FetchCollectionsResponsePtr::create(1); + FetchCollectionsResponse collection(1); CollectionChangeNotification msg; - msg.setCollection(collection); + msg.setCollection(std::move(collection)); msg.setOperation(CollectionChangeNotification::Add); QVERIFY(CollectionChangeNotification::appendAndCompress( list, CollectionChangeNotificationPtr::create(msg))); QCOMPARE(list.count(), 1); msg.setOperation(CollectionChangeNotification::Modify); QVERIFY(!CollectionChangeNotification::appendAndCompress( list, CollectionChangeNotificationPtr::create(msg))); QCOMPARE(list.count(), 1); QCOMPARE(list.first().staticCast()->operation(), CollectionChangeNotification::Add); msg.setOperation(CollectionChangeNotification::Remove); QVERIFY(CollectionChangeNotification::appendAndCompress( list, CollectionChangeNotificationPtr::create(msg))); QCOMPARE(list.count(), 2); } void NotificationMessageTest::testCompress2() { ChangeNotificationList list; - auto collection = FetchCollectionsResponsePtr::create(1); + FetchCollectionsResponse collection(1); CollectionChangeNotification msg; - msg.setCollection(collection); + msg.setCollection(std::move(collection)); msg.setOperation(CollectionChangeNotification::Modify); QVERIFY(CollectionChangeNotification::appendAndCompress( list, CollectionChangeNotificationPtr::create(msg))); QCOMPARE(list.count(), 1); msg.setOperation(CollectionChangeNotification::Remove); QVERIFY(CollectionChangeNotification::appendAndCompress( list, CollectionChangeNotificationPtr::create(msg))); QCOMPARE(list.count(), 2); QCOMPARE(list.first().staticCast()->operation(), CollectionChangeNotification::Modify); QCOMPARE(list.last().staticCast()->operation(), CollectionChangeNotification::Remove); } void NotificationMessageTest::testCompress3() { ChangeNotificationList list; - auto collection = FetchCollectionsResponsePtr::create(1); + FetchCollectionsResponse collection(1); CollectionChangeNotification msg; - msg.setCollection(collection); + msg.setCollection(std::move(collection)); msg.setOperation(CollectionChangeNotification::Modify); QVERIFY(CollectionChangeNotification::appendAndCompress( list, CollectionChangeNotificationPtr::create(msg))); QCOMPARE(list.count(), 1); QVERIFY(!CollectionChangeNotification::appendAndCompress( list, CollectionChangeNotificationPtr::create(msg))); QCOMPARE(list.count(), 1); } void NotificationMessageTest::testPartModificationMerge() { ChangeNotificationList list; - auto collection = FetchCollectionsResponsePtr::create(1); + FetchCollectionsResponse collection(1); CollectionChangeNotification msg; - msg.setCollection(collection); + msg.setCollection(std::move(collection)); msg.setOperation(CollectionChangeNotification::Modify); msg.setChangedParts(QSet() << "PART1"); QVERIFY(CollectionChangeNotification::appendAndCompress( list, CollectionChangeNotificationPtr::create(msg))); QCOMPARE(list.count(), 1); msg.setChangedParts(QSet() << "PART2"); QVERIFY(!CollectionChangeNotification::appendAndCompress( list, CollectionChangeNotificationPtr::create(msg))); QCOMPARE(list.count(), 1); QCOMPARE(list.first().staticCast()->changedParts(), (QSet() << "PART1" << "PART2")); } diff --git a/autotests/server/akappendhandlertest.cpp b/autotests/server/akappendhandlertest.cpp index 0adf639df..30f0ddd50 100644 --- a/autotests/server/akappendhandlertest.cpp +++ b/autotests/server/akappendhandlertest.cpp @@ -1,907 +1,907 @@ /* Copyright (c) 2014 Daniel Vrátil 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 #include #include #include #include #include #include "fakeakonadiserver.h" #include "fakeentities.h" #include #include using namespace Akonadi; using namespace Akonadi::Server; Q_DECLARE_METATYPE(PimItem) Q_DECLARE_METATYPE(QVector) Q_DECLARE_METATYPE(QVector) Q_DECLARE_METATYPE(QVector) class AkAppendHandlerTest : public QObject { Q_OBJECT public: AkAppendHandlerTest() { // Effectively disable external payload parts, we have a dedicated unit-test // for that const QString serverConfigFile = StandardDirs::serverConfigFile(StandardDirs::ReadWrite); QSettings settings(serverConfigFile, QSettings::IniFormat); settings.setValue(QStringLiteral("General/SizeThreshold"), std::numeric_limits::max()); try { FakeAkonadiServer::instance()->init(); } catch (const FakeAkonadiServerException &e) { qWarning() << e.what(); qFatal("Fake Akonadi Server failed to start up, aborting test"); } } ~AkAppendHandlerTest() { FakeAkonadiServer::instance()->quit(); } void updatePimItem(PimItem &pimItem, const QString &remoteId, const qint64 size) { pimItem.setRemoteId(remoteId); pimItem.setGid(remoteId); pimItem.setSize(size); } void updateNotifcationEntity(Protocol::ItemChangeNotificationPtr &ntf, const PimItem &pimItem) { - auto item = Protocol::FetchItemsResponsePtr::create(); - item->setId(pimItem.id()); - item->setRemoteId(pimItem.remoteId()); - item->setRemoteRevision(pimItem.remoteRevision()); - item->setMimeType(pimItem.mimeType().name()); - ntf->setItems({ item }); + Protocol::FetchItemsResponse item; + item.setId(pimItem.id()); + item.setRemoteId(pimItem.remoteId()); + item.setRemoteRevision(pimItem.remoteRevision()); + item.setMimeType(pimItem.mimeType().name()); + ntf->setItems({std::move(item)}); } struct PartHelper { PartHelper(const QString &type_, const QByteArray &data_, int size_, Part::Storage storage_ = Part::Internal, int version_ = 0) : type(type_) , data(data_) , size(size_) , storage(storage_) , version(version_) { } QString type; QByteArray data; int size; Part::Storage storage; int version; }; void updateParts(QVector &parts, const std::vector &updatedParts) { parts.clear(); Q_FOREACH (const PartHelper &helper, updatedParts) { FakePart part; const QStringList types = helper.type.split(QLatin1Char(':')); Q_ASSERT(types.count() == 2); part.setPartType(PartType(types[1], types[0])); part.setData(helper.data); part.setDatasize(helper.size); part.setStorage(helper.storage); part.setVersion(helper.version); parts << part; } } void updateFlags(QVector &flags, const QStringList &updatedFlags) { flags.clear(); Q_FOREACH (const QString &flagName, updatedFlags) { Flag flag; flag.setName(flagName); flags << flag; } } struct TagHelper { TagHelper(const QString &tagType_, const QString &gid_, const QString &remoteId_ = QString()) : tagType(tagType_) , gid(gid_) , remoteId(remoteId_) { } QString tagType; QString gid; QString remoteId; }; void updateTags(QVector &tags, const std::vector &updatedTags) { tags.clear(); Q_FOREACH (const TagHelper &helper, updatedTags) { FakeTag tag; TagType tagType; tagType.setName(helper.tagType); tag.setTagType(tagType); tag.setGid(helper.gid); tag.setRemoteId(helper.remoteId); tags << tag; } } Protocol::CreateItemCommandPtr createCommand(const PimItem &pimItem, const QDateTime &dt, const QSet &parts, qint64 overrideSize = -1) { const qint64 size = overrideSize > -1 ? overrideSize : pimItem.size(); auto cmd = Protocol::CreateItemCommandPtr::create(); cmd->setCollection(Scope(pimItem.collectionId())); cmd->setItemSize(size); cmd->setRemoteId(pimItem.remoteId()); cmd->setRemoteRevision(pimItem.remoteRevision()); cmd->setMimeType(pimItem.mimeType().name()); cmd->setGid(pimItem.gid()); cmd->setDateTime(dt); cmd->setParts(parts); return cmd; } Protocol::FetchItemsResponsePtr createResponse(qint64 expectedId, const PimItem &pimItem, const QDateTime &datetime, const QVector &parts, qint64 overrideSize = -1) { const qint64 size = overrideSize > -1 ? overrideSize : pimItem.size(); auto resp = Protocol::FetchItemsResponsePtr::create(expectedId); resp->setParentId(pimItem.collectionId()); resp->setSize(size); resp->setRemoteId(pimItem.remoteId()); resp->setRemoteRevision(pimItem.remoteRevision()); resp->setMimeType(pimItem.mimeType().name()); resp->setGid(pimItem.gid()); resp->setMTime(datetime); resp->setParts(parts); resp->setAncestors({ Protocol::Ancestor(4, QLatin1String("ColC")) }); return resp; } TestScenario errorResponse(const QString &errorMsg) { auto response = Protocol::CreateItemResponsePtr::create(); response->setError(1, errorMsg); return TestScenario::create(5, TestScenario::ServerCmd, response); } private Q_SLOTS: void testAkAppend_data() { using Notifications = QVector; QTest::addColumn("scenarios"); QTest::addColumn("notifications"); QTest::addColumn("pimItem"); QTest::addColumn >("parts"); QTest::addColumn >("flags"); QTest::addColumn >("tags"); QTest::addColumn("uidnext"); QTest::addColumn("datetime"); QTest::addColumn("expectFail"); TestScenario::List scenarios; auto notification = Protocol::ItemChangeNotificationPtr::create(); qint64 uidnext = 0; QDateTime datetime(QDate(2014, 05, 12), QTime(14, 46, 00), Qt::UTC); PimItem pimItem; QVector parts; QVector flags; QVector tags; pimItem.setCollectionId(4); pimItem.setSize(10); pimItem.setRemoteId(QStringLiteral("TEST-1")); pimItem.setRemoteRevision(QStringLiteral("1")); pimItem.setGid(QStringLiteral("TEST-1")); pimItem.setMimeType(MimeType::retrieveByName(QStringLiteral("application/octet-stream"))); pimItem.setDatetime(datetime); updateParts(parts, { { QLatin1String("PLD:DATA"), "0123456789", 10 } }); notification->setOperation(Protocol::ItemChangeNotification::Add); notification->setParentCollection(4); notification->setResource("akonadi_fake_resource_0"); - auto item = Protocol::FetchItemsResponsePtr::create(); - item->setId(-1); - item->setRemoteId(QStringLiteral("TEST-1")); - item->setRemoteRevision(QStringLiteral("1")); - item->setMimeType(QStringLiteral("application/octet-stream")); - notification->setItems({ item }); + Protocol::FetchItemsResponse item; + item.setId(-1); + item.setRemoteId(QStringLiteral("TEST-1")); + item.setRemoteRevision(QStringLiteral("1")); + item.setMimeType(QStringLiteral("application/octet-stream")); + notification->setItems({std::move(item)}); notification->setSessionId(FakeAkonadiServer::instanceName().toLatin1()); uidnext = 13; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd,createCommand(pimItem, datetime, { "PLD:DATA" })) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::MetaData)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", Protocol::PartMetaData("PLD:DATA", 10))) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::Data)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", "0123456789")) << TestScenario::create(5, TestScenario::ServerCmd, createResponse(uidnext, pimItem, datetime, { Protocol::StreamPayloadResponse("PLD:DATA", Protocol::PartMetaData("PLD:DATA", 10), "0123456789") })) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create()); QTest::newRow("single-part") << scenarios << Notifications{ notification } << pimItem << parts << flags << tags << uidnext << datetime << false; notification = Protocol::ItemChangeNotificationPtr::create(*notification); updatePimItem(pimItem, QStringLiteral("TEST-2"), 20); updateParts(parts, { { QLatin1String("PLD:DATA"), "Random Data", 11 }, { QLatin1String("PLD:PLDTEST"), "Test Data", 9 } }); updateNotifcationEntity(notification, pimItem); ++uidnext; scenarios.clear(); scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, createCommand(pimItem, datetime, { "PLD:DATA", "PLD:PLDTEST" } )) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::MetaData)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", Protocol::PartMetaData("PLD:DATA", 11, 0))) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::Data)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", "Random Data")) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:PLDTEST", Protocol::StreamPayloadCommand::MetaData)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:PLDTEST", Protocol::PartMetaData("PLD:PLDTEST", 9, 0))) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:PLDTEST", Protocol::StreamPayloadCommand::Data)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:PLDTEST", "Test Data")) << TestScenario::create(5, TestScenario::ServerCmd, createResponse(uidnext, pimItem, datetime, { Protocol::StreamPayloadResponse("PLD:DATA", Protocol::PartMetaData("PLD:DATA", 11), "Random Data"), Protocol::StreamPayloadResponse("PLD:PLDTEST", Protocol::PartMetaData("PLD:PLDTEST", 9), "Test Data") })) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create()); QTest::newRow("multi-part") << scenarios << Notifications{ notification } << pimItem << parts << flags << tags << uidnext << datetime << false; TestScenario inScenario, outScenario; { auto cmd = Protocol::CreateItemCommandPtr::create(); cmd->setCollection(Scope(100)); inScenario = TestScenario::create(5, TestScenario::ClientCmd, cmd); } scenarios.clear(); scenarios << FakeAkonadiServer::loginScenario() << inScenario << errorResponse(QStringLiteral("Invalid parent collection")); QTest::newRow("invalid collection") << scenarios << Notifications{} << PimItem() << QVector() << QVector() << QVector() << -1ll << QDateTime() << true; { auto cmd = Protocol::CreateItemCommandPtr::create(); cmd->setCollection(Scope(6)); inScenario = TestScenario::create(5, TestScenario::ClientCmd, cmd); } scenarios.clear(); scenarios << FakeAkonadiServer::loginScenario() << inScenario << errorResponse(QStringLiteral("Cannot append item into virtual collection")); QTest::newRow("virtual collection") << scenarios << Notifications{} << PimItem() << QVector() << QVector() << QVector() << -1ll << QDateTime() << true; updatePimItem(pimItem, QStringLiteral("TEST-3"), 5); updateParts(parts, { { QLatin1String("PLD:DATA"), "12345", 5 } }); updateNotifcationEntity(notification, pimItem); ++uidnext; scenarios.clear(); scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, createCommand(pimItem, datetime, { "PLD:DATA" }, 1)) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::MetaData)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", Protocol::PartMetaData("PLD:DATA", 5))) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::Data)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", "12345")) << TestScenario::create(5, TestScenario::ServerCmd, createResponse(uidnext, pimItem, datetime, { Protocol::StreamPayloadResponse("PLD:DATA", Protocol::PartMetaData("PLD:DATA", 5), "12345") })) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create()); QTest::newRow("mismatch item sizes (smaller)") << scenarios << Notifications{ notification } << pimItem << parts << flags << tags << uidnext << datetime << false; notification = Protocol::ItemChangeNotificationPtr::create(*notification); updatePimItem(pimItem, QStringLiteral("TEST-4"), 10); updateNotifcationEntity(notification, pimItem); ++uidnext; scenarios.clear(); scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, createCommand(pimItem, datetime, { "PLD:DATA" }, 10)) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::MetaData)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", Protocol::PartMetaData("PLD:DATA", 5))) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::Data)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", "12345")) << TestScenario::create(5, TestScenario::ServerCmd, createResponse(uidnext, pimItem, datetime, { Protocol::StreamPayloadResponse("PLD:DATA", Protocol::PartMetaData("PLD:DATA", 5), "12345") }, 10)) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create()); QTest::newRow("mismatch item sizes (bigger)") << scenarios << Notifications{ notification } << pimItem << parts << flags << tags << uidnext << datetime << false; scenarios.clear(); scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, createCommand(pimItem, datetime, { "PLD:DATA" })) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::MetaData)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", Protocol::PartMetaData("PLD:DATA", 5))) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::Data)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", "123")) << errorResponse(QStringLiteral("Payload size mismatch")); QTest::newRow("incomplete part data") << scenarios << Notifications{} << PimItem() << QVector() << QVector() << QVector() << -1ll << QDateTime() << true; scenarios.clear(); scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, createCommand(pimItem, datetime, { "PLD:DATA" })) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::MetaData)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", Protocol::PartMetaData("PLD:DATA", 4))) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::Data)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", "1234567890")) << errorResponse(QStringLiteral("Payload size mismatch")); QTest::newRow("part data larger than advertised") << scenarios << Notifications{} << PimItem() << QVector() << QVector() << QVector() << -1ll << QDateTime() << true; notification = Protocol::ItemChangeNotificationPtr::create(*notification); updatePimItem(pimItem, QStringLiteral("TEST-5"), 0); updateParts(parts, { { QLatin1String("PLD:DATA"), QByteArray(), 0 } }); updateNotifcationEntity(notification, pimItem); ++uidnext; scenarios.clear(); scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, createCommand(pimItem, datetime, { "PLD:DATA" })) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::MetaData)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", Protocol::PartMetaData("PLD:DATA", 0))) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::Data)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", QByteArray())) << TestScenario::create(5, TestScenario::ServerCmd, createResponse(uidnext, pimItem ,datetime, { Protocol::StreamPayloadResponse("PLD:DATA", Protocol::PartMetaData("PLD:DATA", 0), QByteArray()) } )) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create()); QTest::newRow("empty payload part") << scenarios << Notifications{ notification } << pimItem << parts << flags << tags << uidnext << datetime << false; notification = Protocol::ItemChangeNotificationPtr::create(*notification); updatePimItem(pimItem, QStringLiteral("TEST-8"), 1); updateParts(parts, { { QLatin1String("PLD:DATA"), QByteArray("\0", 1), 1 } }); updateNotifcationEntity(notification, pimItem); ++uidnext; scenarios.clear(); scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, createCommand(pimItem, datetime, { "PLD:DATA" })) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::MetaData)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", Protocol::PartMetaData("PLD:DATA", 1))) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::Data)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", QByteArray("\0", 1))) << TestScenario::create(5, TestScenario::ServerCmd, createResponse(uidnext, pimItem, datetime, { Protocol::StreamPayloadResponse("PLD:DATA", Protocol::PartMetaData("PLD:DATA", 1), QByteArray("\0", 1)) })) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create()); QTest::newRow("part data will null character") << scenarios << Notifications{ notification } << pimItem << parts << flags << tags << uidnext << datetime << false; const QString utf8String = QStringLiteral("äöüß@€µøđ¢©®"); notification = Protocol::ItemChangeNotificationPtr::create(*notification); updatePimItem(pimItem, QStringLiteral("TEST-9"), utf8String.toUtf8().size()); updateParts(parts, { { QLatin1String("PLD:DATA"), utf8String.toUtf8(), utf8String.toUtf8().size() } }); updateNotifcationEntity(notification, pimItem); ++uidnext; scenarios.clear(); scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, createCommand(pimItem, datetime, { "PLD:DATA" })) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::MetaData)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", Protocol::PartMetaData("PLD:DATA", parts.first().datasize()))) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::Data)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", utf8String.toUtf8())) << TestScenario::create(5, TestScenario::ServerCmd, createResponse(uidnext, pimItem, datetime, { Protocol::StreamPayloadResponse("PLD:DATA", Protocol::PartMetaData("PLD:DATA", utf8String.toUtf8().size()), utf8String.toUtf8()) })) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create()); QTest::newRow("utf8 part data") << scenarios << Notifications{ notification } << pimItem << parts << flags << tags << uidnext << datetime << false; const QByteArray hugeData = QByteArray("a").repeated(1 << 20); notification = Protocol::ItemChangeNotificationPtr::create(*notification); updatePimItem(pimItem, QStringLiteral("TEST-10"), 1 << 20); updateParts(parts, { { QLatin1String("PLD:DATA"), hugeData, 1 << 20 } }); updateNotifcationEntity(notification, pimItem); ++uidnext; scenarios.clear(); scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, createCommand(pimItem, datetime, { "PLD:DATA" })) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::MetaData)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", Protocol::PartMetaData("PLD:DATA", parts.first().datasize()))) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::Data)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", hugeData)) << TestScenario::create(5, TestScenario::ServerCmd, createResponse(uidnext, pimItem ,datetime, { Protocol::StreamPayloadResponse("PLD:DATA", Protocol::PartMetaData("PLD:DATA", parts.first().datasize()), hugeData) })) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create()); QTest::newRow("huge part data") << scenarios << Notifications{ notification } << pimItem << parts << flags << tags << uidnext << datetime << false; const QByteArray dataWithNewLines = "Bernard, Bernard, Bernard, Bernard, look, look Bernard!\nWHAT!!!!!!!\nI'm a prostitute robot from the future!"; notification = Protocol::ItemChangeNotificationPtr::create(*notification); updatePimItem(pimItem, QStringLiteral("TEST-11"), dataWithNewLines.size()); updateParts(parts, { { QLatin1String("PLD:DATA"), dataWithNewLines, dataWithNewLines.size() } }); updateNotifcationEntity(notification, pimItem); ++uidnext; scenarios.clear(); scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, createCommand(pimItem, datetime, { "PLD:DATA" })) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::MetaData)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", Protocol::PartMetaData("PLD:DATA", parts.first().datasize()))) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::Data)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", dataWithNewLines)) << TestScenario::create(5, TestScenario::ServerCmd, createResponse(uidnext, pimItem, datetime, { Protocol::StreamPayloadResponse("PLD:DATA", Protocol::PartMetaData("PLD:DATA", dataWithNewLines.size()), dataWithNewLines) })) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create()); QTest::newRow("data with newlines") << scenarios << Notifications{ notification } << pimItem << parts << flags << tags << uidnext << datetime << false; const QByteArray lotsOfNewlines = QByteArray("\n").repeated(1 << 20); notification = Protocol::ItemChangeNotificationPtr::create(*notification); updatePimItem(pimItem, QStringLiteral("TEST-12"), lotsOfNewlines.size()); updateParts(parts, { { QLatin1String("PLD:DATA"), lotsOfNewlines, lotsOfNewlines.size() } }); updateNotifcationEntity(notification, pimItem); ++uidnext; scenarios.clear(); scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, createCommand(pimItem, datetime, { "PLD:DATA" })) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::MetaData)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", Protocol::PartMetaData("PLD:DATA", parts.first().datasize()))) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::Data)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", lotsOfNewlines)) << TestScenario::create(5, TestScenario::ServerCmd, createResponse(uidnext, pimItem, datetime, { Protocol::StreamPayloadResponse("PLD:DATA", Protocol::PartMetaData("PLD:DATA", parts.first().datasize()), lotsOfNewlines) })) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create()); QTest::newRow("data with lots of newlines") << scenarios << Notifications{ notification } << pimItem << parts << flags << tags << uidnext << datetime << false; notification = Protocol::ItemChangeNotificationPtr::create(*notification); updatePimItem(pimItem, QStringLiteral("TEST-13"), 20); updateParts(parts, { { QLatin1String("PLD:NEWPARTTYPE1"), "0123456789", 10 }, { QLatin1String("PLD:NEWPARTTYPE2"), "9876543210", 10 } }); updateNotifcationEntity(notification, pimItem); ++uidnext; scenarios.clear(); scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, createCommand(pimItem, datetime, { "PLD:NEWPARTTYPE1", "PLD:NEWPARTTYPE2" })) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:NEWPARTTYPE2", Protocol::StreamPayloadCommand::MetaData)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:NEWPARTTYPE2", Protocol::PartMetaData("PLD:NEWPARTTYPE2", 10))) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:NEWPARTTYPE2", Protocol::StreamPayloadCommand::Data)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:NEWPARTTYPE2", "9876543210")) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:NEWPARTTYPE1", Protocol::StreamPayloadCommand::MetaData)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:NEWPARTTYPE1", Protocol::PartMetaData("PLD:NEWPARTTYPE1", 10))) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:NEWPARTTYPE1", Protocol::StreamPayloadCommand::Data)) << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:NEWPARTTYPE1", "0123456789")) << TestScenario::create(5, TestScenario::ServerCmd, createResponse(uidnext, pimItem, datetime, { Protocol::StreamPayloadResponse("PLD:NEWPARTTYPE2", Protocol::PartMetaData("PLD:NEWPARTTYPE2", 10), "9876543210"), Protocol::StreamPayloadResponse("PLD:NEWPARTTYPE1", Protocol::PartMetaData("PLD:NEWPARTTYPE1", 10), "0123456789") })) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create()); QTest::newRow("non-existent part types") << scenarios << Notifications{ notification } << pimItem << parts << flags << tags << uidnext << datetime << false; notification = Protocol::ItemChangeNotificationPtr::create(*notification); updatePimItem(pimItem, QStringLiteral("TEST-14"), 0); updateParts(parts, {}); updateFlags(flags, QStringList() << QStringLiteral("\\SEEN") << QStringLiteral("\\RANDOM")); updateNotifcationEntity(notification, pimItem); ++uidnext; { auto cmd = createCommand(pimItem, datetime, {}); cmd->setFlags({ "\\SEEN", "\\RANDOM" }); inScenario = TestScenario::create(5, TestScenario::ClientCmd, cmd); auto rsp = createResponse(uidnext, pimItem, datetime, {}); rsp->setFlags({ "\\SEEN", "\\RANDOM" }); outScenario = TestScenario::create(5, TestScenario::ServerCmd, rsp); } scenarios.clear(); scenarios << FakeAkonadiServer::loginScenario() << inScenario << outScenario << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create()); QTest::newRow("item with flags") << scenarios << Notifications{ notification } << pimItem << parts << flags << tags << uidnext << datetime << false; notification = Protocol::ItemChangeNotificationPtr::create(*notification); updatePimItem(pimItem, QStringLiteral("TEST-15"), 0); updateFlags(flags, {}); updateTags(tags, { { QLatin1String("PLAIN"), QLatin1String("TAG-1") }, { QLatin1String("PLAIN"), QLatin1String("TAG-2") } }); updateNotifcationEntity(notification, pimItem); ++uidnext; { auto cmd = createCommand(pimItem, datetime, {}); cmd->setTags(Scope(Scope::Gid, { QLatin1String("TAG-1"), QLatin1String("TAG-2") })); inScenario = TestScenario::create(5, TestScenario::ClientCmd, cmd); auto rsp = createResponse(uidnext, pimItem, datetime, {}); rsp->setTags({ Protocol::FetchTagsResponse(2, "TAG-1", "PLAIN"), Protocol::FetchTagsResponse(3, "TAG-2", "PLAIN") }); outScenario = TestScenario::create(5, TestScenario::ServerCmd, rsp); } scenarios.clear(); scenarios << FakeAkonadiServer::loginScenario() << inScenario << outScenario << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create()); QTest::newRow("item with non-existent tags (GID)") << scenarios << Notifications{ notification } << pimItem << parts << flags << tags << uidnext << datetime << false; notification = Protocol::ItemChangeNotificationPtr::create(*notification); updatePimItem(pimItem, QStringLiteral("TEST-16"), 0); updateTags(tags, { { QLatin1String("PLAIN"), QLatin1String("TAG-3") }, { QLatin1String("PLAIN"), QLatin1String("TAG-4") } }); updateNotifcationEntity(notification, pimItem); ++uidnext; { auto cmd = createCommand(pimItem, datetime, {}); cmd->setTags(Scope(Scope::Rid, { QLatin1String("TAG-3"), QLatin1String("TAG-4") })); inScenario = TestScenario::create(5, TestScenario::ClientCmd, cmd); auto rsp = createResponse(uidnext, pimItem, datetime, {}); rsp->setTags({ Protocol::FetchTagsResponse(4, "TAG-3", "PLAIN"), Protocol::FetchTagsResponse(5, "TAG-4", "PLAIN") }); outScenario = TestScenario::create(5, TestScenario::ServerCmd, rsp); } scenarios.clear(); scenarios << FakeAkonadiServer::loginScenario() << FakeAkonadiServer::selectResourceScenario(QStringLiteral("akonadi_fake_resource_0")) << inScenario << outScenario << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create()); QTest::newRow("item with non-existent tags (RID)") << scenarios << Notifications{ notification } << pimItem << parts << flags << tags << uidnext << datetime << false; notification = Protocol::ItemChangeNotificationPtr::create(*notification); updatePimItem(pimItem, QStringLiteral("TEST-17"), 0); updateNotifcationEntity(notification, pimItem); updateTags(tags, { { QLatin1String("PLAIN"), QLatin1String("TAG-1") }, { QLatin1String("PLAIN"), QLatin1String("TAG-2") } }); ++uidnext; { auto cmd = createCommand(pimItem, datetime, {}); cmd->setTags(Scope(Scope::Rid, { QLatin1String("TAG-1"), QLatin1String("TAG-2") })); inScenario = TestScenario::create(5, TestScenario::ClientCmd, cmd); auto rsp = createResponse(uidnext, pimItem, datetime, {}); rsp->setTags({ Protocol::FetchTagsResponse(2, "TAG-1", "PLAIN"), Protocol::FetchTagsResponse(3, "TAG-2", "PLAIN") }); outScenario = TestScenario::create(5, TestScenario::ServerCmd, rsp); } scenarios.clear(); scenarios << FakeAkonadiServer::loginScenario() << FakeAkonadiServer::selectResourceScenario(QStringLiteral("akonadi_fake_resource_0")) << inScenario << outScenario << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create()); QTest::newRow("item with existing tags (RID)") << scenarios << Notifications{ notification } << pimItem << parts << flags << tags << uidnext << datetime << false; notification = Protocol::ItemChangeNotificationPtr::create(*notification); updatePimItem(pimItem, QStringLiteral("TEST-18"), 0); updateNotifcationEntity(notification, pimItem); updateTags(tags, { { QLatin1String("PLAIN"), QLatin1String("TAG-3") }, { QLatin1String("PLAIN"), QLatin1String("TAG-4") } }); ++uidnext; { auto cmd = createCommand(pimItem, datetime, {}); cmd->setTags(Scope(Scope::Gid, { QLatin1String("TAG-3"), QLatin1String("TAG-4") })); inScenario = TestScenario::create(5, TestScenario::ClientCmd, cmd); auto rsp = createResponse(uidnext, pimItem, datetime, {}); rsp->setTags({ Protocol::FetchTagsResponse(4, "TAG-3", "PLAIN"), Protocol::FetchTagsResponse(5, "TAG-4", "PLAIN") }); outScenario = TestScenario::create(5, TestScenario::ServerCmd, rsp); } scenarios.clear(); scenarios << FakeAkonadiServer::loginScenario() << inScenario << outScenario << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create()); QTest::newRow("item with existing tags (GID)") << scenarios << Notifications{ notification } << pimItem << parts << flags << tags << uidnext << datetime << false; notification = Protocol::ItemChangeNotificationPtr::create(*notification); updatePimItem(pimItem, QStringLiteral("TEST-19"), 0); updateFlags(flags, QStringList() << QStringLiteral("\\SEEN") << QStringLiteral("$FLAG")); updateTags(tags, { { QLatin1String("PLAIN"), QLatin1String("TAG-1") }, { QLatin1String("PLAIN"), QLatin1String("TAG-2") } }); updateNotifcationEntity(notification, pimItem); ++uidnext; { auto cmd = createCommand(pimItem, datetime, {}); cmd->setTags(Scope(Scope::Gid, { QLatin1String("TAG-1"), QLatin1String("TAG-2") })); cmd->setFlags({ "\\SEEN", "$FLAG" }); inScenario = TestScenario::create(5, TestScenario::ClientCmd, cmd); auto rsp = createResponse(uidnext, pimItem, datetime, {}); rsp->setTags({ Protocol::FetchTagsResponse(2, "TAG-1", "PLAIN"), Protocol::FetchTagsResponse(3, "TAG-2", "PLAIN") }); rsp->setFlags({ "\\SEEN", "$FLAG" }); outScenario = TestScenario::create(5, TestScenario::ServerCmd, rsp); } scenarios.clear(); scenarios << FakeAkonadiServer::loginScenario() << inScenario << outScenario << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create()); QTest::newRow("item with flags and tags") << scenarios << Notifications{ notification } << pimItem << parts << flags << tags << uidnext << datetime << false; notification = Protocol::ItemChangeNotificationPtr::create(*notification); updatePimItem(pimItem, QStringLiteral("TEST-20"), 0); updateFlags(flags, {}); updateTags(tags, { { QLatin1String("PLAIN"), utf8String } }); updateNotifcationEntity(notification, pimItem); ++uidnext; { auto cmd = createCommand(pimItem, datetime, {}); cmd->setTags(Scope(Scope::Gid, { utf8String })); inScenario = TestScenario::create(5, TestScenario::ClientCmd, cmd); auto rsp = createResponse(uidnext, pimItem, datetime, {}); rsp->setTags({ Protocol::FetchTagsResponse(6, utf8String.toUtf8(), "PLAIN") }); outScenario = TestScenario::create(5, TestScenario::ServerCmd, rsp); } scenarios.clear(); scenarios << FakeAkonadiServer::loginScenario() << inScenario << outScenario << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create()); QTest::newRow("item with UTF-8 tag") << scenarios << Notifications{ notification }<< pimItem << parts << flags << tags << uidnext << datetime << false; notification = Protocol::ItemChangeNotificationPtr::create(*notification); updatePimItem(pimItem, QStringLiteral("TEST-21"), 0); updateFlags(flags, {}); updateTags(tags, {}); pimItem.setGid(QStringLiteral("GID-21")); updateNotifcationEntity(notification, pimItem); scenarios = FakeAkonadiServer::loginScenario(); // Create a normal item with RID { ++uidnext; auto cmd = createCommand(pimItem, datetime, {}); scenarios << TestScenario::create(5, TestScenario::ClientCmd, cmd); auto rsp = createResponse(uidnext, pimItem, datetime, {}); scenarios << TestScenario::create(5, TestScenario::ServerCmd, rsp) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create()); } // Create the same item again (no merging, so it will just be created) { ++uidnext; auto cmd = createCommand(pimItem, datetime, {}); scenarios << TestScenario::create(6, TestScenario::ClientCmd, cmd); auto rsp = createResponse(uidnext, pimItem, datetime, {}); scenarios << TestScenario::create(6, TestScenario::ServerCmd, rsp) << TestScenario::create(6, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create()); } // Now try to create the item once again, but in merge mode, we should fail now { ++uidnext; auto cmd = createCommand(pimItem, datetime, {}); cmd->setMergeModes(Protocol::CreateItemCommand::RemoteID); scenarios << TestScenario::create(7, TestScenario::ClientCmd, cmd); auto rsp = Protocol::CreateItemResponsePtr::create(); rsp->setError(1, QStringLiteral("Multiple merge canddiates")); scenarios << TestScenario::create(7, TestScenario::ServerCmd, rsp); } Notifications notifications = { notification, Protocol::ItemChangeNotificationPtr::create(*notification) }; QTest::newRow("multiple merge candidates (RID)") << scenarios << notifications << pimItem << parts << flags << tags << uidnext << datetime << true; notification = Protocol::ItemChangeNotificationPtr::create(*notification); updatePimItem(pimItem, QStringLiteral("TEST-22"), 0); pimItem.setGid(QStringLiteral("GID-22")); updateNotifcationEntity(notification, pimItem); scenarios = FakeAkonadiServer::loginScenario(); // Create a normal item with GID { // Don't increase uidnext, we will reuse the one from previous test, // since that did not actually create a new Item auto cmd = createCommand(pimItem, datetime, {}); scenarios << TestScenario::create(5, TestScenario::ClientCmd, cmd); auto rsp = createResponse(uidnext, pimItem, datetime, {}); scenarios << TestScenario::create(5, TestScenario::ServerCmd, rsp) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create()); } // Create the same item again (no merging, so it will just be created) { ++uidnext; auto cmd = createCommand(pimItem, datetime, {}); scenarios << TestScenario::create(6, TestScenario::ClientCmd, cmd); auto rsp = createResponse(uidnext, pimItem, datetime, {}); scenarios << TestScenario::create(6, TestScenario::ServerCmd, rsp) << TestScenario::create(6, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create()); } // Now try to create the item once again, but in merge mode, we should fail now { ++uidnext; auto cmd = createCommand(pimItem, datetime, {}); cmd->setMergeModes(Protocol::CreateItemCommand::GID); scenarios << TestScenario::create(7, TestScenario::ClientCmd, cmd); auto rsp = Protocol::CreateItemResponsePtr::create(); rsp->setError(1, QStringLiteral("Multiple merge candidates")); scenarios << TestScenario::create(7, TestScenario::ServerCmd, rsp); } notifications = { notification, Protocol::ItemChangeNotificationPtr::create(*notification) }; QTest::newRow("multiple merge candidates (GID)") << scenarios << notifications << pimItem << parts << flags << tags << uidnext << datetime << true; notification = Protocol::ItemChangeNotificationPtr::create(*notification); updatePimItem(pimItem, QStringLiteral("TEST-23"), 0); pimItem.setGid(QString()); updateNotifcationEntity(notification, pimItem); scenarios = FakeAkonadiServer::loginScenario(); // Create a normal item with RID, but with empty GID { // Don't increase uidnext, we will reuse the one from previous test, // since that did not actually create a new Item auto cmd = createCommand(pimItem, datetime, {}); scenarios << TestScenario::create(5, TestScenario::ClientCmd, cmd); auto rsp = createResponse(uidnext, pimItem, datetime, {}); scenarios << TestScenario::create(5, TestScenario::ServerCmd, rsp) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create()); } // Merge by GID - should not create a new Item but actually merge by RID, // since an item with matching RID but empty GID exists { ++uidnext; pimItem.setGid(QStringLiteral("GID-23")); auto cmd = createCommand(pimItem, datetime, {}); cmd->setMergeModes(Protocol::CreateItemCommand::GID); scenarios << TestScenario::create(6, TestScenario::ClientCmd, cmd); auto rsp = createResponse(uidnext, pimItem, datetime, {}); scenarios << TestScenario::create(6, TestScenario::ServerCmd, rsp) << TestScenario::create(6, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create()); } notifications = { notification, Protocol::ItemChangeNotificationPtr::create(*notification) }; QTest::newRow("merge into empty GID if RID matches") << scenarios << notifications << pimItem << parts << flags << tags << uidnext << datetime << false; } void testAkAppend() { QFETCH(TestScenario::List, scenarios); QFETCH(QVector, notifications); QFETCH(PimItem, pimItem); QFETCH(QVector, parts); QFETCH(QVector, flags); QFETCH(QVector, tags); QFETCH(qint64, uidnext); QFETCH(bool, expectFail); FakeAkonadiServer::instance()->setScenarios(scenarios); FakeAkonadiServer::instance()->runTest(); auto notificationSpy = FakeAkonadiServer::instance()->notificationSpy(); QCOMPARE(notificationSpy->count(), notifications.count()); for (int i = 0; i < notifications.count(); ++i) { const auto incomingNtfs = notificationSpy->at(i).first().value(); QCOMPARE(incomingNtfs.count(), 1); const auto itemNotification = incomingNtfs.at(0).staticCast(); QVERIFY(AkTest::compareNotifications(itemNotification, notifications.at(i), QFlag(AkTest::NtfAll & ~ AkTest::NtfEntities))); QCOMPARE(itemNotification->items().count(), notifications.at(i)->items().count()); } const PimItem actualItem = PimItem::retrieveById(uidnext); if (expectFail) { QVERIFY(!actualItem.isValid()); } else { QVERIFY(actualItem.isValid()); QCOMPARE(actualItem.remoteId(), pimItem.remoteId()); QCOMPARE(actualItem.remoteRevision(), pimItem.remoteRevision()); QCOMPARE(actualItem.gid(), pimItem.gid()); QCOMPARE(actualItem.size(), pimItem.size()); QCOMPARE(actualItem.datetime(), pimItem.datetime()); QCOMPARE(actualItem.collectionId(), pimItem.collectionId()); QCOMPARE(actualItem.mimeTypeId(), pimItem.mimeTypeId()); const QList actualFlags = actualItem.flags().toList(); QCOMPARE(actualFlags.count(), flags.count()); Q_FOREACH (const Flag &flag, flags) { const QList::const_iterator actualFlagIter = std::find_if(actualFlags.constBegin(), actualFlags.constEnd(), [flag](Flag const & actualFlag) { return flag.name() == actualFlag.name(); }); QVERIFY(actualFlagIter != actualFlags.constEnd()); const Flag actualFlag = *actualFlagIter; QVERIFY(actualFlag.isValid()); } const QList actualTags = actualItem.tags().toList(); QCOMPARE(actualTags.count(), tags.count()); Q_FOREACH (const FakeTag &tag, tags) { const QList::const_iterator actualTagIter = std::find_if(actualTags.constBegin(), actualTags.constEnd(), [tag](Tag const & actualTag) { return tag.gid() == actualTag.gid(); }); QVERIFY(actualTagIter != actualTags.constEnd()); const Tag actualTag = *actualTagIter; QVERIFY(actualTag.isValid()); QCOMPARE(actualTag.tagType().name(), tag.tagType().name()); QCOMPARE(actualTag.gid(), tag.gid()); if (!tag.remoteId().isEmpty()) { SelectQueryBuilder qb; qb.addValueCondition(TagRemoteIdResourceRelation::resourceIdFullColumnName(), Query::Equals, QLatin1String("akonadi_fake_resource_0")); qb.addValueCondition(TagRemoteIdResourceRelation::tagIdColumn(), Query::Equals, actualTag.id()); QVERIFY(qb.exec()); QCOMPARE(qb.result().size(), 1); QCOMPARE(qb.result()[0].remoteId(), tag.remoteId()); } } const QList actualParts = actualItem.parts().toList(); QCOMPARE(actualParts.count(), parts.count()); Q_FOREACH (const FakePart &part, parts) { const QList::const_iterator actualPartIter = std::find_if(actualParts.constBegin(), actualParts.constEnd(), [part](Part const & actualPart) { return part.partType().ns() == actualPart.partType().ns() && part.partType().name() == actualPart.partType().name(); }); QVERIFY(actualPartIter != actualParts.constEnd()); const Part actualPart = *actualPartIter; QVERIFY(actualPart.isValid()); QCOMPARE(QString::fromUtf8(actualPart.data()), QString::fromUtf8(part.data())); QCOMPARE(actualPart.data(), part.data()); QCOMPARE(actualPart.datasize(), part.datasize()); QCOMPARE(actualPart.storage(), part.storage()); } } } }; AKTEST_FAKESERVER_MAIN(AkAppendHandlerTest) #include "akappendhandlertest.moc" diff --git a/autotests/server/collectionreferencetest.cpp b/autotests/server/collectionreferencetest.cpp index 3e713bf1a..3d7d95acb 100644 --- a/autotests/server/collectionreferencetest.cpp +++ b/autotests/server/collectionreferencetest.cpp @@ -1,270 +1,270 @@ /* Copyright (c) 2014 Christian Mollekopf 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 #include #include #include "fakeakonadiserver.h" #include "fakedatastore.h" #include #include "entities.h" #include "collectionreferencemanager.h" #include "dbinitializer.h" #include #include using namespace Akonadi; using namespace Akonadi::Server; Q_DECLARE_METATYPE(Collection::List) class CollectionReferenceTest : public QObject { Q_OBJECT DbInitializer initializer; public: CollectionReferenceTest() { try { FakeAkonadiServer::instance()->setPopulateDb(false); FakeAkonadiServer::instance()->init(); } catch (const FakeAkonadiServerException &e) { qWarning() << "Server exception: " << e.what(); qFatal("Fake Akonadi Server failed to start up, aborting test"); } initializer.createResource("testresource"); initializer.createCollection("col1"); Collection col2 = initializer.createCollection("col2"); col2.setEnabled(false); col2.update(); } ~CollectionReferenceTest() { FakeAkonadiServer::instance()->quit(); } private Q_SLOTS: void testModify_data() { QTest::addColumn("scenarios"); QTest::addColumn("expectedNotifications"); auto notificationTemplate = Protocol::CollectionChangeNotificationPtr::create(); notificationTemplate->setOperation(Protocol::CollectionChangeNotification::Modify); notificationTemplate->setParentCollection(0); notificationTemplate->setResource("testresource"); notificationTemplate->setSessionId(FakeAkonadiServer::instanceName().toLatin1()); - auto collection = Protocol::FetchCollectionsResponsePtr::create(); - collection->setId(initializer.collection("col2").id()); - collection->setRemoteId(QStringLiteral("col2")); - collection->setRemoteRevision(QStringLiteral("")); - notificationTemplate->setCollection(collection); + Protocol::FetchCollectionsResponse collection; + collection.setId(initializer.collection("col2").id()); + collection.setRemoteId(QStringLiteral("col2")); + collection.setRemoteRevision(QStringLiteral("")); + notificationTemplate->setCollection(std::move(collection)); { auto cmd = Protocol::FetchCollectionsCommandPtr::create(); cmd->setDepth(Protocol::FetchCollectionsCommand::AllCollections); cmd->setResource(QStringLiteral("testresource")); cmd->setEnabled(true); TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, cmd) << TestScenario::create(5, TestScenario::ServerCmd, initializer.listResponse(initializer.collection("col1"))) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::FetchCollectionsResponsePtr::create()); QTest::newRow("list before referenced first level") << scenarios << Protocol::ChangeNotificationList(); } { auto cmd = Protocol::ModifyCollectionCommandPtr::create(initializer.collection("col2").id()); cmd->setReferenced(true); TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, cmd) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::ModifyCollectionResponsePtr::create()); auto notification = Protocol::CollectionChangeNotificationPtr::create(*notificationTemplate); notification->setChangedParts(QSet() << "REFERENCED"); QTest::newRow("reference") << scenarios << (Protocol::ChangeNotificationList() << notification); } { auto cmd = Protocol::ModifyCollectionCommandPtr::create(initializer.collection("col2").id()); cmd->setReferenced(true); auto listCmd = Protocol::FetchCollectionsCommandPtr::create(initializer.collection("col2").id()); listCmd->setDepth(Protocol::FetchCollectionsCommand::BaseCollection); listCmd->setEnabled(true); Collection col2 = initializer.collection("col2"); col2.setReferenced(true); TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, cmd) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::ModifyCollectionResponsePtr::create()) << TestScenario::create(6, TestScenario::ClientCmd, listCmd) << TestScenario::create(6, TestScenario::ServerCmd, initializer.listResponse(col2)) << TestScenario::create(6, TestScenario::ServerCmd, Protocol::FetchCollectionsResponsePtr::create()); auto notification = Protocol::CollectionChangeNotificationPtr::create(*notificationTemplate); notification->setChangedParts(QSet() << "REFERENCED"); QTest::newRow("list referenced base") << scenarios << (Protocol::ChangeNotificationList() << notification); } { auto cmd = Protocol::ModifyCollectionCommandPtr::create(initializer.collection("col2").id()); cmd->setReferenced(true); auto listCmd = Protocol::FetchCollectionsCommandPtr::create(); listCmd->setResource(QStringLiteral("testresource")); listCmd->setEnabled(true); listCmd->setDepth(Protocol::FetchCollectionsCommand::ParentCollection); Collection col2 = initializer.collection("col2"); col2.setReferenced(true); TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, cmd) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::ModifyCollectionResponsePtr::create()) << TestScenario::create(6, TestScenario::ClientCmd, listCmd) << TestScenario::create(6, TestScenario::ServerCmd, initializer.listResponse(initializer.collection("col1"))) << TestScenario::create(6, TestScenario::ServerCmd, initializer.listResponse(col2)) << TestScenario::create(6, TestScenario::ServerCmd, Protocol::FetchCollectionsResponsePtr::create()); auto notification = Protocol::CollectionChangeNotificationPtr::create(*notificationTemplate); notification->setChangedParts(QSet() << "REFERENCED"); QTest::newRow("list referenced first level") << scenarios << (Protocol::ChangeNotificationList() << notification); } { auto cmd1 = Protocol::ModifyCollectionCommandPtr::create(initializer.collection("col2").id()); cmd1->setReferenced(true); auto cmd2 = Protocol::ModifyCollectionCommandPtr::create(initializer.collection("col2").id()); cmd2->setReferenced(false); TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, cmd1) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::ModifyCollectionResponsePtr::create()) << TestScenario::create(6, TestScenario::ClientCmd, cmd2) << TestScenario::create(6, TestScenario::ServerCmd, Protocol::ModifyCollectionResponsePtr::create()); auto notification = Protocol::CollectionChangeNotificationPtr::create(*notificationTemplate); notification->setChangedParts(QSet() << "REFERENCED"); QTest::newRow("dereference") << scenarios << (Protocol::ChangeNotificationList() << notification << notification); } } void testModify() { QFETCH(TestScenario::List, scenarios); QFETCH(Protocol::ChangeNotificationList, expectedNotifications); // Clean all references from previous run CollectionReferenceManager::cleanup(); FakeAkonadiServer::instance()->setScenarios(scenarios); FakeAkonadiServer::instance()->runTest(); auto notificationSpy = FakeAkonadiServer::instance()->notificationSpy(); if (expectedNotifications.isEmpty()) { QTRY_VERIFY(notificationSpy->isEmpty() || notificationSpy->takeFirst().first().value().isEmpty()); } else { Protocol::ChangeNotificationList receivedNotifications; for (int q = 0; q < notificationSpy->size(); q++) { //Only one notify call QCOMPARE(notificationSpy->first().count(), 1); const Protocol::ChangeNotificationList n = notificationSpy->first().first().value(); for (int i = 0; i < n.size(); i++) { receivedNotifications.append(n.at(i)); } } QCOMPARE(receivedNotifications.size(), expectedNotifications.count()); for (int i = 0; i < expectedNotifications.size(); i++) { QCOMPARE(*receivedNotifications.at(i), *expectedNotifications.at(i)); } } } void testReferenceCollection() { Collection col = initializer.createCollection("testReferenceCollection"); CollectionReferenceManager::instance()->referenceCollection("testReferenceCollectionSession", col, true); QVERIFY(CollectionReferenceManager::instance()->isReferenced(col.id())); QVERIFY(CollectionReferenceManager::instance()->isReferenced(col.id(), "testReferenceCollectionSession")); CollectionReferenceManager::instance()->referenceCollection("foobar", col, false); QVERIFY(CollectionReferenceManager::instance()->isReferenced(col.id())); QVERIFY(CollectionReferenceManager::instance()->isReferenced(col.id(), "testReferenceCollectionSession")); CollectionReferenceManager::instance()->referenceCollection("testReferenceCollectionSession", col, false); QVERIFY(!CollectionReferenceManager::instance()->isReferenced(col.id())); QVERIFY(!CollectionReferenceManager::instance()->isReferenced(col.id(), "testReferenceCollectionSession")); QVERIFY(col.remove()); } void testSessionClosed() { Collection col = initializer.createCollection("testSessionCollection"); col.setReferenced(true); QVERIFY(col.update()); CollectionReferenceManager::instance()->referenceCollection("testSessionClosedSession", col, true); CollectionReferenceManager::instance()->referenceCollection("testSessionClosedSession2", col, true); //Remove first session CollectionReferenceManager::instance()->removeSession("testSessionClosedSession2"); QVERIFY(Collection::retrieveById(col.id()).referenced()); QVERIFY(!CollectionReferenceManager::instance()->isReferenced(col.id(), "testSessionClosedSession2")); QVERIFY(CollectionReferenceManager::instance()->isReferenced(col.id(), "testSessionClosedSession")); CollectionReferenceManager::instance()->removeSession("testSessionClosedSession"); QVERIFY(!Collection::retrieveById(col.id()).referenced()); QVERIFY(!CollectionReferenceManager::instance()->isReferenced(col.id(), "testSessionClosedSession")); QVERIFY(col.remove()); } void testCleanup() { Collection col = initializer.createCollection("testCleanupCollection"); col.setReferenced(true); QVERIFY(col.update()); CollectionReferenceManager::cleanup(); QVERIFY(!Collection::retrieveById(col.id()).referenced()); QVERIFY(col.remove()); } }; AKTEST_FAKESERVER_MAIN(CollectionReferenceTest) #include "collectionreferencetest.moc" diff --git a/autotests/server/createhandlertest.cpp b/autotests/server/createhandlertest.cpp index 297a84d3c..ac2b46c71 100644 --- a/autotests/server/createhandlertest.cpp +++ b/autotests/server/createhandlertest.cpp @@ -1,200 +1,200 @@ /* Copyright (c) 2014 Christian Mollekopf 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 #include #include #include "fakeakonadiserver.h" #include "dbinitializer.h" #include "aktest.h" #include "entities.h" #include #include using namespace Akonadi; using namespace Akonadi::Server; class CreateHandlerTest : public QObject { Q_OBJECT public: CreateHandlerTest() { try { FakeAkonadiServer::instance()->init(); } catch (const FakeAkonadiServerException &e) { qWarning() << "Server exception: " << e.what(); qFatal("Fake Akonadi Server failed to start up, aborting test"); } } ~CreateHandlerTest() { FakeAkonadiServer::instance()->quit(); } private Q_SLOTS: void testCreate_data() { DbInitializer dbInitializer; QTest::addColumn("scenarios"); QTest::addColumn("notification"); auto notificationTemplate = Protocol::CollectionChangeNotificationPtr::create(); notificationTemplate->setOperation(Protocol::CollectionChangeNotification::Add); notificationTemplate->setParentCollection(3); notificationTemplate->setResource("akonadi_fake_resource_0"); notificationTemplate->setSessionId(FakeAkonadiServer::instanceName().toLatin1()); { auto cmd = Protocol::CreateCollectionCommandPtr::create(); cmd->setName(QStringLiteral("New Name")); cmd->setParent(Scope(3)); cmd->setAttributes({ { "MYRANDOMATTRIBUTE", "" } }); auto resp = Protocol::FetchCollectionsResponsePtr::create(8); resp->setName(QStringLiteral("New Name")); resp->setParentId(3); resp->setAttributes({ { "MYRANDOMATTRIBUTE", "" } }); resp->setResource(QStringLiteral("akonadi_fake_resource_0")); resp->cachePolicy().setLocalParts({ QLatin1String("ALL") }); resp->setMimeTypes({ QLatin1String("application/octet-stream"), QLatin1String("inode/directory") }); TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, cmd) << TestScenario::create(5, TestScenario::ServerCmd, resp) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateCollectionResponsePtr::create()); - auto collection = Protocol::FetchCollectionsResponsePtr::create(*resp); - collection->setMimeTypes({}); // CREATE ntf does not contain mimetypes and attrs - collection->setAttributes({}); + Protocol::FetchCollectionsResponse collection(*resp); + collection.setMimeTypes({}); // CREATE ntf does not contain mimetypes and attrs + collection.setAttributes({}); auto notification = Protocol::CollectionChangeNotificationPtr::create(*notificationTemplate); - notification->setCollection(collection); + notification->setCollection(std::move(collection)); QTest::newRow("create collection") << scenarios << notification; } { auto cmd = Protocol::CreateCollectionCommandPtr::create(); cmd->setName(QStringLiteral("Name 2")); cmd->setParent(Scope(3)); cmd->setEnabled(false); cmd->setDisplayPref(Tristate::True); cmd->setSyncPref(Tristate::True); cmd->setIndexPref(Tristate::True); auto resp = Protocol::FetchCollectionsResponsePtr::create(9); resp->setName(QStringLiteral("Name 2")); resp->setParentId(3); resp->setEnabled(false); resp->setDisplayPref(Tristate::True); resp->setSyncPref(Tristate::True); resp->setIndexPref(Tristate::True); resp->setResource(QStringLiteral("akonadi_fake_resource_0")); resp->cachePolicy().setLocalParts({ QLatin1String("ALL") }); resp->setMimeTypes({ QLatin1String("application/octet-stream"), QLatin1String("inode/directory") }); TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, cmd) << TestScenario::create(5, TestScenario::ServerCmd, resp) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateCollectionResponsePtr::create()); - auto collection = Protocol::FetchCollectionsResponsePtr::create(*resp); - collection->setMimeTypes({}); // CREATE ntf does not contain mimetypes and attrs - collection->setAttributes({}); + Protocol::FetchCollectionsResponse collection(*resp); + collection.setMimeTypes({}); // CREATE ntf does not contain mimetypes and attrs + collection.setAttributes({}); auto notification = Protocol::CollectionChangeNotificationPtr::create(*notificationTemplate); - notification->setCollection(collection); + notification->setCollection(std::move(collection)); QTest::newRow("create collection with local override") << scenarios << notification; } { auto cmd = Protocol::CreateCollectionCommandPtr::create(); cmd->setName(QStringLiteral("TopLevel")); cmd->setParent(Scope(0)); cmd->setMimeTypes({ QLatin1String("inode/directory") }); auto resp = Protocol::FetchCollectionsResponsePtr::create(10); resp->setName(QStringLiteral("TopLevel")); resp->setParentId(0); resp->setEnabled(true); resp->setMimeTypes({ QLatin1String("inode/directory") }); resp->cachePolicy().setLocalParts({ QLatin1String("ALL") }); resp->setResource(QStringLiteral("akonadi_fake_resource_0")); TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario("akonadi_fake_resource_0") << TestScenario::create(5, TestScenario::ClientCmd, cmd) << TestScenario::create(5, TestScenario::ServerCmd, resp) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateCollectionResponsePtr::create()); - auto collection = Protocol::FetchCollectionsResponsePtr::create(*resp); - collection->setMimeTypes({}); // CREATE ntf does not contain mimetypes and attrs - collection->setAttributes({}); + Protocol::FetchCollectionsResponse collection(*resp); + collection.setMimeTypes({}); // CREATE ntf does not contain mimetypes and attrs + collection.setAttributes({}); auto notification = Protocol::CollectionChangeNotificationPtr::create(*notificationTemplate); notification->setSessionId("akonadi_fake_resource_0"); notification->setParentCollection(0); - notification->setCollection(collection); + notification->setCollection(std::move(collection)); QTest::newRow("create top-level collection") << scenarios << notification; } } void testCreate() { QFETCH(TestScenario::List, scenarios); QFETCH(Protocol::CollectionChangeNotificationPtr, notification); FakeAkonadiServer::instance()->setScenarios(scenarios); FakeAkonadiServer::instance()->runTest(); auto notificationSpy = FakeAkonadiServer::instance()->notificationSpy(); if (notification->operation() != Protocol::CollectionChangeNotification::InvalidOp) { QCOMPARE(notificationSpy->count(), 1); const auto notifications = notificationSpy->takeFirst().first().value(); QCOMPARE(notifications.count(), 1); const auto actualNtf = notifications.first().staticCast(); if (*actualNtf != *notification) { qDebug() << "Actual: " << Protocol::debugString(actualNtf); qDebug() << "Expected:" << Protocol::debugString(notification); } QCOMPARE(*notifications.first().staticCast(), *notification); } else { QVERIFY(notificationSpy->isEmpty() || notificationSpy->takeFirst().first().value().isEmpty()); } } }; AKTEST_FAKESERVER_MAIN(CreateHandlerTest) #include "createhandlertest.moc" diff --git a/autotests/server/linkhandlertest.cpp b/autotests/server/linkhandlertest.cpp index 806672c42..21453c9a4 100644 --- a/autotests/server/linkhandlertest.cpp +++ b/autotests/server/linkhandlertest.cpp @@ -1,291 +1,291 @@ /* Copyright (c) 2014 Daniel Vrátil 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 #include #include "fakeakonadiserver.h" #include #include "entities.h" #include #include #include using namespace Akonadi; using namespace Akonadi::Server; class LinkHandlerTest : public QObject { Q_OBJECT public: LinkHandlerTest() { qRegisterMetaType(); try { FakeAkonadiServer::instance()->init(); } catch (const FakeAkonadiServerException &e) { qWarning() << "Server exception: " << e.what(); qFatal("Fake Akonadi Server failed to start up, aborting test"); } } ~LinkHandlerTest() { FakeAkonadiServer::instance()->quit(); } Protocol::LinkItemsResponsePtr createError(const QString &error) { auto resp = Protocol::LinkItemsResponsePtr::create(); resp->setError(1, error); return resp; } - Protocol::FetchItemsResponsePtr itemResponse(qint64 id, const QString &rid, const QString &rrev, const QString &mimeType) + Protocol::FetchItemsResponse itemResponse(qint64 id, const QString &rid, const QString &rrev, const QString &mimeType) { - auto item = Protocol::FetchItemsResponsePtr::create(); - item->setId(id); - item->setRemoteId(rid); - item->setRemoteRevision(rrev); - item->setMimeType(mimeType); + Protocol::FetchItemsResponse item; + item.setId(id); + item.setRemoteId(rid); + item.setRemoteRevision(rrev); + item.setMimeType(mimeType); return item; } private Q_SLOTS: void testLink_data() { QTest::addColumn("scenarios"); QTest::addColumn("notification"); QTest::addColumn("expectFail"); TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, Protocol::LinkItemsCommandPtr::create(Protocol::LinkItemsCommand::Link, ImapInterval(1, 3), 3)) << TestScenario::create(5, TestScenario::ServerCmd, createError(QStringLiteral("Can't link items to non-virtual collections"))); QTest::newRow("non-virtual collection") << scenarios << Protocol::ItemChangeNotificationPtr::create() << true; auto notification = Protocol::ItemChangeNotificationPtr::create(); notification->setOperation(Protocol::ItemChangeNotification::Link); notification->setItems({ itemResponse(1, QLatin1String("A"), QString(), QLatin1String("application/octet-stream")), itemResponse(2, QLatin1String("B"), QString(), QLatin1String("application/octet-stream")), itemResponse(3, QLatin1String("C"), QString(), QLatin1String("application/octet-stream")) }); notification->setParentCollection(6); notification->setResource("akonadi_fake_resource_with_virtual_collections_0"); notification->setSessionId(FakeAkonadiServer::instanceName().toLatin1()); scenarios.clear(); scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, Protocol::LinkItemsCommandPtr::create(Protocol::LinkItemsCommand::Link, ImapInterval(1, 3), 6)) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::LinkItemsResponsePtr::create()); QTest::newRow("normal") << scenarios << notification << false; notification = Protocol::ItemChangeNotificationPtr::create(*notification); notification->setItems({ itemResponse(4, QLatin1String("D"), QString(), QLatin1String("application/octet-stream")) }); scenarios.clear(); scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, Protocol::LinkItemsCommandPtr::create(Protocol::LinkItemsCommand::Link, QVector{ 4, 123456 }, 6)) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::LinkItemsResponsePtr::create()); QTest::newRow("existent and non-existent item") << scenarios << notification << false; scenarios.clear(); scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, Protocol::LinkItemsCommandPtr::create(Protocol::LinkItemsCommand::Link, 4, 6)) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::LinkItemsResponsePtr::create()); QTest::newRow("non-existent item only") << scenarios << Protocol::ItemChangeNotificationPtr::create() << false; //FIXME: All RID related operations are currently broken because we reset the collection context before every command, //and LINK still relies on SELECT to set the collection context. // scenario.clear(); // scenario << FakeAkonadiServer::defaultScenario() // << FakeAkonadiServer::selectCollectionScenario(QLatin1String("Collection B")) // << "C: 3 UID LINK 6 RID (\"F\" \"G\")\n" // << "S: 3 OK LINK complete"; // notification.clearEntities(); // notification.clearEntities(); // notification.addEntity(6, QLatin1String("F"), QString(), QLatin1String("application/octet-stream")); // notification.addEntity(7, QLatin1String("G"), QString(), QLatin1String("application/octet-stream")); // QTest::newRow("RID items") << scenario << notification << false; // scenario.clear(); // scenario << FakeAkonadiServer::defaultScenario() // << FakeAkonadiServer::selectResourceScenario(QLatin1String("akonadi_fake_resource_with_virtual_collections_0")) // << "C: 4 HRID LINK ((-1, \"virtual2\") (-1, \"virtual\") (-1, \"\")) UID 5" // << "S: 4 OK LINK complete"; // notification.setParentCollection(7); // notification.clearEntities(); // notification.addEntity(5, QLatin1String("E"), QString(), QLatin1String("application/octet-stream")); // QTest::newRow("HRID collection") << scenario << notification << false; // scenario.clear(); // scenario << FakeAkonadiServer::defaultScenario() // << FakeAkonadiServer::selectResourceScenario(QLatin1String("akonadi_fake_resource_with_virtual_collections_0")) // << FakeAkonadiServer::selectCollectionScenario(QLatin1String("Collection B")) // << "C: 4 HRID LINK ((-1, \"virtual2\") (-1, \"virtual\") (-1, \"\")) RID \"H\"" // << "S: 4 OK LINK complete"; // notification.clearEntities(); // notification.addEntity(8, QLatin1String("H"), QString(), QLatin1String("application/octet-stream")); // QTest::newRow("HRID collection, RID items") << scenario << notification << false; } void testLink() { QFETCH(TestScenario::List, scenarios); QFETCH(Protocol::ItemChangeNotificationPtr, notification); QFETCH(bool, expectFail); FakeAkonadiServer::instance()->setScenarios(scenarios); FakeAkonadiServer::instance()->runTest(); auto notificationSpy = FakeAkonadiServer::instance()->notificationSpy(); if (notification->operation() != Protocol::ItemChangeNotification::InvalidOp) { QCOMPARE(notificationSpy->count(), 1); const Protocol::ChangeNotificationList notifications = notificationSpy->takeFirst().first().value(); QCOMPARE(notifications.count(), 1); QCOMPARE(*notifications.first().staticCast(), *notification); } else { QVERIFY(notificationSpy->isEmpty() || notificationSpy->takeFirst().first().value().isEmpty()); } Q_FOREACH (const auto &entity, notification->items()) { if (expectFail) { - QVERIFY(!Collection::relatesToPimItem(notification->parentCollection(), entity->id())); + QVERIFY(!Collection::relatesToPimItem(notification->parentCollection(), entity.id())); } else { - QVERIFY(Collection::relatesToPimItem(notification->parentCollection(), entity->id())); + QVERIFY(Collection::relatesToPimItem(notification->parentCollection(), entity.id())); } } } void testUnlink_data() { QTest::addColumn("scenarios"); QTest::addColumn("notification"); QTest::addColumn("expectFail"); TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, Protocol::LinkItemsCommandPtr::create(Protocol::LinkItemsCommand::Unlink, ImapInterval(1, 3), 3)) << TestScenario::create(5, TestScenario::ServerCmd, createError(QStringLiteral("Can't link items to non-virtual collections"))); QTest::newRow("non-virtual collection") << scenarios << Protocol::ItemChangeNotificationPtr::create() << true; auto notification = Protocol::ItemChangeNotificationPtr::create(); notification->setOperation(Protocol::ItemChangeNotification::Unlink); notification->setItems({ itemResponse(1, QLatin1String("A"), QString(), QLatin1String("application/octet-stream")), itemResponse(2, QLatin1String("B"), QString(), QLatin1String("application/octet-stream")), itemResponse(3, QLatin1String("C"), QString(), QLatin1String("application/octet-stream")) }); notification->setParentCollection(6); notification->setResource("akonadi_fake_resource_with_virtual_collections_0"); notification->setSessionId(FakeAkonadiServer::instanceName().toLatin1()); scenarios.clear(); scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, Protocol::LinkItemsCommandPtr::create(Protocol::LinkItemsCommand::Unlink, ImapInterval(1, 3), 6)) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::LinkItemsResponsePtr::create()); QTest::newRow("normal") << scenarios << notification << false; notification = Protocol::ItemChangeNotificationPtr::create(*notification); notification->setItems({ itemResponse(4, QLatin1String("D"), QString(), QLatin1String("application/octet-stream")) }); scenarios.clear(); scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, Protocol::LinkItemsCommandPtr::create(Protocol::LinkItemsCommand::Unlink, QVector{ 4, 2048 }, 6)) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::LinkItemsResponsePtr::create()); QTest::newRow("existent and non-existent item") << scenarios << notification << false; scenarios.clear(); scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, Protocol::LinkItemsCommandPtr::create(Protocol::LinkItemsCommand::Unlink, 4096, 6)) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::LinkItemsResponsePtr::create()); QTest::newRow("non-existent item only") << scenarios << Protocol::ItemChangeNotificationPtr::create() << false; //FIXME: All RID related operations are currently broken because we reset the collection context before every command, //and LINK still relies on SELECT to set the collection context. // scenario.clear(); // scenario << FakeAkonadiServer::defaultScenario() // << FakeAkonadiServer::selectCollectionScenario(QLatin1String("Collection B")) // << "C: 4 UID UNLINK 6 RID (\"F\" \"G\")" // << "S: 4 OK LINK complete"; // notification.clearEntities(); // notification.clearEntities(); // notification.addEntity(6, QLatin1String("F"), QString(), QLatin1String("application/octet-stream")); // notification.addEntity(7, QLatin1String("G"), QString(), QLatin1String("application/octet-stream")); // QTest::newRow("RID items") << scenario << notification << false; // scenario.clear(); // scenario << FakeAkonadiServer::defaultScenario() // << FakeAkonadiServer::selectResourceScenario(QLatin1String("akonadi_fake_resource_with_virtual_collections_0")) // << "C: 4 HRID UNLINK ((-1, \"virtual2\") (-1, \"virtual\") (-1, \"\")) UID 5" // << "S: 4 OK LINK complete"; // notification.setParentCollection(7); // notification.clearEntities(); // notification.addEntity(5, QLatin1String("E"), QString(), QLatin1String("application/octet-stream")); // QTest::newRow("HRID collection") << scenario << notification << false; // scenario.clear(); // scenario << FakeAkonadiServer::defaultScenario() // << FakeAkonadiServer::selectCollectionScenario(QLatin1String("Collection B")) // << FakeAkonadiServer::selectResourceScenario(QLatin1String("akonadi_fake_resource_with_virtual_collections_0")) // << "C: 4 HRID UNLINK ((-1, \"virtual2\") (-1, \"virtual\") (-1, \"\")) RID \"H\"" // << "S: 4 OK LINK complete"; // notification.clearEntities(); // notification.addEntity(8, QLatin1String("H"), QString(), QLatin1String("application/octet-stream")); // QTest::newRow("HRID collection, RID items") << scenario << notification << false; } void testUnlink() { QFETCH(TestScenario::List, scenarios); QFETCH(Protocol::ItemChangeNotificationPtr, notification); QFETCH(bool, expectFail); FakeAkonadiServer::instance()->setScenarios(scenarios); FakeAkonadiServer::instance()->runTest(); auto notificationSpy = FakeAkonadiServer::instance()->notificationSpy(); if (notification->operation() != Protocol::ItemChangeNotification::InvalidOp) { QCOMPARE(notificationSpy->count(), 1); const auto notifications = notificationSpy->takeFirst().first().value(); QCOMPARE(notifications.count(), 1); QCOMPARE(*notifications.first().staticCast(), *notification); } else { QVERIFY(notificationSpy->isEmpty() || notificationSpy->takeFirst().first().value().isEmpty()); } Q_FOREACH (const auto &entity, notification->items()) { if (expectFail) { - QVERIFY(Collection::relatesToPimItem(notification->parentCollection(), entity->id())); + QVERIFY(Collection::relatesToPimItem(notification->parentCollection(), entity.id())); } else { - QVERIFY(!Collection::relatesToPimItem(notification->parentCollection(), entity->id())); + QVERIFY(!Collection::relatesToPimItem(notification->parentCollection(), entity.id())); } } } }; AKTEST_FAKESERVER_MAIN(LinkHandlerTest) #include "linkhandlertest.moc" diff --git a/autotests/server/modifyhandlertest.cpp b/autotests/server/modifyhandlertest.cpp index f88fa1249..580cd5dab 100644 --- a/autotests/server/modifyhandlertest.cpp +++ b/autotests/server/modifyhandlertest.cpp @@ -1,207 +1,206 @@ /* Copyright (c) 2014 Christian Mollekopf 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 #include #include #include "fakeakonadiserver.h" #include "aktest.h" #include "entities.h" #include #include #include using namespace Akonadi; using namespace Akonadi::Server; class ModifyHandlerTest : public QObject { Q_OBJECT public: ModifyHandlerTest() { try { FakeAkonadiServer::instance()->init(); } catch (const FakeAkonadiServerException &e) { qWarning() << "Server exception: " << e.what(); qFatal("Fake Akonadi Server failed to start up, aborting test"); } } ~ModifyHandlerTest() { FakeAkonadiServer::instance()->quit(); } private Q_SLOTS: void testModify_data() { QTest::addColumn("scenarios"); QTest::addColumn("expectedNotifications"); QTest::addColumn("newValue"); auto notificationTemplate = Protocol::CollectionChangeNotificationPtr::create(); notificationTemplate->setOperation(Protocol::CollectionChangeNotification::Modify); notificationTemplate->setParentCollection(4); notificationTemplate->setResource("akonadi_fake_resource_0"); notificationTemplate->setSessionId(FakeAkonadiServer::instanceName().toLatin1()); auto collectionTemplate = Protocol::FetchCollectionsResponsePtr::create(); collectionTemplate->setId(5); collectionTemplate->setRemoteId(QStringLiteral("ColD")); collectionTemplate->setRemoteRevision(QStringLiteral("")); collectionTemplate->setName(QStringLiteral("New Name")); collectionTemplate->setParentId(4); collectionTemplate->setResource(QStringLiteral("akonadi_fake_resource_0")); { auto cmd = Protocol::ModifyCollectionCommandPtr::create(5); cmd->setName(QStringLiteral("New Name")); TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, cmd) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::ModifyCollectionResponsePtr::create()); auto notification = Protocol::CollectionChangeNotificationPtr::create(*notificationTemplate); notification->setChangedParts(QSet() << "NAME"); - notification->setCollection(Protocol::FetchCollectionsResponsePtr::create(*collectionTemplate)); + notification->setCollection(*collectionTemplate); QTest::newRow("modify collection") << scenarios << Protocol::ChangeNotificationList{ notification } << QVariant::fromValue(QStringLiteral("New Name")); } { auto cmd = Protocol::ModifyCollectionCommandPtr::create(5); cmd->setEnabled(false); TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, cmd) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::ModifyCollectionResponsePtr::create()); - auto collection = Protocol::FetchCollectionsResponsePtr::create(*collectionTemplate); - collection->setEnabled(false); + Protocol::FetchCollectionsResponse collection(*collectionTemplate); + collection.setEnabled(false); auto notification = Protocol::CollectionChangeNotificationPtr::create(*notificationTemplate); notification->setChangedParts(QSet() << "ENABLED"); - notification->setCollection(collection); + notification->setCollection(std::move(collection)); auto unsubscribeNotification = Protocol::CollectionChangeNotificationPtr::create(*notificationTemplate); unsubscribeNotification->setOperation(Protocol::CollectionChangeNotification::Unsubscribe); unsubscribeNotification->setCollection(collection); QTest::newRow("disable collection") << scenarios << Protocol::ChangeNotificationList{ notification, unsubscribeNotification} << QVariant::fromValue(false); } { auto cmd = Protocol::ModifyCollectionCommandPtr::create(5); cmd->setEnabled(true); TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, cmd) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::ModifyCollectionResponsePtr::create()); - auto collection = Protocol::FetchCollectionsResponsePtr::create(*collectionTemplate); auto notification = Protocol::CollectionChangeNotificationPtr::create(*notificationTemplate); notification->setChangedParts(QSet() << "ENABLED"); - notification->setCollection(collection); + notification->setCollection(*collectionTemplate); auto subscribeNotification = Protocol::CollectionChangeNotificationPtr::create(*notificationTemplate); subscribeNotification->setOperation(Protocol::CollectionChangeNotification::Subscribe); - subscribeNotification->setCollection(collection); + subscribeNotification->setCollection(*collectionTemplate); QTest::newRow("enable collection") << scenarios << Protocol::ChangeNotificationList{ notification, subscribeNotification } << QVariant::fromValue(true); } { auto cmd = Protocol::ModifyCollectionCommandPtr::create(5); cmd->setEnabled(false); cmd->setSyncPref(Tristate::True); cmd->setDisplayPref(Tristate::True); cmd->setIndexPref(Tristate::True); TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, cmd) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::ModifyCollectionResponsePtr::create()); - auto collection = Protocol::FetchCollectionsResponsePtr::create(*collectionTemplate); - collection->setEnabled(false); - collection->setSyncPref(Tristate::True); - collection->setDisplayPref(Tristate::True); - collection->setIndexPref(Tristate::True); + Protocol::FetchCollectionsResponse collection(*collectionTemplate); + collection.setEnabled(false); + collection.setSyncPref(Tristate::True); + collection.setDisplayPref(Tristate::True); + collection.setIndexPref(Tristate::True); auto notification = Protocol::CollectionChangeNotificationPtr::create(*notificationTemplate); notification->setChangedParts(QSet() << "ENABLED" << "SYNC" << "DISPLAY" << "INDEX"); notification->setCollection(collection); auto unsubscribeNotification = Protocol::CollectionChangeNotificationPtr::create(*notificationTemplate); unsubscribeNotification->setOperation(Protocol::CollectionChangeNotification::Unsubscribe); - unsubscribeNotification->setCollection(collection); + unsubscribeNotification->setCollection(std::move(collection)); QTest::newRow("local override enable") << scenarios << Protocol::ChangeNotificationList{ notification, unsubscribeNotification } << QVariant::fromValue(true); } } void testModify() { QFETCH(TestScenario::List, scenarios); QFETCH(Protocol::ChangeNotificationList, expectedNotifications); QFETCH(QVariant, newValue); FakeAkonadiServer::instance()->setScenarios(scenarios); FakeAkonadiServer::instance()->runTest(); auto notificationSpy = FakeAkonadiServer::instance()->notificationSpy(); if (expectedNotifications.isEmpty()) { QVERIFY(notificationSpy->isEmpty() || notificationSpy->takeFirst().first().value().isEmpty()); return; } QCOMPARE(notificationSpy->count(), 1); //Only one notify call QCOMPARE(notificationSpy->first().count(), 1); const auto receivedNotifications = notificationSpy->first().first().value(); QCOMPARE(receivedNotifications.size(), expectedNotifications.count()); for (int i = 0; i < expectedNotifications.size(); i++) { const auto recvNtf = receivedNotifications.at(i).staticCast(); const auto expNtf = expectedNotifications.at(i).staticCast(); if (*recvNtf != *expNtf) { qDebug() << "Actual: " << Protocol::debugString(recvNtf); qDebug() << "Expected:" << Protocol::debugString(expNtf); } QCOMPARE(*recvNtf, *expNtf); const auto notification = receivedNotifications.at(i).staticCast(); if (notification->changedParts().contains("NAME")) { - Collection col = Collection::retrieveById(notification->collection()->id()); + Collection col = Collection::retrieveById(notification->collection().id()); QCOMPARE(col.name(), newValue.toString()); } if (!notification->changedParts().intersects({ "ENABLED", "SYNC", "DISPLAY", "INDEX" })) { - Collection col = Collection::retrieveById(notification->collection()->id()); + Collection col = Collection::retrieveById(notification->collection().id()); const bool sync = col.syncPref() == Collection::Undefined ? col.enabled() : col.syncPref() == Collection::True; QCOMPARE(sync, newValue.toBool()); const bool display = col.displayPref() == Collection::Undefined ? col.enabled() : col.displayPref() == Collection::True; QCOMPARE(display, newValue.toBool()); const bool index = col.indexPref() == Collection::Undefined ? col.enabled() : col.indexPref() == Collection::True; QCOMPARE(index, newValue.toBool()); } } } }; AKTEST_FAKESERVER_MAIN(ModifyHandlerTest) #include "modifyhandlertest.moc" diff --git a/autotests/server/movehandlertest.cpp b/autotests/server/movehandlertest.cpp index ad03c797e..56c3a7be9 100644 --- a/autotests/server/movehandlertest.cpp +++ b/autotests/server/movehandlertest.cpp @@ -1,155 +1,155 @@ /* Copyright (c) 2016 Daniel Vrátil 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 #include #include #include "fakeakonadiserver.h" #include "aktest.h" #include "entities.h" #include #include #include using namespace Akonadi; using namespace Akonadi::Server; class MoveHandlerTest : public QObject { Q_OBJECT public: MoveHandlerTest() { try { FakeAkonadiServer::instance()->init(); } catch (const FakeAkonadiServerException &e) { qWarning() << "Server exception: " << e.what(); qFatal("Fake Akonadi Server failed to start up, aborting test"); } } ~MoveHandlerTest() { FakeAkonadiServer::instance()->quit(); } - Protocol::FetchItemsResponsePtr fetchResponse(quint64 id, const QString &rid, const QString &rrev, const QString &mt) + Protocol::FetchItemsResponse fetchResponse(quint64 id, const QString &rid, const QString &rrev, const QString &mt) { - auto item = Protocol::FetchItemsResponsePtr::create(); - item->setId(id); - item->setRemoteId(rid); - item->setRemoteRevision(rrev); - item->setMimeType(mt); + Protocol::FetchItemsResponse item; + item.setId(id); + item.setRemoteId(rid); + item.setRemoteRevision(rrev); + item.setMimeType(mt); return item; } private Q_SLOTS: void testMove_data() { const Collection srcCol = Collection::retrieveByName(QStringLiteral("Collection B")); const Collection destCol = Collection::retrieveByName(QStringLiteral("Collection A")); QTest::addColumn("scenarios"); QTest::addColumn("expectedNotifications"); QTest::addColumn("newValue"); auto notificationTemplate = Protocol::ItemChangeNotificationPtr::create(); notificationTemplate->setOperation(Protocol::ItemChangeNotification::Move); notificationTemplate->setResource("akonadi_fake_resource_0"); notificationTemplate->setDestinationResource("akonadi_fake_resource_0"); notificationTemplate->setSessionId(FakeAkonadiServer::instanceName().toLatin1()); notificationTemplate->setParentCollection(srcCol.id()); notificationTemplate->setParentDestCollection(destCol.id()); { auto cmd = Protocol::MoveItemsCommandPtr::create(1, destCol.id()); TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, cmd) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::MoveItemsResponsePtr::create()); auto notification = Protocol::ItemChangeNotificationPtr::create(*notificationTemplate); notification->setItems({ fetchResponse(1, QStringLiteral("A"), QString(), QStringLiteral("application/octet-stream")) }); QTest::newRow("move item") << scenarios << Protocol::ChangeNotificationList{ notification } << QVariant::fromValue(destCol.id()); } { auto cmd = Protocol::MoveItemsCommandPtr::create(QVector{ 2, 3 }, destCol.id()); TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, cmd) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::MoveItemsResponsePtr::create()); auto notification = Protocol::ItemChangeNotificationPtr::create(*notificationTemplate); notification->setItems({ fetchResponse(3, QStringLiteral("C"), QString(), QStringLiteral("application/octet-stream")), fetchResponse(2, QStringLiteral("B"), QString(), QStringLiteral("application/octet-stream")) }); QTest::newRow("move items") << scenarios << Protocol::ChangeNotificationList{ notification } << QVariant::fromValue(destCol.id()); } } void testMove() { QFETCH(TestScenario::List, scenarios); QFETCH(Protocol::ChangeNotificationList, expectedNotifications); QFETCH(QVariant, newValue); FakeAkonadiServer::instance()->setScenarios(scenarios); FakeAkonadiServer::instance()->runTest(); auto notificationSpy = FakeAkonadiServer::instance()->notificationSpy(); if (expectedNotifications.isEmpty()) { QVERIFY(notificationSpy->isEmpty() || notificationSpy->takeFirst().first().value().isEmpty()); return; } QCOMPARE(notificationSpy->count(), 1); //Only one notify call QCOMPARE(notificationSpy->first().count(), 1); const auto receivedNotifications = notificationSpy->first().first().value(); QCOMPARE(receivedNotifications.size(), expectedNotifications.count()); for (int i = 0; i < expectedNotifications.size(); i++) { QCOMPARE(*receivedNotifications.at(i).staticCast(), *expectedNotifications.at(i).staticCast()); const auto notification = receivedNotifications.at(i).staticCast(); QCOMPARE(notification->parentDestCollection(), newValue.toInt()); Q_FOREACH (const auto &ntfItem, notification->items()) { - const PimItem item = PimItem::retrieveById(ntfItem->id()); + const PimItem item = PimItem::retrieveById(ntfItem.id()); QCOMPARE(item.collectionId(), newValue.toInt()); } } } }; AKTEST_FAKESERVER_MAIN(MoveHandlerTest) #include "movehandlertest.moc" diff --git a/autotests/server/notificationmanagertest.cpp b/autotests/server/notificationmanagertest.cpp index f8843cba7..f57ff47fa 100644 --- a/autotests/server/notificationmanagertest.cpp +++ b/autotests/server/notificationmanagertest.cpp @@ -1,400 +1,404 @@ /* Copyright (c) 2013 Daniel Vrátil 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 #include "entities.h" #include "notificationmanager.h" #include "notificationsubscriber.h" #include #include using namespace Akonadi; using namespace Akonadi::Server; Q_DECLARE_METATYPE(QVector) class TestableNotificationSubscriber : public NotificationSubscriber { public: TestableNotificationSubscriber() : NotificationSubscriber() { mSubscriber = "TestSubscriber"; } void setAllMonitored(bool allMonitored) { mAllMonitored = allMonitored; } void setMonitoredCollection(qint64 collection, bool monitored) { if (monitored) { mMonitoredCollections.insert(collection); } else { mMonitoredCollections.remove(collection); } } void setMonitoredItem(qint64 item, bool monitored) { if (monitored) { mMonitoredItems.insert(item); } else { mMonitoredItems.remove(item); } } void setMonitoredResource(const QByteArray &resource, bool monitored) { if (monitored) { mMonitoredResources.insert(resource); } else { mMonitoredResources.remove(resource); } } void setMonitoredMimeType(const QString &mimeType, bool monitored) { if (monitored) { mMonitoredMimeTypes.insert(mimeType); } else { mMonitoredMimeTypes.remove(mimeType); } } void setIgnoredSession(const QByteArray &session, bool ignored) { if (ignored) { mIgnoredSessions.insert(session); } else { mIgnoredSessions.remove(session); } } void writeNotification(const Protocol::ChangeNotificationPtr ¬ification) override { emittedNotifications << notification; } Protocol::ChangeNotificationList emittedNotifications; }; class NotificationManagerTest : public QObject { Q_OBJECT typedef QList NSList; - Protocol::FetchItemsResponsePtr itemResponse(qint64 id, const QString &rid, const QString &rrev, const QString &mt) + Protocol::FetchItemsResponse itemResponse(qint64 id, const QString &rid, const QString &rrev, const QString &mt) { - auto item = Protocol::FetchItemsResponsePtr::create(); - item->setId(id); - item->setRemoteId(rid); - item->setRemoteRevision(rrev); - item->setMimeType(mt); + Protocol::FetchItemsResponse item; + item.setId(id); + item.setRemoteId(rid); + item.setRemoteRevision(rrev); + item.setMimeType(mt); return item; } private Q_SLOTS: void testSourceFilter_data() { qRegisterMetaType(); QTest::addColumn("allMonitored"); QTest::addColumn >("monitoredCollections"); QTest::addColumn >("monitoredItems"); QTest::addColumn >("monitoredResources"); QTest::addColumn >("monitoredMimeTypes"); QTest::addColumn >("ignoredSessions"); QTest::addColumn("notification"); QTest::addColumn("accepted"); #define EmptyList(T) (QVector()) #define List(T,x) (QVector() << x) auto itemMsg = Protocol::ItemChangeNotificationPtr::create(); itemMsg->setOperation(Protocol::ItemChangeNotification::Add); itemMsg->setParentCollection(1); QTest::newRow("monitorAll vs notification without items") << true << EmptyList(Entity::Id) << EmptyList(Entity::Id) << EmptyList(QByteArray) << EmptyList(QString) << EmptyList(QByteArray) << itemMsg.staticCast() << false; itemMsg = Protocol::ItemChangeNotificationPtr::create(*itemMsg); itemMsg->setItems({ itemResponse(1, QString(), QString(), QStringLiteral("message/rfc822")) }); QTest::newRow("monitorAll vs notification with one item") << true << EmptyList(Entity::Id) << EmptyList(Entity::Id) << EmptyList(QByteArray) << EmptyList(QString) << EmptyList(QByteArray) << itemMsg.staticCast() << true; QTest::newRow("item monitored but different mimetype") << false << EmptyList(Entity::Id) << List(Entity::Id, 1 << 2) << EmptyList(QByteArray) << List(QString, QStringLiteral("random/mimetype")) << EmptyList(QByteArray) << Protocol::ItemChangeNotificationPtr::create(*itemMsg).staticCast() << false; QTest::newRow("item not monitored, but mimetype matches") << false << EmptyList(Entity::Id) << EmptyList(Entity::Id) << EmptyList(QByteArray) << List(QString, QStringLiteral("message/rfc822")) << EmptyList(QByteArray) << Protocol::ItemChangeNotificationPtr::create(*itemMsg).staticCast() << true; itemMsg = Protocol::ItemChangeNotificationPtr::create(*itemMsg); itemMsg->setSessionId("testSession"); QTest::newRow("item monitored but session ignored") << false << EmptyList(Entity::Id) << List(Entity::Id, 1) << EmptyList(QByteArray) << EmptyList(QString) << List(QByteArray, "testSession") << itemMsg.staticCast() << false; // Simulate adding a new resource auto colMsg = Protocol::CollectionChangeNotificationPtr::create(); colMsg->setOperation(Protocol::CollectionChangeNotification::Add); - auto col = Protocol::FetchCollectionsResponsePtr::create(); - col->setId(1); - col->setRemoteId(QStringLiteral("imap://user@some.domain/")); - colMsg->setCollection(col); + Protocol::FetchCollectionsResponse col; + col.setId(1); + col.setRemoteId(QStringLiteral("imap://user@some.domain/")); + colMsg->setCollection(std::move(col)); colMsg->setParentCollection(0); colMsg->setSessionId("akonadi_imap_resource_0"); colMsg->setResource("akonadi_imap_resource_0"); QTest::newRow("new root collection in non-monitored resource") << false << List(Entity::Id, 0) << EmptyList(Entity::Id) << List(QByteArray, "akonadi_search_resource") << List(QString, QStringLiteral("message/rfc822")) << EmptyList(QByteArray) << colMsg.staticCast() << true; itemMsg = Protocol::ItemChangeNotificationPtr::create(); itemMsg->setOperation(Protocol::ItemChangeNotification::Move); itemMsg->setResource("akonadi_resource_1"); itemMsg->setDestinationResource("akonadi_resource_2"); itemMsg->setParentCollection(1); itemMsg->setParentDestCollection(2); itemMsg->setSessionId("kmail"); itemMsg->setItems({ itemResponse(10, QStringLiteral("123"), QStringLiteral("1"), QStringLiteral("message/rfc822")) }); QTest::newRow("inter-resource move, source source") << false << EmptyList(Entity::Id) << EmptyList(Entity::Id) << List(QByteArray, "akonadi_resource_1") << List(QString, QStringLiteral("message/rfc822")) << List(QByteArray, "akonadi_resource_1") << itemMsg.staticCast() << true; QTest::newRow("inter-resource move, destination source") << false << EmptyList(Entity::Id) << EmptyList(Entity::Id) << List(QByteArray, "akonadi_resource_2") << List(QString, QStringLiteral("message/rfc822")) << List(QByteArray, "akonadi_resource_2") << itemMsg.staticCast() << true; QTest::newRow("inter-resource move, uninterested party") << false << List(Entity::Id, 12) << EmptyList(Entity::Id) << EmptyList(QByteArray) << List(QString, QStringLiteral("inode/directory")) << EmptyList(QByteArray) << itemMsg.staticCast() << false; itemMsg = Protocol::ItemChangeNotificationPtr::create(); itemMsg->setOperation(Protocol::ItemChangeNotification::Move); itemMsg->setResource("akonadi_resource_0"); itemMsg->setDestinationResource("akonadi_resource_0"); itemMsg->setParentCollection(1); itemMsg->setParentDestCollection(2); itemMsg->setSessionId("kmail"); itemMsg->setItems({ itemResponse(10, QStringLiteral("123"), QStringLiteral("1"), QStringLiteral("message/rfc822")), itemResponse(11, QStringLiteral("456"), QStringLiteral("1"), QStringLiteral("message/rfc822")) }); QTest::newRow("intra-resource move, owning resource") << false << EmptyList(Entity::Id) << EmptyList(Entity::Id) << List(QByteArray, "akonadi_imap_resource_0") << List(QString, QStringLiteral("message/rfc822")) << List(QByteArray, "akonadi_imap_resource_0") << itemMsg.staticCast() << true; colMsg = Protocol::CollectionChangeNotificationPtr::create(); colMsg->setOperation(Protocol::CollectionChangeNotification::Add); colMsg->setSessionId("kmail"); colMsg->setResource("akonadi_resource_1"); colMsg->setParentCollection(1); QTest::newRow("new subfolder") << false << List(Entity::Id, 0) << EmptyList(Entity::Id) << EmptyList(QByteArray) << List(QString, QStringLiteral("message/rfc822")) << EmptyList(QByteArray) << colMsg.staticCast() << false; itemMsg = Protocol::ItemChangeNotificationPtr::create(); itemMsg->setOperation(Protocol::ItemChangeNotification::Add); itemMsg->setSessionId("randomSession"); itemMsg->setResource("randomResource"); itemMsg->setParentCollection(1); itemMsg->setItems({ itemResponse(10, QString(), QString(), QStringLiteral("message/rfc822")) }); QTest::newRow("new mail for mailfilter or maildispatcher") << false << List(Entity::Id, 0) << EmptyList(Entity::Id) << EmptyList(QByteArray) << List(QString, QStringLiteral("message/rfc822")) << EmptyList(QByteArray) << itemMsg.staticCast() << true; auto tagMsg = Protocol::TagChangeNotificationPtr::create(); tagMsg->setOperation(Protocol::TagChangeNotification::Remove); tagMsg->setSessionId("randomSession"); tagMsg->setResource("akonadi_random_resource_0"); - auto tagMsgTag = Protocol::FetchTagsResponsePtr::create(); - tagMsgTag->setId(1); - tagMsgTag->setRemoteId("TAG"); - tagMsg->setTag(tagMsgTag); + { + Protocol::FetchTagsResponse tagMsgTag; + tagMsgTag.setId(1); + tagMsgTag.setRemoteId("TAG"); + tagMsg->setTag(std::move(tagMsgTag)); + } QTest::newRow("Tag removal - resource notification - matching resource source") << false << EmptyList(Entity::Id) << EmptyList(Entity::Id) << EmptyList(QByteArray) << EmptyList(QString) << List(QByteArray, "akonadi_random_resource_0") << tagMsg.staticCast() << true; QTest::newRow("Tag removal - resource notification - wrong resource source") << false << EmptyList(Entity::Id) << EmptyList(Entity::Id) << EmptyList(QByteArray) << EmptyList(QString) << List(QByteArray, "akonadi_another_resource_1") << tagMsg.staticCast() << false; tagMsg = Protocol::TagChangeNotificationPtr::create(); tagMsg->setOperation(Protocol::TagChangeNotification::Remove); tagMsg->setSessionId("randomSession"); - tagMsgTag = Protocol::FetchTagsResponsePtr::create(); - tagMsgTag->setId(1); - tagMsgTag->setRemoteId("TAG"); - tagMsg->setTag(tagMsgTag); + { + Protocol::FetchTagsResponse tagMsgTag; + tagMsgTag.setId(1); + tagMsgTag.setRemoteId("TAG"); + tagMsg->setTag(std::move(tagMsgTag)); + } QTest::newRow("Tag removal - client notification - client source") << false << EmptyList(Entity::Id) << EmptyList(Entity::Id) << EmptyList(QByteArray) << EmptyList(QString) << EmptyList(QByteArray) << tagMsg.staticCast() << true; QTest::newRow("Tag removal - client notification - resource source") << false << EmptyList(Entity::Id) << EmptyList(Entity::Id) << EmptyList(QByteArray) << EmptyList(QString) << List( QByteArray, "akonadi_some_resource_0" ) << tagMsg.staticCast() << false; } void testSourceFilter() { QFETCH(bool, allMonitored); QFETCH(QVector, monitoredCollections); QFETCH(QVector, monitoredItems); QFETCH(QVector, monitoredResources); QFETCH(QVector, monitoredMimeTypes); QFETCH(QVector, ignoredSessions); QFETCH(Protocol::ChangeNotificationPtr, notification); QFETCH(bool, accepted); TestableNotificationSubscriber subscriber; subscriber.setAllMonitored(allMonitored); Q_FOREACH (Entity::Id id, monitoredCollections) { subscriber.setMonitoredCollection(id, true); } Q_FOREACH (Entity::Id id, monitoredItems) { subscriber.setMonitoredItem(id, true); } Q_FOREACH (const QByteArray &res, monitoredResources) { subscriber.setMonitoredResource(res, true); } Q_FOREACH (const QString &mimeType, monitoredMimeTypes) { subscriber.setMonitoredMimeType(mimeType, true); } Q_FOREACH (const QByteArray &session, ignoredSessions) { subscriber.setIgnoredSession(session, true); } subscriber.notify({ notification }); QTRY_COMPARE(subscriber.emittedNotifications.count(), accepted ? 1 : 0); if (accepted) { const Protocol::ChangeNotificationPtr ntf = subscriber.emittedNotifications.at(0); QVERIFY(ntf->isValid()); } } }; AKTEST_MAIN(NotificationManagerTest) #include "notificationmanagertest.moc" diff --git a/autotests/server/relationhandlertest.cpp b/autotests/server/relationhandlertest.cpp index cb1d50d36..4b32bccb1 100644 --- a/autotests/server/relationhandlertest.cpp +++ b/autotests/server/relationhandlertest.cpp @@ -1,405 +1,405 @@ /* Copyright (c) 2014 Christian Mollekopf 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 #include #include "fakeakonadiserver.h" #include "aktest.h" #include "entities.h" #include "dbinitializer.h" #include using namespace Akonadi; using namespace Akonadi::Server; Q_DECLARE_METATYPE(Akonadi::Server::Relation::List) Q_DECLARE_METATYPE(Akonadi::Server::Relation) static Protocol::ChangeNotificationList extractNotifications(const QSharedPointer ¬ificationSpy) { Protocol::ChangeNotificationList receivedNotifications; for (int q = 0; q < notificationSpy->size(); q++) { //Only one notify call if (notificationSpy->at(q).count() != 1) { qWarning() << "Error: We're assuming only one notify call."; return Protocol::ChangeNotificationList(); } const Protocol::ChangeNotificationList n = notificationSpy->at(q).first().value(); for (int i = 0; i < n.size(); i++) { // qDebug() << n.at(i); receivedNotifications.append(n.at(i)); } } return receivedNotifications; } class RelationHandlerTest : public QObject { Q_OBJECT public: RelationHandlerTest() : QObject() { qRegisterMetaType(); try { FakeAkonadiServer::instance()->setPopulateDb(false); FakeAkonadiServer::instance()->init(); } catch (const FakeAkonadiServerException &e) { qDebug() << "Server exception: " << e.what(); qFatal("Fake Akonadi Server failed to start up, aborting test"); } RelationType type; type.setName(QStringLiteral("type")); type.insert(); RelationType type2; type2.setName(QStringLiteral("type2")); type2.insert(); } ~RelationHandlerTest() { FakeAkonadiServer::instance()->quit(); } QScopedPointer initializer; Protocol::RelationChangeNotificationPtr relationNotification(Protocol::RelationChangeNotification::Operation op, const PimItem &item1, const PimItem &item2, const QString &rid, const QString &type = QStringLiteral("type")) { auto notification = Protocol::RelationChangeNotificationPtr::create(); notification->setOperation(op); notification->setSessionId(FakeAkonadiServer::instanceName().toLatin1()); - auto relation = Protocol::FetchRelationsResponsePtr::create(); - relation->setLeft(item1.id()); - relation->setLeftMimeType(item1.mimeType().name().toLatin1()); - relation->setRight(item2.id()); - relation->setRightMimeType(item2.mimeType().name().toLatin1()); - relation->setRemoteId(rid.toLatin1()); - relation->setType(type.toLatin1()); - notification->setRelation(relation); + Protocol::FetchRelationsResponse relation; + relation.setLeft(item1.id()); + relation.setLeftMimeType(item1.mimeType().name().toLatin1()); + relation.setRight(item2.id()); + relation.setRightMimeType(item2.mimeType().name().toLatin1()); + relation.setRemoteId(rid.toLatin1()); + relation.setType(type.toLatin1()); + notification->setRelation(std::move(relation)); return notification; } private Q_SLOTS: void testStoreRelation_data() { initializer.reset(new DbInitializer); Resource res = initializer->createResource("testresource"); Collection col1 = initializer->createCollection("col1"); PimItem item1 = initializer->createItem("item1", col1); PimItem item2 = initializer->createItem("item2", col1); QTest::addColumn("scenarios"); QTest::addColumn("expectedRelations"); QTest::addColumn("expectedNotifications"); { TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, Protocol::ModifyRelationCommandPtr::create(item1.id(), item2.id(), "type")) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::ModifyRelationResponsePtr::create()); Relation rel; rel.setLeftId(item1.id()); rel.setRightId(item2.id()); RelationType type; type.setName(QStringLiteral("type")); rel.setRelationType(type); auto itemNotification = Protocol::ItemChangeNotificationPtr::create(); itemNotification->setOperation(Protocol::ItemChangeNotification::ModifyRelations); itemNotification->setSessionId(FakeAkonadiServer::instanceName().toLatin1()); itemNotification->setResource("testresource"); itemNotification->setParentCollection(col1.id()); - itemNotification->setItems({ initializer->fetchResponse(item1), initializer->fetchResponse(item2) }); + itemNotification->setItems({ *initializer->fetchResponse(item1), *initializer->fetchResponse(item2) }); itemNotification->setAddedRelations({ Protocol::ItemChangeNotification::Relation(item1.id(), item2.id(), QStringLiteral("type")) }); const auto notification = relationNotification(Protocol::RelationChangeNotification::Add, item1, item2, rel.remoteId()); QTest::newRow("uid create relation") << scenarios << (Relation::List() << rel) << (Protocol::ChangeNotificationList() << notification << itemNotification); } } void testStoreRelation() { QFETCH(TestScenario::List, scenarios); QFETCH(Relation::List, expectedRelations); QFETCH(Protocol::ChangeNotificationList, expectedNotifications); FakeAkonadiServer::instance()->setScenarios(scenarios); FakeAkonadiServer::instance()->runTest(); const auto receivedNotifications = extractNotifications(FakeAkonadiServer::instance()->notificationSpy()); QCOMPARE(receivedNotifications.size(), expectedNotifications.count()); for (int i = 0; i < expectedNotifications.size(); i++) { QCOMPARE(*receivedNotifications.at(i), *expectedNotifications.at(i)); } const Relation::List relations = Relation::retrieveAll(); // Q_FOREACH (const Relation &rel, relations) { // akDebug() << rel.leftId() << rel.rightId(); // } QCOMPARE(relations.size(), expectedRelations.size()); for (int i = 0; i < relations.size(); i++) { QCOMPARE(relations.at(i).leftId(), expectedRelations.at(i).leftId()); QCOMPARE(relations.at(i).rightId(), expectedRelations.at(i).rightId()); // QCOMPARE(relations.at(i).typeId(), expectedRelations.at(i).typeId()); QCOMPARE(relations.at(i).remoteId(), expectedRelations.at(i).remoteId()); } QueryBuilder qb(Relation::tableName(), QueryBuilder::Delete); qb.exec(); } void testRemoveRelation_data() { initializer.reset(new DbInitializer); QCOMPARE(Relation::retrieveAll().size(), 0); Resource res = initializer->createResource("testresource"); Collection col1 = initializer->createCollection("col1"); PimItem item1 = initializer->createItem("item1", col1); PimItem item2 = initializer->createItem("item2", col1); Relation rel; rel.setLeftId(item1.id()); rel.setRightId(item2.id()); rel.setRelationType(RelationType::retrieveByName(QStringLiteral("type"))); QVERIFY(rel.insert()); Relation rel2; rel2.setLeftId(item1.id()); rel2.setRightId(item2.id()); rel2.setRelationType(RelationType::retrieveByName(QStringLiteral("type2"))); QVERIFY(rel2.insert()); QTest::addColumn("scenarios"); QTest::addColumn("expectedRelations"); QTest::addColumn("expectedNotifications"); { TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, Protocol::RemoveRelationsCommandPtr::create(item1.id(), item2.id(), "type")) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::RemoveRelationsResponsePtr::create()); auto itemNotification = Protocol::ItemChangeNotificationPtr::create(); itemNotification->setOperation(Protocol::ItemChangeNotification::ModifyRelations); itemNotification->setSessionId(FakeAkonadiServer::instanceName().toLatin1()); itemNotification->setResource("testresource"); itemNotification->setParentCollection(col1.id()); - itemNotification->setItems({ initializer->fetchResponse(item1), initializer->fetchResponse(item2) }); + itemNotification->setItems({ *initializer->fetchResponse(item1), *initializer->fetchResponse(item2) }); itemNotification->setRemovedRelations({ Protocol::ItemChangeNotification::Relation(item1.id(), item2.id(), QStringLiteral("type")) }); const auto notification = relationNotification(Protocol::RelationChangeNotification::Remove, item1, item2, rel.remoteId()); QTest::newRow("uid remove relation") << scenarios << (Relation::List() << rel2) << (Protocol::ChangeNotificationList() << notification << itemNotification); } { TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, Protocol::RemoveRelationsCommandPtr::create(item1.id(), item2.id())) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::RemoveRelationsResponsePtr::create()); auto itemNotification = Protocol::ItemChangeNotificationPtr::create(); itemNotification->setOperation(Protocol::ItemChangeNotification::ModifyRelations); itemNotification->setSessionId(FakeAkonadiServer::instanceName().toLatin1()); itemNotification->setResource("testresource"); itemNotification->setParentCollection(col1.id()); - itemNotification->setItems({ initializer->fetchResponse(item1), initializer->fetchResponse(item2) }); + itemNotification->setItems({ *initializer->fetchResponse(item1), *initializer->fetchResponse(item2) }); itemNotification->setRemovedRelations({ Protocol::ItemChangeNotification::Relation(item1.id(), item2.id(), QStringLiteral("type2")) }); const auto notification = relationNotification(Protocol::RelationChangeNotification::Remove, item1, item2, rel.remoteId(), QStringLiteral("type2")); QTest::newRow("uid remove relation without type") << scenarios << Relation::List() << (Protocol::ChangeNotificationList() << notification << itemNotification); } } void testRemoveRelation() { QFETCH(TestScenario::List, scenarios); QFETCH(Relation::List, expectedRelations); QFETCH(Protocol::ChangeNotificationList, expectedNotifications); FakeAkonadiServer::instance()->setScenarios(scenarios); FakeAkonadiServer::instance()->runTest(); const auto receivedNotifications = extractNotifications(FakeAkonadiServer::instance()->notificationSpy()); QCOMPARE(receivedNotifications.size(), expectedNotifications.count()); for (int i = 0; i < expectedNotifications.size(); i++) { QCOMPARE(*receivedNotifications.at(i), *expectedNotifications.at(i)); } const Relation::List relations = Relation::retrieveAll(); // Q_FOREACH (const Relation &rel, relations) { // akDebug() << rel.leftId() << rel.rightId() << rel.relationType().name() << rel.remoteId(); // } QCOMPARE(relations.size(), expectedRelations.size()); for (int i = 0; i < relations.size(); i++) { QCOMPARE(relations.at(i).leftId(), expectedRelations.at(i).leftId()); QCOMPARE(relations.at(i).rightId(), expectedRelations.at(i).rightId()); QCOMPARE(relations.at(i).typeId(), expectedRelations.at(i).typeId()); QCOMPARE(relations.at(i).remoteId(), expectedRelations.at(i).remoteId()); } } void testListRelation_data() { QueryBuilder qb(Relation::tableName(), QueryBuilder::Delete); qb.exec(); initializer.reset(new DbInitializer); QCOMPARE(Relation::retrieveAll().size(), 0); Resource res = initializer->createResource("testresource"); Collection col1 = initializer->createCollection("col1"); PimItem item1 = initializer->createItem("item1", col1); PimItem item2 = initializer->createItem("item2", col1); PimItem item3 = initializer->createItem("item3", col1); PimItem item4 = initializer->createItem("item4", col1); Relation rel; rel.setLeftId(item1.id()); rel.setRightId(item2.id()); rel.setRelationType(RelationType::retrieveByName(QStringLiteral("type"))); rel.setRemoteId(QStringLiteral("foobar1")); QVERIFY(rel.insert()); Relation rel2; rel2.setLeftId(item1.id()); rel2.setRightId(item2.id()); rel2.setRelationType(RelationType::retrieveByName(QStringLiteral("type2"))); rel2.setRemoteId(QStringLiteral("foobar2")); QVERIFY(rel2.insert()); Relation rel3; rel3.setLeftId(item3.id()); rel3.setRightId(item4.id()); rel3.setRelationType(RelationType::retrieveByName(QStringLiteral("type"))); rel3.setRemoteId(QStringLiteral("foobar3")); QVERIFY(rel3.insert()); Relation rel4; rel4.setLeftId(item4.id()); rel4.setRightId(item3.id()); rel4.setRelationType(RelationType::retrieveByName(QStringLiteral("type"))); rel4.setRemoteId(QStringLiteral("foobar4")); QVERIFY(rel4.insert()); QTest::addColumn("scenarios"); { TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, Protocol::FetchRelationsCommandPtr::create(-1, QVector{ "type" })) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::FetchRelationsResponsePtr::create(item1.id(), item1.mimeType().name().toUtf8(), item2.id(), item2.mimeType().name().toUtf8(), "type", "foobar1")) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::FetchRelationsResponsePtr::create(item3.id(), item3.mimeType().name().toUtf8(), item4.id(), item4.mimeType().name().toUtf8(), "type", "foobar3")) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::FetchRelationsResponsePtr::create(item4.id(), item4.mimeType().name().toUtf8(), item3.id(), item3.mimeType().name().toUtf8(), "type", "foobar4")) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::FetchRelationsResponsePtr::create()); QTest::newRow("filter by type") << scenarios; } { TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, Protocol::FetchRelationsCommandPtr::create()) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::FetchRelationsResponsePtr::create(item1.id(), item1.mimeType().name().toUtf8(), item2.id(), item2.mimeType().name().toUtf8(), "type", "foobar1")) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::FetchRelationsResponsePtr::create(item1.id(), item1.mimeType().name().toUtf8(), item2.id(), item2.mimeType().name().toUtf8(), "type2", "foobar2")) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::FetchRelationsResponsePtr::create(item3.id(), item3.mimeType().name().toUtf8(), item4.id(), item4.mimeType().name().toUtf8(), "type", "foobar3")) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::FetchRelationsResponsePtr::create(item4.id(), item4.mimeType().name().toUtf8(), item3.id(), item3.mimeType().name().toUtf8(), "type", "foobar4")) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::FetchRelationsResponsePtr::create()); QTest::newRow("no filter") << scenarios; } { TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, Protocol::FetchRelationsCommandPtr::create(-1, QVector{}, QLatin1String("testresource"))) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::FetchRelationsResponsePtr::create(item1.id(), item1.mimeType().name().toUtf8(), item2.id(), item2.mimeType().name().toUtf8(), "type", "foobar1")) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::FetchRelationsResponsePtr::create(item1.id(), item1.mimeType().name().toUtf8(), item2.id(), item2.mimeType().name().toUtf8(), "type2", "foobar2")) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::FetchRelationsResponsePtr::create(item3.id(), item3.mimeType().name().toUtf8(), item4.id(), item4.mimeType().name().toUtf8(), "type", "foobar3")) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::FetchRelationsResponsePtr::create(item4.id(), item4.mimeType().name().toUtf8(), item3.id(), item3.mimeType().name().toUtf8(), "type", "foobar4")) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::FetchRelationsResponsePtr::create()); QTest::newRow("filter by resource with matching resource") << scenarios; } { Resource res; res.setName(QStringLiteral("testresource2")); res.insert(); TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, Protocol::FetchRelationsCommandPtr::create(-1, QVector{}, QLatin1String("testresource2"))) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::FetchRelationsResponsePtr::create()); QTest::newRow("filter by resource with nonmatching resource") << scenarios; } { TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, Protocol::FetchRelationsCommandPtr::create(item1.id(), -1, QVector{ "type" })) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::FetchRelationsResponsePtr::create(item1.id(), item1.mimeType().name().toUtf8(), item2.id(), item2.mimeType().name().toUtf8(), "type", "foobar1")) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::FetchRelationsResponsePtr::create()); QTest::newRow("filter by left and type") << scenarios; } { TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, Protocol::FetchRelationsCommandPtr::create(-1, item2.id(), QVector{ "type" })) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::FetchRelationsResponsePtr::create(item1.id(), item1.mimeType().name().toUtf8(), item2.id(), item2.mimeType().name().toUtf8(), "type", "foobar1")) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::FetchRelationsResponsePtr::create()); QTest::newRow("filter by right and type") << scenarios; } { TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, Protocol::FetchRelationsCommandPtr::create(item3.id(), QVector{ "type" })) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::FetchRelationsResponsePtr::create(item3.id(), item3.mimeType().name().toUtf8(), item4.id(), item4.mimeType().name().toUtf8(), "type", "foobar3")) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::FetchRelationsResponsePtr::create(item4.id(), item4.mimeType().name().toUtf8(), item3.id(), item3.mimeType().name().toUtf8(), "type", "foobar4")) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::FetchRelationsResponsePtr::create()); QTest::newRow("fetch by side with typefilter") << scenarios; } } void testListRelation() { QFETCH(TestScenario::List, scenarios); FakeAkonadiServer::instance()->setScenarios(scenarios); FakeAkonadiServer::instance()->runTest(); } }; AKTEST_FAKESERVER_MAIN(RelationHandlerTest) #include "relationhandlertest.moc" diff --git a/autotests/server/taghandlertest.cpp b/autotests/server/taghandlertest.cpp index b98668d8b..f4a965913 100644 --- a/autotests/server/taghandlertest.cpp +++ b/autotests/server/taghandlertest.cpp @@ -1,483 +1,475 @@ /* Copyright (c) 2014 Christian Mollekopf 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 #include #include "fakeakonadiserver.h" #include "aktest.h" #include "entities.h" #include "dbinitializer.h" #include #include using namespace Akonadi; using namespace Akonadi::Server; typedef QPair TagTagAttributeListPair; Q_DECLARE_METATYPE(Akonadi::Server::Tag::List) Q_DECLARE_METATYPE(Akonadi::Server::Tag) Q_DECLARE_METATYPE(QVector) static Protocol::ChangeNotificationList extractNotifications(const QSharedPointer ¬ificationSpy) { Protocol::ChangeNotificationList receivedNotifications; for (int q = 0; q < notificationSpy->size(); q++) { //Only one notify call if (notificationSpy->at(q).count() != 1) { qWarning() << "Error: We're assuming only one notify call."; return Protocol::ChangeNotificationList(); } const auto n = notificationSpy->at(q).first().value(); for (int i = 0; i < n.size(); i++) { // qDebug() << n.at(i); receivedNotifications.append(n.at(i)); } } return receivedNotifications; } class TagHandlerTest : public QObject { Q_OBJECT public: TagHandlerTest() : QObject() { qRegisterMetaType(); try { FakeAkonadiServer::instance()->setPopulateDb(false); FakeAkonadiServer::instance()->init(); } catch (const FakeAkonadiServerException &e) { qWarning() << "Server exception: " << e.what(); qFatal("Fake Akonadi Server failed to start up, aborting test"); } } ~TagHandlerTest() { FakeAkonadiServer::instance()->quit(); } Protocol::FetchTagsResponsePtr createResponse(const Tag &tag, const QByteArray &remoteId = QByteArray(), - const Protocol::Attributes &attrs = Protocol::Attributes()) + const Protocol::Attributes &attrs = Protocol::Attributes()) { auto resp = Protocol::FetchTagsResponsePtr::create(tag.id()); resp->setGid(tag.gid().toUtf8()); resp->setParentId(tag.parentId()); resp->setType(tag.tagType().name().toUtf8()); resp->setRemoteId(remoteId); resp->setAttributes(attrs); return resp; } QScopedPointer initializer; private Q_SLOTS: void testStoreTag_data() { initializer.reset(new DbInitializer); Resource res = initializer->createResource("testresource"); // Make sure the type exists TagType type = type.retrieveByName(QStringLiteral("PLAIN")); if (!type.isValid()) { type.setName(QStringLiteral("PLAIN")); type.insert(); } QTest::addColumn("scenarios"); QTest::addColumn>>("expectedTags"); QTest::addColumn("expectedNotifications"); { auto cmd = Protocol::CreateTagCommandPtr::create(); cmd->setGid("tag"); cmd->setParentId(0); cmd->setType("PLAIN"); cmd->setAttributes({ { "TAG", "(\\\"tag2\\\" \\\"\\\" \\\"\\\" \\\"\\\" \\\"0\\\" () () \\\"-1\\\")" } }); auto resp = Protocol::FetchTagsResponsePtr::create(1); resp->setGid(cmd->gid()); resp->setParentId(cmd->parentId()); resp->setType(cmd->type()); resp->setAttributes(cmd->attributes()); TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, cmd) << TestScenario::create(5, TestScenario::ServerCmd, resp) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateTagResponsePtr::create()); Tag tag; tag.setId(1); tag.setTagType(type); tag.setParentId(0); TagAttribute attribute; attribute.setTagId(1); attribute.setType("TAG"); attribute.setValue("(\\\"tag2\\\" \\\"\\\" \\\"\\\" \\\"\\\" \\\"0\\\" () () \\\"-1\\\")"); auto notification = Protocol::TagChangeNotificationPtr::create(); notification->setOperation(Protocol::TagChangeNotification::Add); notification->setSessionId(FakeAkonadiServer::instanceName().toLatin1()); - auto ntfTag = Protocol::FetchTagsResponsePtr::create(); - ntfTag->setId(1); - notification->setTag(ntfTag); + notification->setTag(Protocol::FetchTagsResponse(1)); QTest::newRow("uid create relation") << scenarios << QVector{ { tag, { attribute } } } << Protocol::ChangeNotificationList{ notification }; } { auto cmd = Protocol::CreateTagCommandPtr::create(); cmd->setGid("tag2"); cmd->setParentId(1); cmd->setType("PLAIN"); cmd->setAttributes({ { "TAG", "(\\\"tag3\\\" \\\"\\\" \\\"\\\" \\\"\\\" \\\"0\\\" () () \\\"-1\\\")" } }); auto resp = Protocol::FetchTagsResponsePtr::create(2); resp->setGid(cmd->gid()); resp->setParentId(cmd->parentId()); resp->setType(cmd->type()); resp->setAttributes(cmd->attributes()); TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, cmd) << TestScenario::create(5, TestScenario::ServerCmd, resp) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateTagResponsePtr::create()); Tag tag; tag.setId(2); tag.setTagType(type); tag.setParentId(1); TagAttribute attribute; attribute.setTagId(2); attribute.setType("TAG"); attribute.setValue("(\\\"tag3\\\" \\\"\\\" \\\"\\\" \\\"\\\" \\\"0\\\" () () \\\"-1\\\")"); auto notification = Protocol::TagChangeNotificationPtr::create(); notification->setOperation(Protocol::TagChangeNotification::Add); notification->setSessionId(FakeAkonadiServer::instanceName().toLatin1()); - auto ntfTag = Protocol::FetchTagsResponsePtr::create(); - ntfTag->setId(2); - notification->setTag(ntfTag); + notification->setTag(Protocol::FetchTagsResponse(2)); QTest::newRow("create child tag") << scenarios << QVector{ { tag, { attribute } } } << Protocol::ChangeNotificationList{ notification }; } } void testStoreTag() { QFETCH(TestScenario::List, scenarios); QFETCH(QVector, expectedTags); QFETCH(Protocol::ChangeNotificationList, expectedNotifications); FakeAkonadiServer::instance()->setScenarios(scenarios); FakeAkonadiServer::instance()->runTest(); const auto receivedNotifications = extractNotifications(FakeAkonadiServer::instance()->notificationSpy()); QVariantList ids; QCOMPARE(receivedNotifications.size(), expectedNotifications.count()); for (int i = 0; i < expectedNotifications.size(); i++) { QCOMPARE(*receivedNotifications.at(i), *expectedNotifications.at(i)); - ids << Protocol::cmdCast(receivedNotifications.at(i)).tag()->id(); + ids << Protocol::cmdCast(receivedNotifications.at(i)).tag().id(); } SelectQueryBuilder qb; qb.addValueCondition(Tag::idColumn(), Query::In, ids); QVERIFY(qb.exec()); const Tag::List tags = qb.result(); QCOMPARE(tags.size(), expectedTags.size()); for (int i = 0; i < tags.size(); i++) { const Tag actual = tags.at(i); const Tag expected = expectedTags.at(i).first; const TagAttribute::List expectedAttrs = expectedTags.at(i).second; QCOMPARE(actual.id(), expected.id()); QCOMPARE(actual.typeId(), expected.typeId()); QCOMPARE(actual.parentId(), expected.parentId()); TagAttribute::List attributes = TagAttribute::retrieveFiltered( TagAttribute::tagIdColumn(), tags.at(i).id()); QCOMPARE(attributes.size(), expectedAttrs.size()); for (int j = 0; j < attributes.size(); ++j) { const TagAttribute actualAttr = attributes.at(i); const TagAttribute expectedAttr = expectedAttrs.at(i); QCOMPARE(actualAttr.tagId(), expectedAttr.tagId()); QCOMPARE(actualAttr.type(), expectedAttr.type()); QCOMPARE(actualAttr.value(), expectedAttr.value()); } } } void testModifyTag_data() { initializer.reset(new DbInitializer); Resource res = initializer->createResource("testresource"); Resource res2 = initializer->createResource("testresource2"); Collection col = initializer->createCollection("Col 1"); PimItem pimItem = initializer->createItem("Item 1", col); Tag tag; TagType type; type.setName(QStringLiteral("PLAIN")); type.insert(); tag.setTagType(type); tag.setGid(QStringLiteral("gid")); tag.insert(); pimItem.addTag(tag); TagRemoteIdResourceRelation rel; rel.setRemoteId(QStringLiteral("TAG1RES2RID")); rel.setResource(res2); rel.setTag(tag); rel.insert(); QTest::addColumn("scenarios"); QTest::addColumn("expectedTags"); QTest::addColumn("expectedNotifications"); { auto cmd = Protocol::ModifyTagCommandPtr::create(tag.id()); cmd->setAttributes({ { "TAG", "(\\\"tag2\\\" \\\"\\\" \\\"\\\" \\\"\\\" \\\"0\\\" () () \\\"-1\\\")" } }); TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, cmd) << TestScenario::create(5, TestScenario::ServerCmd, createResponse(tag, QByteArray(), cmd->attributes())) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::ModifyTagResponsePtr::create()); auto notification = Protocol::TagChangeNotificationPtr::create(); notification->setOperation(Protocol::TagChangeNotification::Modify); notification->setSessionId(FakeAkonadiServer::instanceName().toLatin1()); - auto ntfTag = Protocol::FetchTagsResponsePtr::create(); - ntfTag->setId(tag.id()); - notification->setTag(ntfTag); + notification->setTag(Protocol::FetchTagsResponse(tag.id())); QTest::newRow("uid store name") << scenarios << (Tag::List() << tag) << (Protocol::ChangeNotificationList() << notification); } { auto cmd = Protocol::ModifyTagCommandPtr::create(tag.id()); cmd->setRemoteId("remote1"); TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << FakeAkonadiServer::selectResourceScenario(QStringLiteral("testresource")) << TestScenario::create(5, TestScenario::ClientCmd, cmd) << TestScenario::create(5, TestScenario::ServerCmd, createResponse(tag, "remote1", { { "TAG", "(\\\"tag2\\\" \\\"\\\" \\\"\\\" \\\"\\\" \\\"0\\\" () () \\\"-1\\\")" } })) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::ModifyTagResponsePtr::create()); // RID-only changes don't emit notifications /* Akonadi::Protocol::ChangeNotification notification; notification.setType(Protocol::ChangeNotification::Tags); notification.setOperation(Protocol::ChangeNotification::Modify); notification.setSessionId(FakeAkonadiServer::instanceName().toLatin1()); notification.addEntity(tag.id()); */ QTest::newRow("uid store rid") << scenarios << (Tag::List() << tag) << Protocol::ChangeNotificationList(); } { auto cmd = Protocol::ModifyTagCommandPtr::create(tag.id()); cmd->setRemoteId(QByteArray()); TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << FakeAkonadiServer::selectResourceScenario(res.name()) << TestScenario::create(5, TestScenario::ClientCmd, cmd) << TestScenario::create(5, TestScenario::ServerCmd, createResponse(tag, QByteArray(), { { "TAG", "(\\\"tag2\\\" \\\"\\\" \\\"\\\" \\\"\\\" \\\"0\\\" () () \\\"-1\\\")" } })) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::ModifyTagResponsePtr::create()); // RID-only changes don't emit notifications /* Akonadi::Protocol::ChangeNotification tagChangeNtf; tagChangeNtf.setType(Protocol::ChangeNotification::Tags); tagChangeNtf.setOperation(Protocol::ChangeNotification::Modify); tagChangeNtf.setSessionId(FakeAkonadiServer::instanceName().toLatin1()); tagChangeNtf.addEntity(tag.id()); */ QTest::newRow("uid store unset one rid") << scenarios << (Tag::List() << tag) << Protocol::ChangeNotificationList(); } { auto cmd = Protocol::ModifyTagCommandPtr::create(tag.id()); cmd->setRemoteId(QByteArray()); TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << FakeAkonadiServer::selectResourceScenario(res2.name()) << TestScenario::create(5, TestScenario::ClientCmd, cmd) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::DeleteTagResponsePtr::create()) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::ModifyTagResponsePtr::create()); auto itemUntaggedNtf = Protocol::ItemChangeNotificationPtr::create(); itemUntaggedNtf->setOperation(Protocol::ItemChangeNotification::ModifyTags); itemUntaggedNtf->setSessionId(FakeAkonadiServer::instanceName().toLatin1()); - itemUntaggedNtf->setItems({ initializer->fetchResponse(pimItem) }); + itemUntaggedNtf->setItems({ *initializer->fetchResponse(pimItem) }); itemUntaggedNtf->setResource(res2.name().toLatin1()); itemUntaggedNtf->setParentCollection(col.id()); itemUntaggedNtf->setRemovedTags(QSet() << tag.id()); auto tagRemoveNtf = Protocol::TagChangeNotificationPtr::create(); tagRemoveNtf->setOperation(Protocol::TagChangeNotification::Remove); tagRemoveNtf->setSessionId(FakeAkonadiServer::instanceName().toLatin1()); - auto ntfTag = Protocol::FetchTagsResponsePtr::create(); - ntfTag->setId(tag.id()); - ntfTag->setGid("gid"); - ntfTag->setType("PLAIN"); - tagRemoveNtf->setTag(ntfTag); + Protocol::FetchTagsResponse ntfTag; + ntfTag.setId(tag.id()); + ntfTag.setGid("gid"); + ntfTag.setType("PLAIN"); + tagRemoveNtf->setTag(std::move(ntfTag)); QTest::newRow("uid store unset last rid") << scenarios << Tag::List() << (Protocol::ChangeNotificationList() << itemUntaggedNtf << tagRemoveNtf); } } void testModifyTag() { QFETCH(TestScenario::List, scenarios); QFETCH(Tag::List, expectedTags); QFETCH(Protocol::ChangeNotificationList, expectedNotifications); FakeAkonadiServer::instance()->setScenarios(scenarios); FakeAkonadiServer::instance()->runTest(); const auto receivedNotifications = extractNotifications(FakeAkonadiServer::instance()->notificationSpy()); QCOMPARE(receivedNotifications.size(), expectedNotifications.count()); for (int i = 0; i < receivedNotifications.size(); i++) { qDebug() << Protocol::debugString(receivedNotifications.at(i)); qDebug() << Protocol::debugString(expectedNotifications.at(i)); QCOMPARE(*receivedNotifications.at(i), *expectedNotifications.at(i)); } const Tag::List tags = Tag::retrieveAll(); QCOMPARE(tags.size(), expectedTags.size()); for (int i = 0; i < tags.size(); i++) { QCOMPARE(tags.at(i).id(), expectedTags.at(i).id()); QCOMPARE(tags.at(i).tagType().name(), expectedTags.at(i).tagType().name()); } } void testRemoveTag_data() { initializer.reset(new DbInitializer); Resource res1 = initializer->createResource("testresource3"); Resource res2 = initializer->createResource("testresource4"); Tag tag; TagType type; type.setName(QStringLiteral("PLAIN")); type.insert(); tag.setTagType(type); tag.setGid(QStringLiteral("gid2")); tag.insert(); TagRemoteIdResourceRelation rel1; rel1.setRemoteId(QStringLiteral("TAG2RES1RID")); rel1.setResource(res1); rel1.setTag(tag); rel1.insert(); TagRemoteIdResourceRelation rel2; rel2.setRemoteId(QStringLiteral("TAG2RES2RID")); rel2.setResource(res2); rel2.setTag(tag); rel2.insert(); QTest::addColumn("scenarios"); QTest::addColumn("expectedTags"); QTest::addColumn("expectedNotifications"); { TestScenario::List scenarios; scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, Protocol::DeleteTagCommandPtr::create(tag.id())) << TestScenario::create(5, TestScenario::ServerCmd, Protocol::DeleteTagResponsePtr::create()); auto ntf = Protocol::TagChangeNotificationPtr::create(); ntf->setOperation(Protocol::TagChangeNotification::Remove); ntf->setSessionId(FakeAkonadiServer::instanceName().toLatin1()); auto res1Ntf = Protocol::TagChangeNotificationPtr::create(*ntf); - auto res1NtfTag = Protocol::FetchTagsResponsePtr::create(); - res1NtfTag->setId(tag.id()); - res1NtfTag->setRemoteId(rel1.remoteId().toLatin1()); - res1Ntf->setTag(res1NtfTag); + Protocol::FetchTagsResponse res1NtfTag; + res1NtfTag.setId(tag.id()); + res1NtfTag.setRemoteId(rel1.remoteId().toLatin1()); + res1Ntf->setTag(std::move(res1NtfTag)); res1Ntf->setResource(res1.name().toLatin1()); auto res2Ntf = Protocol::TagChangeNotificationPtr::create(*ntf); - auto res2NtfTag = Protocol::FetchTagsResponsePtr::create(); - res2NtfTag->setId(tag.id()); - res2NtfTag->setRemoteId(rel2.remoteId().toLatin1()); - res2Ntf->setTag(res2NtfTag); + Protocol::FetchTagsResponse res2NtfTag; + res2NtfTag.setId(tag.id()); + res2NtfTag.setRemoteId(rel2.remoteId().toLatin1()); + res2Ntf->setTag(std::move(res2NtfTag)); res2Ntf->setResource(res2.name().toLatin1()); auto clientNtf = Protocol::TagChangeNotificationPtr::create(*ntf); - auto clientNtfTag = Protocol::FetchTagsResponsePtr::create(); - clientNtfTag->setId(tag.id()); - clientNtf->setTag(clientNtfTag); + clientNtf->setTag(Protocol::FetchTagsResponse(tag.id())); QTest::newRow("uid remove") << scenarios << Tag::List() << (Protocol::ChangeNotificationList() << res1Ntf << res2Ntf << clientNtf); } } void testRemoveTag() { QFETCH(TestScenario::List, scenarios); QFETCH(Tag::List, expectedTags); QFETCH(Protocol::ChangeNotificationList, expectedNotifications); FakeAkonadiServer::instance()->setScenarios(scenarios); FakeAkonadiServer::instance()->runTest(); const auto receivedNotifications = extractNotifications(FakeAkonadiServer::instance()->notificationSpy()); QCOMPARE(receivedNotifications.size(), expectedNotifications.count()); for (int i = 0; i < receivedNotifications.size(); i++) { QCOMPARE(*receivedNotifications.at(i), *expectedNotifications.at(i)); } const Tag::List tags = Tag::retrieveAll(); QCOMPARE(tags.size(), 0); } }; AKTEST_FAKESERVER_MAIN(TagHandlerTest) #include "taghandlertest.moc" diff --git a/src/core/changerecorder_p.cpp b/src/core/changerecorder_p.cpp index 244a439bb..226275706 100644 --- a/src/core/changerecorder_p.cpp +++ b/src/core/changerecorder_p.cpp @@ -1,1241 +1,1241 @@ /* Copyright (c) 2007 Volker Krause 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 "changerecorder_p.h" #include "akonadicore_debug.h" #include #include #include #include #include using namespace Akonadi; ChangeRecorderPrivate::ChangeRecorderPrivate(ChangeNotificationDependenciesFactory *dependenciesFactory_, ChangeRecorder *parent) : MonitorPrivate(dependenciesFactory_, parent) , settings(nullptr) , enableChangeRecording(true) , m_lastKnownNotificationsCount(0) , m_startOffset(0) , m_needFullSave(true) { } int ChangeRecorderPrivate::pipelineSize() const { if (enableChangeRecording) { return 0; // we fill the pipeline ourselves when using change recording } return MonitorPrivate::pipelineSize(); } void ChangeRecorderPrivate::slotNotify(const Protocol::ChangeNotificationPtr &msg) { Q_Q(ChangeRecorder); const int oldChanges = pendingNotifications.size(); // with change recording disabled this will automatically take care of dispatching notification messages and saving MonitorPrivate::slotNotify(msg); if (enableChangeRecording && pendingNotifications.size() != oldChanges) { emit q->changesAdded(); } } // The QSettings object isn't actually used anymore, except for migrating old data // and it gives us the base of the filename to use. This is all historical. QString ChangeRecorderPrivate::notificationsFileName() const { return settings->fileName() + QStringLiteral("_changes.dat"); } void ChangeRecorderPrivate::loadNotifications() { pendingNotifications.clear(); Q_ASSERT(pipeline.isEmpty()); pipeline.clear(); const QString changesFileName = notificationsFileName(); /** * In an older version we recorded changes inside the settings object, however * for performance reasons we changed that to store them in a separated file. * If this file doesn't exists, it means we run the new version the first time, * so we have to read in the legacy list of changes first. */ if (!QFile::exists(changesFileName)) { QStringList list; settings->beginGroup(QStringLiteral("ChangeRecorder")); const int size = settings->beginReadArray(QStringLiteral("change")); for (int i = 0; i < size; ++i) { settings->setArrayIndex(i); Protocol::ChangeNotificationPtr msg; switch (static_cast(settings->value(QStringLiteral("type")).toInt())) { case Item: msg = loadItemNotification(settings); break; case Collection: msg = loadCollectionNotification(settings); break; case Tag: case Relation: case InvalidType: qWarning() << "Unexpected notification type in legacy store"; continue; } if (msg->isValid()) { pendingNotifications << msg; } } settings->endArray(); // save notifications to the new file... saveNotifications(); // ...delete the legacy list... settings->remove(QString()); settings->endGroup(); // ...and continue as usually } QFile file(changesFileName); if (file.open(QIODevice::ReadOnly)) { m_needFullSave = false; pendingNotifications = loadFrom(&file, m_needFullSave); } else { m_needFullSave = true; } notificationsLoaded(); } static const quint64 s_currentVersion = Q_UINT64_C(0x000700000000); static const quint64 s_versionMask = Q_UINT64_C(0xFFFF00000000); static const quint64 s_sizeMask = Q_UINT64_C(0x0000FFFFFFFF); QQueue ChangeRecorderPrivate::loadFrom(QFile *device, bool &needsFullSave) const { QDataStream stream(device); stream.setVersion(QDataStream::Qt_4_6); QByteArray sessionId; int type; QQueue list; quint64 sizeAndVersion; stream >> sizeAndVersion; const quint64 size = sizeAndVersion & s_sizeMask; const quint64 version = (sizeAndVersion & s_versionMask) >> 32; quint64 startOffset = 0; if (version >= 1) { stream >> startOffset; } // If we skip the first N items, then we'll need to rewrite the file on saving. // Also, if the file is old, it needs to be rewritten. needsFullSave = startOffset > 0 || version == 0; for (quint64 i = 0; i < size && !stream.atEnd(); ++i) { Protocol::ChangeNotificationPtr msg; stream >> sessionId; stream >> type; if (stream.status() != QDataStream::Ok) { qCWarning(AKONADICORE_LOG) << "Error reading saved notifications! Aborting. Corrupt file:" << device->fileName(); break; } switch (static_cast(type)) { case Item: msg = loadItemNotification(stream, version); break; case Collection: msg = loadCollectionNotification(stream, version); break; case Tag: msg = loadTagNotification(stream, version); break; case Relation: msg = loadRelationNotification(stream, version); break; default: qCWarning(AKONADICORE_LOG) << "Unknown notification type"; break; } if (i < startOffset) { continue; } if (msg && msg->isValid()) { msg->setSessionId(sessionId); list << msg; } } return list; } QString ChangeRecorderPrivate::dumpNotificationListToString() const { if (!settings) { return QStringLiteral("No settings set in ChangeRecorder yet."); } const QString changesFileName = notificationsFileName(); QFile file(changesFileName); if (!file.open(QIODevice::ReadOnly)) { return QLatin1String("Error reading ") + changesFileName; } QString result; bool dummy; const QQueue notifications = loadFrom(&file, dummy); for (const Protocol::ChangeNotificationPtr &n : notifications) { result += Protocol::debugString(n) + QLatin1Char('\n'); } return result; } void ChangeRecorderPrivate::addToStream(QDataStream &stream, const Protocol::ChangeNotificationPtr &msg) { // We deliberately don't use Factory::serialize(), because the internal // serialization format could change at any point stream << msg->sessionId(); stream << int(mapToLegacyType(msg->type())); switch (msg->type()) { case Protocol::Command::ItemChangeNotification: saveItemNotification(stream, Protocol::cmdCast(msg)); break; case Protocol::Command::CollectionChangeNotification: saveCollectionNotification(stream, Protocol::cmdCast(msg)); break; case Protocol::Command::TagChangeNotification: saveTagNotification(stream, Protocol::cmdCast(msg)); break; case Protocol::Command::RelationChangeNotification: saveRelationNotification(stream, Protocol::cmdCast(msg)); break; default: qCWarning(AKONADICORE_LOG) << "Unexpected type?"; return; } } void ChangeRecorderPrivate::writeStartOffset() { if (!settings) { return; } QFile file(notificationsFileName()); if (!file.open(QIODevice::ReadWrite)) { qCWarning(AKONADICORE_LOG) << "Could not update notifications in file" << file.fileName(); return; } // Skip "countAndVersion" file.seek(8); //qCDebug(AKONADICORE_LOG) << "Writing start offset=" << m_startOffset; QDataStream stream(&file); stream.setVersion(QDataStream::Qt_4_6); stream << static_cast(m_startOffset); // Everything else stays unchanged } void ChangeRecorderPrivate::saveNotifications() { if (!settings) { return; } QFile file(notificationsFileName()); QFileInfo info(file); if (!QFile::exists(info.absolutePath())) { QDir dir; dir.mkpath(info.absolutePath()); } if (!file.open(QIODevice::WriteOnly)) { qCWarning(AKONADICORE_LOG) << "Could not save notifications to file" << file.fileName(); return; } saveTo(&file); m_needFullSave = false; m_startOffset = 0; } void ChangeRecorderPrivate::saveTo(QIODevice *device) { // Version 0 of this file format was writing a quint64 count, followed by the notifications. // Version 1 bundles a version number into that quint64, to be able to detect a version number at load time. const quint64 countAndVersion = static_cast(pendingNotifications.count()) | s_currentVersion; QDataStream stream(device); stream.setVersion(QDataStream::Qt_4_6); stream << countAndVersion; stream << quint64(0); // no start offset //qCDebug(AKONADICORE_LOG) << "Saving" << pendingNotifications.count() << "notifications (full save)"; for (int i = 0; i < pendingNotifications.count(); ++i) { const Protocol::ChangeNotificationPtr msg = pendingNotifications.at(i); addToStream(stream, msg); } } void ChangeRecorderPrivate::notificationsEnqueued(int count) { // Just to ensure the contract is kept, and these two methods are always properly called. if (enableChangeRecording) { m_lastKnownNotificationsCount += count; if (m_lastKnownNotificationsCount != pendingNotifications.count()) { qCWarning(AKONADICORE_LOG) << this << "The number of pending notifications changed without telling us! Expected" << m_lastKnownNotificationsCount << "but got" << pendingNotifications.count() << "Caller just added" << count; Q_ASSERT(pendingNotifications.count() == m_lastKnownNotificationsCount); } saveNotifications(); } } void ChangeRecorderPrivate::dequeueNotification() { if (pendingNotifications.isEmpty()) { return; } pendingNotifications.dequeue(); if (enableChangeRecording) { Q_ASSERT(pendingNotifications.count() == m_lastKnownNotificationsCount - 1); --m_lastKnownNotificationsCount; if (m_needFullSave || pendingNotifications.isEmpty()) { saveNotifications(); } else { ++m_startOffset; writeStartOffset(); } } } void ChangeRecorderPrivate::notificationsErased() { if (enableChangeRecording) { m_lastKnownNotificationsCount = pendingNotifications.count(); m_needFullSave = true; saveNotifications(); } } void ChangeRecorderPrivate::notificationsLoaded() { m_lastKnownNotificationsCount = pendingNotifications.count(); m_startOffset = 0; } bool ChangeRecorderPrivate::emitNotification(const Protocol::ChangeNotificationPtr &msg) { const bool someoneWasListening = MonitorPrivate::emitNotification(msg); if (!someoneWasListening && enableChangeRecording) { //If no signal was emitted (e.g. because no one was connected to it), no one is going to call changeProcessed, so we help ourselves. dequeueNotification(); QMetaObject::invokeMethod(q_ptr, "replayNext", Qt::QueuedConnection); } return someoneWasListening; } Protocol::ChangeNotificationPtr ChangeRecorderPrivate::loadItemNotification(QSettings *settings) const { auto msg = Protocol::ItemChangeNotificationPtr::create(); msg->setSessionId(settings->value(QStringLiteral("sessionId")).toByteArray()); msg->setOperation(mapItemOperation(static_cast(settings->value(QStringLiteral("op")).toInt()))); - auto item = Protocol::FetchItemsResponsePtr::create(); - item->setId(settings->value(QStringLiteral("uid")).toLongLong()); - item->setRemoteId(settings->value(QStringLiteral("rid")).toString()); - item->setMimeType(settings->value(QStringLiteral("mimeType")).toString()); - msg->setItems({ item }); + Protocol::FetchItemsResponse item; + item.setId(settings->value(QStringLiteral("uid")).toLongLong()); + item.setRemoteId(settings->value(QStringLiteral("rid")).toString()); + item.setMimeType(settings->value(QStringLiteral("mimeType")).toString()); + msg->setItems({std::move(item)}); msg->addMetadata("FETCH_ITEM"); msg->setResource(settings->value(QStringLiteral("resource")).toByteArray()); msg->setParentCollection(settings->value(QStringLiteral("parentCol")).toLongLong()); msg->setParentDestCollection(settings->value(QStringLiteral("parentDestCol")).toLongLong()); const QStringList list = settings->value(QStringLiteral("itemParts")).toStringList(); QSet itemParts; for (const QString &entry : list) { itemParts.insert(entry.toLatin1()); } msg->setItemParts(itemParts); return msg; } Protocol::ChangeNotificationPtr ChangeRecorderPrivate::loadCollectionNotification(QSettings *settings) const { auto msg = Protocol::CollectionChangeNotificationPtr::create(); msg->setSessionId(settings->value(QStringLiteral("sessionId")).toByteArray()); msg->setOperation(mapCollectionOperation(static_cast(settings->value(QStringLiteral("op")).toInt()))); - auto collection = Protocol::FetchCollectionsResponsePtr::create(); - collection->setId(settings->value(QStringLiteral("uid")).toLongLong()); - collection->setRemoteId(settings->value(QStringLiteral("rid")).toString()); - msg->setCollection(collection); + Protocol::FetchCollectionsResponse collection; + collection.setId(settings->value(QStringLiteral("uid")).toLongLong()); + collection.setRemoteId(settings->value(QStringLiteral("rid")).toString()); + msg->setCollection(std::move(collection)); msg->addMetadata("FETCH_COLLECTION"); msg->setResource(settings->value(QStringLiteral("resource")).toByteArray()); msg->setParentCollection(settings->value(QStringLiteral("parentCol")).toLongLong()); msg->setParentDestCollection(settings->value(QStringLiteral("parentDestCol")).toLongLong()); const QStringList list = settings->value(QStringLiteral("itemParts")).toStringList(); QSet changedParts; for (const QString &entry : list) { changedParts.insert(entry.toLatin1()); } msg->setChangedParts(changedParts); return msg; } QSet ChangeRecorderPrivate::extractRelations(QSet &flags) const { QSet relations; auto iter = flags.begin(); while (iter != flags.end()) { if (iter->startsWith("RELATION")) { const QByteArrayList parts = iter->split(' '); Q_ASSERT(parts.size() == 4); Protocol::ItemChangeNotification::Relation relation; relation.type = QString::fromLatin1(parts[1]); relation.leftId = parts[2].toLongLong(); relation.rightId = parts[3].toLongLong(); relations.insert(relation); iter = flags.erase(iter); } else { ++iter; } } return relations; } Protocol::ChangeNotificationPtr ChangeRecorderPrivate::loadItemNotification(QDataStream &stream, quint64 version) const { QByteArray resource, destinationResource; int operation, entityCnt; qint64 uid, parentCollection, parentDestCollection; QString remoteId, mimeType, remoteRevision; QSet itemParts, addedFlags, removedFlags; QSet addedTags, removedTags; - QVector items; + QVector items; auto msg = Protocol::ItemChangeNotificationPtr::create(); if (version == 1) { stream >> operation; stream >> uid; stream >> remoteId; stream >> resource; stream >> parentCollection; stream >> parentDestCollection; stream >> mimeType; stream >> itemParts; - auto item = Protocol::FetchItemsResponsePtr::create(); - item->setId(uid); - item->setRemoteId(remoteId); - item->setMimeType(mimeType); - items << item; + Protocol::FetchItemsResponse item; + item.setId(uid); + item.setRemoteId(remoteId); + item.setMimeType(mimeType); + items.push_back(std::move(item)); msg->addMetadata("FETCH_ITEM"); } else if (version >= 2) { stream >> operation; stream >> entityCnt; if (version >= 7) { QByteArray ba; qint64 i64; int i; QDateTime dt; QString str; QVector bav; QVector i64v; QMap babaMap; int cnt; for (int j = 0; j < entityCnt; ++j) { - auto item = Protocol::FetchItemsResponsePtr::create(); + Protocol::FetchItemsResponse item; stream >> i64; - item->setId(i64); + item.setId(i64); stream >> i; - item->setRevision(i); + item.setRevision(i); stream >> i64; - item->setParentId(i64); + item.setParentId(i64); stream >> str; - item->setRemoteId(str); + item.setRemoteId(str); stream >> str; - item->setRemoteRevision(str); + item.setRemoteRevision(str); stream >> str; - item->setGid(str); + item.setGid(str); stream >> i64; - item->setSize(i64); + item.setSize(i64); stream >> str; - item->setMimeType(str); + item.setMimeType(str); stream >> dt; - item->setMTime(dt); + item.setMTime(dt); stream >> bav; - item->setFlags(bav); + item.setFlags(bav); stream >> cnt; QVector tags; for (int k = 0; k < cnt; ++k) { Protocol::FetchTagsResponse tag; stream >> i64; tag.setId(i64); stream >> i64; tag.setParentId(i64); stream >> ba; tag.setGid(ba); stream >> ba; tag.setType(ba); stream >> ba; tag.setRemoteId(ba); stream >> babaMap; tag.setAttributes(babaMap); tags << tag; } - item->setTags(tags); + item.setTags(tags); stream >> i64v; - item->setVirtualReferences(i64v); + item.setVirtualReferences(i64v); stream >> cnt; QVector relations; for (int k = 0; k < cnt; ++k) { Protocol::FetchRelationsResponse relation; stream >> i64; relation.setLeft(i64); stream >> ba; relation.setLeftMimeType(ba); stream >> i64; relation.setRight(i64); stream >> ba; relation.setRightMimeType(ba); stream >> ba; relation.setType(ba); stream >> ba; relation.setRemoteId(ba); relations << relation; } - item->setRelations(relations); + item.setRelations(relations); stream >> cnt; QVector ancestors; for (int k = 0; k < cnt; ++k) { Protocol::Ancestor ancestor; stream >> i64; ancestor.setId(i64); stream >> str; ancestor.setRemoteId(str); stream >> str; ancestor.setName(str); stream >> babaMap; ancestor.setAttributes(babaMap); ancestors << ancestor; } - item->setAncestors(ancestors); + item.setAncestors(ancestors); stream >> cnt; QVector parts; for (int k = 0; k < cnt; ++k) { Protocol::StreamPayloadResponse part; stream >> ba; part.setPayloadName(ba); Protocol::PartMetaData metaData; stream >> ba; metaData.setName(ba); stream >> i64; metaData.setSize(i64); stream >> i; metaData.setVersion(i); stream >> i; metaData.setStorageType(static_cast(i)); part.setMetaData(metaData); stream >> ba; part.setData(ba); parts << part; } - item->setParts(parts); + item.setParts(parts); stream >> bav; - item->setCachedParts(bav); - items << item; + item.setCachedParts(bav); + items.push_back(std::move(item)); } } else { for (int j = 0; j < entityCnt; ++j) { stream >> uid; stream >> remoteId; stream >> remoteRevision; stream >> mimeType; if (stream.status() != QDataStream::Ok) { qCWarning(AKONADICORE_LOG) << "Error reading saved notifications! Aborting"; return msg; } - auto item = Protocol::FetchItemsResponsePtr::create(); - item->setId(uid); - item->setRemoteId(remoteId); - item->setRemoteRevision(remoteRevision); - item->setMimeType(mimeType); - items << item; + Protocol::FetchItemsResponse item; + item.setId(uid); + item.setRemoteId(remoteId); + item.setRemoteRevision(remoteRevision); + item.setMimeType(mimeType); + items.push_back(std::move(item)); } msg->addMetadata("FETCH_ITEM"); } stream >> resource; stream >> destinationResource; stream >> parentCollection; stream >> parentDestCollection; stream >> itemParts; stream >> addedFlags; stream >> removedFlags; if (version >= 3) { stream >> addedTags; stream >> removedTags; } } else { qCWarning(AKONADICORE_LOG) << "Error version is not correct here" << version; return msg; } if (version >= 5) { msg->setOperation(static_cast(operation)); } else { msg->setOperation(mapItemOperation(static_cast(operation))); } msg->setItems(items); msg->setResource(resource); msg->setDestinationResource(destinationResource); msg->setParentCollection(parentCollection); msg->setParentDestCollection(parentDestCollection); msg->setItemParts(itemParts); msg->setAddedRelations(extractRelations(addedFlags)); msg->setAddedFlags(addedFlags); msg->setRemovedRelations(extractRelations(removedFlags)); msg->setRemovedFlags(removedFlags); msg->setAddedTags(addedTags); msg->setRemovedTags(removedTags); return msg; } QSet ChangeRecorderPrivate::encodeRelations(const QSet &relations) const { QSet rv; for (const auto &rel : relations) { rv.insert("RELATION " + rel.type.toLatin1() + ' ' + QByteArray::number(rel.leftId) + ' ' + QByteArray::number(rel.rightId)); } return rv; } void ChangeRecorderPrivate::saveItemNotification(QDataStream &stream, const Protocol::ItemChangeNotification &msg) { // Version 7 stream << int(msg.operation()); - const auto items = msg.items(); + const auto &items = msg.items(); stream << items.count(); for (const auto &item : items) { - stream << item->id() - << item->revision() - << item->parentId() - << item->remoteId() - << item->remoteRevision() - << item->gid() - << item->size() - << item->mimeType() - << item->mTime() - << item->flags(); - const auto tags = item->tags(); + stream << item.id() + << item.revision() + << item.parentId() + << item.remoteId() + << item.remoteRevision() + << item.gid() + << item.size() + << item.mimeType() + << item.mTime() + << item.flags(); + const auto tags = item.tags(); stream << tags.count(); for (const auto &tag : tags) { stream << tag.id() << tag.parentId() << tag.gid() << tag.type() << tag.remoteId() << tag.attributes(); } - stream << item->virtualReferences(); - const auto relations = item->relations(); + stream << item.virtualReferences(); + const auto relations = item.relations(); stream << relations.count(); for (const auto &relation : relations) { stream << relation.left() << relation.leftMimeType() << relation.right() << relation.rightMimeType() << relation.type() << relation.remoteId(); } - const auto ancestors = item->ancestors(); + const auto ancestors = item.ancestors(); stream << ancestors.count(); for (const auto &ancestor : ancestors) { stream << ancestor.id() << ancestor.remoteId() << ancestor.name() << ancestor.attributes(); } - const auto parts = item->parts(); + const auto parts = item.parts(); stream << parts.count(); for (const auto &part : parts) { const auto metaData = part.metaData(); stream << part.payloadName() << metaData.name() << metaData.size() << metaData.version() << static_cast(metaData.storageType()) << part.data(); } - stream << item->cachedParts(); + stream << item.cachedParts(); } stream << msg.resource(); stream << msg.destinationResource(); stream << quint64(msg.parentCollection()); stream << quint64(msg.parentDestCollection()); stream << msg.itemParts(); stream << msg.addedFlags() + encodeRelations(msg.addedRelations()); stream << msg.removedFlags() + encodeRelations(msg.removedRelations()); stream << msg.addedTags(); stream << msg.removedTags(); } Protocol::ChangeNotificationPtr ChangeRecorderPrivate::loadCollectionNotification(QDataStream &stream, quint64 version) const { QByteArray resource, destinationResource; int operation, entityCnt; quint64 uid, parentCollection, parentDestCollection; QString remoteId, remoteRevision, dummyString; QSet changedParts, dummyBa; QSet dummyIv; auto msg = Protocol::CollectionChangeNotificationPtr::create(); if (version == 1) { stream >> operation; stream >> uid; stream >> remoteId; stream >> resource; stream >> parentCollection; stream >> parentDestCollection; stream >> dummyString; stream >> changedParts; - auto collection = Protocol::FetchCollectionsResponsePtr::create(); - collection->setId(uid); - collection->setRemoteId(remoteId); - msg->setCollection(collection); + Protocol::FetchCollectionsResponse collection; + collection.setId(uid); + collection.setRemoteId(remoteId); + msg->setCollection(std::move(collection)); msg->addMetadata("FETCH_COLLECTION"); } else if (version >= 2) { stream >> operation; stream >> entityCnt; if (version >= 7) { QString str; QStringList stringList; qint64 i64; QVector vb; QMap attrs; bool b; int i; Tristate tristate; - auto collection = Protocol::FetchCollectionsResponsePtr::create(); + Protocol::FetchCollectionsResponse collection; stream >> uid; - collection->setId(uid); + collection.setId(uid); stream >> uid; - collection->setParentId(uid); + collection.setParentId(uid); stream >> str; - collection->setName(str); + collection.setName(str); stream >> stringList; - collection->setMimeTypes(stringList); + collection.setMimeTypes(stringList); stream >> str; - collection->setRemoteId(str); + collection.setRemoteId(str); stream >> str; - collection->setRemoteRevision(str); + collection.setRemoteRevision(str); stream >> str; - collection->setResource(str); + collection.setResource(str); Protocol::FetchCollectionStatsResponse stats; stream >> i64; stats.setCount(i64); stream >> i64; stats.setUnseen(i64); stream >> i64; stats.setSize(i64); - collection->setStatistics(stats); + collection.setStatistics(stats); stream >> str; - collection->setSearchQuery(str); + collection.setSearchQuery(str); stream >> vb; - collection->setSearchCollections(vb); + collection.setSearchCollections(vb); stream >> entityCnt; QVector ancestors; for (int i = 0; i < entityCnt; ++i) { Protocol::Ancestor ancestor; stream >> i64; ancestor.setId(i64); stream >> str; ancestor.setRemoteId(str); stream >> str; ancestor.setName(str); stream >> attrs; ancestor.setAttributes(attrs); ancestors.push_back(ancestor); if (stream.status() != QDataStream::Ok) { qCWarning(AKONADICORE_LOG) << "Erorr reading saved notifications! Aborting"; return msg; } } - collection->setAncestors(ancestors); + collection.setAncestors(ancestors); Protocol::CachePolicy cachePolicy; stream >> b; cachePolicy.setInherit(b); stream >> i; cachePolicy.setCheckInterval(i); stream >> i; cachePolicy.setCacheTimeout(i); stream >> b; cachePolicy.setSyncOnDemand(b); stream >> stringList; cachePolicy.setLocalParts(stringList); - collection->setCachePolicy(cachePolicy); + collection.setCachePolicy(cachePolicy); stream >> attrs; - collection->setAttributes(attrs); + collection.setAttributes(attrs); stream >> b; - collection->setEnabled(b); + collection.setEnabled(b); stream >> reinterpret_cast(tristate); - collection->setDisplayPref(tristate); + collection.setDisplayPref(tristate); stream >> reinterpret_cast(tristate); - collection->setSyncPref(tristate); + collection.setSyncPref(tristate); stream >> reinterpret_cast(tristate); - collection->setIndexPref(tristate); + collection.setIndexPref(tristate); stream >> b; - collection->setReferenced(b); + collection.setReferenced(b); stream >> b; - collection->setIsVirtual(b); + collection.setIsVirtual(b); - msg->setCollection(collection); + msg->setCollection(std::move(collection)); } else { for (int j = 0; j < entityCnt; ++j) { stream >> uid; stream >> remoteId; stream >> remoteRevision; stream >> dummyString; if (stream.status() != QDataStream::Ok) { qCWarning(AKONADICORE_LOG) << "Error reading saved notifications! Aborting"; return msg; } - auto collection = Protocol::FetchCollectionsResponsePtr::create(); - collection->setId(uid); - collection->setRemoteId(remoteId); - collection->setRemoteRevision(remoteRevision); - msg->setCollection(collection); + Protocol::FetchCollectionsResponse collection; + collection.setId(uid); + collection.setRemoteId(remoteId); + collection.setRemoteRevision(remoteRevision); + msg->setCollection(std::move(collection)); msg->addMetadata("FETCH_COLLECTION"); } } stream >> resource; stream >> destinationResource; stream >> parentCollection; stream >> parentDestCollection; stream >> changedParts; stream >> dummyBa; stream >> dummyBa; if (version >= 3) { stream >> dummyIv; stream >> dummyIv; } } else { qCWarning(AKONADICORE_LOG) << "Error version is not correct here" << version; return msg; } if (version >= 5) { msg->setOperation(static_cast(operation)); } else { msg->setOperation(mapCollectionOperation(static_cast(operation))); } msg->setResource(resource); msg->setDestinationResource(destinationResource); msg->setParentCollection(parentCollection); msg->setParentDestCollection(parentDestCollection); msg->setChangedParts(changedParts); return msg; } void Akonadi::ChangeRecorderPrivate::saveCollectionNotification(QDataStream &stream, const Protocol::CollectionChangeNotification &msg) { // Version 7 - const auto col = msg.collection(); + const auto &col = msg.collection(); stream << int(msg.operation()); stream << int(1); - stream << col->id(); - stream << col->parentId(); - stream << col->name(); - stream << col->mimeTypes(); - stream << col->remoteId(); - stream << col->remoteRevision(); - stream << col->resource(); - const auto stats = col->statistics(); + stream << col.id(); + stream << col.parentId(); + stream << col.name(); + stream << col.mimeTypes(); + stream << col.remoteId(); + stream << col.remoteRevision(); + stream << col.resource(); + const auto stats = col.statistics(); stream << stats.count(); stream << stats.unseen(); stream << stats.size(); - stream << col->searchQuery(); - stream << col->searchCollections(); - const auto ancestors = col->ancestors(); + stream << col.searchQuery(); + stream << col.searchCollections(); + const auto ancestors = col.ancestors(); stream << ancestors.count(); for (const auto &ancestor : ancestors) { stream << ancestor.id() << ancestor.remoteId() << ancestor.name() << ancestor.attributes(); } - const auto cachePolicy = col->cachePolicy(); + const auto cachePolicy = col.cachePolicy(); stream << cachePolicy.inherit(); stream << cachePolicy.checkInterval(); stream << cachePolicy.cacheTimeout(); stream << cachePolicy.syncOnDemand(); stream << cachePolicy.localParts(); - stream << col->attributes(); - stream << col->enabled(); - stream << static_cast(col->displayPref()); - stream << static_cast(col->syncPref()); - stream << static_cast(col->indexPref()); - stream << col->referenced(); - stream << col->isVirtual(); + stream << col.attributes(); + stream << col.enabled(); + stream << static_cast(col.displayPref()); + stream << static_cast(col.syncPref()); + stream << static_cast(col.indexPref()); + stream << col.referenced(); + stream << col.isVirtual(); stream << msg.resource(); stream << msg.destinationResource(); stream << quint64(msg.parentCollection()); stream << quint64(msg.parentDestCollection()); stream << msg.changedParts(); stream << QSet(); stream << QSet(); stream << QSet(); stream << QSet(); } Protocol::ChangeNotificationPtr ChangeRecorderPrivate::loadTagNotification(QDataStream &stream, quint64 version) const { QByteArray resource, dummyBa; int operation, entityCnt; quint64 uid, dummyI; QString remoteId, dummyString; QSet dummyBaV; QSet dummyIv; auto msg = Protocol::TagChangeNotificationPtr::create(); if (version == 1) { stream >> operation; stream >> uid; stream >> remoteId; stream >> dummyBa; stream >> dummyI; stream >> dummyI; stream >> dummyString; stream >> dummyBaV; - auto tag = Protocol::FetchTagsResponsePtr::create(); - tag->setId(uid); - tag->setRemoteId(remoteId.toLatin1()); - msg->setTag(tag); + Protocol::FetchTagsResponse tag; + tag.setId(uid); + tag.setRemoteId(remoteId.toLatin1()); + msg->setTag(std::move(tag)); msg->addMetadata("FETCH_TAG"); } else if (version >= 2) { stream >> operation; stream >> entityCnt; if (version >= 7) { QByteArray ba; QMap attrs; - auto tag = Protocol::FetchTagsResponsePtr::create(); + Protocol::FetchTagsResponse tag; stream >> uid; - tag->setId(uid); + tag.setId(uid); stream >> ba; - tag->setParentId(uid); + tag.setParentId(uid); stream >> attrs; - tag->setGid(ba); + tag.setGid(ba); stream >> ba; - tag->setType(ba); + tag.setType(ba); stream >> uid; - tag->setRemoteId(ba); + tag.setRemoteId(ba); stream >> ba; - tag->setAttributes(attrs); - msg->setTag(tag); + tag.setAttributes(attrs); + msg->setTag(std::move(tag)); stream >> resource; } else { for (int j = 0; j < entityCnt; ++j) { stream >> uid; stream >> remoteId; stream >> dummyString; stream >> dummyString; if (stream.status() != QDataStream::Ok) { qCWarning(AKONADICORE_LOG) << "Error reading saved notifications! Aborting"; return msg; } - auto tag = Protocol::FetchTagsResponsePtr::create(); - tag->setId(uid); - tag->setRemoteId(remoteId.toLatin1()); - msg->setTag(tag); + Protocol::FetchTagsResponse tag; + tag.setId(uid); + tag.setRemoteId(remoteId.toLatin1()); + msg->setTag(std::move(tag)); msg->addMetadata("FETCH_TAG"); } stream >> resource; stream >> dummyBa; stream >> dummyI; stream >> dummyI; stream >> dummyBaV; stream >> dummyBaV; stream >> dummyBaV; if (version >= 3) { stream >> dummyIv; stream >> dummyIv; } } if (version >= 5) { msg->setOperation(static_cast(operation)); } else { msg->setOperation(mapTagOperation(static_cast(operation))); } } msg->setResource(resource); return msg; } void Akonadi::ChangeRecorderPrivate::saveTagNotification(QDataStream &stream, const Protocol::TagChangeNotification &msg) { - const auto tag = msg.tag(); + const auto &tag = msg.tag(); stream << int(msg.operation()); stream << int(1); - stream << tag->id(); - stream << tag->parentId(); - stream << tag->gid(); - stream << tag->type(); - stream << tag->remoteId(); - stream << tag->attributes(); + stream << tag.id(); + stream << tag.parentId(); + stream << tag.gid(); + stream << tag.type(); + stream << tag.remoteId(); + stream << tag.attributes(); stream << msg.resource(); } Protocol::ChangeNotificationPtr ChangeRecorderPrivate::loadRelationNotification(QDataStream &stream, quint64 version) const { QByteArray dummyBa; int operation, entityCnt; quint64 dummyI; QString dummyString; QSet itemParts, dummyBaV; QSet dummyIv; auto msg = Protocol::RelationChangeNotificationPtr::create(); if (version == 1) { qCWarning(AKONADICORE_LOG) << "Invalid version of relation notification"; return msg; } else if (version >= 2) { stream >> operation; stream >> entityCnt; if (version >= 7) { - auto relation = Protocol::FetchRelationsResponsePtr::create(); + Protocol::FetchRelationsResponse relation; qint64 i64; QByteArray ba; stream >> i64; - relation->setLeft(i64); + relation.setLeft(i64); stream >> ba; - relation->setLeftMimeType(ba); + relation.setLeftMimeType(ba); stream >> i64; - relation->setRight(i64); + relation.setRight(i64); stream >>ba; - relation->setRightMimeType(ba); + relation.setRightMimeType(ba); stream >> ba; - relation->setRemoteId(ba); + relation.setRemoteId(ba); stream >> ba; - relation->setType(ba); + relation.setType(ba); - msg->setRelation(relation); + msg->setRelation(std::move(relation)); } else { for (int j = 0; j < entityCnt; ++j) { stream >> dummyI; stream >> dummyString; stream >> dummyString; stream >> dummyString; if (stream.status() != QDataStream::Ok) { qCWarning(AKONADICORE_LOG) << "Error reading saved notifications! Aborting"; return msg; } } stream >> dummyBa; if (version == 5) { // there was a bug in version 5 serializer that serialized this // field as qint64 (8 bytes) instead of empty QByteArray (which is // 4 bytes) stream >> dummyI; } else { stream >> dummyBa; } stream >> dummyI; stream >> dummyI; stream >> itemParts; stream >> dummyBaV; stream >> dummyBaV; if (version >= 3) { stream >> dummyIv; stream >> dummyIv; } - auto relation = Protocol::FetchRelationsResponsePtr::create(); + Protocol::FetchRelationsResponse relation; for (const QByteArray &part : qAsConst(itemParts)) { const QByteArrayList p = part.split(' '); if (p.size() < 2) { continue; } if (p[0] == "LEFT") { - relation->setLeft(p[1].toLongLong()); + relation.setLeft(p[1].toLongLong()); } else if (p[0] == "RIGHT") { - relation->setRight(p[1].toLongLong()); + relation.setRight(p[1].toLongLong()); } else if (p[0] == "RID") { - relation->setRemoteId(p[1]); + relation.setRemoteId(p[1]); } else if (p[0] == "TYPE") { - relation->setType(p[1]); + relation.setType(p[1]); } } - msg->setRelation(relation); + msg->setRelation(std::move(relation)); } if (version >= 5) { msg->setOperation(static_cast(operation)); } else { msg->setOperation(mapRelationOperation(static_cast(operation))); } } return msg; } void Akonadi::ChangeRecorderPrivate::saveRelationNotification(QDataStream &stream, const Protocol::RelationChangeNotification &msg) { - const auto rel = msg.relation(); + const auto &rel = msg.relation(); stream << int(msg.operation()); stream << int(0); - stream << rel->left(); - stream << rel->leftMimeType(); - stream << rel->right(); - stream << rel->rightMimeType(); - stream << rel->remoteId(); - stream << rel->type(); + stream << rel.left(); + stream << rel.leftMimeType(); + stream << rel.right(); + stream << rel.rightMimeType(); + stream << rel.remoteId(); + stream << rel.type(); } Protocol::ItemChangeNotification::Operation ChangeRecorderPrivate::mapItemOperation(LegacyOp op) const { switch (op) { case Add: return Protocol::ItemChangeNotification::Add; case Modify: return Protocol::ItemChangeNotification::Modify; case Move: return Protocol::ItemChangeNotification::Move; case Remove: return Protocol::ItemChangeNotification::Remove; case Link: return Protocol::ItemChangeNotification::Link; case Unlink: return Protocol::ItemChangeNotification::Unlink; case ModifyFlags: return Protocol::ItemChangeNotification::ModifyFlags; case ModifyTags: return Protocol::ItemChangeNotification::ModifyTags; case ModifyRelations: return Protocol::ItemChangeNotification::ModifyRelations; default: qWarning() << "Unexpected operation type in item notification"; return Protocol::ItemChangeNotification::InvalidOp; } } Protocol::CollectionChangeNotification::Operation ChangeRecorderPrivate::mapCollectionOperation(LegacyOp op) const { switch (op) { case Add: return Protocol::CollectionChangeNotification::Add; case Modify: return Protocol::CollectionChangeNotification::Modify; case Move: return Protocol::CollectionChangeNotification::Move; case Remove: return Protocol::CollectionChangeNotification::Remove; case Subscribe: return Protocol::CollectionChangeNotification::Subscribe; case Unsubscribe: return Protocol::CollectionChangeNotification::Unsubscribe; default: qCWarning(AKONADICORE_LOG) << "Unexpected operation type in collection notification"; return Protocol::CollectionChangeNotification::InvalidOp; } } Protocol::TagChangeNotification::Operation ChangeRecorderPrivate::mapTagOperation(LegacyOp op) const { switch (op) { case Add: return Protocol::TagChangeNotification::Add; case Modify: return Protocol::TagChangeNotification::Modify; case Remove: return Protocol::TagChangeNotification::Remove; default: qCWarning(AKONADICORE_LOG) << "Unexpected operation type in tag notification"; return Protocol::TagChangeNotification::InvalidOp; } } Protocol::RelationChangeNotification::Operation ChangeRecorderPrivate::mapRelationOperation(LegacyOp op) const { switch (op) { case Add: return Protocol::RelationChangeNotification::Add; case Remove: return Protocol::RelationChangeNotification::Remove; default: qCWarning(AKONADICORE_LOG) << "Unexpected operation type in relation notification"; return Protocol::RelationChangeNotification::InvalidOp; } } ChangeRecorderPrivate::LegacyType ChangeRecorderPrivate::mapToLegacyType(Protocol::Command::Type type) const { switch (type) { case Protocol::Command::ItemChangeNotification: return Item; case Protocol::Command::CollectionChangeNotification: return Collection; case Protocol::Command::TagChangeNotification: return Tag; case Protocol::Command::RelationChangeNotification: return Relation; default: qCWarning(AKONADICORE_LOG) << "Unexpected notification type"; return InvalidType; } } diff --git a/src/core/connection.cpp b/src/core/connection.cpp index dac1d6f32..7eeed3f83 100644 --- a/src/core/connection.cpp +++ b/src/core/connection.cpp @@ -1,357 +1,357 @@ /* * Copyright 2015 Daniel Vrátil * * 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 "connection_p.h" #include "session_p.h" #include "servermanager_p.h" #include "akonadicore_debug.h" #include "commandbuffer_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include +#include using namespace Akonadi; Connection::Connection(ConnectionType connType, const QByteArray &sessionId, CommandBuffer *commandBuffer, QObject *parent) : QObject(parent) , mConnectionType(connType) , mSessionId(sessionId) , mCommandBuffer(commandBuffer) { qRegisterMetaType(); qRegisterMetaType(); const QByteArray sessionLogFile = qgetenv("AKONADI_SESSION_LOGFILE"); if (!sessionLogFile.isEmpty()) { mLogFile = new QFile(QStringLiteral("%1.%2.%3.%4-%5").arg(QString::fromLatin1(sessionLogFile)) .arg(QString::number(QApplication::applicationPid())) .arg(QString::number(reinterpret_cast(this), 16)) .arg(QString::fromLatin1(mSessionId.replace('/', '_'))) .arg(connType == CommandConnection ? QStringLiteral("Cmd") : QStringLiteral("Ntf"))); if (!mLogFile->open(QIODevice::WriteOnly | QIODevice::Truncate)) { qCWarning(AKONADICORE_LOG) << "Failed to open Akonadi Session log file" << mLogFile->fileName(); delete mLogFile; mLogFile = nullptr; } } } Connection::~Connection() { delete mLogFile; if (mSocket) { mSocket->disconnect(); mSocket->disconnectFromServer(); mSocket->close(); mSocket.reset(); } } void Connection::reconnect() { #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) const bool ok = QMetaObject::invokeMethod(this, &Connection::doReconnect, Qt::QueuedConnection); #else const bool ok = QMetaObject::invokeMethod(this, "doReconnect", Qt::QueuedConnection); #endif Q_ASSERT(ok); Q_UNUSED(ok) } QString Connection::defaultAddressForTypeAndMethod(ConnectionType type, const QString &method) { if (method == QLatin1String("UnixPath")) { const QString defaultSocketDir = StandardDirs::saveDir("data"); if (type == CommandConnection) { return defaultSocketDir % QStringLiteral("akonadiserver-cmd.socket"); } else if (type == NotificationConnection) { return defaultSocketDir % QStringLiteral("akonadiserver-ntf.socket"); } } else if (method == QLatin1String("NamedPipe")) { QString suffix; if (Instance::hasIdentifier()) { suffix += QStringLiteral("%1-").arg(Instance::identifier()); } suffix += QString::fromUtf8(QUrl::toPercentEncoding(qApp->applicationDirPath())); if (type == CommandConnection) { return QStringLiteral("Akonadi-Cmd-") % suffix; } else if (type == NotificationConnection) { return QStringLiteral("Akonadi-Ntf-") % suffix; } } Q_UNREACHABLE(); } void Connection::doReconnect() { Q_ASSERT(QThread::currentThread() == thread()); if (mSocket && (mSocket->state() == QLocalSocket::ConnectedState || mSocket->state() == QLocalSocket::ConnectingState)) { // nothing to do, we are still/already connected return; } if (ServerManager::self()->state() != ServerManager::Running) { return; } // try to figure out where to connect to QString serverAddress; // env var has precedence const QByteArray serverAddressEnvVar = qgetenv("AKONADI_SERVER_ADDRESS"); if (!serverAddressEnvVar.isEmpty()) { const int pos = serverAddressEnvVar.indexOf(':'); const QByteArray protocol = serverAddressEnvVar.left(pos); QMap options; const QStringList lst = QString::fromLatin1(serverAddressEnvVar.mid(pos + 1)).split(QLatin1Char(',')); for (const QString &entry : lst) { const QStringList pair = entry.split(QLatin1Char('=')); if (pair.size() != 2) { continue; } options.insert(pair.first(), pair.last()); } qCDebug(AKONADICORE_LOG) << protocol << options; if (protocol == "unix") { serverAddress = options.value(QStringLiteral("path")); } else if (protocol == "pipe") { serverAddress = options.value(QStringLiteral("name")); } } // try config file next, fall back to defaults if that fails as well if (serverAddress.isEmpty()) { const QString connectionConfigFile = StandardDirs::connectionConfigFile(); const QFileInfo fileInfo(connectionConfigFile); if (!fileInfo.exists()) { qCDebug(AKONADICORE_LOG) << "Akonadi Client Session: connection config file '" "akonadi/akonadiconnectionrc' can not be found!"; } QSettings connectionSettings(connectionConfigFile, QSettings::IniFormat); QString connectionType; if (mConnectionType == CommandConnection) { connectionType = QStringLiteral("Data"); } else if (mConnectionType == NotificationConnection) { connectionType = QStringLiteral("Notifications"); } connectionSettings.beginGroup(connectionType); const auto method = connectionSettings.value(QStringLiteral("Method"), QStringLiteral("UnixPath")).toString(); serverAddress = connectionSettings.value(method, defaultAddressForTypeAndMethod(mConnectionType, method)).toString(); } // create sockets if not yet done, note that this does not yet allow changing socket types on the fly // but that's probably not something we need to support anyway if (!mSocket) { mSocket.reset(new QLocalSocket(this)); connect(mSocket.data(), static_cast(&QLocalSocket::error), this, [this](QLocalSocket::LocalSocketError) { qCWarning(AKONADICORE_LOG) << mSocket->errorString() << mSocket->serverName(); Q_EMIT socketError(mSocket->errorString()); Q_EMIT socketDisconnected(); }); connect(mSocket.data(), &QLocalSocket::disconnected, this, &Connection::socketDisconnected); connect(mSocket.data(), &QLocalSocket::readyRead, this, &Connection::handleIncomingData); } // actually do connect qCDebug(AKONADICORE_LOG) << "connectToServer" << serverAddress; mSocket->connectToServer(serverAddress); if (!mSocket->waitForConnected()) { qCWarning(AKONADICORE_LOG) << "Failed to connect to server!"; Q_EMIT socketError(tr("Failed to connect to server!")); mSocket.reset(); return; } QTimer::singleShot(0, this, &Connection::handleIncomingData); Q_EMIT reconnected(); } void Connection::forceReconnect() { #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) const bool ok = QMetaObject::invokeMethod(this, &Connection::doForceReconnect, Qt::QueuedConnection); #else const bool ok = QMetaObject::invokeMethod(this, "doForceReconnect", Qt::QueuedConnection); #endif Q_ASSERT(ok); Q_UNUSED(ok) } void Connection::doForceReconnect() { Q_ASSERT(QThread::currentThread() == thread()); if (mSocket) { mSocket->disconnect(this, SIGNAL(socketDisconnected())); mSocket->disconnectFromServer(); mSocket.reset(); } } void Connection::closeConnection() { #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) const bool ok = QMetaObject::invokeMethod(this, &Connection::doCloseConnection, Qt::QueuedConnection); #else const bool ok = QMetaObject::invokeMethod(this, "doCloseConnection", Qt::QueuedConnection); #endif Q_ASSERT(ok); Q_UNUSED(ok) } void Connection::doCloseConnection() { Q_ASSERT(QThread::currentThread() == thread()); if (mSocket) { mSocket->close(); mSocket->readAll(); mSocket.reset(); } } QLocalSocket *Connection::socket() const { return mSocket.data(); } void Connection::handleIncomingData() { Q_ASSERT(QThread::currentThread() == thread()); if (!mSocket) { // not connected yet return; } while (mSocket->bytesAvailable() >= int(sizeof(qint64))) { - QDataStream stream(mSocket.data()); + Protocol::DataStream stream(mSocket.data()); qint64 tag; - // TODO: Verify the tag matches the last tag we sent stream >> tag; Protocol::CommandPtr cmd; try { cmd = Protocol::deserialize(mSocket.data()); } catch (const Akonadi::ProtocolException &e) { qCWarning(AKONADICORE_LOG) << "Protocol exception:" << e.what(); // cmd's type will be Invalid by default, so fall-through } if (!cmd || (cmd->type() == Protocol::Command::Invalid)) { qCWarning(AKONADICORE_LOG) << "Invalid command, the world is going to end!"; mSocket->close(); mSocket->readAll(); reconnect(); return; } if (mLogFile) { mLogFile->write("S: "); mLogFile->write(QDateTime::currentDateTime().toString(QStringLiteral("yyyy-MM-dd hh:mm:ss.zzz ")).toUtf8()); mLogFile->write(QByteArray::number(tag)); mLogFile->write(" "); mLogFile->write(Protocol::debugString(cmd).toUtf8()); mLogFile->write("\n\n"); mLogFile->flush(); } if (cmd->type() == Protocol::Command::Hello) { Q_ASSERT(cmd->isResponse()); } { CommandBufferLocker locker(mCommandBuffer); mCommandBuffer->enqueue(tag, cmd); } } } void Connection::sendCommand(qint64 tag, const Protocol::CommandPtr &cmd) { const bool ok = QMetaObject::invokeMethod(this, "doSendCommand", Qt::QueuedConnection, Q_ARG(qint64, tag), Q_ARG(Akonadi::Protocol::CommandPtr, cmd)); Q_ASSERT(ok); Q_UNUSED(ok) } void Connection::doSendCommand(qint64 tag, const Protocol::CommandPtr &cmd) { Q_ASSERT(QThread::currentThread() == thread()); if (mLogFile) { mLogFile->write("C: "); mLogFile->write(QDateTime::currentDateTime().toString(QStringLiteral("yyyy-MM-dd hh:mm:ss.zzz ")).toUtf8()); mLogFile->write(QByteArray::number(tag)); mLogFile->write(" "); mLogFile->write(Protocol::debugString(cmd).toUtf8()); mLogFile->write("\n\n"); mLogFile->flush(); } if (mSocket && mSocket->isOpen()) { - QDataStream stream(mSocket.data()); + Protocol::DataStream stream(mSocket.data()); stream << tag; try { Protocol::serialize(mSocket.data(), cmd); } catch (const Akonadi::ProtocolException &e) { qCWarning(AKONADICORE_LOG) << "Protocol Exception:" << QString::fromUtf8(e.what()); mSocket->close(); mSocket->readAll(); reconnect(); return; } if (!mSocket->waitForBytesWritten()) { qCWarning(AKONADICORE_LOG) << "Socket write timeout"; mSocket->close(); mSocket->readAll(); reconnect(); return; } } else { // TODO: Queue the commands and resend on reconnect? } } diff --git a/src/core/monitor_p.cpp b/src/core/monitor_p.cpp index d46fe671a..fb1e6d6e0 100644 --- a/src/core/monitor_p.cpp +++ b/src/core/monitor_p.cpp @@ -1,1429 +1,1429 @@ /* Copyright (c) 2007 Tobias Koenig 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. */ // @cond PRIVATE #include "monitor_p.h" #include "collectionfetchjob.h" #include "collectionstatistics.h" #include "itemfetchjob.h" #include "notificationmanagerinterface.h" #include "session.h" #include "changemediator_p.h" #include "vectorhelper.h" #include "akonadicore_debug.h" #include "notificationsubscriber.h" #include "changenotification.h" #include "protocolhelper_p.h" using namespace Akonadi; class operation; static const int PipelineSize = 5; MonitorPrivate::MonitorPrivate(ChangeNotificationDependenciesFactory *dependenciesFactory_, Monitor *parent) : q_ptr(parent) , dependenciesFactory(dependenciesFactory_ ? dependenciesFactory_ : new ChangeNotificationDependenciesFactory) , ntfConnection(nullptr) , monitorAll(false) , exclusive(false) , mFetchChangedOnly(false) , session(Session::defaultSession()) , collectionCache(nullptr) , itemCache(nullptr) , tagCache(nullptr) , mCommandBuffer(parent, "handleCommands") , pendingModificationChanges(Protocol::ModifySubscriptionCommand::None) , pendingModificationTimer(nullptr) , monitorReady(false) , fetchCollection(false) , fetchCollectionStatistics(false) , collectionMoveTranslationEnabled(true) , useRefCounting(false) { } MonitorPrivate::~MonitorPrivate() { disconnectFromNotificationManager(); delete dependenciesFactory; delete collectionCache; delete itemCache; delete tagCache; } void MonitorPrivate::init() { // needs to be at least 3x pipeline size for the collection move case collectionCache = dependenciesFactory->createCollectionCache(3 * PipelineSize, session); // needs to be at least 1x pipeline size itemCache = dependenciesFactory->createItemListCache(PipelineSize, session); // 20 tags looks like a reasonable amount to keep around tagCache = dependenciesFactory->createTagListCache(20, session); QObject::connect(collectionCache, SIGNAL(dataAvailable()), q_ptr, SLOT(dataAvailable())); QObject::connect(itemCache, SIGNAL(dataAvailable()), q_ptr, SLOT(dataAvailable())); QObject::connect(tagCache, SIGNAL(dataAvailable()), q_ptr, SLOT(dataAvailable())); QObject::connect(ServerManager::self(), SIGNAL(stateChanged(Akonadi::ServerManager::State)), q_ptr, SLOT(serverStateChanged(Akonadi::ServerManager::State))); statisticsCompressionTimer.setSingleShot(true); statisticsCompressionTimer.setInterval(500); QObject::connect(&statisticsCompressionTimer, SIGNAL(timeout()), q_ptr, SLOT(slotFlushRecentlyChangedCollections())); } bool MonitorPrivate::connectToNotificationManager() { if (ntfConnection) { ntfConnection->deleteLater(); ntfConnection = nullptr; } if (!session) { return false; } ntfConnection = dependenciesFactory->createNotificationConnection(session, &mCommandBuffer); if (!ntfConnection) { return false; } slotUpdateSubscription(); ntfConnection->reconnect(); return true; } void MonitorPrivate::disconnectFromNotificationManager() { if (ntfConnection) { ntfConnection->disconnect(q_ptr); dependenciesFactory->destroyNotificationConnection(session, ntfConnection.data()); } } void MonitorPrivate::serverStateChanged(ServerManager::State state) { if (state == ServerManager::Running) { connectToNotificationManager(); } } void MonitorPrivate::invalidateCollectionCache(qint64 id) { collectionCache->update(id, mCollectionFetchScope); } void MonitorPrivate::invalidateItemCache(qint64 id) { itemCache->update(QList() << id, mItemFetchScope); } void MonitorPrivate::invalidateTagCache(qint64 id) { tagCache->update({ id }, mTagFetchScope); } int MonitorPrivate::pipelineSize() const { return PipelineSize; } void MonitorPrivate::scheduleSubscriptionUpdate() { if (pendingModificationTimer || !monitorReady) { return; } pendingModificationTimer = new QTimer(); pendingModificationTimer->setSingleShot(true); pendingModificationTimer->setInterval(0); pendingModificationTimer->start(); q_ptr->connect(pendingModificationTimer, SIGNAL(timeout()), q_ptr, SLOT(slotUpdateSubscription())); } void MonitorPrivate::slotUpdateSubscription() { delete pendingModificationTimer; pendingModificationTimer = nullptr; if (pendingModificationChanges & Protocol::ModifySubscriptionCommand::ItemFetchScope) { pendingModification.setItemFetchScope(ProtocolHelper::itemFetchScopeToProtocol(mItemFetchScope)); } if (pendingModificationChanges & Protocol::ModifySubscriptionCommand::CollectionFetchScope) { pendingModification.setCollectionFetchScope(ProtocolHelper::collectionFetchScopeToProtocol(mCollectionFetchScope)); } if (pendingModificationChanges & Protocol::ModifySubscriptionCommand::TagFetchScope) { pendingModification.setTagFetchScope(ProtocolHelper::tagFetchScopeToProtocol(mTagFetchScope)); } pendingModificationChanges = Protocol::ModifySubscriptionCommand::None; if (ntfConnection) { ntfConnection->sendCommand(3, Protocol::ModifySubscriptionCommandPtr::create(pendingModification)); pendingModification = Protocol::ModifySubscriptionCommand(); } } bool MonitorPrivate::isLazilyIgnored(const Protocol::ChangeNotificationPtr &msg, bool allowModifyFlagsConversion) const { if (msg->type() == Protocol::Command::CollectionChangeNotification) { // Lazy fetching can only affects items. return false; } if (msg->type() == Protocol::Command::TagChangeNotification) { const auto op = Protocol::cmdCast(msg).operation(); return ((op == Protocol::TagChangeNotification::Add && q_ptr->receivers(SIGNAL(tagAdded(Akonadi::Tag))) == 0) || (op == Protocol::TagChangeNotification::Modify && q_ptr->receivers(SIGNAL(tagChanged(Akonadi::Tag))) == 0) || (op == Protocol::TagChangeNotification::Remove && q_ptr->receivers(SIGNAL(tagRemoved(Akonadi::Tag))) == 0)); } if (!fetchCollectionStatistics && msg->type() == Protocol::Command::ItemChangeNotification) { const auto &itemNtf = Protocol::cmdCast(msg); const auto op = itemNtf.operation(); if ((op == Protocol::ItemChangeNotification::Add && q_ptr->receivers(SIGNAL(itemAdded(Akonadi::Item,Akonadi::Collection))) == 0) || (op == Protocol::ItemChangeNotification::Remove && q_ptr->receivers(SIGNAL(itemRemoved(Akonadi::Item))) == 0 && q_ptr->receivers(SIGNAL(itemsRemoved(Akonadi::Item::List))) == 0) || (op == Protocol::ItemChangeNotification::Modify && q_ptr->receivers(SIGNAL(itemChanged(Akonadi::Item,QSet))) == 0) || (op == Protocol::ItemChangeNotification::ModifyFlags && (q_ptr->receivers(SIGNAL(itemsFlagsChanged(Akonadi::Item::List,QSet,QSet))) == 0 // Newly delivered ModifyFlags notifications will be converted to // itemChanged(item, "FLAGS") for legacy clients. && (!allowModifyFlagsConversion || q_ptr->receivers(SIGNAL(itemChanged(Akonadi::Item,QSet))) == 0))) || (op == Protocol::ItemChangeNotification::ModifyTags && q_ptr->receivers(SIGNAL(itemsTagsChanged(Akonadi::Item::List,QSet,QSet))) == 0) || (op == Protocol::ItemChangeNotification::Move && q_ptr->receivers(SIGNAL(itemMoved(Akonadi::Item,Akonadi::Collection,Akonadi::Collection))) == 0 && q_ptr->receivers(SIGNAL(itemsMoved(Akonadi::Item::List,Akonadi::Collection,Akonadi::Collection))) == 0) || (op == Protocol::ItemChangeNotification::Link && q_ptr->receivers(SIGNAL(itemLinked(Akonadi::Item,Akonadi::Collection))) == 0 && q_ptr->receivers(SIGNAL(itemsLinked(Akonadi::Item::List,Akonadi::Collection))) == 0) || (op == Protocol::ItemChangeNotification::Unlink && q_ptr->receivers(SIGNAL(itemUnlinked(Akonadi::Item,Akonadi::Collection))) == 0 && q_ptr->receivers(SIGNAL(itemsUnlinked(Akonadi::Item::List,Akonadi::Collection))) == 0)) { return true; } if (!useRefCounting) { return false; } const Collection::Id parentCollectionId = itemNtf.parentCollection(); if ((op == Protocol::ItemChangeNotification::Add) || (op == Protocol::ItemChangeNotification::Remove) || (op == Protocol::ItemChangeNotification::Modify) || (op == Protocol::ItemChangeNotification::ModifyFlags) || (op == Protocol::ItemChangeNotification::ModifyTags) || (op == Protocol::ItemChangeNotification::Link) || (op == Protocol::ItemChangeNotification::Unlink)) { if (isMonitored(parentCollectionId)) { return false; } } if (op == Protocol::ItemChangeNotification::Move) { if (!isMonitored(parentCollectionId) && !isMonitored(itemNtf.parentDestCollection())) { return true; } // We can't ignore the move. It must be transformed later into a removal or insertion. return false; } return true; } return false; } void MonitorPrivate::checkBatchSupport(const Protocol::ChangeNotificationPtr &msg, bool &needsSplit, bool &batchSupported) const { if (msg->type() != Protocol::Command::ItemChangeNotification) { needsSplit = false; batchSupported = false; return; } const auto &itemNtf = Protocol::cmdCast(msg); const bool isBatch = (itemNtf.items().count() > 1); switch (itemNtf.operation()) { case Protocol::ItemChangeNotification::Add: needsSplit = isBatch; batchSupported = false; return; case Protocol::ItemChangeNotification::Modify: needsSplit = isBatch; batchSupported = false; return; case Protocol::ItemChangeNotification::ModifyFlags: batchSupported = q_ptr->receivers(SIGNAL(itemsFlagsChanged(Akonadi::Item::List,QSet,QSet))) > 0; needsSplit = isBatch && !batchSupported && q_ptr->receivers(SIGNAL(itemChanged(Akonadi::Item,QSet))) > 0; return; case Protocol::ItemChangeNotification::ModifyTags: // Tags were added after batch notifications, so they are always supported batchSupported = true; needsSplit = false; return; case Protocol::ItemChangeNotification::ModifyRelations: // Relations were added after batch notifications, so they are always supported batchSupported = true; needsSplit = false; return; case Protocol::ItemChangeNotification::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; return; case Protocol::ItemChangeNotification::Remove: needsSplit = isBatch && q_ptr->receivers(SIGNAL(itemRemoved(Akonadi::Item))) > 0; batchSupported = q_ptr->receivers(SIGNAL(itemsRemoved(Akonadi::Item::List))) > 0; return; case Protocol::ItemChangeNotification::Link: needsSplit = isBatch && q_ptr->receivers(SIGNAL(itemLinked(Akonadi::Item,Akonadi::Collection))) > 0; batchSupported = q_ptr->receivers(SIGNAL(itemsLinked(Akonadi::Item::List,Akonadi::Collection))) > 0; return; case Protocol::ItemChangeNotification::Unlink: needsSplit = isBatch && q_ptr->receivers(SIGNAL(itemUnlinked(Akonadi::Item,Akonadi::Collection))) > 0; batchSupported = q_ptr->receivers(SIGNAL(itemsUnlinked(Akonadi::Item::List,Akonadi::Collection))) > 0; return; default: needsSplit = isBatch; batchSupported = false; qCDebug(AKONADICORE_LOG) << "Unknown operation type" << itemNtf.operation() << "in item change notification"; return; } } Protocol::ChangeNotificationList MonitorPrivate::splitMessage(const Protocol::ItemChangeNotification &msg, bool legacy) const { Protocol::ChangeNotificationList list; Protocol::ItemChangeNotification baseMsg; baseMsg.setSessionId(msg.sessionId()); if (legacy && msg.operation() == Protocol::ItemChangeNotification::ModifyFlags) { baseMsg.setOperation(Protocol::ItemChangeNotification::Modify); baseMsg.setItemParts(QSet() << "FLAGS"); } else { baseMsg.setOperation(msg.operation()); baseMsg.setItemParts(msg.itemParts()); } baseMsg.setParentCollection(msg.parentCollection()); baseMsg.setParentDestCollection(msg.parentDestCollection()); baseMsg.setResource(msg.resource()); baseMsg.setDestinationResource(msg.destinationResource()); baseMsg.setAddedFlags(msg.addedFlags()); baseMsg.setRemovedFlags(msg.removedFlags()); baseMsg.setAddedTags(msg.addedTags()); baseMsg.setRemovedTags(msg.removedTags()); const auto items = msg.items(); list.reserve(items.count()); for (const auto &item : items) { auto copy = Protocol::ItemChangeNotificationPtr::create(baseMsg); - copy->setItems({ Protocol::FetchItemsResponsePtr::create(*item) }); - list << copy; + copy->setItems({std::move(Protocol::FetchItemsResponse(item))}); + list.push_back(std::move(copy)); } return list; } bool MonitorPrivate::fetchCollections() const { return fetchCollection; } bool MonitorPrivate::fetchItems() const { return !mItemFetchScope.isEmpty(); } bool MonitorPrivate::ensureDataAvailable(const Protocol::ChangeNotificationPtr &msg) { if (msg->type() == Protocol::Command::TagChangeNotification) { const auto tagMsg = Protocol::cmdCast(msg); if (tagMsg.metadata().contains("FETCH_TAG")) { - if (!tagCache->ensureCached({ tagMsg.tag()->id() }, mTagFetchScope)) { + if (!tagCache->ensureCached({ tagMsg.tag().id() }, mTagFetchScope)) { return false; } } return true; } if (msg->type() == Protocol::Command::RelationChangeNotification) { return true; } if (msg->type() == Protocol::Command::SubscriptionChangeNotification) { return true; } if (msg->type() == Protocol::Command::DebugChangeNotification) { return true; } if (msg->type() == Protocol::Command::CollectionChangeNotification && Protocol::cmdCast(msg).operation() == Protocol::CollectionChangeNotification::Remove) { // For collection removals the collection is gone already, so we can't fetch it, // but we have to at least obtain the ancestor chain. const qint64 parentCollection = Protocol::cmdCast(msg).parentCollection(); return parentCollection <= -1 || collectionCache->ensureCached(parentCollection, mCollectionFetchScope); } bool allCached = true; if (fetchCollections()) { const qint64 parentCollection = (msg->type() == Protocol::Command::ItemChangeNotification) ? Protocol::cmdCast(msg).parentCollection() : (msg->type() == Protocol::Command::CollectionChangeNotification) ? Protocol::cmdCast(msg).parentCollection() : -1; if (parentCollection > -1 && !collectionCache->ensureCached(parentCollection, mCollectionFetchScope)) { allCached = false; } qint64 parentDestCollection = -1; if ((msg->type() == Protocol::Command::ItemChangeNotification) && (Protocol::cmdCast(msg).operation() == Protocol::ItemChangeNotification::Move)) { parentDestCollection = Protocol::cmdCast(msg).parentDestCollection(); } else if ((msg->type() == Protocol::Command::CollectionChangeNotification) && (Protocol::cmdCast(msg).operation() == Protocol::CollectionChangeNotification::Move)) { parentDestCollection = Protocol::cmdCast(msg).parentDestCollection(); } if (parentDestCollection > -1 && !collectionCache->ensureCached(parentDestCollection, mCollectionFetchScope)) { allCached = false; } } if (msg->isRemove()) { return allCached; } if (msg->type() == Protocol::Command::ItemChangeNotification && fetchItems()) { const auto &itemNtf = Protocol::cmdCast(msg); if (mFetchChangedOnly && (itemNtf.operation() == Protocol::ItemChangeNotification::Modify || itemNtf.operation() == Protocol::ItemChangeNotification::ModifyFlags)) { const auto changedParts = itemNtf.itemParts(); const auto requestedParts = mItemFetchScope.payloadParts(); const auto requestedAttrs = mItemFetchScope.attributes(); QSet missingParts, missingAttributes; for (const QByteArray &part : changedParts) { const auto partName = part.mid(4); if (part.startsWith("PLD:") && //krazy:exclude=strings since QByteArray (!mItemFetchScope.fullPayload() || !requestedParts.contains(partName))) { missingParts.insert(partName); } else if (part.startsWith("ATR:") && //krazy:exclude=strings since QByteArray (!mItemFetchScope.allAttributes() || !requestedAttrs.contains(partName))) { missingAttributes.insert(partName); } } if (!missingParts.isEmpty() || !missingAttributes.isEmpty()) { ItemFetchScope scope(mItemFetchScope); scope.fetchFullPayload(false); for (const auto &part : requestedParts) { scope.fetchPayloadPart(part, false); } for (const auto &attr : requestedAttrs) { scope.fetchAttribute(attr, false); } for (const auto &part : missingParts) { scope.fetchPayloadPart(part, true); } for (const auto &attr : missingAttributes) { scope.fetchAttribute(attr, true); } if (!itemCache->ensureCached(Protocol::ChangeNotification::itemsToUids(itemNtf.items()), scope)) { return false; } } return allCached; } // Make sure all tags for ModifyTags operation are in cache too if (itemNtf.operation() == Protocol::ItemChangeNotification::ModifyTags) { if (!tagCache->ensureCached((itemNtf.addedTags() + itemNtf.removedTags()).toList(), mTagFetchScope)) { return false; } } if (itemNtf.metadata().contains("FETCH_ITEM")) { if (!itemCache->ensureCached(Protocol::ChangeNotification::itemsToUids(itemNtf.items()), mItemFetchScope)) { return false; } } return allCached; } else if (msg->type() == Protocol::Command::CollectionChangeNotification && fetchCollections()) { const auto &colMsg = Protocol::cmdCast(msg); if (colMsg.metadata().contains("FETCH_COLLECTION")) { - if (!collectionCache->ensureCached(colMsg.collection()->id(), mCollectionFetchScope)) { + if (!collectionCache->ensureCached(colMsg.collection().id(), mCollectionFetchScope)) { return false; } } return allCached; } return allCached; } bool MonitorPrivate::emitNotification(const Protocol::ChangeNotificationPtr &msg) { bool someoneWasListening = false; if (msg->type() == Protocol::Command::TagChangeNotification) { const auto &tagNtf = Protocol::cmdCast(msg); const bool fetched = tagNtf.metadata().contains("FETCH_TAG"); Tag tag; if (fetched) { - const auto tags = tagCache->retrieve({ tagNtf.tag()->id() }); + const auto tags = tagCache->retrieve({ tagNtf.tag().id() }); tag = tags.isEmpty() ? Tag() : tags.at(0); } else { - tag = ProtocolHelper::parseTag(*tagNtf.tag()); + tag = ProtocolHelper::parseTag(tagNtf.tag()); } someoneWasListening = emitTagNotification(tagNtf, tag); } else if (msg->type() == Protocol::Command::RelationChangeNotification) { const auto &relNtf = Protocol::cmdCast(msg); - const Relation rel = ProtocolHelper::parseRelationFetchResult(*relNtf.relation()); + const Relation rel = ProtocolHelper::parseRelationFetchResult(relNtf.relation()); someoneWasListening = emitRelationNotification(relNtf, rel); } else if (msg->type() == Protocol::Command::CollectionChangeNotification) { const auto &colNtf = Protocol::cmdCast(msg); const Collection parent = collectionCache->retrieve(colNtf.parentCollection()); Collection destParent; if (colNtf.operation() == Protocol::CollectionChangeNotification::Move) { destParent = collectionCache->retrieve(colNtf.parentDestCollection()); } const bool fetched = colNtf.metadata().contains("FETCH_COLLECTION"); //For removals this will retrieve an invalid collection. We'll deal with that in emitCollectionNotification - const Collection col = fetched ? collectionCache->retrieve(colNtf.collection()->id()) : ProtocolHelper::parseCollection(*colNtf.collection(), true); + const Collection col = fetched ? collectionCache->retrieve(colNtf.collection().id()) : ProtocolHelper::parseCollection(colNtf.collection(), true); //It is possible that the retrieval fails also in the non-removal case (e.g. because the item was meanwhile removed while //the changerecorder stored the notification or the notification was in the queue). In order to drop such invalid notifications we have to ignore them. if (col.isValid() || colNtf.operation() == Protocol::CollectionChangeNotification::Remove || !fetchCollections()) { someoneWasListening = emitCollectionNotification(colNtf, col, parent, destParent); } } else if (msg->type() == Protocol::Command::ItemChangeNotification) { const auto &itemNtf = Protocol::cmdCast(msg); const Collection parent = collectionCache->retrieve(itemNtf.parentCollection()); Collection destParent; if (itemNtf.operation() == Protocol::ItemChangeNotification::Move) { destParent = collectionCache->retrieve(itemNtf.parentDestCollection()); } const bool fetched = itemNtf.metadata().contains("FETCH_ITEM"); //For removals this will retrieve an empty set. We'll deal with that in emitItemNotification Item::List items; if (fetched) { items = itemCache->retrieve(Protocol::ChangeNotification::itemsToUids(itemNtf.items())); } else { const auto ntfItems = itemNtf.items(); items.reserve(ntfItems.size()); for (const auto &ntfItem : ntfItems) { - items.push_back(ProtocolHelper::parseItemFetchResult(*ntfItem)); + items.push_back(ProtocolHelper::parseItemFetchResult(ntfItem)); } } //It is possible that the retrieval fails also in the non-removal case (e.g. because the item was meanwhile removed while //the changerecorder stored the notification or the notification was in the queue). In order to drop such invalid notifications we have to ignore them. if (!items.isEmpty() || itemNtf.operation() == Protocol::ItemChangeNotification::Remove || !fetchItems()) { someoneWasListening = emitItemsNotification(itemNtf, items, parent, destParent); } } else if (msg->type() == Protocol::Command::SubscriptionChangeNotification) { const auto &subNtf = Protocol::cmdCast(msg); NotificationSubscriber subscriber; subscriber.setSubscriber(subNtf.subscriber()); subscriber.setSessionId(subNtf.sessionId()); subscriber.setMonitoredCollections(subNtf.collections()); subscriber.setMonitoredItems(subNtf.items()); subscriber.setMonitoredTags(subNtf.tags()); QSet monitorTypes; Q_FOREACH (auto type, subNtf.types()) { monitorTypes.insert(static_cast(type)); } subscriber.setMonitoredTypes(monitorTypes); subscriber.setMonitoredMimeTypes(subNtf.mimeTypes()); subscriber.setMonitoredResources(subNtf.resources()); subscriber.setIgnoredSessions(subNtf.ignoredSessions()); subscriber.setIsAllMonitored(subNtf.allMonitored()); subscriber.setIsExclusive(subNtf.exclusive()); subscriber.setItemFetchScope(ProtocolHelper::parseItemFetchScope(subNtf.itemFetchScope())); subscriber.setCollectionFetchScope(ProtocolHelper::parseCollectionFetchScope(subNtf.collectionFetchScope())); someoneWasListening = emitSubscriptionChangeNotification(subNtf, subscriber); } else if (msg->type() == Protocol::Command::DebugChangeNotification) { const auto &changeNtf = Protocol::cmdCast(msg); ChangeNotification notification; notification.setListeners(changeNtf.listeners()); notification.setTimestamp(QDateTime::fromMSecsSinceEpoch(changeNtf.timestamp())); notification.setNotification(changeNtf.notification()); switch (changeNtf.notification()->type()) { case Protocol::Command::ItemChangeNotification: notification.setType(ChangeNotification::Items); break; case Protocol::Command::CollectionChangeNotification: notification.setType(ChangeNotification::Collection); break; case Protocol::Command::TagChangeNotification: notification.setType(ChangeNotification::Tag); break; case Protocol::Command::RelationChangeNotification: notification.setType(ChangeNotification::Relation); break; case Protocol::Command::SubscriptionChangeNotification: notification.setType(ChangeNotification::Subscription); break; default: Q_ASSERT(false); // huh? return false; } someoneWasListening = emitDebugChangeNotification(changeNtf, notification); } return someoneWasListening; } void MonitorPrivate::updatePendingStatistics(const Protocol::ChangeNotificationPtr &msg) { if (msg->type() == Protocol::Command::ItemChangeNotification) { const auto &itemNtf = Protocol::cmdCast(msg); notifyCollectionStatisticsWatchers(itemNtf.parentCollection(), itemNtf.resource()); // FIXME use the proper resource of the target collection, for cross resource moves notifyCollectionStatisticsWatchers(itemNtf.parentDestCollection(), itemNtf.destinationResource()); } else if (msg->type() == Protocol::Command::CollectionChangeNotification) { const auto &colNtf = Protocol::cmdCast(msg); if (colNtf.operation() == Protocol::CollectionChangeNotification::Remove) { // no need for statistics updates anymore - recentlyChangedCollections.remove(colNtf.collection()->id()); + recentlyChangedCollections.remove(colNtf.collection().id()); } } } void MonitorPrivate::slotSessionDestroyed(QObject *object) { Session *objectSession = qobject_cast(object); if (objectSession) { sessions.removeAll(objectSession->sessionId()); pendingModification.stopIgnoringSession(objectSession->sessionId()); scheduleSubscriptionUpdate(); } } void MonitorPrivate::slotStatisticsChangedFinished(KJob *job) { if (job->error()) { qCWarning(AKONADICORE_LOG) << "Error on fetching collection statistics: " << job->errorText(); } else { CollectionStatisticsJob *statisticsJob = static_cast(job); Q_ASSERT(statisticsJob->collection().isValid()); emit q_ptr->collectionStatisticsChanged(statisticsJob->collection().id(), statisticsJob->statistics()); } } void MonitorPrivate::slotFlushRecentlyChangedCollections() { for (Collection::Id collection : qAsConst(recentlyChangedCollections)) { Q_ASSERT(collection >= 0); if (fetchCollectionStatistics) { fetchStatistics(collection); } else { static const CollectionStatistics dummyStatistics; emit q_ptr->collectionStatisticsChanged(collection, dummyStatistics); } } recentlyChangedCollections.clear(); } int MonitorPrivate::translateAndCompress(QQueue ¬ificationQueue, const Protocol::ChangeNotificationPtr &msg) { // Always handle tags and relations if (msg->type() == Protocol::Command::TagChangeNotification || msg->type() == Protocol::Command::RelationChangeNotification) { notificationQueue.enqueue(msg); return 1; } // We have to split moves into insert or remove if the source or destination // is not monitored. if (!msg->isMove()) { notificationQueue.enqueue(msg); return 1; } bool sourceWatched = false; bool destWatched = false; if (msg->type() == Protocol::Command::ItemChangeNotification) { const auto &itemNtf = Protocol::cmdCast(msg); if (useRefCounting) { sourceWatched = isMonitored(itemNtf.parentCollection()); destWatched = isMonitored(itemNtf.parentDestCollection()); } else { if (!resources.isEmpty()) { sourceWatched = resources.contains(itemNtf.resource()); destWatched = isMoveDestinationResourceMonitored(itemNtf); } if (!sourceWatched) { sourceWatched = isCollectionMonitored(itemNtf.parentCollection()); } if (!destWatched) { destWatched = isCollectionMonitored(itemNtf.parentDestCollection()); } } } else if (msg->type() == Protocol::Command::CollectionChangeNotification) { const auto &colNtf = Protocol::cmdCast(msg); if (!resources.isEmpty()) { sourceWatched = resources.contains(colNtf.resource()); destWatched = isMoveDestinationResourceMonitored(colNtf); } if (!sourceWatched) { sourceWatched = isCollectionMonitored(colNtf.parentCollection()); } if (!destWatched) { destWatched = isCollectionMonitored(colNtf.parentDestCollection()); } } else { Q_ASSERT(false); return 0; } if (!sourceWatched && !destWatched) { return 0; } if ((sourceWatched && destWatched) || (!collectionMoveTranslationEnabled && msg->type() == Protocol::Command::CollectionChangeNotification)) { notificationQueue.enqueue(msg); return 1; } if (sourceWatched) { if (msg->type() == Protocol::Command::ItemChangeNotification) { auto removalMessage = Protocol::ItemChangeNotificationPtr::create( Protocol::cmdCast(msg)); removalMessage->setOperation(Protocol::ItemChangeNotification::Remove); removalMessage->setParentDestCollection(-1); notificationQueue.enqueue(removalMessage); return 1; } else { auto removalMessage = Protocol::CollectionChangeNotificationPtr::create( Protocol::cmdCast(msg)); removalMessage->setOperation(Protocol::CollectionChangeNotification::Remove); removalMessage->setParentDestCollection(-1); notificationQueue.enqueue(removalMessage); return 1; } } // Transform into an insertion if (msg->type() == Protocol::Command::ItemChangeNotification) { auto insertionMessage = Protocol::ItemChangeNotificationPtr::create( Protocol::cmdCast(msg)); insertionMessage->setOperation(Protocol::ItemChangeNotification::Add); insertionMessage->setParentCollection(insertionMessage->parentDestCollection()); insertionMessage->setParentDestCollection(-1); // We don't support batch insertion, so we have to do it one by one const auto split = splitMessage(*insertionMessage, false); for (const Protocol::ChangeNotificationPtr &insertion : split) { notificationQueue.enqueue(insertion); } return split.count(); } else if (msg->type() == Protocol::Command::CollectionChangeNotification) { auto insertionMessage = Protocol::CollectionChangeNotificationPtr::create( Protocol::cmdCast(msg)); insertionMessage->setOperation(Protocol::CollectionChangeNotification::Add); insertionMessage->setParentCollection(insertionMessage->parentDestCollection()); insertionMessage->setParentDestCollection(-1); notificationQueue.enqueue(insertionMessage); return 1; } Q_ASSERT(false); return 0; } void MonitorPrivate::handleCommands() { Q_Q(Monitor); CommandBufferLocker lock(&mCommandBuffer); CommandBufferNotifyBlocker notify(&mCommandBuffer); while (!mCommandBuffer.isEmpty()) { const auto cmd = mCommandBuffer.dequeue(); lock.unlock(); const auto command = cmd.command; if (command->isResponse()) { switch (command->type()) { case Protocol::Command::Hello: { qCDebug(AKONADICORE_LOG) << q_ptr << "Connected to notification bus"; QByteArray subname; if (!q->objectName().isEmpty()) { subname = q->objectName().toLatin1(); } else { subname = session->sessionId(); } subname += " - " + QByteArray::number(quintptr(q)); qCDebug(AKONADICORE_LOG) << q_ptr << "Subscribing as \"" << subname << "\""; auto subCmd = Protocol::CreateSubscriptionCommandPtr::create(subname, session->sessionId()); ntfConnection->sendCommand(2, subCmd); break; } case Protocol::Command::CreateSubscription: { auto msubCmd = Protocol::ModifySubscriptionCommandPtr::create(); for (const auto &col : qAsConst(collections)) { msubCmd->startMonitoringCollection(col.id()); } for (const auto &res : qAsConst(resources)) { msubCmd->startMonitoringResource(res); } for (auto itemId : qAsConst(items)) { msubCmd->startMonitoringItem(itemId); } for (auto tagId : qAsConst(tags)) { msubCmd->startMonitoringTag(tagId); } for (auto type : qAsConst(types)) { msubCmd->startMonitoringType(static_cast(type)); } for (const auto &mimetype : qAsConst(mimetypes)) { msubCmd->startMonitoringMimeType(mimetype); } for (const auto &session : qAsConst(sessions)) { msubCmd->startIgnoringSession(session); } msubCmd->setAllMonitored(monitorAll); msubCmd->setIsExclusive(exclusive); msubCmd->setItemFetchScope(ProtocolHelper::itemFetchScopeToProtocol(mItemFetchScope)); msubCmd->setCollectionFetchScope(ProtocolHelper::collectionFetchScopeToProtocol(mCollectionFetchScope)); msubCmd->setTagFetchScope(ProtocolHelper::tagFetchScopeToProtocol(mTagFetchScope)); pendingModification = Protocol::ModifySubscriptionCommand(); ntfConnection->sendCommand(3, msubCmd); break; } case Protocol::Command::ModifySubscription: // TODO: Handle errors if (!monitorReady) { monitorReady = true; Q_EMIT q_ptr->monitorReady(); } break; default: qCWarning(AKONADICORE_LOG) << "Received an unexpected response on Notification stream: " << Protocol::debugString(command); break; } } else { switch (command->type()) { case Protocol::Command::ItemChangeNotification: case Protocol::Command::CollectionChangeNotification: case Protocol::Command::TagChangeNotification: case Protocol::Command::RelationChangeNotification: case Protocol::Command::SubscriptionChangeNotification: case Protocol::Command::DebugChangeNotification: slotNotify(command.staticCast()); break; default: qCWarning(AKONADICORE_LOG) << "Received an unexpected message on Notification stream:" << Protocol::debugString(command); break; } } lock.relock(); } notify.unblock(); lock.unlock(); } /* server notification --> ?accepted --> pendingNotifications --> ?dataAvailable --> emit | | x --> discard x --> pipeline fetchJobDone --> pipeline ?dataAvailable --> emit */ void MonitorPrivate::slotNotify(const Protocol::ChangeNotificationPtr &msg) { int appendedMessages = 0; int modifiedMessages = 0; int erasedMessages = 0; invalidateCaches(msg); updatePendingStatistics(msg); bool needsSplit = true; bool supportsBatch = false; if (isLazilyIgnored(msg, true)) { return; } checkBatchSupport(msg, needsSplit, supportsBatch); const bool isModifyFlags = (msg->type() == Protocol::Command::ItemChangeNotification && Protocol::cmdCast(msg).operation() == Protocol::ItemChangeNotification::ModifyFlags); if (supportsBatch || (!needsSplit && !supportsBatch && !isModifyFlags) || msg->type() == Protocol::Command::CollectionChangeNotification) { // Make sure the batch msg is always queued before the split notifications const int oldSize = pendingNotifications.size(); const int appended = translateAndCompress(pendingNotifications, msg); if (appended > 0) { appendedMessages += appended; } else { ++modifiedMessages; } // translateAndCompress can remove an existing "modify" when msg is a "delete". // Or it can merge two ModifyFlags and return false. // We need to detect such removals, for ChangeRecorder. if (pendingNotifications.count() != oldSize + appended) { ++erasedMessages; // this count isn't exact, but it doesn't matter } } else if (needsSplit) { // If it's not queued at least make sure we fetch all the items from split // notifications in one go. if (msg->type() == Protocol::Command::ItemChangeNotification) { const auto items = Protocol::cmdCast(msg).items(); itemCache->ensureCached(Protocol::ChangeNotification::itemsToUids(items), mItemFetchScope); } } // if the message contains more items, but we need to emit single-item notification, // split the message into one message per item and queue them // if the message contains only one item, but batches are not supported // (and thus neither is flagsModified), splitMessage() will convert the // notification to regular Modify with "FLAGS" part changed if (needsSplit || (!needsSplit && !supportsBatch && isModifyFlags)) { // Make sure inter-resource move notifications are translated into // Add/Remove notifications if (msg->type() == Protocol::Command::ItemChangeNotification) { const auto &itemNtf = Protocol::cmdCast(msg); if (itemNtf.operation() == Protocol::ItemChangeNotification::Move && itemNtf.resource() != itemNtf.destinationResource()) { if (needsSplit) { const Protocol::ChangeNotificationList split = splitMessage(itemNtf, !supportsBatch); for (const auto &splitMsg : split) { appendedMessages += translateAndCompress(pendingNotifications, splitMsg); } } else { appendedMessages += translateAndCompress(pendingNotifications, msg); } } else { const Protocol::ChangeNotificationList split = splitMessage(itemNtf, !supportsBatch); pendingNotifications << split.toList(); appendedMessages += split.count(); } } } // tell ChangeRecorder (even if 0 appended, the compression could have made changes to existing messages) if (appendedMessages > 0 || modifiedMessages > 0 || erasedMessages > 0) { if (erasedMessages > 0) { notificationsErased(); } else { notificationsEnqueued(appendedMessages); } } dispatchNotifications(); } void MonitorPrivate::flushPipeline() { while (!pipeline.isEmpty()) { const auto msg = pipeline.head(); if (ensureDataAvailable(msg)) { // dequeue should be before emit, otherwise stuff might happen (like dataAvailable // being called again) and we end up dequeuing an empty pipeline pipeline.dequeue(); emitNotification(msg); } else { break; } } } void MonitorPrivate::dataAvailable() { flushPipeline(); dispatchNotifications(); } void MonitorPrivate::dispatchNotifications() { // Note that this code is not used in a ChangeRecorder (pipelineSize==0) while (pipeline.size() < pipelineSize() && !pendingNotifications.isEmpty()) { const auto msg = pendingNotifications.dequeue(); const bool avail = ensureDataAvailable(msg); if (avail && pipeline.isEmpty()) { emitNotification(msg); } else { pipeline.enqueue(msg); } } } static Relation::List extractRelations(const QSet &rels) { Relation::List relations; if (rels.isEmpty()) { return relations; } relations.reserve(rels.size()); for (const auto &rel : rels) { relations.push_back(Relation(rel.type.toLatin1(), Akonadi::Item(rel.leftId), Akonadi::Item(rel.rightId))); } return relations; } bool MonitorPrivate::emitItemsNotification(const Protocol::ItemChangeNotification &msg_, const Item::List &items, const Collection &collection, const Collection &collectionDest) { Protocol::ItemChangeNotification msg = msg_; Collection col = collection; Collection colDest = collectionDest; if (!col.isValid()) { col = Collection(msg.parentCollection()); col.setResource(QString::fromUtf8(msg.resource())); } if (!colDest.isValid()) { colDest = Collection(msg.parentDestCollection()); // HACK: destination resource is delivered in the parts field... if (!msg.itemParts().isEmpty()) { colDest.setResource(QString::fromLatin1(*(msg.itemParts().cbegin()))); } } const QSet addedFlags = msg.addedFlags(); const QSet removedFlags = msg.removedFlags(); Relation::List addedRelations, removedRelations; if (msg.operation() == Protocol::ItemChangeNotification::ModifyRelations) { addedRelations = extractRelations(msg.addedRelations()); removedRelations = extractRelations(msg.removedRelations()); } Tag::List addedTags, removedTags; if (msg.operation() == Protocol::ItemChangeNotification::ModifyTags) { addedTags = tagCache->retrieve(msg.addedTags().toList()); removedTags = tagCache->retrieve(msg.removedTags().toList()); } Item::List its = items; for (auto it = its.begin(), end = its.end(); it != end; ++it) { if (msg.operation() == Protocol::ItemChangeNotification::Move) { it->setParentCollection(colDest); } else { it->setParentCollection(col); } } bool handled = false; switch (msg.operation()) { case Protocol::ItemChangeNotification::Add: if (q_ptr->receivers(SIGNAL(itemAdded(Akonadi::Item,Akonadi::Collection))) > 0) { Q_ASSERT(its.count() == 1); emit q_ptr->itemAdded(its.first(), col); return true; } return false; case Protocol::ItemChangeNotification::Modify: if (q_ptr->receivers(SIGNAL(itemChanged(Akonadi::Item,QSet))) > 0) { Q_ASSERT(its.count() == 1); emit q_ptr->itemChanged(its.first(), msg.itemParts()); return true; } return false; case Protocol::ItemChangeNotification::ModifyFlags: if (q_ptr->receivers(SIGNAL(itemsFlagsChanged(Akonadi::Item::List,QSet,QSet))) > 0) { emit q_ptr->itemsFlagsChanged(its, msg.addedFlags(), msg.removedFlags()); handled = true; } return handled; case Protocol::ItemChangeNotification::Move: if (q_ptr->receivers(SIGNAL(itemMoved(Akonadi::Item,Akonadi::Collection,Akonadi::Collection))) > 0) { Q_ASSERT(its.count() == 1); emit q_ptr->itemMoved(its.first(), col, colDest); handled = true; } if (q_ptr->receivers(SIGNAL(itemsMoved(Akonadi::Item::List,Akonadi::Collection,Akonadi::Collection))) > 0) { emit q_ptr->itemsMoved(its, col, colDest); handled = true; } return handled; case Protocol::ItemChangeNotification::Remove: if (q_ptr->receivers(SIGNAL(itemRemoved(Akonadi::Item))) > 0) { Q_ASSERT(its.count() == 1); emit q_ptr->itemRemoved(its.first()); handled = true; } if (q_ptr->receivers(SIGNAL(itemsRemoved(Akonadi::Item::List))) > 0) { emit q_ptr->itemsRemoved(its); handled = true; } return handled; case Protocol::ItemChangeNotification::Link: if (q_ptr->receivers(SIGNAL(itemLinked(Akonadi::Item,Akonadi::Collection))) > 0) { Q_ASSERT(its.count() == 1); emit q_ptr->itemLinked(its.first(), col); handled = true; } if (q_ptr->receivers(SIGNAL(itemsLinked(Akonadi::Item::List,Akonadi::Collection))) > 0) { emit q_ptr->itemsLinked(its, col); handled = true; } return handled; case Protocol::ItemChangeNotification::Unlink: if (q_ptr->receivers(SIGNAL(itemUnlinked(Akonadi::Item,Akonadi::Collection))) > 0) { Q_ASSERT(its.count() == 1); emit q_ptr->itemUnlinked(its.first(), col); handled = true; } if (q_ptr->receivers(SIGNAL(itemsUnlinked(Akonadi::Item::List,Akonadi::Collection))) > 0) { emit q_ptr->itemsUnlinked(its, col); handled = true; } return handled; case Protocol::ItemChangeNotification::ModifyTags: if (q_ptr->receivers(SIGNAL(itemsTagsChanged(Akonadi::Item::List,QSet,QSet))) > 0) { emit q_ptr->itemsTagsChanged(its, Akonadi::vectorToSet(addedTags), Akonadi::vectorToSet(removedTags)); return true; } return false; case Protocol::ItemChangeNotification::ModifyRelations: if (q_ptr->receivers(SIGNAL(itemsRelationsChanged(Akonadi::Item::List,Akonadi::Relation::List,Akonadi::Relation::List))) > 0) { emit q_ptr->itemsRelationsChanged(its, addedRelations, removedRelations); return true; } return false; default: qCDebug(AKONADICORE_LOG) << "Unknown operation type" << msg.operation() << "in item change notification"; } return false; } bool MonitorPrivate::emitCollectionNotification(const Protocol::CollectionChangeNotification &msg, const Collection &col, const Collection &par, const Collection &dest) { Collection parent = par; if (!parent.isValid()) { parent = Collection(msg.parentCollection()); } Collection destination = dest; if (!destination.isValid()) { destination = Collection(msg.parentDestCollection()); } Collection collection = col; Q_ASSERT(collection.isValid()); if (!collection.isValid()) { qCWarning(AKONADICORE_LOG) << "Failed to get valid Collection for a Collection change!"; return true; // prevent Monitor disconnecting from a signal } if (msg.operation() == Protocol::CollectionChangeNotification::Move) { collection.setParentCollection(destination); } else { collection.setParentCollection(parent); } switch (msg.operation()) { case Protocol::CollectionChangeNotification::Add: if (q_ptr->receivers(SIGNAL(collectionAdded(Akonadi::Collection,Akonadi::Collection))) == 0) { return false; } emit q_ptr->collectionAdded(collection, parent); return true; case Protocol::CollectionChangeNotification::Modify: if (q_ptr->receivers(SIGNAL(collectionChanged(Akonadi::Collection))) == 0 && q_ptr->receivers(SIGNAL(collectionChanged(Akonadi::Collection,QSet))) == 0) { return false; } emit q_ptr->collectionChanged(collection); emit q_ptr->collectionChanged(collection, msg.changedParts()); return true; case Protocol::CollectionChangeNotification::Move: if (q_ptr->receivers(SIGNAL(collectionMoved(Akonadi::Collection,Akonadi::Collection,Akonadi::Collection))) == 0) { return false; } emit q_ptr->collectionMoved(collection, parent, destination); return true; case Protocol::CollectionChangeNotification::Remove: if (q_ptr->receivers(SIGNAL(collectionRemoved(Akonadi::Collection))) == 0) { return false; } emit q_ptr->collectionRemoved(collection); return true; case Protocol::CollectionChangeNotification::Subscribe: if (q_ptr->receivers(SIGNAL(collectionSubscribed(Akonadi::Collection,Akonadi::Collection))) == 0) { return false; } if (!monitorAll) { // ### why?? emit q_ptr->collectionSubscribed(collection, parent); } return true; case Protocol::CollectionChangeNotification::Unsubscribe: if (q_ptr->receivers(SIGNAL(collectionUnsubscribed(Akonadi::Collection))) == 0) { return false; } if (!monitorAll) { // ### why?? emit q_ptr->collectionUnsubscribed(collection); } return true; default: qCDebug(AKONADICORE_LOG) << "Unknown operation type" << msg.operation() << "in collection change notification"; } return false; } bool MonitorPrivate::emitTagNotification(const Protocol::TagChangeNotification &msg, const Tag &tag) { Q_UNUSED(msg); switch (msg.operation()) { case Protocol::TagChangeNotification::Add: if (q_ptr->receivers(SIGNAL(tagAdded(Akonadi::Tag))) == 0) { return false; } Q_EMIT q_ptr->tagAdded(tag); return true; case Protocol::TagChangeNotification::Modify: if (q_ptr->receivers(SIGNAL(tagChanged(Akonadi::Tag))) == 0) { return false; } Q_EMIT q_ptr->tagChanged(tag); return true; case Protocol::TagChangeNotification::Remove: if (q_ptr->receivers(SIGNAL(tagRemoved(Akonadi::Tag))) == 0) { return false; } Q_EMIT q_ptr->tagRemoved(tag); return true; default: qCDebug(AKONADICORE_LOG) << "Unknown operation type" << msg.operation() << "in tag change notification"; } return false; } bool MonitorPrivate::emitRelationNotification(const Protocol::RelationChangeNotification &msg, const Relation &relation) { if (!relation.isValid()) { return false; } switch (msg.operation()) { case Protocol::RelationChangeNotification::Add: if (q_ptr->receivers(SIGNAL(relationAdded(Akonadi::Relation))) == 0) { return false; } Q_EMIT q_ptr->relationAdded(relation); return true; case Protocol::RelationChangeNotification::Remove: if (q_ptr->receivers(SIGNAL(relationRemoved(Akonadi::Relation))) == 0) { return false; } Q_EMIT q_ptr->relationRemoved(relation); return true; default: qCDebug(AKONADICORE_LOG) << "Unknown operation type" << msg.operation() << "in tag change notification"; } return false; } bool MonitorPrivate::emitSubscriptionChangeNotification(const Protocol::SubscriptionChangeNotification &msg, const Akonadi::NotificationSubscriber &subscriber) { if (!subscriber.isValid()) { return false; } switch (msg.operation()) { case Protocol::SubscriptionChangeNotification::Add: if (q_ptr->receivers(SIGNAL(notificationSubscriberAdded(Akonadi::NotificationSubscriber))) == 0) { return false; } Q_EMIT q_ptr->notificationSubscriberAdded(subscriber); return true; case Protocol::SubscriptionChangeNotification::Modify: if (q_ptr->receivers(SIGNAL(notificationSubscriberChanged(Akonadi::NotificationSubscriber))) == 0) { return false; } Q_EMIT q_ptr->notificationSubscriberChanged(subscriber); return true; case Protocol::SubscriptionChangeNotification::Remove: if (q_ptr->receivers(SIGNAL(notificationSubscriberRemoved(Akonadi::NotificationSubscriber))) == 0) { return false; } Q_EMIT q_ptr->notificationSubscriberRemoved(subscriber); return true; default: break; } return false; } bool MonitorPrivate::emitDebugChangeNotification(const Protocol::DebugChangeNotification &msg, const ChangeNotification &ntf) { Q_UNUSED(msg); if (!ntf.isValid()) { return false; } if (q_ptr->receivers(SIGNAL(debugNotification(Akonadi::ChangeNotification))) == 0) { return false; } Q_EMIT q_ptr->debugNotification(ntf); return true; } void MonitorPrivate::invalidateCaches(const Protocol::ChangeNotificationPtr &msg) { // remove invalidates // modify removes the cache entry, as we need to re-fetch // And subscription modify the visibility of the collection by the collectionFetchScope. switch (msg->type()) { case Protocol::Command::CollectionChangeNotification: { const auto &colNtf = Protocol::cmdCast(msg); switch (colNtf.operation()) { case Protocol::CollectionChangeNotification::Modify: case Protocol::CollectionChangeNotification::Move: case Protocol::CollectionChangeNotification::Subscribe: - collectionCache->update(colNtf.collection()->id(), mCollectionFetchScope); + collectionCache->update(colNtf.collection().id(), mCollectionFetchScope); break; case Protocol::CollectionChangeNotification::Remove: - collectionCache->invalidate(colNtf.collection()->id()); + collectionCache->invalidate(colNtf.collection().id()); break; default: break; } } break; case Protocol::Command::ItemChangeNotification: { const auto &itemNtf = Protocol::cmdCast(msg); switch (itemNtf.operation()) { case Protocol::ItemChangeNotification::Modify: case Protocol::ItemChangeNotification::ModifyFlags: case Protocol::ItemChangeNotification::ModifyTags: case Protocol::ItemChangeNotification::ModifyRelations: case Protocol::ItemChangeNotification::Move: itemCache->update(Protocol::ChangeNotification::itemsToUids(itemNtf.items()), mItemFetchScope); break; case Protocol::ItemChangeNotification::Remove: itemCache->invalidate(Protocol::ChangeNotification::itemsToUids(itemNtf.items())); break; default: break; } } break; case Protocol::Command::TagChangeNotification: { const auto &tagNtf = Protocol::cmdCast(msg); switch (tagNtf.operation()) { case Protocol::TagChangeNotification::Modify: - tagCache->update({ tagNtf.tag()->id() }, mTagFetchScope); + tagCache->update({ tagNtf.tag().id() }, mTagFetchScope); break; case Protocol::TagChangeNotification::Remove: - tagCache->invalidate({ tagNtf.tag()->id() }); + tagCache->invalidate({ tagNtf.tag().id() }); break; default: break; } } break; default: break; } } void MonitorPrivate::invalidateCache(const Collection &col) { collectionCache->update(col.id(), mCollectionFetchScope); } void MonitorPrivate::ref(Collection::Id id) { if (!refCountMap.contains(id)) { refCountMap.insert(id, 0); } ++refCountMap[id]; if (m_buffer.isBuffered(id)) { m_buffer.purge(id); } } Akonadi::Collection::Id MonitorPrivate::deref(Collection::Id id) { Q_ASSERT(refCountMap.contains(id)); if (--refCountMap[id] == 0) { refCountMap.remove(id); return m_buffer.buffer(id); } return -1; } void MonitorPrivate::PurgeBuffer::purge(Collection::Id id) { m_buffer.removeOne(id); } Akonadi::Collection::Id MonitorPrivate::PurgeBuffer::buffer(Collection::Id id) { // Ensure that we don't put a duplicate @p id into the buffer. purge(id); Collection::Id bumpedId = -1; if (m_buffer.size() == MAXBUFFERSIZE) { bumpedId = m_buffer.dequeue(); purge(bumpedId); } m_buffer.enqueue(id); return bumpedId; } int MonitorPrivate::PurgeBuffer::buffersize() { return MAXBUFFERSIZE; } bool MonitorPrivate::isMonitored(Collection::Id colId) const { if (!useRefCounting) { return true; } return refCountMap.contains(colId) || m_buffer.isBuffered(colId); } void MonitorPrivate::notifyCollectionStatisticsWatchers(Collection::Id collection, const QByteArray &resource) { if (collection > 0 && (monitorAll || isCollectionMonitored(collection) || resources.contains(resource))) { recentlyChangedCollections.insert(collection); if (!statisticsCompressionTimer.isActive()) { statisticsCompressionTimer.start(); } } } // @endcond diff --git a/src/private/protocol.cpp b/src/private/protocol.cpp index a299866b0..ed56f9bd4 100644 --- a/src/private/protocol.cpp +++ b/src/private/protocol.cpp @@ -1,1035 +1,1029 @@ /* * Copyright (c) 2015 Daniel Vrátil * Copyright (c) 2016 Daniel Vrátil * * 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 "protocol_p.h" #include "scope_p.h" #include "imapset_p.h" #include "datastream_p_p.h" #include #include #include #include #include #include #undef AKONADI_DECLARE_PRIVATE #define AKONADI_DECLARE_PRIVATE(Class) \ inline Class##Private* Class::d_func() {\ return reinterpret_cast(d_ptr.data()); \ } \ inline const Class##Private* Class::d_func() const {\ return reinterpret_cast(d_ptr.constData()); \ } #define COMPARE(prop) \ (prop == ((decltype(this)) other)->prop) namespace Akonadi { namespace Protocol { QDebug operator<<(QDebug _dbg, Command::Type type) { QDebug dbg(_dbg.noquote()); switch (type) { case Command::Invalid: return dbg << "Invalid"; case Command::Hello: return dbg << "Hello"; case Command::Login: return dbg << "Login"; case Command::Logout: return dbg << "Logout"; case Command::Transaction: return dbg << "Transaction"; case Command::CreateItem: return dbg << "CreateItem"; case Command::CopyItems: return dbg << "CopyItems"; case Command::DeleteItems: return dbg << "DeleteItems"; case Command::FetchItems: return dbg << "FetchItems"; case Command::LinkItems: return dbg << "LinkItems"; case Command::ModifyItems: return dbg << "ModifyItems"; case Command::MoveItems: return dbg << "MoveItems"; case Command::CreateCollection: return dbg << "CreateCollection"; case Command::CopyCollection: return dbg << "CopyCollection"; case Command::DeleteCollection: return dbg << "DeleteCollection"; case Command::FetchCollections: return dbg << "FetchCollections"; case Command::FetchCollectionStats: return dbg << "FetchCollectionStats"; case Command::ModifyCollection: return dbg << "ModifyCollection"; case Command::MoveCollection: return dbg << "MoveCollection"; case Command::Search: return dbg << "Search"; case Command::SearchResult: return dbg << "SearchResult"; case Command::StoreSearch: return dbg << "StoreSearch"; case Command::CreateTag: return dbg << "CreateTag"; case Command::DeleteTag: return dbg << "DeleteTag"; case Command::FetchTags: return dbg << "FetchTags"; case Command::ModifyTag: return dbg << "ModifyTag"; case Command::FetchRelations: return dbg << "FetchRelations"; case Command::ModifyRelation: return dbg << "ModifyRelation"; case Command::RemoveRelations: return dbg << "RemoveRelations"; case Command::SelectResource: return dbg << "SelectResource"; case Command::StreamPayload: return dbg << "StreamPayload"; case Command::ItemChangeNotification: return dbg << "ItemChangeNotification"; case Command::CollectionChangeNotification: return dbg << "CollectionChangeNotification"; case Command::TagChangeNotification: return dbg << "TagChangeNotification"; case Command::RelationChangeNotification: return dbg << "RelationChangeNotification"; case Command::SubscriptionChangeNotification: return dbg << "SubscriptionChangeNotification"; case Command::DebugChangeNotification: return dbg << "DebugChangeNotification"; case Command::CreateSubscription: return dbg << "CreateSubscription"; case Command::ModifySubscription: return dbg << "ModifySubscription"; case Command::_ResponseBit: Q_ASSERT(false); return dbg << static_cast(type); } Q_ASSERT(false); return dbg << static_cast(type); } template DataStream &operator<<(DataStream &stream, const QSharedPointer &ptr) { Protocol::serialize(stream.device(), ptr); return stream; } template DataStream &operator>>(DataStream &stream, QSharedPointer &ptr) { ptr = Protocol::deserialize(stream.device()).staticCast(); return stream; } /******************************************************************************/ Command::Command() : mType(Invalid) { } Command::Command(const Command &other) : mType(other.mType) { } Command::Command(quint8 type) : mType(type) { } Command::~Command() { } Command& Command::operator=(const Command &other) { mType = other.mType; return *this; } bool Command::operator==(const Command &other) const { return mType == other.mType; } void Command::toJson(QJsonObject &json) const { json[QStringLiteral("response")] = static_cast(mType & Command::_ResponseBit); #define case_label(x) case Command::x: { \ json[QStringLiteral("type")] = QStringLiteral(#x); \ } break; switch (mType & ~Command::_ResponseBit) { case_label(Invalid) case_label(Hello) case_label(Login) case_label(Logout) case_label(Transaction) case_label(CreateItem) case_label(CopyItems) case_label(DeleteItems) case_label(FetchItems) case_label(LinkItems) case_label(ModifyItems) case_label(MoveItems) case_label(CreateCollection) case_label(CopyCollection) case_label(DeleteCollection) case_label(FetchCollections) case_label(FetchCollectionStats) case_label(ModifyCollection) case_label(MoveCollection) case_label(Search) case_label(SearchResult) case_label(StoreSearch) case_label(CreateTag) case_label(DeleteTag) case_label(FetchTags) case_label(ModifyTag) case_label(FetchRelations) case_label(ModifyRelation) case_label(RemoveRelations) case_label(SelectResource) case_label(StreamPayload) case_label(CreateSubscription) case_label(ModifySubscription) case_label(DebugChangeNotification) case_label(ItemChangeNotification) case_label(CollectionChangeNotification) case_label(TagChangeNotification) case_label(RelationChangeNotification) case_label(SubscriptionChangeNotification) } #undef case_label } DataStream &operator<<(DataStream &stream, const Command &cmd) { return stream << cmd.mType; } DataStream &operator>>(DataStream &stream, Command &cmd) { return stream >> cmd.mType; } QDebug operator<<(QDebug dbg, const Command &cmd) { return dbg.noquote() << ((cmd.mType & Command::_ResponseBit) ? "Response:" : "Command:") << static_cast(cmd.mType & ~Command::_ResponseBit) << "\n"; } void toJson(const Akonadi::Protocol::Command *command, QJsonObject &json) { #define case_notificationlabel(x, class) case Command::x: { \ static_cast(command)->toJson(json); \ } break; #define case_commandlabel(x, cmd, resp) \ case Command::x: { \ static_cast(command)->toJson(json); \ } break; \ case Command::x | Command::_ResponseBit: { \ static_cast(command)->toJson(json); \ } break; switch (command->mType) { case Command::Invalid: break; case Command::Hello | Command::_ResponseBit: { static_cast(command)->toJson(json); } break; case_commandlabel(Login, LoginCommand, LoginResponse) case_commandlabel(Logout, LogoutCommand, LogoutResponse) case_commandlabel(Transaction, TransactionCommand, TransactionResponse) case_commandlabel(CreateItem, CreateItemCommand, CreateItemResponse) case_commandlabel(CopyItems, CopyItemsCommand, CopyItemsResponse) case_commandlabel(DeleteItems, DeleteItemsCommand, DeleteItemsResponse) case_commandlabel(FetchItems, FetchItemsCommand, FetchItemsResponse) case_commandlabel(LinkItems, LinkItemsCommand, LinkItemsResponse) case_commandlabel(ModifyItems, ModifyItemsCommand, ModifyItemsResponse) case_commandlabel(MoveItems, MoveItemsCommand, MoveItemsResponse) case_commandlabel(CreateCollection, CreateCollectionCommand, CreateCollectionResponse) case_commandlabel(CopyCollection, CopyCollectionCommand, CopyCollectionResponse) case_commandlabel(DeleteCollection, DeleteCollectionCommand, DeleteCollectionResponse) case_commandlabel(FetchCollections, FetchCollectionsCommand, FetchCollectionsResponse) case_commandlabel(FetchCollectionStats, FetchCollectionStatsCommand, FetchCollectionStatsResponse) case_commandlabel(ModifyCollection, ModifyCollectionCommand, ModifyCollectionResponse) case_commandlabel(MoveCollection, MoveCollectionCommand, MoveCollectionResponse) case_commandlabel(Search, SearchCommand, SearchResponse) case_commandlabel(SearchResult, SearchResultCommand, SearchResultResponse) case_commandlabel(StoreSearch, StoreSearchCommand, StoreSearchResponse) case_commandlabel(CreateTag, CreateTagCommand, CreateTagResponse) case_commandlabel(DeleteTag, DeleteTagCommand, DeleteTagResponse) case_commandlabel(FetchTags, FetchTagsCommand, FetchTagsResponse) case_commandlabel(ModifyTag, ModifyTagCommand, ModifyTagResponse) case_commandlabel(FetchRelations, FetchRelationsCommand, FetchRelationsResponse) case_commandlabel(ModifyRelation, ModifyRelationCommand, ModifyRelationResponse) case_commandlabel(RemoveRelations, RemoveRelationsCommand, RemoveRelationsResponse) case_commandlabel(SelectResource, SelectResourceCommand, SelectResourceResponse) case_commandlabel(StreamPayload, StreamPayloadCommand, StreamPayloadResponse) case_commandlabel(CreateSubscription, CreateSubscriptionCommand, CreateSubscriptionResponse) case_commandlabel(ModifySubscription, ModifySubscriptionCommand, ModifySubscriptionResponse) case_notificationlabel(DebugChangeNotification, DebugChangeNotification) case_notificationlabel(ItemChangeNotification, ItemChangeNotification) case_notificationlabel(CollectionChangeNotification, CollectionChangeNotification) case_notificationlabel(TagChangeNotification, TagChangeNotification) case_notificationlabel(RelationChangeNotification, RelationChangeNotification) case_notificationlabel(SubscriptionChangeNotification, SubscriptionChangeNotification) } #undef case_notificationlabel #undef case_commandlabel } /******************************************************************************/ Response::Response() : Response(Command::Invalid) { } Response::Response(const Response &other) : Command(other) , mErrorCode(other.mErrorCode) , mErrorMsg(other.mErrorMsg) { } Response::Response(Command::Type type) : Command(type | Command::_ResponseBit) , mErrorCode(0) { } Response &Response::operator=(const Response &other) { Command::operator=(other); mErrorMsg = other.mErrorMsg; mErrorCode = other.mErrorCode; return *this; } bool Response::operator==(const Response &other) const { return *static_cast(this) == static_cast(other) && mErrorCode == other.mErrorCode && mErrorMsg == other.mErrorMsg; } void Response::toJson(QJsonObject &json) const { static_cast(this)->toJson(json); if (isError()) { QJsonObject error; error[QStringLiteral("code")] = errorCode(); error[QStringLiteral("message")] = errorMessage(); json[QStringLiteral("error")] = error; } else { json[QStringLiteral("error")] = false; } } DataStream &operator<<(DataStream &stream, const Response &cmd) { return stream << static_cast(cmd) << cmd.mErrorCode << cmd.mErrorMsg; } DataStream &operator>>(DataStream &stream, Response &cmd) { return stream >> static_cast(cmd) >> cmd.mErrorCode >> cmd.mErrorMsg; } QDebug operator<<(QDebug dbg, const Response &resp) { return dbg.noquote() << static_cast(resp) << "Error code:" << resp.mErrorCode << "\n" << "Error msg:" << resp.mErrorMsg << "\n"; } /******************************************************************************/ class FactoryPrivate { public: typedef CommandPtr (*CommandFactoryFunc)(); typedef ResponsePtr (*ResponseFactoryFunc)(); FactoryPrivate() { // Session management registerType(); registerType(); registerType(); // Transactions registerType(); // Items registerType(); registerType(); registerType(); registerType(); registerType(); registerType(); registerType(); // Collections registerType(); registerType(); registerType(); registerType(); registerType(); registerType(); registerType(); // Search registerType(); registerType(); registerType(); // Tag registerType(); registerType(); registerType(); registerType(); // Relation registerType(); registerType(); registerType(); // Resources registerType(); // Other...? registerType(); registerType(); registerType(); registerType(); registerType(); registerType(); registerType(); registerType(); registerType(); } // clang has problem resolving the right qHash() overload for Command::Type, // so use its underlying integer type instead QHash::type, QPair> registrar; private: template static CommandPtr commandFactoryFunc() { return QSharedPointer::create(); } template static ResponsePtr responseFactoryFunc() { return QSharedPointer::create(); } template void registerType() { CommandFactoryFunc cmdFunc = &commandFactoryFunc; ResponseFactoryFunc respFunc = &responseFactoryFunc; registrar.insert(T, qMakePair(cmdFunc, respFunc)); } }; Q_GLOBAL_STATIC(FactoryPrivate, sFactoryPrivate) CommandPtr Factory::command(Command::Type type) { auto iter = sFactoryPrivate->registrar.constFind(type); if (iter == sFactoryPrivate->registrar.constEnd()) { return QSharedPointer::create(); } return iter->first(); } ResponsePtr Factory::response(Command::Type type) { auto iter = sFactoryPrivate->registrar.constFind(type); if (iter == sFactoryPrivate->registrar.constEnd()) { return QSharedPointer::create(); } return iter->second(); } /******************************************************************************/ /******************************************************************************/ ItemFetchScope::ItemFetchScope() : mAncestorDepth(NoAncestor) , mFlags(None) { } ItemFetchScope::ItemFetchScope(const ItemFetchScope &other) : mAncestorDepth(other.mAncestorDepth) , mFlags(other.mFlags) , mRequestedParts(other.mRequestedParts) , mChangedSince(other.mChangedSince) , mTagFetchScope(other.mTagFetchScope) { } ItemFetchScope::~ItemFetchScope() { } ItemFetchScope &ItemFetchScope::operator=(const ItemFetchScope &other) { mAncestorDepth = other.mAncestorDepth; mFlags = other.mFlags; mRequestedParts = other.mRequestedParts; mChangedSince = other.mChangedSince; mTagFetchScope = other.mTagFetchScope; return *this; } bool ItemFetchScope::operator==(const ItemFetchScope &other) const { return mRequestedParts == other.mRequestedParts && mChangedSince == other.mChangedSince && mTagFetchScope == other.mTagFetchScope && mAncestorDepth == other.mAncestorDepth && mFlags == other.mFlags; } QVector ItemFetchScope::requestedPayloads() const { QVector rv; std::copy_if(mRequestedParts.begin(), mRequestedParts.end(), std::back_inserter(rv), [](const QByteArray &ba) { return ba.startsWith("PLD:"); }); return rv; } void ItemFetchScope::setFetch(FetchFlags attributes, bool fetch) { if (fetch) { mFlags |= attributes; if (attributes & FullPayload) { if (!mRequestedParts.contains(AKONADI_PARAM_PLD_RFC822)) { mRequestedParts << AKONADI_PARAM_PLD_RFC822; } } } else { mFlags &= ~attributes; } } bool ItemFetchScope::fetch(FetchFlags flags) const { if (flags == None) { return mFlags == None; } else { return mFlags & flags; } } void ItemFetchScope::toJson(QJsonObject &json) const { json[QStringLiteral("flags")] = static_cast(mFlags); QJsonArray tagFetchArray; for (const auto &tag : qAsConst(mTagFetchScope)) { tagFetchArray.append(QString::fromUtf8(tag)); } json[QStringLiteral("TagFetchScope")] = tagFetchArray; json[QStringLiteral("ChangedSince")] = mChangedSince.toString(); json[QStringLiteral("AncestorDepth")] = static_cast::type>(mAncestorDepth); QJsonArray requestedPartsArray; for (const auto &part : qAsConst(mRequestedParts)) { requestedPartsArray.append(QString::fromUtf8(part)); } json[QStringLiteral("RequestedParts")] = requestedPartsArray; } QDebug operator<<(QDebug dbg, ItemFetchScope::AncestorDepth depth) { switch (depth) { case ItemFetchScope::NoAncestor: return dbg << "No ancestor"; case ItemFetchScope::ParentAncestor: return dbg << "Parent ancestor"; case ItemFetchScope::AllAncestors: return dbg << "All ancestors"; } Q_UNREACHABLE(); } DataStream &operator<<(DataStream &stream, const ItemFetchScope &scope) { return stream << scope.mRequestedParts << scope.mChangedSince << scope.mTagFetchScope << scope.mAncestorDepth << scope.mFlags; } DataStream &operator>>(DataStream &stream, ItemFetchScope &scope) { return stream >> scope.mRequestedParts >> scope.mChangedSince >> scope.mTagFetchScope >> scope.mAncestorDepth >> scope.mFlags; } QDebug operator<<(QDebug dbg, const ItemFetchScope &scope) { return dbg.noquote() << "FetchScope(\n" << "Fetch Flags:" << scope.mFlags << "\n" << "Tag Fetch Scope:" << scope.mTagFetchScope << "\n" << "Changed Since:" << scope.mChangedSince << "\n" << "Ancestor Depth:" << scope.mAncestorDepth << "\n" << "Requested Parts:" << scope.mRequestedParts << ")\n"; } /******************************************************************************/ ScopeContext::ScopeContext() { } ScopeContext::ScopeContext(Type type, qint64 id) { if (type == ScopeContext::Tag) { mTagCtx = id; } else if (type == ScopeContext::Collection) { mColCtx = id; } } ScopeContext::ScopeContext(Type type, const QString &ctx) { if (type == ScopeContext::Tag) { mTagCtx = ctx; } else if (type == ScopeContext::Collection) { mColCtx = ctx; } } ScopeContext::ScopeContext(const ScopeContext &other) : mColCtx(other.mColCtx) , mTagCtx(other.mTagCtx) { } ScopeContext::~ScopeContext() { } ScopeContext &ScopeContext::operator=(const ScopeContext &other) { mColCtx = other.mColCtx; mTagCtx = other.mTagCtx; return *this; } bool ScopeContext::operator==(const ScopeContext &other) const { return mColCtx == other.mColCtx && mTagCtx == other.mTagCtx; } void ScopeContext::toJson(QJsonObject &json) const { if (isEmpty()) { json[QStringLiteral("scopeContext")] = false; } else if (hasContextId(ScopeContext::Tag)) { json[QStringLiteral("scopeContext")] = QStringLiteral("tag"); json[QStringLiteral("TagID")] = contextId(ScopeContext::Tag); } else if (hasContextId(ScopeContext::Collection)) { json[QStringLiteral("scopeContext")] = QStringLiteral("collection"); json[QStringLiteral("ColID")] = contextId(ScopeContext::Collection); } else if (hasContextRID(ScopeContext::Tag)) { json[QStringLiteral("scopeContext")] = QStringLiteral("tagrid"); json[QStringLiteral("TagRID")] = contextRID(ScopeContext::Tag); } else if (hasContextRID(ScopeContext::Collection)) { json[QStringLiteral("scopeContext")] = QStringLiteral("colrid"); json[QStringLiteral("ColRID")] = contextRID(ScopeContext::Collection); } } DataStream &operator<<(DataStream &stream, const ScopeContext &context) { // We don't have a custom generic DataStream streaming operator for QVariant // because it's very hard, esp. without access to QVariant private // stuff, so we have have to decompose it manually here. QVariant::Type vType = context.mColCtx.type(); stream << vType; if (vType == QVariant::LongLong) { stream << context.mColCtx.toLongLong(); } else if (vType == QVariant::String) { stream << context.mColCtx.toString(); } vType = context.mTagCtx.type(); stream << vType; if (vType == QVariant::LongLong) { stream << context.mTagCtx.toLongLong(); } else if (vType == QVariant::String) { stream << context.mTagCtx.toString(); } return stream; } DataStream &operator>>(DataStream &stream, ScopeContext &context) { QVariant::Type vType; qint64 id; QString rid; for (ScopeContext::Type type : { ScopeContext::Collection, ScopeContext::Tag }) { stream >> vType; if (vType == QVariant::LongLong) { stream >> id; context.setContext(type, id); } else if (vType == QVariant::String) { stream >> rid; context.setContext(type, rid); } } return stream; } QDebug operator<<(QDebug _dbg, const ScopeContext &ctx) { QDebug dbg(_dbg.noquote()); dbg << "ScopeContext("; if (ctx.isEmpty()) { dbg << "empty"; } else if (ctx.hasContextId(ScopeContext::Tag)) { dbg << "Tag ID:" << ctx.contextId(ScopeContext::Tag); } else if (ctx.hasContextId(ScopeContext::Collection)) { dbg << "Col ID:" << ctx.contextId(ScopeContext::Collection); } else if (ctx.hasContextRID(ScopeContext::Tag)) { dbg << "Tag RID:" << ctx.contextRID(ScopeContext::Tag); } else if (ctx.hasContextRID(ScopeContext::Collection)) { dbg << "Col RID:" << ctx.contextRID(ScopeContext::Collection); } return dbg << ")\n"; } /******************************************************************************/ ChangeNotification::ChangeNotification(Command::Type type) : Command(type) { } ChangeNotification::ChangeNotification(const ChangeNotification &other) : Command(other) , mSessionId(other.mSessionId) , mMetaData(other.mMetaData) { } ChangeNotification &ChangeNotification::operator=(const ChangeNotification &other) { *static_cast(this) = static_cast(other); mSessionId = other.mSessionId; mMetaData = other.mMetaData; return *this; } bool ChangeNotification::operator==(const ChangeNotification &other) const { return static_cast(*this) == other && mSessionId == other.mSessionId; // metadata are not compared } -QList ChangeNotification::itemsToUids(const QVector &items) +QList ChangeNotification::itemsToUids(const QVector &items) { QList rv; rv.reserve(items.size()); std::transform(items.cbegin(), items.cend(), std::back_inserter(rv), - [](const FetchItemsResponsePtr &item) { return item->id(); }); + [](const FetchItemsResponse &item) { return item.id(); }); return rv; } bool ChangeNotification::isRemove() const { switch (type()) { case Command::Invalid: return false; case Command::ItemChangeNotification: return static_cast(this)->operation() == ItemChangeNotification::Remove; case Command::CollectionChangeNotification: return static_cast(this)->operation() == CollectionChangeNotification::Remove; case Command::TagChangeNotification: return static_cast(this)->operation() == TagChangeNotification::Remove; case Command::RelationChangeNotification: return static_cast(this)->operation() == RelationChangeNotification::Remove; case Command::SubscriptionChangeNotification: return static_cast(this)->operation() == SubscriptionChangeNotification::Remove; case Command::DebugChangeNotification: return false; default: Q_ASSERT_X(false, __FUNCTION__, "Unknown ChangeNotification type"); } return false; } bool ChangeNotification::isMove() const { switch (type()) { case Command::Invalid: return false; case Command::ItemChangeNotification: return static_cast(this)->operation() == ItemChangeNotification::Move; case Command::CollectionChangeNotification: return static_cast(this)->operation() == CollectionChangeNotification::Move; case Command::TagChangeNotification: case Command::RelationChangeNotification: case Command::SubscriptionChangeNotification: case Command::DebugChangeNotification: return false; default: Q_ASSERT_X(false, __FUNCTION__, "Unknown ChangeNotification type"); } return false; } bool ChangeNotification::appendAndCompress(ChangeNotificationList &list, const ChangeNotificationPtr &msg) { //It is likely that compressable notifications are within the last few notifications, so avoid searching a list that is potentially huge static const int maxCompressionSearchLength = 10; int searchCounter = 0; // There are often multiple Collection Modify notifications in the queue, // so we optimize for this case. if (msg->type() == Command::CollectionChangeNotification) { const auto &cmsg = Protocol::cmdCast(msg); - if (!cmsg.collection()) { // drop the message if collection is null - return false; - } if (cmsg.operation() == CollectionChangeNotification::Modify) { // We are iterating from end, since there's higher probability of finding // matching notification for (auto iter = list.end(), begin = list.begin(); iter != begin;) { --iter; if ((*iter)->type() == Protocol::Command::CollectionChangeNotification) { auto &it = Protocol::cmdCast(*iter); - const auto msgCol = cmsg.collection(); - const auto itCol = it.collection(); - if (!itCol) { - continue; // WTH?! - } - if (msgCol->id() == itCol->id() - && msgCol->remoteId() == itCol->remoteId() - && msgCol->remoteRevision() == itCol->remoteRevision() - && msgCol->resource() == itCol->resource() + const auto &msgCol = cmsg.collection(); + const auto &itCol = it.collection(); + if (msgCol.id() == itCol.id() + && msgCol.remoteId() == itCol.remoteId() + && msgCol.remoteRevision() == itCol.remoteRevision() + && msgCol.resource() == itCol.resource() && cmsg.destinationResource() == it.destinationResource() && cmsg.parentCollection() == it.parentCollection() && cmsg.parentDestCollection() == it.parentDestCollection()) { // both are modifications, merge them together and drop the new one if (cmsg.operation() == CollectionChangeNotification::Modify && it.operation() == CollectionChangeNotification::Modify) { const auto parts = it.changedParts(); it.setChangedParts(parts + cmsg.changedParts()); return false; } // we found Add notification, which means we can drop this modification if (it.operation() == CollectionChangeNotification::Add) { return false; } } } searchCounter++; if (searchCounter >= maxCompressionSearchLength) { break; } } } } // All other cases are just append, as the compression becomes too expensive in large batches list.append(msg); return true; } void ChangeNotification::toJson(QJsonObject &json) const { static_cast(this)->toJson(json); json[QStringLiteral("session")] = QString::fromUtf8(mSessionId); QJsonArray metadata; for (const auto &m : qAsConst(mMetaData)) { metadata.append(QString::fromUtf8(m)); } json[QStringLiteral("metadata")] = metadata; } DataStream &operator<<(DataStream &stream, const ChangeNotification &ntf) { return stream << static_cast(ntf) << ntf.mSessionId; } DataStream &operator>>(DataStream &stream, ChangeNotification &ntf) { return stream >> static_cast(ntf) >> ntf.mSessionId; } QDebug operator<<(QDebug dbg, const ChangeNotification &ntf) { return dbg.noquote() << static_cast(ntf) << "Session:" << ntf.mSessionId << "\n" << "MetaData:" << ntf.mMetaData << "\n"; } DataStream &operator>>(DataStream &stream, ChangeNotification::Relation &relation) { return stream >> relation.type >> relation.leftId >> relation.rightId; } DataStream &operator<<(DataStream &stream, const ChangeNotification::Relation &relation) { return stream << relation.type << relation.leftId << relation.rightId; } QDebug operator<<(QDebug _dbg, const ChangeNotification::Relation &rel) { QDebug dbg(_dbg.noquote()); return dbg << "Left: " << rel.leftId << ", Right:" << rel.rightId << ", Type: " << rel.type; } } // namespace Protocol } // namespace Akonadi // Helpers for the generated code namespace Akonadi { namespace Protocol { template class Container> inline bool containerComparator(const Container &c1, const Container &c2) { return c1 == c2; } template class Container> inline bool containerComparator(const Container> &c1, const Container> &c2) { if (c1.size() != c2.size()) { return false; } for (auto it1 = c1.cbegin(), it2 = c2.cbegin(), end1 = c1.cend(); it1 != end1; ++it1, ++it2) { if (**it1 != **it2) { return false; } } return true; } } } /******************************************************************************/ // Here comes the generated protocol implementation #include "protocol_gen.cpp" /******************************************************************************/ diff --git a/src/private/protocol.xml b/src/private/protocol.xml index 941afe687..f44686cd4 100644 --- a/src/private/protocol.xml +++ b/src/private/protocol.xml @@ -1,1110 +1,1110 @@ - + - + - + - + diff --git a/src/private/protocol_p.h b/src/private/protocol_p.h index f7de7a914..607eb9eea 100644 --- a/src/private/protocol_p.h +++ b/src/private/protocol_p.h @@ -1,637 +1,637 @@ /* Copyright (c) 2007 Volker Krause Copyright (c) 2015 Daniel Vrátil Copyright (c) 2016 Daniel Vrátil 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_PROTOCOL_COMMON_P_H #define AKONADI_PROTOCOL_COMMON_P_H #include "akonadiprivate_export.h" #include #include #include #include #include #include #include "tristate_p.h" #include "scope_p.h" /** @file protocol_p.h Shared constants used in the communication protocol between the Akonadi server and its clients. */ namespace Akonadi { namespace Protocol { class Factory; class DataStream; class Command; class Response; class ItemFetchScope; class ScopeContext; class ChangeNotification; using Attributes = QMap; } // namespace Protocol } // namespace Akonadi namespace Akonadi { namespace Protocol { AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator<<( Akonadi::Protocol::DataStream &stream, const Akonadi::Protocol::Command &cmd); AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator>>( Akonadi::Protocol::DataStream &stream, Akonadi::Protocol::Command &cmd); AKONADIPRIVATE_EXPORT QDebug operator<<(QDebug dbg, const Akonadi::Protocol::Command &cmd); AKONADIPRIVATE_EXPORT void toJson(const Command *cmd, QJsonObject &json); using CommandPtr = QSharedPointer; class AKONADIPRIVATE_EXPORT Command { public: enum Type : quint8 { Invalid = 0, // Session management Hello = 1, Login, Logout, // Transactions Transaction = 10, // Items CreateItem = 20, CopyItems, DeleteItems, FetchItems, LinkItems, ModifyItems, MoveItems, // Collections CreateCollection = 40, CopyCollection, DeleteCollection, FetchCollections, FetchCollectionStats, ModifyCollection, MoveCollection, // Search Search = 60, SearchResult, StoreSearch, // Tag CreateTag = 70, DeleteTag, FetchTags, ModifyTag, // Relation FetchRelations = 80, ModifyRelation, RemoveRelations, // Resources SelectResource = 90, // Other StreamPayload = 100, // Notifications ItemChangeNotification = 110, CollectionChangeNotification, TagChangeNotification, RelationChangeNotification, SubscriptionChangeNotification, DebugChangeNotification, CreateSubscription, ModifySubscription, // _MaxValue = 127 _ResponseBit = 0x80 // reserved }; explicit Command(); explicit Command(const Command &other); ~Command(); Command &operator=(const Command &other); bool operator==(const Command &other) const; inline bool operator!=(const Command &other) const { return !operator==(other); } inline Type type() const { return static_cast(mType & ~_ResponseBit); } inline bool isValid() const { return type() != Invalid; } inline bool isResponse() const { return mType & _ResponseBit; } void toJson(QJsonObject &stream) const; protected: explicit Command(quint8 type); quint8 mType; // unused 7 bytes private: friend class Factory; friend AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator<<(Akonadi::Protocol::DataStream &stream, const Akonadi::Protocol::Command &cmd); friend AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator>>(Akonadi::Protocol::DataStream &stream, Akonadi::Protocol::Command &cmd); friend AKONADIPRIVATE_EXPORT QDebug operator<<(::QDebug dbg, const Akonadi::Protocol::Command &cmd); friend AKONADIPRIVATE_EXPORT void toJson(const Akonadi::Protocol::Command *cmd, QJsonObject &json); }; } // namespace Protocol } // namespace Akonadi Q_DECLARE_METATYPE(Akonadi::Protocol::Command::Type) Q_DECLARE_METATYPE(Akonadi::Protocol::CommandPtr) namespace Akonadi { namespace Protocol { AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator<<( Akonadi::Protocol::DataStream &stream, const Akonadi::Protocol::Response &cmd); AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator>>( Akonadi::Protocol::DataStream &stream, Akonadi::Protocol::Response &cmd); AKONADIPRIVATE_EXPORT QDebug operator<<(QDebug dbg, const Akonadi::Protocol::Response &response); using ResponsePtr = QSharedPointer; class AKONADIPRIVATE_EXPORT Response : public Command { public: explicit Response(); explicit Response(const Response &other); Response &operator=(const Response &other); inline void setError(int code, const QString &message) { mErrorCode = code; mErrorMsg = message; } bool operator==(const Response &other) const; inline bool operator!=(const Response &other) const { return !operator==(other); } inline bool isError() const { return mErrorCode > 0; } inline int errorCode() const { return mErrorCode; } inline QString errorMessage() const { return mErrorMsg; } void toJson(QJsonObject &json) const; protected: explicit Response(Command::Type type); int mErrorCode; QString mErrorMsg; private: friend class Factory; friend AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator<<(Akonadi::Protocol::DataStream &stream, const Akonadi::Protocol::Response &cmd); friend AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator>>(Akonadi::Protocol::DataStream &stream, Akonadi::Protocol::Response &cmd); friend AKONADIPRIVATE_EXPORT QDebug operator<<(QDebug dbg, const Akonadi::Protocol::Response &cmd); }; } // namespace Protocol } // namespace Akonadi namespace Akonadi { namespace Protocol { template inline const X &cmdCast(const QSharedPointer &p) { return static_cast(*p); } template inline X &cmdCast(QSharedPointer &p) { return static_cast(*p); } class AKONADIPRIVATE_EXPORT Factory { public: static CommandPtr command(Command::Type type); static ResponsePtr response(Command::Type type); private: template friend AKONADIPRIVATE_EXPORT CommandPtr deserialize(QIODevice *device); }; AKONADIPRIVATE_EXPORT void serialize(QIODevice *device, const CommandPtr &command); AKONADIPRIVATE_EXPORT CommandPtr deserialize(QIODevice *device); AKONADIPRIVATE_EXPORT QString debugString(const Command &command); AKONADIPRIVATE_EXPORT inline QString debugString(const CommandPtr &command) { return debugString(*command); } } // namespace Protocol } // namespace Akonadi namespace Akonadi { namespace Protocol { AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator<<( Akonadi::Protocol::DataStream &stream, const Akonadi::Protocol::ItemFetchScope &scope); AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator>>( Akonadi::Protocol::DataStream &stream, Akonadi::Protocol::ItemFetchScope &scope); AKONADIPRIVATE_EXPORT QDebug operator<<(QDebug dbg, const Akonadi::Protocol::ItemFetchScope &scope); class AKONADIPRIVATE_EXPORT ItemFetchScope { public: enum FetchFlag : int { None = 0, CacheOnly = 1 << 0, CheckCachedPayloadPartsOnly = 1 << 1, FullPayload = 1 << 2, AllAttributes = 1 << 3, Size = 1 << 4, MTime = 1 << 5, RemoteRevision = 1 << 6, IgnoreErrors = 1 << 7, Flags = 1 << 8, RemoteID = 1 << 9, GID = 1 << 10, Tags = 1 << 11, Relations = 1 << 12, VirtReferences = 1 << 13 }; Q_DECLARE_FLAGS(FetchFlags, FetchFlag) enum AncestorDepth : ushort { NoAncestor, ParentAncestor, AllAncestors }; explicit ItemFetchScope(); ItemFetchScope(const ItemFetchScope &other); ~ItemFetchScope(); ItemFetchScope &operator=(const ItemFetchScope &other); bool operator==(const ItemFetchScope &other) const; inline bool operator!=(const ItemFetchScope &other) const { return !operator==(other); } inline void setRequestedParts(const QVector &requestedParts) { mRequestedParts = requestedParts; } inline QVector requestedParts() const { return mRequestedParts; } QVector requestedPayloads() const; inline void setChangedSince(const QDateTime &changedSince) { mChangedSince = changedSince; } inline QDateTime changedSince() const { return mChangedSince; } inline void setTagFetchScope(const QSet &tagFetchScope) { mTagFetchScope = tagFetchScope; } inline QSet tagFetchScope() const { return mTagFetchScope; } inline void setAncestorDepth(AncestorDepth depth) { mAncestorDepth = depth; } inline AncestorDepth ancestorDepth() const { return mAncestorDepth; } inline bool cacheOnly() const { return mFlags & CacheOnly; } inline bool checkCachedPayloadPartsOnly() const { return mFlags & CheckCachedPayloadPartsOnly; } inline bool fullPayload() const { return mFlags & FullPayload; } inline bool allAttributes() const { return mFlags & AllAttributes; } inline bool fetchSize() const { return mFlags & Size; } inline bool fetchMTime() const { return mFlags & MTime; } inline bool fetchRemoteRevision() const { return mFlags & RemoteRevision; } inline bool ignoreErrors() const { return mFlags & IgnoreErrors; } inline bool fetchFlags() const { return mFlags & Flags; } inline bool fetchRemoteId() const { return mFlags & RemoteID; } inline bool fetchGID() const { return mFlags & GID; } inline bool fetchTags() const { return mFlags & Tags; } inline bool fetchRelations() const { return mFlags & Relations; } inline bool fetchVirtualReferences() const { return mFlags & VirtReferences; } void setFetch(FetchFlags attributes, bool fetch = true); bool fetch(FetchFlags flags) const; void toJson(QJsonObject &json) const; private: AncestorDepth mAncestorDepth; // 2 bytes free FetchFlags mFlags; QVector mRequestedParts; QDateTime mChangedSince; QSet mTagFetchScope; friend AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator<<(Akonadi::Protocol::DataStream &stream, const Akonadi::Protocol::ItemFetchScope &scope); friend AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator>>(Akonadi::Protocol::DataStream &stream, Akonadi::Protocol::ItemFetchScope &scope); friend AKONADIPRIVATE_EXPORT QDebug operator<<(QDebug dbg, const Akonadi::Protocol::ItemFetchScope &scope); }; } // namespace Protocol } // namespace Akonadi Q_DECLARE_OPERATORS_FOR_FLAGS(Akonadi::Protocol::ItemFetchScope::FetchFlags) namespace Akonadi { namespace Protocol { AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator<<( Akonadi::Protocol::DataStream &stream, const Akonadi::Protocol::ScopeContext &ctx); AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator>>( Akonadi::Protocol::DataStream &stream, Akonadi::Protocol::ScopeContext &ctx); AKONADIPRIVATE_EXPORT QDebug operator<<(QDebug dbg, const Akonadi::Protocol::ScopeContext &ctx); class AKONADIPRIVATE_EXPORT ScopeContext { public: enum Type : uchar { Any = 0, Collection, Tag }; explicit ScopeContext(); ScopeContext(Type type, qint64 id); ScopeContext(Type type, const QString &id); ScopeContext(const ScopeContext &other); ~ScopeContext(); ScopeContext &operator=(const ScopeContext &other); bool operator==(const ScopeContext &other) const; inline bool operator!=(const ScopeContext &other) const { return !operator==(other); } inline bool isEmpty() const { return mColCtx.isNull() && mTagCtx.isNull(); } inline void setContext(Type type, qint64 id) { setCtx(type, id); } inline void setContext(Type type, const QString &id) { setCtx(type, id); } inline void clearContext(Type type) { setCtx(type, QVariant()); } inline bool hasContextId(Type type) const { return ctx(type).type() == QVariant::LongLong; } inline qint64 contextId(Type type) const { return hasContextId(type) ? ctx(type).toLongLong() : 0; } inline bool hasContextRID(Type type) const { return ctx(type).type() == QVariant::String; } inline QString contextRID(Type type) const { return hasContextRID(type) ? ctx(type).toString() : QString(); } void toJson(QJsonObject &json) const; private: QVariant mColCtx; QVariant mTagCtx; inline QVariant ctx(Type type) const { return type == Collection ? mColCtx : type == Tag ? mTagCtx : QVariant(); } inline void setCtx(Type type, const QVariant &v) { if (type == Collection) { mColCtx = v; } else if (type == Tag) { mTagCtx = v; } } friend AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator<<(Akonadi::Protocol::DataStream &stream, const Akonadi::Protocol::ScopeContext &context); friend AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator>>(Akonadi::Protocol::DataStream &stream, Akonadi::Protocol::ScopeContext &context); friend AKONADIPRIVATE_EXPORT QDebug operator<<(QDebug dbg, const Akonadi::Protocol::ScopeContext &ctx); }; } // namespace Protocol } // namespace akonadi namespace Akonadi { namespace Protocol { class FetchItemsResponse; typedef QSharedPointer FetchItemsResponsePtr; AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator<<( Akonadi::Protocol::DataStream &stream, const Akonadi::Protocol::ChangeNotification &ntf); AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator>>( Akonadi::Protocol::DataStream &stream, Akonadi::Protocol::ChangeNotification &ntf); AKONADIPRIVATE_EXPORT QDebug operator<<(QDebug dbg, const Akonadi::Protocol::ChangeNotification &ntf); using ChangeNotificationPtr = QSharedPointer; using ChangeNotificationList = QVector; class AKONADIPRIVATE_EXPORT ChangeNotification : public Command { public: - static QList itemsToUids(const QVector &items); + static QList itemsToUids(const QVector &items); class Relation { public: inline Relation() : leftId(-1) , rightId(-1) { } inline Relation(qint64 leftId, qint64 rightId, const QString &type) : leftId(leftId) , rightId(rightId) , type(type) { } inline bool operator==(const Relation &other) const { return leftId == other.leftId && rightId == other.rightId && type == other.type; } void toJson(QJsonObject &json) const { json[QStringLiteral("leftId")] = leftId; json[QStringLiteral("rightId")] = rightId; json[QStringLiteral("type")] = type; } qint64 leftId; qint64 rightId; QString type; }; ChangeNotification &operator=(const ChangeNotification &other); bool operator==(const ChangeNotification &other) const; inline bool operator!=(const ChangeNotification &other) const { return !operator==(other); } bool isRemove() const; bool isMove() const; inline QByteArray sessionId() const { return mSessionId; } inline void setSessionId(const QByteArray &sessionId) { mSessionId = sessionId; } inline void addMetadata(const QByteArray &metadata) { mMetaData << metadata; } inline void removeMetadata(const QByteArray &metadata) { mMetaData.removeAll(metadata); } QVector metadata() const { return mMetaData; } static bool appendAndCompress(ChangeNotificationList &list, const ChangeNotificationPtr &msg); void toJson(QJsonObject &json) const; protected: explicit ChangeNotification(Command::Type type); ChangeNotification(const ChangeNotification &other); QByteArray mSessionId; // For internal use only: Akonadi server can add some additional information // that might be useful when evaluating the notification for example, but // it is never transferred to clients QVector mMetaData; friend AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator<<(Akonadi::Protocol::DataStream &stream, const Akonadi::Protocol::ChangeNotification &ntf); friend AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator>>(Akonadi::Protocol::DataStream &stream, Akonadi::Protocol::ChangeNotification &ntf); friend AKONADIPRIVATE_EXPORT QDebug operator<<(QDebug dbg, const Akonadi::Protocol::ChangeNotification &ntf); }; inline uint qHash(const ChangeNotification::Relation &rel) { return ::qHash(rel.leftId + rel.rightId); } // TODO: Internalize? AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator<<( Akonadi::Protocol::DataStream &stream, const Akonadi::Protocol::ChangeNotification::Relation &relation); AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator>>( Akonadi::Protocol::DataStream &stream, Akonadi::Protocol::ChangeNotification::Relation &relation); } // namespace Protocol } // namespace Akonadi Q_DECLARE_METATYPE(Akonadi::Protocol::ChangeNotificationPtr) Q_DECLARE_METATYPE(Akonadi::Protocol::ChangeNotificationList) /******************************************************************************/ // Here comes the actual generated Protocol. See protocol.xml for definitions, // and genprotocol folder for the generator. #include "protocol_gen.h" /******************************************************************************/ // Command parameters #define AKONADI_PARAM_ATR "ATR:" #define AKONADI_PARAM_CACHEPOLICY "CACHEPOLICY" #define AKONADI_PARAM_DISPLAY "DISPLAY" #define AKONADI_PARAM_ENABLED "ENABLED" #define AKONADI_PARAM_FLAGS "FLAGS" #define AKONADI_PARAM_TAGS "TAGS" #define AKONADI_PARAM_GID "GID" #define AKONADI_PARAM_INDEX "INDEX" #define AKONADI_PARAM_MIMETYPE "MIMETYPE" #define AKONADI_PARAM_NAME "NAME" #define AKONADI_PARAM_PARENT "PARENT" #define AKONADI_PARAM_PERSISTENTSEARCH "PERSISTENTSEARCH" #define AKONADI_PARAM_PLD "PLD:" #define AKONADI_PARAM_PLD_RFC822 "PLD:RFC822" #define AKONADI_PARAM_RECURSIVE "RECURSIVE" #define AKONADI_PARAM_REFERENCED "REFERENCED" #define AKONADI_PARAM_REMOTE "REMOTE" #define AKONADI_PARAM_REMOTEID "REMOTEID" #define AKONADI_PARAM_REMOTEREVISION "REMOTEREVISION" #define AKONADI_PARAM_REVISION "REV" #define AKONADI_PARAM_SIZE "SIZE" #define AKONADI_PARAM_SYNC "SYNC" #define AKONADI_PARAM_TAG "TAG" #define AKONADI_PARAM_TYPE "TYPE" #define AKONADI_PARAM_VIRTUAL "VIRTUAL" // Flags #define AKONADI_FLAG_GID "\\Gid" #define AKONADI_FLAG_IGNORED "$IGNORED" #define AKONADI_FLAG_MIMETYPE "\\MimeType" #define AKONADI_FLAG_REMOTEID "\\RemoteId" #define AKONADI_FLAG_REMOTEREVISION "\\RemoteRevision" #define AKONADI_FLAG_TAG "\\Tag" #define AKONADI_FLAG_RTAG "\\RTag" #define AKONADI_FLAG_SEEN "\\SEEN" // Attributes #define AKONADI_ATTRIBUTE_HIDDEN "ATR:HIDDEN" #define AKONADI_ATTRIBUTE_MESSAGES "MESSAGES" #define AKONADI_ATTRIBUTE_UNSEEN "UNSEEN" // special resource names #define AKONADI_SEARCH_RESOURCE "akonadi_search_resource" #endif diff --git a/src/private/protocolgen/cppgenerator.cpp b/src/private/protocolgen/cppgenerator.cpp index f6d87b071..79fe5e21d 100644 --- a/src/private/protocolgen/cppgenerator.cpp +++ b/src/private/protocolgen/cppgenerator.cpp @@ -1,773 +1,777 @@ /* Copyright (c) 2017 Daniel Vrátil 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 "cppgenerator.h" #include "nodetree.h" #include "typehelper.h" #include "cpphelper.h" #include #include CppGenerator::CppGenerator() { } CppGenerator::~CppGenerator() { } bool CppGenerator::generate(Node const *node) { Q_ASSERT(node->type() == Node::Document); mHeaderFile.setFileName(QStringLiteral("protocol_gen.h")); if (!mHeaderFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) { std::cerr << qPrintable(mHeaderFile.errorString()) << std::endl; return false; } mHeader.setDevice(&mHeaderFile); mImplFile.setFileName(QStringLiteral("protocol_gen.cpp")); if (!mImplFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) { std::cerr << qPrintable(mImplFile.errorString()) << std::endl; return false; } mImpl.setDevice(&mImplFile); return generateDocument(static_cast(node)); } void CppGenerator::writeHeaderHeader(DocumentNode const *node) { mHeader << "// This is an auto-generated file.\n" "// Any changes to this file will be overwritten\n" "\n" "namespace Akonadi {\n" "namespace Protocol {\n" "\n" "AKONADIPRIVATE_EXPORT int version();\n" "\n"; // Forward declarations for (auto child : qAsConst(node->children())) { if (child->type() == Node::Class) { mHeader << "class " << static_cast(child)->className() << ";\n"; } } mHeader << "\n" "} // namespace Protocol\n" "} // namespace Akonadi\n\n"; } void CppGenerator::writeHeaderFooter(DocumentNode const * /*node*/) { // Nothing to do } void CppGenerator::writeImplHeader(DocumentNode const *node) { mImpl << "// This is an auto-generated file.\n" "// Any changs to this file will be overwritten\n" "\n" "namespace Akonadi {\n" "namespace Protocol {\n" "\n" "int version()\n" "{\n" " return " << node->version() << ";\n" "}\n" "\n"; } void CppGenerator::writeImplFooter(DocumentNode const *) { mImpl << "} // namespace Protocol\n" "} // namespace Akonadi\n"; } bool CppGenerator::generateDocument(DocumentNode const *node) { writeHeaderHeader(node); writeImplHeader(node); writeImplSerializer(node); for (auto classNode : node->children()) { if (!generateClass(static_cast(classNode))) { return false; } } writeHeaderFooter(node); writeImplFooter(node); return true; } void CppGenerator::writeImplSerializer(DocumentNode const *node) { mImpl << "void serialize(QIODevice *device, const CommandPtr &cmd)\n" "{\n" " DataStream stream(device);\n" " switch (static_cast(cmd->type() | (cmd->isResponse() ? Command::_ResponseBit : 0))) {\n" " case Command::Invalid:\n" " stream << cmdCast(cmd);\n" " break;\n" " case Command::Invalid | Command::_ResponseBit:\n" " stream << cmdCast(cmd);\n" " break;\n"; for (auto child : qAsConst(node->children())) { auto classNode = static_cast(child); if (classNode->classType() == ClassNode::Response) { mImpl << " case Command::" << classNode->name() << " | Command::_ResponseBit:\n" " stream << cmdCast<" << classNode->className() << ">(cmd);\n" " break;\n"; } else if (classNode->classType() == ClassNode::Command) { mImpl << " case Command::" << classNode->name() << ":\n" " stream << cmdCast<" << classNode->className() << ">(cmd);\n" " break;\n"; } else if (classNode->classType() == ClassNode::Notification) { mImpl << " case Command::" << classNode->name() << "Notification:\n" " stream << cmdCast<" << classNode->className() << ">(cmd);\n" " break;\n"; } } mImpl << " }\n" "}\n\n"; mImpl << "CommandPtr deserialize(QIODevice *device)\n" "{\n" " DataStream stream(device);\n" " stream.waitForData(sizeof(Command::Type));\n" " Command::Type cmdType;\n" " if (Q_UNLIKELY(device->peek((char *) &cmdType, sizeof(Command::Type)) != sizeof(Command::Type))) {\n" " throw ProtocolException(\"Failed to peek command type\");\n" " }\n" " CommandPtr cmd;\n" " if (cmdType & Command::_ResponseBit) {\n" " cmd = Factory::response(Command::Type(cmdType & ~Command::_ResponseBit));\n" " } else {\n" " cmd = Factory::command(cmdType);\n" " }\n\n" " switch (static_cast(cmdType)) {\n" " case Command::Invalid:\n" " stream >> cmdCast(cmd);\n" " return cmd;\n" " case Command::Invalid | Command::_ResponseBit:\n" " stream >> cmdCast(cmd);\n" " return cmd;\n"; for (auto child : qAsConst(node->children())) { auto classNode = static_cast(child); if (classNode->classType() == ClassNode::Response) { mImpl << " case Command::" << classNode->name() << " | Command::_ResponseBit:\n" " stream >> cmdCast<" << classNode->className() << ">(cmd);\n" " return cmd;\n"; } else if (classNode->classType() == ClassNode::Command) { mImpl << " case Command::" << classNode->name() << ":\n" " stream >> cmdCast<" << classNode->className() << ">(cmd);\n" " return cmd;\n"; } else if (classNode->classType() == ClassNode::Notification) { mImpl << " case Command::" << classNode->name() << "Notification:\n" " stream >> cmdCast<" << classNode->className() << ">(cmd);\n" " return cmd;\n"; } } mImpl << " }\n" " return CommandPtr::create();\n" "}\n" "\n"; mImpl << "QString debugString(const Command &cmd)\n" "{\n" " QString out;\n" " switch (static_cast(cmd.type() | (cmd.isResponse() ? Command::_ResponseBit : 0))) {\n" " case Command::Invalid:\n" " QDebug(&out).noquote() << static_cast(cmd);\n" " return out;\n" " case Command::Invalid | Command::_ResponseBit:\n" " QDebug(&out).noquote() << static_cast(cmd);\n" " return out;\n"; for (auto child : qAsConst(node->children())) { auto classNode = static_cast(child); if (classNode->classType() == ClassNode::Response) { mImpl << " case Command::" << classNode->name() << " | Command::_ResponseBit:\n" " QDebug(&out).noquote() << static_castclassName() << " &>(cmd);\n" " return out;\n"; } else if (classNode->classType() == ClassNode::Command) { mImpl << " case Command::" << classNode->name() << ":\n" " QDebug(&out).noquote() << static_castclassName() << " &>(cmd);\n" " return out;\n"; } else if (classNode->classType() == ClassNode::Notification) { mImpl << " case Command::" << classNode->name() << "Notification:\n" " QDebug(&out).noquote() << static_castclassName() << " &>(cmd);\n" " return out;\n"; } } mImpl << " }\n" " return QString();\n" "}\n" "\n"; } void CppGenerator::writeHeaderEnum(EnumNode const *node) { mHeader << " enum " << node->name() << " {\n"; for (auto enumChild : node->children()) { Q_ASSERT(enumChild->type() == Node::EnumValue); const auto valueNode = static_cast(enumChild); mHeader << " " << valueNode->name(); if (!valueNode->value().isEmpty()) { mHeader << " = " << valueNode->value(); } mHeader << ",\n"; } mHeader << " };\n"; if (node->enumType() == EnumNode::TypeFlag) { mHeader << " Q_DECLARE_FLAGS(" << node->name() << "s, " << node->name() << ")\n\n"; } } void CppGenerator::writeHeaderClass(ClassNode const *node) { // Begin class const QString parentClass = node->parentClassName(); const bool isTypeClass = node->classType() == ClassNode::Class; mHeader << "namespace Akonadi {\n" "namespace Protocol {\n\n" "AKONADIPRIVATE_EXPORT DataStream &operator<<(DataStream &stream, const " << node->className() << " &obj);\n" "AKONADIPRIVATE_EXPORT DataStream &operator>>(DataStream &stream, " << node->className() << " &obj);\n" "AKONADIPRIVATE_EXPORT QDebug operator<<(QDebug dbg, const " << node->className() << " &obj);\n" "\n" "using " << node->className() << "Ptr = QSharedPointer<" << node->className() << ">;\n" "\n"; if (isTypeClass) { mHeader << "class AKONADIPRIVATE_EXPORT " << node->className() << "\n"; } else { mHeader << "class AKONADIPRIVATE_EXPORT " << node->className() << " : public " << parentClass << "\n"; } mHeader << "{\n\n" "public:\n"; // Enums for (auto child : node->children()) { if (child->type() == Node::Enum) { const auto enumNode = static_cast(child); writeHeaderEnum(enumNode); } } // Ctors, dtor for (auto child : qAsConst(node->children())) { if (child->type() == Node::Ctor) { const auto ctor = static_cast(child); const auto args = ctor->arguments(); mHeader << " explicit " << node->className() << "("; for (int i = 0; i < args.count(); ++i) { const auto &arg = args[i]; if (TypeHelper::isNumericType(arg.type) || TypeHelper::isBoolType(arg.type)) { mHeader << arg.type << " " << arg.name; } else { mHeader << "const " << arg.type << " &" << arg.name; } if (!arg.defaultValue.isEmpty()) { mHeader << " = " << arg.defaultValue; } if (i < args.count() - 1) { mHeader << ", "; } } mHeader << ");\n"; } } mHeader << " " << node->className() << "(const " << node->className() << " &other);\n" " ~" << node->className() << "();\n" "\n" " " << node->className() << " &operator=(const " << node->className() << " &other);\n" " bool operator==(const " << node->className() << " &other) const;\n" " inline bool operator!=(const " << node->className() << " &other) const { return !operator==(other); }\n"; // Properties for (auto child : node->children()) { if (child->type() == Node::Property) { const auto prop = static_cast(child); if (prop->asReference()) { mHeader << " inline const " << prop->type() << " &" << prop->name() << "() const { return " << prop->mVariableName() << "; }\n" " inline " << prop->type() << " &" << prop->name() << "() { return " << prop->mVariableName() << "; }\n"; } else { mHeader << " inline " << prop->type() << " " << prop->name() << "() const { return " << prop->mVariableName() << "; }\n"; } if (!prop->readOnly()) { if (auto setter = prop->setter()) { mHeader << " void " << setter->name << "(const " << setter->type << " &" << prop->name() << ");\n"; } else if (!prop->dependencies().isEmpty()) { QString varType; if (TypeHelper::isNumericType(prop->type()) || TypeHelper::isBoolType(prop->type())) { varType = QLatin1String("(") + prop->type() + QLatin1String(" "); } else { varType = QLatin1String("(const ") + prop->type() + QLatin1String(" &"); } mHeader << " void " << prop->setterName() << varType << prop->name() << ");\n"; } else { QString varType; if (TypeHelper::isNumericType(prop->type()) || TypeHelper::isBoolType(prop->type())) { varType = QLatin1String("(") + prop->type() + QLatin1String(" "); } else { varType = QLatin1String("(const ") + prop->type() + QLatin1String(" &"); } mHeader << " inline void " << prop->setterName() << varType << prop->name() << ") { " << prop->mVariableName() << " = " << prop->name() << "; }\n"; + if (!TypeHelper::isNumericType(prop->type()) && !TypeHelper::isBoolType(prop->type())) { + mHeader << " inline void " << prop->setterName() << "(" << prop->type() << " &&" << prop->name() << ") { " + << prop->mVariableName() << " = std::move(" << prop->name() << "); }\n"; + } } } mHeader << "\n"; } } mHeader << " void toJson(QJsonObject &stream) const;\n"; // End of class mHeader << "protected:\n"; const auto properties = node->properties(); for (auto prop : properties) { mHeader << " " << prop->type() << " " << prop->mVariableName() << ";\n"; } mHeader << "\n" "private:\n" " friend AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator<<(Akonadi::Protocol::DataStream &stream, const Akonadi::Protocol::" << node->className() << " &obj);\n" " friend AKONADIPRIVATE_EXPORT Akonadi::Protocol::DataStream &operator>>(Akonadi::Protocol::DataStream &stream, Akonadi::Protocol::" << node->className() << " &obj);\n" " friend AKONADIPRIVATE_EXPORT QDebug operator<<(QDebug dbg, const Akonadi::Protocol::" << node->className() << " &obj);\n" "};\n\n" "} // namespace Protocol\n" "} // namespace Akonadi\n" "\n"; mHeader << "Q_DECLARE_METATYPE(Akonadi::Protocol::" << node->className() << ")\n\n"; if (node->classType() != ClassNode::Class) { mHeader << "Q_DECLARE_METATYPE(Akonadi::Protocol::" << node->className() << "Ptr)\n\n"; } } void CppGenerator::writeImplSerializer(PropertyNode const *node, const char *streamingOperator) { const auto deps = node->dependencies(); if (deps.isEmpty()) { mImpl << " stream " << streamingOperator << " obj." << node->mVariableName() << ";\n"; } else { mImpl << " if ("; auto it = deps.cend(); while (1 + 1 == 2) { --it; const QString mVar = it.key(); mImpl << "(obj." << "m" << mVar[0].toUpper() << mVar.midRef(1) << " & " << it.value() << ")"; if (it == deps.cbegin()) { break; } else { mImpl << " && "; } } mImpl << ") {\n" " stream " << streamingOperator << " obj." << node->mVariableName() << ";\n" " }\n"; } } void CppGenerator::writeImplClass(ClassNode const *node) { const QString parentClass = node->parentClassName(); const auto children = node->children(); const auto properties = node->properties(); // Ctors for (auto child : children) { if (child->type() == Node::Ctor) { const auto ctor = static_cast(child); const auto args = ctor->arguments(); mImpl << node->className() << "::" << node->className() << "("; for (int i = 0; i < args.count(); ++i) { const auto &arg = args[i]; if (TypeHelper::isNumericType(arg.type) || TypeHelper::isBoolType(arg.type)) { mImpl << arg.type << " " << arg.name; } else { mImpl << "const " << arg.type << " &" << arg.name; } if (i < args.count() - 1) { mImpl << ", "; } } mImpl << ")\n"; char startChar = ','; if (!parentClass.isEmpty()) { const QString type = node->name() + ((node->classType() == ClassNode::Notification) ? QStringLiteral("Notification") : QString()); mImpl << " : " << parentClass << "(Command::" << type << ")\n"; } else { startChar = ':'; } for (auto prop : properties) { const auto defaultValue = prop->defaultValue(); auto arg = std::find_if(args.cbegin(), args.cend(), [prop](const CtorNode::Argument &arg) { return arg.name == prop->name(); }); if (arg != args.cend()) { mImpl << " " << startChar << " " << prop->mVariableName() << "(" << arg->name << ")\n"; startChar = ','; } else { const bool isDefaultValue = !defaultValue.isEmpty(); const bool isNumeric = TypeHelper::isNumericType(prop->type()); const bool isBool = TypeHelper::isBoolType(prop->type()); if (isDefaultValue || isNumeric || isBool) { mImpl << " " << startChar << " " << prop->mVariableName() << "("; startChar = ','; if (isDefaultValue) { mImpl << defaultValue; } else if (isNumeric) { mImpl << "0"; } else if (isBool) { mImpl << "false"; } mImpl << ")\n"; } } } mImpl << "{\n" "}\n" "\n"; } } // Copy ctor mImpl << node->className() << "::" << node->className() << "(const " << node->className() << "&other)\n"; char startChar = ':'; if (!parentClass.isEmpty()) { mImpl << " : " << parentClass << "(other)\n"; startChar = ','; } for (auto prop : properties) { mImpl << " " << startChar << " " << prop->mVariableName() << "(other." << prop->mVariableName() << ")\n"; startChar = ','; } mImpl << "{\n" "}\n" "\n"; // Dtor mImpl << node->className() << "::~" << node->className() << "()\n" "{\n" "}\n" "\n"; // Assignment operator mImpl << node->className() << " &" << node->className() << "::operator=(const " << node->className() << " &other)\n" << "{\n"; if (!parentClass.isEmpty()) { mImpl << " " << parentClass << "::operator=(other);\n"; } for (auto prop : properties) { mImpl << " " << prop->mVariableName() << " = other." << prop->mVariableName() << ";\n"; } mImpl << " return *this;\n" "}\n" "\n"; // Comparision operator mImpl << "bool " << node->className() << "::operator==(const " << node->className() << " &other) const\n" "{\n"; mImpl << " return true // simplifies generation\n"; if (!parentClass.isEmpty()) { mImpl << " && " << parentClass << "::operator==(other)\n"; } for (auto prop : properties) { if (prop->isPointer()) { mImpl << " && *" << prop->mVariableName() << " == *other." << prop->mVariableName() << "\n"; } else if (TypeHelper::isContainer(prop->type())) { mImpl << " && containerComparator(" << prop->mVariableName() << ", other." << prop->mVariableName() << ")\n"; } else { mImpl << " && " << prop->mVariableName() << " == other." << prop->mVariableName() << "\n"; } } mImpl << " ;\n" "}\n" "\n"; // non-trivial setters for (auto prop : properties) { if (prop->readOnly()) { continue; } if (const auto setter = prop->setter()) { mImpl << "void " << node->className() << "::" << setter->name << "(const " << setter->type << " &val)\n" "{\n"; if (!setter->append.isEmpty()) { mImpl << " m" << setter->append[0].toUpper() << setter->append.midRef(1) << " << val;\n"; } if (!setter->remove.isEmpty()) { const QString mVar = QStringLiteral("m") + setter->remove[0].toUpper() + setter->remove.midRef(1); mImpl << " auto it = std::find(" << mVar << ".begin(), " << mVar << ".end(), val);\n" " if (it != " << mVar << ".end()) {\n" " " << mVar << ".erase(it);\n" " }\n"; } writeImplPropertyDependencies(prop); mImpl << "}\n\n"; } else if (!prop->dependencies().isEmpty()) { QString varType; if (TypeHelper::isNumericType(prop->type()) || TypeHelper::isBoolType(prop->type())) { mImpl << "void " << node->className() << "::" << prop->setterName() << "(" << prop->type() << " val)\n" "{\n" " " << prop->mVariableName() << " = val;\n"; } else { mImpl << "void " << node->className() << "::" << prop->setterName() << "(const " << prop->type() << " &val)\n" "{\n" " " << prop->mVariableName() << " = val;\n"; } writeImplPropertyDependencies(prop); mImpl << "}\n\n"; } } // serialize auto serializeProperties = properties; CppHelper::sortMembersForSerialization(serializeProperties); mImpl << "DataStream &operator<<(DataStream &stream, const " << node->className() << " &obj)\n" "{\n"; if (!parentClass.isEmpty()) { mImpl << " stream << static_cast(obj);\n"; } for (auto prop : qAsConst(serializeProperties)) { writeImplSerializer(prop, "<<"); } mImpl << " return stream;\n" "}\n" "\n"; // deserialize mImpl << "DataStream &operator>>(DataStream &stream, " << node->className() << " &obj)\n" "{\n"; if (!parentClass.isEmpty()) { mImpl << " stream >> static_cast<" << parentClass << " &>(obj);\n"; } for (auto prop : qAsConst(serializeProperties)) { writeImplSerializer(prop, ">>"); } mImpl << " return stream;\n" "}\n" "\n"; // debug mImpl << "QDebug operator<<(QDebug dbg, const " << node->className() << " &obj)\n" "{\n"; if (!parentClass.isEmpty()) { mImpl << " dbg.noquote() << static_cast(obj)\n"; } else { mImpl << " dbg.noquote()\n"; } for (auto prop : qAsConst(serializeProperties)) { if (prop->isPointer()) { mImpl << " << \"" << prop->name() << ":\" << *obj." << prop->mVariableName() << " << \"\\n\"\n"; } else if (TypeHelper::isContainer(prop->type())) { mImpl << " << \"" << prop->name() << ": [\\n\";\n" " for (const auto &type : qAsConst(obj." << prop->mVariableName() << ")) {\n" " dbg.noquote() << \" \" << "; if (TypeHelper::isPointerType(TypeHelper::containerType(prop->type()))) { mImpl << "*type"; } else { mImpl << "type"; } mImpl << " << \"\\n\";\n" " }\n" " dbg.noquote() << \"]\\n\"\n"; } else { mImpl << " << \"" << prop->name() << ":\" << obj." << prop->mVariableName() << " << \"\\n\"\n"; } } mImpl << " ;\n" " return dbg;\n" "}\n" "\n"; // toJson mImpl << "void " << node->className() << "::toJson(QJsonObject &json) const\n" "{\n"; if (!parentClass.isEmpty()) { mImpl << " static_cast(this)->toJson(json);\n"; } else if (serializeProperties.isEmpty()) { mImpl << " Q_UNUSED(json);\n"; } for (auto prop : qAsConst(serializeProperties)) { if (prop->isPointer()) { mImpl << " {\n" " QJsonObject jsonObject;\n" " " << prop->mVariableName() << "->toJson(jsonObject);\n" " json[QStringLiteral(\"" << prop->name() << "\")] = jsonObject;\n" " }\n"; } else if (TypeHelper::isContainer(prop->type())) { const auto &containerType = TypeHelper::containerType(prop->type()); mImpl << " {\n" " QJsonArray jsonArray;\n" " for (const auto &type : qAsConst(" << prop->mVariableName() << ")) {\n"; if (TypeHelper::isPointerType(containerType)) { mImpl << " QJsonObject jsonObject;\n" " type->toJson(jsonObject); /* " << containerType << " */\n" " jsonArray.append(jsonObject);\n"; } else if (TypeHelper::isNumericType(containerType)) { mImpl << " jsonArray.append(type); /* "<< containerType << " */\n"; } else if (TypeHelper::isBoolType(containerType)) { mImpl << " jsonArray.append(type); /* "<< containerType << " */\n"; } else if (containerType == QStringLiteral("QByteArray")) { mImpl << " jsonArray.append(QString::fromUtf8(type)); /* "<< containerType << "*/\n"; } else if (TypeHelper::isBuiltInType(containerType)) { if (TypeHelper::containerType(prop->type()) == QStringLiteral("Akonadi::Protocol::ChangeNotification::Relation")) { mImpl << " QJsonObject jsonObject;\n" " type.toJson(jsonObject); /* " << containerType << " */\n" " jsonArray.append(jsonObject);\n"; } else { mImpl << " jsonArray.append(type); /* "<< containerType << " */\n"; } } else { mImpl << " QJsonObject jsonObject;\n" " type.toJson(jsonObject); /* " << containerType << " */\n" " jsonArray.append(jsonObject);\n"; } mImpl << " }\n" << " json[QStringLiteral(\"" << prop->name() << "\")] = jsonArray;\n" << " }\n"; } else if (prop->type() == QStringLiteral("uint")) { mImpl << " json[QStringLiteral(\"" << prop->name() << "\")] = static_cast(" << prop->mVariableName() << ");/* "<< prop->type() << " */\n"; } else if (TypeHelper::isNumericType(prop->type())) { mImpl << " json[QStringLiteral(\"" << prop->name() << "\")] = " << prop->mVariableName() << ";/* "<< prop->type() << " */\n"; } else if (TypeHelper::isBoolType(prop->type())) { mImpl << " json[QStringLiteral(\"" << prop->name() << "\")] = " << prop->mVariableName() << ";/* "<< prop->type() << " */\n"; } else if (TypeHelper::isBuiltInType(prop->type())) { if (prop->type() == QStringLiteral("QStringList")) { mImpl << " json[QStringLiteral(\"" << prop->name() << "\")] = QJsonArray::fromStringList(" << prop->mVariableName() << ");/* "<< prop->type() << " */\n"; } else if (prop->type() == QStringLiteral("QDateTime")) { mImpl << " json[QStringLiteral(\"" << prop->name() << "\")] = " << prop->mVariableName() << ".toString()/* "<< prop->type() << " */;\n"; } else if (prop->type() == QStringLiteral("QByteArray")) { mImpl << " json[QStringLiteral(\"" << prop->name() << "\")] = QString::fromUtf8(" << prop->mVariableName() << ")/* "<< prop->type() << " */;\n"; } else if (prop->type() == QStringLiteral("Scope")) { mImpl << " {\n" " QJsonObject jsonObject;\n" " " << prop->mVariableName() << ".toJson(jsonObject); /* " << prop->type() << " */\n" " json[QStringLiteral(\"" << prop->name() << "\")] = " << "jsonObject;\n" " }\n"; } else if (prop->type() == QStringLiteral("Tristate")) { mImpl << " switch (" << prop->mVariableName() << ") {\n;" " case Tristate::True:\n" " json[QStringLiteral(\"" << prop->name() << "\")] = QStringLiteral(\"True\");\n" " break;\n" " case Tristate::False:\n" " json[QStringLiteral(\"" << prop->name() << "\")] = QStringLiteral(\"False\");\n" " break;\n" " case Tristate::Undefined:\n" " json[QStringLiteral(\"" << prop->name() << "\")] = QStringLiteral(\"Undefined\");\n" " break;\n" " }\n"; } else if (prop->type() == QStringLiteral("Akonadi::Protocol::Attributes")) { mImpl << " {\n" " QJsonObject jsonObject;\n" " auto i = " << prop->mVariableName() << ".constBegin();\n" " const auto &end = " << prop->mVariableName() << ".constEnd();\n" " while (i != end) {\n" " jsonObject[QString::fromUtf8(i.key())] = QString::fromUtf8(i.value());\n" " ++i;\n" " }\n" " json[QStringLiteral(\"" << prop->name() << "\")] = jsonObject;\n" " }\n"; } else if (prop->type() == QStringLiteral("ModifySubscriptionCommand::ModifiedParts") || prop->type() == QStringLiteral("ModifyTagCommand::ModifiedParts") || prop->type() == QStringLiteral("ModifyCollectionCommand::ModifiedParts") || prop->type() == QStringLiteral("ModifyItemsCommand::ModifiedParts") || prop->type() == QStringLiteral("CreateItemCommand::MergeModes")) { mImpl << " json[QStringLiteral(\"" << prop->name() << "\")] = static_cast(" << prop->mVariableName() << ");/* "<< prop->type() << "*/\n"; } else { mImpl << " json[QStringLiteral(\"" << prop->name() << "\")] = " << prop->mVariableName() << ";/* "<< prop->type() << "*/\n"; } } else { mImpl << " {\n" " QJsonObject jsonObject;\n" " " << prop->mVariableName() << ".toJson(jsonObject); /* " << prop->type() << " */\n" " json[QStringLiteral(\"" << prop->name() << "\")] = jsonObject;\n" " }\n"; } } mImpl << "}\n" "\n"; } void CppGenerator::writeImplPropertyDependencies(const PropertyNode* node) { const auto deps = node->dependencies(); QString key; QStringList values; QString enumType; for (auto it = deps.cbegin(), end = deps.cend(); it != end; ++it) { if (key != it.key()) { key = it.key(); const auto children = node->parent()->children(); for (auto child : children) { if (child->type() == Node::Property && child != node) { auto prop = static_cast(child); if (prop->name() == key) { enumType = prop->type(); break; } } } if (!values.isEmpty()) { mImpl << " m" << key[0].toUpper() << key.midRef(1) << " |= " << enumType << "(" << values.join(QStringLiteral(" | ")) << ");\n"; values.clear(); } } values << *it; } if (!values.isEmpty()) { mImpl << " m" << key[0].toUpper() << key.midRef(1) << " |= " << enumType << "(" << values.join(QStringLiteral(" | ")) << ");\n"; } } bool CppGenerator::generateClass(ClassNode const *node) { writeHeaderClass(node); mImpl << "\n\n/************************* " << node->className() << " *************************/\n\n"; writeImplClass(node); return true; } diff --git a/src/server/akonadi.cpp b/src/server/akonadi.cpp index 10a15ef51..860b7f487 100644 --- a/src/server/akonadi.cpp +++ b/src/server/akonadi.cpp @@ -1,424 +1,425 @@ /*************************************************************************** * Copyright (C) 2006 by Till Adam * * * * This program 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 program 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 General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "akonadi.h" +#include "handler.h" #include "connection.h" #include "serveradaptor.h" #include "akonadiserver_debug.h" #include "cachecleaner.h" #include "intervalcheck.h" #include "storagejanitor.h" #include "storage/dbconfig.h" #include "storage/datastore.h" #include "notificationmanager.h" #include "resourcemanager.h" #include "tracer.h" #include "utils.h" #include "debuginterface.h" #include "storage/itemretrievalmanager.h" #include "storage/collectionstatistics.h" #include "preprocessormanager.h" #include "search/searchmanager.h" #include "search/searchtaskmanager.h" #include "aklocalserver.h" #include "collectionreferencemanager.h" #include #include #include #include #include #include #include #include #include #include #include using namespace Akonadi; using namespace Akonadi::Server; AkonadiServer *AkonadiServer::s_instance = nullptr; AkonadiServer::AkonadiServer(QObject *parent) : QObject(parent) { // Register bunch of useful types qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType("quintptr"); } bool AkonadiServer::init() { const QString serverConfigFile = StandardDirs::serverConfigFile(StandardDirs::ReadWrite); QSettings settings(serverConfigFile, QSettings::IniFormat); // Restrict permission to 600, as the file might contain database password in plaintext QFile::setPermissions(serverConfigFile, QFile::ReadOwner | QFile::WriteOwner); if (!DbConfig::configuredDatabase()) { quit(); return false; } if (DbConfig::configuredDatabase()->useInternalServer()) { if (!startDatabaseProcess()) { quit(); return false; } } else { if (!createDatabase()) { quit(); return false; } } DbConfig::configuredDatabase()->setup(); s_instance = this; const QString connectionSettingsFile = StandardDirs::connectionConfigFile(StandardDirs::WriteOnly); QSettings connectionSettings(connectionSettingsFile, QSettings::IniFormat); mCmdServer = new AkLocalServer(this); connect(mCmdServer, QOverload::of(&AkLocalServer::newConnection), this, &AkonadiServer::newCmdConnection); mNotificationManager = new NotificationManager(); mNtfServer = new AkLocalServer(this); // Note: this is a queued connection, as NotificationManager lives in its // own thread connect(mNtfServer, QOverload::of(&AkLocalServer::newConnection), mNotificationManager, &NotificationManager::registerConnection); // TODO: share socket setup with client #ifdef Q_OS_WIN // use the installation prefix as uid QString suffix; if (Instance::hasIdentifier()) { suffix = QStringLiteral("%1-").arg(Instance::identifier()); } suffix += QString::fromUtf8(QUrl::toPercentEncoding(qApp->applicationDirPath())); const QString defaultCmdPipe = QStringLiteral("Akonadi-Cmd-") % suffix; const QString cmdPipe = settings.value(QStringLiteral("Connection/NamedPipe"), defaultCmdPipe).toString(); if (!mCmdServer->listen(cmdPipe)) { qCCritical(AKONADISERVER_LOG) << "Unable to listen on Named Pipe" << cmdPipe; quit(); return false; } const QString defaultNtfPipe = QStringLiteral("Akonadi-Ntf-") % suffix; const QString ntfPipe = settings.value(QStringLiteral("Connection/NtfNamedPipe"), defaultNtfPipe).toString(); if (!mNtfServer->listen(ntfPipe)) { qCCritical(AKONADISERVER_LOG) << "Unable to listen on Named Pipe" << ntfPipe; quit(); return false; } connectionSettings.setValue(QStringLiteral("Data/Method"), QStringLiteral("NamedPipe")); connectionSettings.setValue(QStringLiteral("Data/NamedPipe"), cmdPipe); connectionSettings.setValue(QStringLiteral("Notifications/Method"), QStringLiteral("NamedPipe")); connectionSettings.setValue(QStringLiteral("Notifications/NamedPipe"), ntfPipe); #else const QString socketDir = Utils::preferredSocketDirectory(StandardDirs::saveDir("data")); const QString cmdSocketFile = socketDir % QStringLiteral("/akonadiserver-cmd.socket"); QFile::remove(cmdSocketFile); if (!mCmdServer->listen(cmdSocketFile)) { qCCritical(AKONADISERVER_LOG) << "Unable to listen on Unix socket" << cmdSocketFile; quit(); return false; } const QString ntfSocketFile = socketDir % QStringLiteral("/akonadiserver-ntf.socket"); QFile::remove(ntfSocketFile); if (!mNtfServer->listen(ntfSocketFile)) { qCCritical(AKONADISERVER_LOG) << "Unable to listen on Unix socket" << ntfSocketFile; quit(); return false; } connectionSettings.setValue(QStringLiteral("Data/Method"), QStringLiteral("UnixPath")); connectionSettings.setValue(QStringLiteral("Data/UnixPath"), cmdSocketFile); connectionSettings.setValue(QStringLiteral("Notifications/Method"), QStringLiteral("UnixPath")); connectionSettings.setValue(QStringLiteral("Notifications/UnixPath"), ntfSocketFile); #endif // initialize the database DataStore *db = DataStore::self(); if (!db->database().isOpen()) { qCCritical(AKONADISERVER_LOG) << "Unable to open database" << db->database().lastError().text(); quit(); return false; } if (!db->init()) { qCCritical(AKONADISERVER_LOG) << "Unable to initialize database."; quit(); return false; } Tracer::self(); new DebugInterface(this); ResourceManager::self(); CollectionStatistics::self(); // Initialize the preprocessor manager PreprocessorManager::init(); // Forcibly disable it if configuration says so if (settings.value(QStringLiteral("General/DisablePreprocessing"), false).toBool()) { PreprocessorManager::instance()->setEnabled(false); } if (settings.value(QStringLiteral("Cache/EnableCleaner"), true).toBool()) { mCacheCleaner = new CacheCleaner(); } mIntervalCheck = new IntervalCheck(); mStorageJanitor = new StorageJanitor(); mItemRetrieval = new ItemRetrievalManager(); mAgentSearchManager = new SearchTaskManager(); const QStringList searchManagers = settings.value(QStringLiteral("Search/Manager"), QStringList() << QStringLiteral("Agent")).toStringList(); mSearchManager = new SearchManager(searchManagers); new ServerAdaptor(this); QDBusConnection::sessionBus().registerObject(QStringLiteral("/Server"), this); const QByteArray dbusAddress = qgetenv("DBUS_SESSION_BUS_ADDRESS"); if (!dbusAddress.isEmpty()) { connectionSettings.setValue(QStringLiteral("DBUS/Address"), QLatin1String(dbusAddress)); } QDBusServiceWatcher *watcher = new QDBusServiceWatcher(DBus::serviceName(DBus::Control), QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForOwnerChange, this); connect(watcher, &QDBusServiceWatcher::serviceOwnerChanged, this, &AkonadiServer::serviceOwnerChanged); // Unhide all the items that are actually hidden. // The hidden flag was probably left out after an (abrupt) // server quit. We don't attempt to resume preprocessing // for the items as we don't actually know at which stage the // operation was interrupted... db->unhideAllPimItems(); // Cleanup referenced collections from the last run CollectionReferenceManager::cleanup(); // We are ready, now register org.freedesktop.Akonadi service to DBus and // the fun can begin if (!QDBusConnection::sessionBus().registerService(DBus::serviceName(DBus::Server))) { qCCritical(AKONADISERVER_LOG) << "Unable to connect to dbus service: " << QDBusConnection::sessionBus().lastError().message(); quit(); return false; } return true; } AkonadiServer::~AkonadiServer() { } template static void quitThread(T &thread) { if (thread) { thread->quit(); thread->wait(); delete thread; thread = nullptr; } } bool AkonadiServer::quit() { if (mAlreadyShutdown) { return true; } mAlreadyShutdown = true; qCDebug(AKONADISERVER_LOG) << "terminating connection threads"; qDeleteAll(mConnections); mConnections.clear(); qCDebug(AKONADISERVER_LOG) << "terminating service threads"; delete mCacheCleaner; delete mIntervalCheck; delete mStorageJanitor; delete mItemRetrieval; delete mAgentSearchManager; delete mSearchManager; delete mNotificationManager; // Terminate the preprocessor manager before the database but after all connections are gone PreprocessorManager::done(); CollectionStatistics::destroy(); if (DbConfig::isConfigured()) { if (DataStore::hasDataStore()) { DataStore::self()->close(); } qCDebug(AKONADISERVER_LOG) << "stopping db process"; stopDatabaseProcess(); } //QSettings settings(StandardDirs::serverConfigFile(), QSettings::IniFormat); const QString connectionSettingsFile = StandardDirs::connectionConfigFile(StandardDirs::WriteOnly); if (!QDir::home().remove(connectionSettingsFile)) { qCCritical(AKONADISERVER_LOG) << "Failed to remove runtime connection config file"; } QTimer::singleShot(0, this, &AkonadiServer::doQuit); return true; } void AkonadiServer::doQuit() { QCoreApplication::exit(); } void AkonadiServer::newCmdConnection(quintptr socketDescriptor) { if (mAlreadyShutdown) { return; } Connection *connection = new Connection(socketDescriptor); connect(connection, &Connection::disconnected, this, &AkonadiServer::connectionDisconnected); mConnections.append(connection); } void AkonadiServer::connectionDisconnected() { auto conn = qobject_cast(sender()); mConnections.removeOne(conn); delete conn; } AkonadiServer *AkonadiServer::instance() { if (!s_instance) { s_instance = new AkonadiServer(); } return s_instance; } bool AkonadiServer::startDatabaseProcess() { if (!DbConfig::configuredDatabase()->useInternalServer()) { qCCritical(AKONADISERVER_LOG) << "Trying to start external database!"; } // create the database directories if they don't exists StandardDirs::saveDir("data"); StandardDirs::saveDir("data", QStringLiteral("file_db_data")); return DbConfig::configuredDatabase()->startInternalServer(); } bool AkonadiServer::createDatabase() { bool success = true; const QLatin1String initCon("initConnection"); QSqlDatabase db = QSqlDatabase::addDatabase(DbConfig::configuredDatabase()->driverName(), initCon); DbConfig::configuredDatabase()->apply(db); db.setDatabaseName(DbConfig::configuredDatabase()->databaseName()); if (!db.isValid()) { qCCritical(AKONADISERVER_LOG) << "Invalid database object during initial database connection"; return false; } if (db.open()) { db.close(); } else { qCCritical(AKONADISERVER_LOG) << "Failed to use database" << DbConfig::configuredDatabase()->databaseName(); qCCritical(AKONADISERVER_LOG) << "Database error:" << db.lastError().text(); qCDebug(AKONADISERVER_LOG) << "Trying to create database now..."; db.close(); db.setDatabaseName(QString()); if (db.open()) { { QSqlQuery query(db); if (!query.exec(QStringLiteral("CREATE DATABASE %1").arg(DbConfig::configuredDatabase()->databaseName()))) { qCCritical(AKONADISERVER_LOG) << "Failed to create database"; qCCritical(AKONADISERVER_LOG) << "Query error:" << query.lastError().text(); qCCritical(AKONADISERVER_LOG) << "Database error:" << db.lastError().text(); success = false; } } // make sure query is destroyed before we close the db db.close(); } else { qCCritical(AKONADISERVER_LOG) << "Failed to connect to database!"; qCCritical(AKONADISERVER_LOG) << "Database error:" << db.lastError().text(); success = false; } } return success; } void AkonadiServer::stopDatabaseProcess() { if (!DbConfig::configuredDatabase()->useInternalServer()) { // closing initConnection this late to work around QTBUG-63108 QSqlDatabase::removeDatabase(QStringLiteral("initConnection")); return; } DbConfig::configuredDatabase()->stopInternalServer(); } void AkonadiServer::serviceOwnerChanged(const QString &name, const QString &oldOwner, const QString &newOwner) { Q_UNUSED(name); Q_UNUSED(oldOwner); if (newOwner.isEmpty()) { qCCritical(AKONADISERVER_LOG) << "Control process died, committing suicide!"; quit(); } } CacheCleaner *AkonadiServer::cacheCleaner() { return mCacheCleaner; } IntervalCheck *AkonadiServer::intervalChecker() { return mIntervalCheck; } NotificationManager *AkonadiServer::notificationManager() { return mNotificationManager; } QString AkonadiServer::serverPath() const { return StandardDirs::saveDir("config"); } diff --git a/src/server/connection.cpp b/src/server/connection.cpp index cdddd5147..832cc6706 100644 --- a/src/server/connection.cpp +++ b/src/server/connection.cpp @@ -1,524 +1,523 @@ /*************************************************************************** * Copyright (C) 2006 by Till Adam * * Copyright (C) 2013 by Volker Krause * * * * This program 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 program 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 General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "connection.h" #include "akonadiserver_debug.h" #include #include #include #include #include #include "storage/datastore.h" #include "handler.h" #include "notificationmanager.h" #include "tracer.h" #include "collectionreferencemanager.h" #include #ifndef Q_OS_WIN #include #endif #include #include #include #include using namespace Akonadi; using namespace Akonadi::Server; #define IDLE_TIMER_TIMEOUT 180000 // 3 min static QString connectionIdentifier(Connection *c) { QString id; id.sprintf("%p", static_cast(c)); return id; } namespace { Q_GLOBAL_STATIC(QThreadStorage>, sConnectionStore) } Connection::Connection(QObject *parent) : AkThread(connectionIdentifier(this), QThread::InheritPriority, parent) { } Connection::Connection(quintptr socketDescriptor, QObject *parent) : AkThread(connectionIdentifier(this), QThread::InheritPriority, parent) { m_socketDescriptor = socketDescriptor; m_identifier = connectionIdentifier(this); // same as objectName() const QSettings settings(Akonadi::StandardDirs::serverConfigFile(), QSettings::IniFormat); m_verifyCacheOnRetrieval = settings.value(QStringLiteral("Cache/VerifyOnRetrieval"), m_verifyCacheOnRetrieval).toBool(); } Connection *Connection::self() { Q_ASSERT(sConnectionStore->hasLocalData()); return sConnectionStore->localData(); } void Connection::init() { AkThread::init(); sConnectionStore->setLocalData(this); QLocalSocket *socket = new QLocalSocket(); if (!socket->setSocketDescriptor(m_socketDescriptor)) { qCWarning(AKONADISERVER_LOG) << "Connection(" << m_identifier << ")::run: failed to set socket descriptor: " << socket->error() << "(" << socket->errorString() << ")"; delete socket; return; } m_socket = socket; connect(socket, &QLocalSocket::disconnected, this, &Connection::slotSocketDisconnected); m_idleTimer = new QTimer(this); connect(m_idleTimer, &QTimer::timeout, this, &Connection::slotConnectionIdle); if (socket->state() == QLocalSocket::ConnectedState) { QTimer::singleShot(0, this, &Connection::handleIncomingData); } else { connect(socket, &QLocalSocket::connected, this, &Connection::handleIncomingData, Qt::QueuedConnection); } try { slotSendHello(); } catch (const ProtocolException &e) { qCWarning(AKONADISERVER_LOG) << "Protocol Exception sending \"hello\":" << e.what(); m_socket->disconnectFromServer(); } } void Connection::quit() { if (QThread::currentThread()->loopLevel() > 1) { m_connectionClosing = true; Q_EMIT connectionClosing(); return; } Tracer::self()->endConnection(m_identifier, QString()); collectionReferenceManager()->removeSession(m_sessionId); delete m_socket; m_socket = nullptr; if (m_idleTimer) { m_idleTimer->stop(); } delete m_idleTimer; AkThread::quit(); } void Connection::slotSendHello() { SchemaVersion version = SchemaVersion::retrieveAll().first(); - auto hello = Protocol::HelloResponsePtr::create(); - hello->setServerName(QStringLiteral("Akonadi")); - hello->setMessage(QStringLiteral("Not Really IMAP server")); - hello->setProtocolVersion(Protocol::version()); - hello->setGeneration(version.generation()); - sendResponse(0, hello); + Protocol::HelloResponse hello; + hello.setServerName(QStringLiteral("Akonadi")); + hello.setMessage(QStringLiteral("Not Really IMAP server")); + hello.setProtocolVersion(Protocol::version()); + hello.setGeneration(version.generation()); + sendResponse(0, std::move(hello)); } DataStore *Connection::storageBackend() { if (!m_backend) { m_backend = DataStore::self(); } return m_backend; } CollectionReferenceManager *Connection::collectionReferenceManager() { return CollectionReferenceManager::instance(); } Connection::~Connection() { quitThread(); if (m_reportTime) { reportTime(); } } void Connection::slotConnectionIdle() { Q_ASSERT(m_currentHandler == nullptr); if (m_backend && m_backend->isOpened()) { if (m_backend->inTransaction()) { // This is a programming error, the timer should not have fired. // But it is safer to abort and leave the connection open, until // a later operation causes the idle timer to fire (than crash // the akonadi server). qCDebug(AKONADISERVER_LOG) << m_sessionId << "NOT Closing idle db connection; we are in transaction"; return; } m_backend->close(); } } void Connection::slotSocketDisconnected() { // If we have active handler, wait for it to finish, then we emit the signal // from slotNewDate() if (m_currentHandler) { return; } Q_EMIT disconnected(); } void Connection::handleIncomingData() { Q_FOREVER { if (m_connectionClosing || !m_socket || m_socket->state() != QLocalSocket::ConnectedState) { break; } // Blocks with event loop until some data arrive, allows us to still use QTimers // and similar while waiting for some data to arrive if (m_socket->bytesAvailable() < int(sizeof(qint64))) { QEventLoop loop; connect(m_socket, &QLocalSocket::readyRead, &loop, &QEventLoop::quit); connect(m_socket, &QLocalSocket::stateChanged, &loop, &QEventLoop::quit); connect(this, &Connection::connectionClosing, &loop, &QEventLoop::quit); loop.exec(); } if (m_connectionClosing || !m_socket || m_socket->state() != QLocalSocket::ConnectedState) { break; } m_idleTimer->stop(); // will only open() a previously idle backend. // Otherwise, a new backend could lazily be constructed by later calls. if (!storageBackend()->isOpened()) { m_backend->open(); } QString currentCommand; while (m_socket->bytesAvailable() >= int(sizeof(qint64))) { - QDataStream stream(m_socket); - + Protocol::DataStream stream(m_socket); qint64 tag = -1; stream >> tag; // TODO: Check tag is incremental sequence Protocol::CommandPtr cmd; try { cmd = Protocol::deserialize(m_socket); } catch (const Akonadi::ProtocolException &e) { qCWarning(AKONADISERVER_LOG) << "ProtocolException:" << e.what(); setState(Server::LoggingOut); return; } catch (const std::exception &e) { qCWarning(AKONADISERVER_LOG) << "Unknown exception:" << e.what(); setState(Server::LoggingOut); return; } if (cmd->type() == Protocol::Command::Invalid) { qCWarning(AKONADISERVER_LOG) << "Received an invalid command: resetting connection"; setState(Server::LoggingOut); return; } // Tag context and collection context is not persistent. context()->setTag(-1); context()->setCollection(Collection()); if (Tracer::self()->currentTracer() != QLatin1String("null")) { Tracer::self()->connectionInput(m_identifier, tag, cmd); } m_currentHandler = std::unique_ptr(findHandlerForCommand(cmd->type())); if (!m_currentHandler) { qCWarning(AKONADISERVER_LOG) << "Invalid command: no such handler for" << cmd->type(); setState(Server::LoggingOut); return; } if (m_reportTime) { startTime(); } m_currentHandler->setConnection(this); m_currentHandler->setTag(tag); m_currentHandler->setCommand(cmd); try { if (!m_currentHandler->parseStream()) { try { m_currentHandler->failureResponse("Unknown error while handling a command"); } catch (...) { qCWarning(AKONADISERVER_LOG) << "Unknown error while handling a command"; m_connectionClosing = true; } } } catch (const Akonadi::Server::HandlerException &e) { if (m_currentHandler) { try { m_currentHandler->failureResponse(e.what()); } catch (...) { qCWarning(AKONADISERVER_LOG) << "Handler exception:" << e.what(); m_connectionClosing = true; } } } catch (const Akonadi::Server::Exception &e) { if (m_currentHandler) { try { m_currentHandler->failureResponse(QString::fromUtf8(e.type()) + QLatin1String(": ") + QString::fromUtf8(e.what())); } catch (...) { qCWarning(AKONADISERVER_LOG) << e.type() << "exception:" << e.what(); m_connectionClosing = true; } } } catch (const Akonadi::ProtocolException &e) { // No point trying to send anything back to client, the connection is // already messed up qCWarning(AKONADISERVER_LOG) << "Protocol exception:" << e.what(); m_connectionClosing = true; #if defined(Q_OS_LINUX) } catch (abi::__forced_unwind&) { // HACK: NPTL throws __forced_unwind during thread cancellation and // we *must* rethrow it otherwise the program aborts. Due to the issue // described in #376385 we might end up destroying (cancelling) the // thread from a nested loop executed inside parseStream() above, // so the exception raised in there gets caught by this try..catch // statement and it must be rethrown at all cost. Remove this hack // once the root problem is fixed. throw; #endif } catch (...) { qCCritical(AKONADISERVER_LOG) << "Unknown exception caught in Connection for session" << m_sessionId; if (m_currentHandler) { try { m_currentHandler->failureResponse("Unknown exception caught"); } catch (...) { qCWarning(AKONADISERVER_LOG) << "Unknown exception caught"; m_connectionClosing = true; } } } if (m_reportTime) { stopTime(currentCommand); } m_currentHandler.reset(); if (!m_socket || m_socket->state() != QLocalSocket::ConnectedState) { Q_EMIT disconnected(); return; } if (m_connectionClosing) { break; } } // reset, arm the timer m_idleTimer->start(IDLE_TIMER_TIMEOUT); if (m_connectionClosing) { break; } } if (m_connectionClosing) { m_socket->disconnect(this); m_socket->close(); QTimer::singleShot(0, this, &Connection::quit); } } CommandContext *Connection::context() const { return const_cast(&m_context); } Handler *Connection::findHandlerForCommand(Protocol::Command::Type command) { Handler *handler = Handler::findHandlerForCommandAlwaysAllowed(command); if (handler) { return handler; } switch (m_connectionState) { case NonAuthenticated: handler = Handler::findHandlerForCommandNonAuthenticated(command); break; case Authenticated: handler = Handler::findHandlerForCommandAuthenticated(command); break; case Selected: break; case LoggingOut: break; } return handler; } +qint64 Connection::currentTag() const +{ + return m_currentHandler->tag(); +} + void Connection::setState(ConnectionState state) { if (state == m_connectionState) { return; } m_connectionState = state; switch (m_connectionState) { case NonAuthenticated: assert(0); // can't happen, it's only the initial state, we can't go back to it break; case Authenticated: break; case Selected: break; case LoggingOut: m_socket->disconnectFromServer(); break; } } void Connection::setSessionId(const QByteArray &id) { m_identifier.sprintf("%s (%p)", id.data(), static_cast(this)); Tracer::self()->beginConnection(m_identifier, QString()); //m_streamParser->setTracerIdentifier(m_identifier); m_sessionId = id; setObjectName(QString::fromLatin1(id)); // this races with the use of objectName() in QThreadPrivate::start //thread()->setObjectName(objectName() + QStringLiteral("-Thread")); storageBackend()->setSessionId(id); storageBackend()->notificationCollector()->setSessionId(id); } QByteArray Connection::sessionId() const { return m_sessionId; } bool Connection::isOwnerResource(const PimItem &item) const { if (context()->resource().isValid() && item.collection().resourceId() == context()->resource().id()) { return true; } // fallback for older resources if (sessionId() == item.collection().resource().name().toUtf8()) { return true; } return false; } bool Connection::isOwnerResource(const Collection &collection) const { if (context()->resource().isValid() && collection.resourceId() == context()->resource().id()) { return true; } if (sessionId() == collection.resource().name().toUtf8()) { return true; } return false; } bool Connection::verifyCacheOnRetrieval() const { return m_verifyCacheOnRetrieval; } void Connection::startTime() { m_time.start(); } void Connection::stopTime(const QString &identifier) { int elapsed = m_time.elapsed(); m_totalTime += elapsed; m_totalTimeByHandler[identifier] += elapsed; m_executionsByHandler[identifier]++; qCDebug(AKONADISERVER_LOG) << identifier << " time : " << elapsed << " total: " << m_totalTime; } void Connection::reportTime() const { qCDebug(AKONADISERVER_LOG) << "===== Time report for " << m_identifier << " ====="; qCDebug(AKONADISERVER_LOG) << " total: " << m_totalTime; for (auto it = m_totalTimeByHandler.cbegin(), end = m_totalTimeByHandler.cend(); it != end; ++it) { const QString &handler = it.key(); qCDebug(AKONADISERVER_LOG) << "handler : " << handler << " time: " << m_totalTimeByHandler.value(handler) << " executions " << m_executionsByHandler.value(handler) << " avg: " << m_totalTimeByHandler.value(handler) / m_executionsByHandler.value(handler); } } void Connection::sendResponse(qint64 tag, const Protocol::CommandPtr &response) { if (Tracer::self()->currentTracer() != QLatin1String("null")) { Tracer::self()->connectionOutput(m_identifier, tag, response); } - QDataStream stream(m_socket); + Protocol::DataStream stream(m_socket); stream << tag; Protocol::serialize(m_socket, response); if (!m_socket->waitForBytesWritten()) { if (m_socket->state() == QLocalSocket::ConnectedState) { throw ProtocolException("Server write timeout"); } else { // The client has disconnected before we managed to send our response, // which is not an error } } } -void Connection::sendResponse(const Protocol::CommandPtr &response) -{ - Q_ASSERT(m_currentHandler); - sendResponse(m_currentHandler->tag(), response); -} Protocol::CommandPtr Connection::readCommand() { while (m_socket->bytesAvailable() < (int) sizeof(qint64)) { Protocol::DataStream::waitForData(m_socket, 10000); // 10 seconds, just in case client is busy } - QDataStream stream(m_socket); + Protocol::DataStream stream(m_socket); qint64 tag; stream >> tag; // TODO: compare tag with m_currentHandler->tag() ? return Protocol::deserialize(m_socket); } diff --git a/src/server/connection.h b/src/server/connection.h index 9f982afe2..6cbdd6f94 100644 --- a/src/server/connection.h +++ b/src/server/connection.h @@ -1,140 +1,176 @@ /*************************************************************************** * Copyright (C) 2006 by Till Adam * * * * This program 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 program 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 General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef AKONADI_CONNECTION_H #define AKONADI_CONNECTION_H #include #include #include #include #include #include "akthread.h" #include "entities.h" #include "global.h" #include "commandcontext.h" +#include "tracer.h" #include +#include class QEventLoop; namespace Akonadi { namespace Server { class Handler; class Response; class DataStore; class Collection; class CollectionReferenceManager; /** An Connection represents one connection of a client to the server. */ class Connection : public AkThread { Q_OBJECT public: explicit Connection(quintptr socketDescriptor, QObject *parent = nullptr); ~Connection() override; static Connection *self(); virtual DataStore *storageBackend(); CollectionReferenceManager *collectionReferenceManager(); CommandContext *context() const; /** Returns @c true if this connection belongs to the owning resource of @p item. */ bool isOwnerResource(const PimItem &item) const; bool isOwnerResource(const Collection &collection) const; void setSessionId(const QByteArray &id); QByteArray sessionId() const; /** Returns @c true if permanent cache verification is enabled. */ bool verifyCacheOnRetrieval() const; Protocol::CommandPtr readCommand(); void setState(ConnectionState state); -public Q_SLOTS: - virtual void sendResponse(const Protocol::CommandPtr &response); + template + inline typename std::enable_if::value>::type + sendResponse(T &&response); + + void sendResponse(qint64 tag, const Protocol::CommandPtr &response); Q_SIGNALS: void disconnected(); void connectionClosing(); protected Q_SLOTS: void handleIncomingData(); void slotConnectionIdle(); void slotSocketDisconnected(); void slotSendHello(); protected: Connection(QObject *parent = nullptr); // used for testing void init() override; void quit() override; Handler *findHandlerForCommand(Protocol::Command::Type cmd); + qint64 currentTag() const; + protected: quintptr m_socketDescriptor = {}; QLocalSocket *m_socket = nullptr; std::unique_ptr m_currentHandler; ConnectionState m_connectionState = NonAuthenticated; mutable DataStore *m_backend = nullptr; QList m_statusMessageQueue; QString m_identifier; QByteArray m_sessionId; bool m_verifyCacheOnRetrieval = false; CommandContext m_context; QTimer *m_idleTimer = nullptr; QEventLoop *m_waitLoop = nullptr; QTime m_time; qint64 m_totalTime = 0; QHash m_totalTimeByHandler; QHash m_executionsByHandler; bool m_connectionClosing = false; private: - void sendResponse(qint64 tag, const Protocol::CommandPtr &response); + template + inline typename std::enable_if::value>::type + sendResponse(qint64 tag, T &&response); /** For debugging */ void startTime(); void stopTime(const QString &identifier); void reportTime() const; bool m_reportTime = false; }; +template +inline typename std::enable_if::value>::type +Connection::sendResponse(T &&response) +{ + sendResponse(currentTag(), std::move(response)); +} + +template +inline typename std::enable_if::value>::type +Connection::sendResponse(qint64 tag, T &&response) +{ + if (Tracer::self()->currentTracer() != QLatin1String("null")) { + Tracer::self()->connectionOutput(m_identifier, tag, response); + } + Protocol::DataStream stream(m_socket); + stream << tag; + stream << std::move(response); + if (!m_socket->waitForBytesWritten()) { + if (m_socket->state() == QLocalSocket::ConnectedState) { + throw ProtocolException("Server write timeout"); + } else { + // The client has disconnected before we managed to send our response, + // which is not an error + } + } +} + } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler.cpp b/src/server/handler.cpp index a5a3c1686..73bfac2f6 100644 --- a/src/server/handler.cpp +++ b/src/server/handler.cpp @@ -1,265 +1,260 @@ /*************************************************************************** * Copyright (C) 2006 by Till Adam * * * * This program 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 program 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 General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "handler.h" #include #include #include "connection.h" #include "handler/akappend.h" #include "handler/copy.h" #include "handler/colcopy.h" #include "handler/colmove.h" #include "handler/create.h" #include "handler/delete.h" #include "handler/fetch.h" #include "handler/link.h" #include "handler/list.h" #include "handler/login.h" #include "handler/logout.h" #include "handler/modify.h" #include "handler/move.h" #include "handler/remove.h" #include "handler/resourceselect.h" #include "handler/search.h" #include "handler/searchpersistent.h" #include "handler/searchresult.h" #include "handler/status.h" #include "handler/store.h" #include "handler/transaction.h" #include "handler/tagappend.h" #include "handler/tagfetch.h" #include "handler/tagremove.h" #include "handler/tagstore.h" #include "handler/relationstore.h" #include "handler/relationremove.h" #include "handler/relationfetch.h" #include "storage/querybuilder.h" using namespace Akonadi; using namespace Akonadi::Server; Handler *Handler::findHandlerForCommandNonAuthenticated(Protocol::Command::Type cmd) { // allowed are LOGIN if (cmd == Protocol::Command::Login) { return new Login(); } return nullptr; } Handler *Handler::findHandlerForCommandAlwaysAllowed(Protocol::Command::Type cmd) { // allowed is LOGOUT if (cmd == Protocol::Command::Logout) { return new Logout(); } return nullptr; } Handler *Handler::findHandlerForCommandAuthenticated(Protocol::Command::Type cmd) { switch (cmd) { case Protocol::Command::Invalid: Q_ASSERT_X(cmd != Protocol::Command::Invalid, __FUNCTION__, "Invalid command is not allowed"); return nullptr; case Protocol::Command::Hello: Q_ASSERT_X(cmd != Protocol::Command::Hello, __FUNCTION__, "Hello command is not allowed in this context"); return nullptr; case Protocol::Command::Login: return nullptr; case Protocol::Command::Logout: return nullptr; case Protocol::Command::_ResponseBit: Q_ASSERT_X(cmd != Protocol::Command::_ResponseBit, __FUNCTION__, "ResponseBit is not a valid command type"); return nullptr; case Protocol::Command::Transaction: return new TransactionHandler(); case Protocol::Command::CreateItem: return new AkAppend(); case Protocol::Command::CopyItems: return new Copy(); case Protocol::Command::DeleteItems: return new Remove(); case Protocol::Command::FetchItems: return new Fetch(); case Protocol::Command::LinkItems: return new Link(); case Protocol::Command::ModifyItems: return new Store(); case Protocol::Command::MoveItems: return new Move(); case Protocol::Command::CreateCollection: return new Create(); case Protocol::Command::CopyCollection: return new ColCopy(); case Protocol::Command::DeleteCollection: return new Delete(); case Protocol::Command::FetchCollections: return new List(); case Protocol::Command::FetchCollectionStats: return new Status(); case Protocol::Command::ModifyCollection: return new Modify(); case Protocol::Command::MoveCollection: return new ColMove(); case Protocol::Command::Search: return new Search(); case Protocol::Command::SearchResult: return new SearchResult(); case Protocol::Command::StoreSearch: return new SearchPersistent(); case Protocol::Command::CreateTag: return new TagAppend(); case Protocol::Command::DeleteTag: return new TagRemove(); case Protocol::Command::FetchTags: return new TagFetch(); case Protocol::Command::ModifyTag: return new TagStore(); case Protocol::Command::FetchRelations: return new RelationFetch(); case Protocol::Command::ModifyRelation: return new RelationStore(); case Protocol::Command::RemoveRelations: return new RelationRemove(); case Protocol::Command::SelectResource: return new ResourceSelect(); case Protocol::Command::StreamPayload: Q_ASSERT_X(cmd != Protocol::Command::StreamPayload, __FUNCTION__, "StreamPayload command is not allowed in this context"); return nullptr; case Protocol::Command::ItemChangeNotification: Q_ASSERT_X(cmd != Protocol::Command::ItemChangeNotification, __FUNCTION__, "ItemChangeNotification command is not allowed on this connection"); return nullptr; case Protocol::Command::CollectionChangeNotification: Q_ASSERT_X(cmd != Protocol::Command::CollectionChangeNotification, __FUNCTION__, "CollectionChangeNotification command is not allowed on this connection"); return nullptr; case Protocol::Command::TagChangeNotification: Q_ASSERT_X(cmd != Protocol::Command::TagChangeNotification, __FUNCTION__, "TagChangeNotification command is not allowed on this connection"); return nullptr; case Protocol::Command::RelationChangeNotification: Q_ASSERT_X(cmd != Protocol::Command::RelationChangeNotification, __FUNCTION__, "RelationChangeNotification command is not allowed on this connection"); return nullptr; case Protocol::Command::SubscriptionChangeNotification: Q_ASSERT_X(cmd != Protocol::Command::SubscriptionChangeNotification, __FUNCTION__, "SubscriptionChangeNotification command is not allowed on this connection"); return nullptr; case Protocol::Command::DebugChangeNotification: Q_ASSERT_X(cmd != Protocol::Command::DebugChangeNotification, __FUNCTION__, "DebugChangeNotification command is not allowed on this connection"); return nullptr; case Protocol::Command::ModifySubscription: Q_ASSERT_X(cmd != Protocol::Command::ModifySubscription, __FUNCTION__, "ModifySubscription command is not allowed on this connection"); return nullptr; case Protocol::Command::CreateSubscription: Q_ASSERT_X(cmd != Protocol::Command::CreateSubscription, __FUNCTION__, "CreateSubscription command is not allowed on this connection"); return nullptr; } return nullptr; } void Handler::setTag(quint64 tag) { m_tag = tag; } quint64 Handler::tag() const { return m_tag; } void Handler::setCommand(const Protocol::CommandPtr &cmd) { m_command = cmd; } Protocol::CommandPtr Handler::command() const { return m_command; } void Handler::setConnection(Connection *connection) { m_connection = connection; } Connection *Handler::connection() const { return m_connection; } bool Handler::failureResponse(const QByteArray &failureMessage) { return failureResponse(QString::fromUtf8(failureMessage)); } bool Handler::failureResponse(const char *failureMessage) { return failureResponse(QString::fromUtf8(failureMessage)); } bool Handler::failureResponse(const QString &failureMessage) { // Prevent sending multiple error responses from a single handler (or from // a handler and then from Connection, since clients only expect a single // error response if (!m_sentFailureResponse) { m_sentFailureResponse = true; Protocol::ResponsePtr r = Protocol::Factory::response(m_command->type()); // FIXME: Error enums? r->setError(1, failureMessage); - sendResponse(r); + m_connection->sendResponse(m_tag, r); } return false; } -void Handler::sendResponse(const Protocol::CommandPtr &response) -{ - m_connection->sendResponse(response); -} - bool Handler::checkScopeConstraints(const Akonadi::Scope &scope, int permittedScopes) { return scope.scope() & permittedScopes; } diff --git a/src/server/handler.h b/src/server/handler.h index 285ff6db6..3cb92917a 100644 --- a/src/server/handler.h +++ b/src/server/handler.h @@ -1,144 +1,153 @@ /*************************************************************************** * Copyright (C) 2006 by Till Adam * * * * This program 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 program 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 General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef AKONADIHANDLER_H #define AKONADIHANDLER_H #include #include "global.h" #include "exception.h" #include "connection.h" #include namespace Akonadi { namespace Server { class Response; -class Connection; AKONADI_EXCEPTION_MAKE_INSTANCE(HandlerException); /** \defgroup akonadi_server_handler Command handlers All commands supported by the Akonadi server are implemented as sub-classes of Akonadi::Handler. */ /** The handler interfaces describes an entity capable of handling an AkonadiIMAP command.*/ class Handler { public: Handler() = default; virtual ~Handler() = default; /** * Set the tag of the command to be processed, and thus of the response * generated by this handler. * @param tag The command tag, an alphanumerical string, normally. */ void setTag(quint64 tag); /** * The tag of the command associated with this handler. */ quint64 tag() const; void setCommand(const Protocol::CommandPtr &cmd); Protocol::CommandPtr command() const; /** * Find a handler for a command that is always allowed, like LOGOUT. * @param cmd the command string * @return an instance to the handler. The handler is deleted after @see handelLine is executed. The caller needs to delete the handler in case an exception is thrown from handelLine. */ static Handler *findHandlerForCommandAlwaysAllowed(Protocol::Command::Type cmd); /** * Find a handler for a command that is allowed when the client is not yet authenticated, like LOGIN. * @param cmd the command string * @return an instance to the handler. The handler is deleted after @see handelLine is executed. The caller needs to delete the handler in case an exception is thrown from handelLine. */ static Handler *findHandlerForCommandNonAuthenticated(Protocol::Command::Type cmd); /** * Find a handler for a command that is allowed when the client is authenticated, like LIST, FETCH, etc. * @param cmd the command string * @return an instance to the handler. The handler is deleted after @see handelLine is executed. The caller needs to delete the handler in case an exception is thrown from handelLine. */ static Handler *findHandlerForCommandAuthenticated(Protocol::Command::Type cmd); void setConnection(Connection *connection); Connection *connection() const; bool failureResponse(const char *response); bool failureResponse(const QByteArray &response); bool failureResponse(const QString &response); template - typename std::enable_if::value, bool>::type - successResponse(const QSharedPointer &response = QSharedPointer()); + inline bool successResponse(); + template + inline bool successResponse(T &&response); template - typename std::enable_if::value, void>::type - sendResponse(const QSharedPointer &response = QSharedPointer()); + inline void sendResponse(T &&response); + template + inline void sendResponse(); /** * Parse and handle the IMAP message using the streaming parser. The implementation MUST leave the trailing newline character(s) in the stream! * @return true if parsed successfully, false in case of parse failure */ virtual bool parseStream() = 0; bool checkScopeConstraints(const Scope &scope, int permittedScopes); -protected: - void sendResponse(const Protocol::CommandPtr &response); - private: quint64 m_tag = 0; Connection *m_connection = nullptr; bool m_sentFailureResponse = false; protected: Protocol::CommandPtr m_command; }; template -typename std::enable_if::value, bool>::type -Handler::successResponse(const QSharedPointer &response) +inline bool Handler::successResponse() { - sendResponse(response ? response : QSharedPointer::create()); + sendResponse(T{}); return true; } template -typename std::enable_if::value, void>::type -Handler::sendResponse(const QSharedPointer &response) +inline bool Handler::successResponse(T &&response) +{ + sendResponse(std::move(response)); + return true; +} + +template +inline void Handler::sendResponse() +{ + m_connection->sendResponse(T{}); +} + +template +inline void Handler::sendResponse(T &&response) { - sendResponse(response.template staticCast()); + m_connection->sendResponse(std::move(response)); } } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/akappend.cpp b/src/server/handler/akappend.cpp index bef824ba9..2e8c4f4f2 100644 --- a/src/server/handler/akappend.cpp +++ b/src/server/handler/akappend.cpp @@ -1,452 +1,452 @@ /*************************************************************************** * Copyright (C) 2007 by Robert Zwerus * * * * This program 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 program 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 General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "akappend.h" #include "fetchhelper.h" #include "connection.h" #include "preprocessormanager.h" #include "handlerhelper.h" #include "storage/datastore.h" #include "storage/transaction.h" #include "storage/parttypehelper.h" #include "storage/dbconfig.h" #include "storage/partstreamer.h" #include "storage/parthelper.h" #include "storage/selectquerybuilder.h" #include #include //std::accumulate using namespace Akonadi; using namespace Akonadi::Server; static QVector localFlagsToPreserve = QVector() << "$ATTACHMENT" << "$INVITATION" << "$ENCRYPTED" << "$SIGNED" << "$WATCHED"; bool AkAppend::buildPimItem(const Protocol::CreateItemCommand &cmd, PimItem &item, Collection &parentCol) { parentCol = HandlerHelper::collectionFromScope(cmd.collection(), connection()); if (!parentCol.isValid()) { return failureResponse(QStringLiteral("Invalid parent collection")); } if (parentCol.isVirtual()) { return failureResponse(QStringLiteral("Cannot append item into virtual collection")); } MimeType mimeType = MimeType::retrieveByNameOrCreate(cmd.mimeType()); if (!mimeType.isValid()) { return failureResponse(QStringLiteral("Unable to create mimetype '") % cmd.mimeType() % QStringLiteral("'.")); } item.setRev(0); item.setSize(cmd.itemSize()); item.setMimeTypeId(mimeType.id()); item.setCollectionId(parentCol.id()); item.setDatetime(cmd.dateTime()); if (cmd.remoteId().isEmpty()) { // from application item.setDirty(true); } else { // from resource item.setRemoteId(cmd.remoteId()); item.setDirty(false); } item.setRemoteRevision(cmd.remoteRevision()); item.setGid(cmd.gid()); item.setAtime(QDateTime::currentDateTimeUtc()); return true; } bool AkAppend::insertItem(const Protocol::CreateItemCommand &cmd, PimItem &item, const Collection &parentCol) { if (!item.datetime().isValid()) { item.setDatetime(QDateTime::currentDateTimeUtc()); } if (!item.insert()) { return failureResponse(QStringLiteral("Failed to append item")); } // set message flags const QSet flags = cmd.mergeModes() == Protocol::CreateItemCommand::None ? cmd.flags() : cmd.addedFlags(); if (!flags.isEmpty()) { // This will hit an entry in cache inserted there in buildPimItem() const Flag::List flagList = HandlerHelper::resolveFlags(flags); bool flagsChanged = false; if (!DataStore::self()->appendItemsFlags(PimItem::List() << item, flagList, &flagsChanged, false, parentCol, true)) { return failureResponse("Unable to append item flags."); } } const Scope tags = cmd.mergeModes() == Protocol::CreateItemCommand::None ? cmd.tags() : cmd.addedTags(); if (!tags.isEmpty()) { const Tag::List tagList = HandlerHelper::tagsFromScope(tags, connection()); bool tagsChanged = false; if (!DataStore::self()->appendItemsTags(PimItem::List() << item, tagList, &tagsChanged, false, parentCol, true)) { return failureResponse(QStringLiteral("Unable to append item tags.")); } } // Handle individual parts qint64 partSizes = 0; PartStreamer streamer(connection(), item); Q_FOREACH (const QByteArray &partName, cmd.parts()) { qint64 partSize = 0; if (!streamer.stream(true, partName, partSize)) { return failureResponse(streamer.error()); } partSizes += partSize; } const Protocol::Attributes attrs = cmd.attributes(); for (auto iter = attrs.cbegin(), end = attrs.cend(); iter != end; ++iter) { if (!streamer.streamAttribute(true, iter.key(), iter.value())) { return failureResponse(streamer.error()); } } // TODO: Try to avoid this addition query if (partSizes > item.size()) { item.setSize(partSizes); item.update(); } // Preprocessing if (PreprocessorManager::instance()->isActive()) { Part hiddenAttribute; hiddenAttribute.setPimItemId(item.id()); hiddenAttribute.setPartType(PartTypeHelper::fromFqName(QStringLiteral(AKONADI_ATTRIBUTE_HIDDEN))); hiddenAttribute.setData(QByteArray()); hiddenAttribute.setDatasize(0); // TODO: Handle errors? Technically, this is not a critical issue as no data are lost PartHelper::insert(&hiddenAttribute); } const bool seen = flags.contains(AKONADI_FLAG_SEEN) || flags.contains(AKONADI_FLAG_IGNORED); notify(item, seen, item.collection()); sendResponse(item, Protocol::CreateItemCommand::None); return true; } bool AkAppend::mergeItem(const Protocol::CreateItemCommand &cmd, PimItem &newItem, PimItem ¤tItem, const Collection &parentCol) { bool needsUpdate = false; QSet changedParts; if (!newItem.remoteId().isEmpty() && currentItem.remoteId() != newItem.remoteId()) { currentItem.setRemoteId(newItem.remoteId()); changedParts.insert(AKONADI_PARAM_REMOTEID); needsUpdate = true; } if (!newItem.remoteRevision().isEmpty() && currentItem.remoteRevision() != newItem.remoteRevision()) { currentItem.setRemoteRevision(newItem.remoteRevision()); changedParts.insert(AKONADI_PARAM_REMOTEREVISION); needsUpdate = true; } if (!newItem.gid().isEmpty() && currentItem.gid() != newItem.gid()) { currentItem.setGid(newItem.gid()); changedParts.insert(AKONADI_PARAM_GID); needsUpdate = true; } if (newItem.datetime().isValid() && newItem.datetime() != currentItem.datetime()) { currentItem.setDatetime(newItem.datetime()); needsUpdate = true; } if (newItem.size() > 0 && newItem.size() != currentItem.size()) { currentItem.setSize(newItem.size()); needsUpdate = true; } const Collection col = Collection::retrieveById(parentCol.id()); if (cmd.flags().isEmpty() && !cmd.flagsOverwritten()) { bool flagsAdded = false, flagsRemoved = false; if (!cmd.addedFlags().isEmpty()) { const Flag::List addedFlags = HandlerHelper::resolveFlags(cmd.addedFlags()); DataStore::self()->appendItemsFlags(PimItem::List() << currentItem, addedFlags, &flagsAdded, true, col, true); } if (!cmd.removedFlags().isEmpty()) { const Flag::List removedFlags = HandlerHelper::resolveFlags(cmd.removedFlags()); DataStore::self()->removeItemsFlags(PimItem::List() << currentItem, removedFlags, &flagsRemoved, col, true); } if (flagsAdded || flagsRemoved) { changedParts.insert(AKONADI_PARAM_FLAGS); needsUpdate = true; } } else { bool flagsChanged = false; QSet flagNames = cmd.flags(); // Make sure we don't overwrite some local-only flags that can't come // through from Resource during ItemSync, like $ATTACHMENT, because the // resource is not aware of them (they are usually assigned by client // upon inspecting the payload) Q_FOREACH (const Flag ¤tFlag, currentItem.flags()) { const QByteArray currentFlagName = currentFlag.name().toLatin1(); if (localFlagsToPreserve.contains(currentFlagName)) { flagNames.insert(currentFlagName); } } const Flag::List flags = HandlerHelper::resolveFlags(flagNames); DataStore::self()->setItemsFlags(PimItem::List() << currentItem, flags, &flagsChanged, col, true); if (flagsChanged) { changedParts.insert(AKONADI_PARAM_FLAGS); needsUpdate = true; } } if (cmd.tags().isEmpty()) { bool tagsAdded = false, tagsRemoved = false; if (!cmd.addedTags().isEmpty()) { const Tag::List addedTags = HandlerHelper::tagsFromScope(cmd.addedTags(), connection()); DataStore::self()->appendItemsTags(PimItem::List() << currentItem, addedTags, &tagsAdded, true, col, true); } if (!cmd.removedTags().isEmpty()) { const Tag::List removedTags = HandlerHelper::tagsFromScope(cmd.removedTags(), connection()); DataStore::self()->removeItemsTags(PimItem::List() << currentItem, removedTags, &tagsRemoved, true); } if (tagsAdded || tagsRemoved) { changedParts.insert(AKONADI_PARAM_TAGS); needsUpdate = true; } } else { bool tagsChanged = false; const Tag::List tags = HandlerHelper::tagsFromScope(cmd.tags(), connection()); DataStore::self()->setItemsTags(PimItem::List() << currentItem, tags, &tagsChanged, true); if (tagsChanged) { changedParts.insert(AKONADI_PARAM_TAGS); needsUpdate = true; } } const Part::List existingParts = Part::retrieveFiltered(Part::pimItemIdColumn(), currentItem.id()); QMap partsSizes; for (const Part &part : existingParts) { partsSizes.insert(PartTypeHelper::fullName(part.partType()).toLatin1(), part.datasize()); } PartStreamer streamer(connection(), currentItem); Q_FOREACH (const QByteArray &partName, cmd.parts()) { bool changed = false; qint64 partSize = 0; if (!streamer.stream(true, partName, partSize, &changed)) { return failureResponse(streamer.error()); } if (changed) { changedParts.insert(partName); partsSizes.insert(partName, partSize); needsUpdate = true; } } const qint64 size = std::accumulate(partsSizes.begin(), partsSizes.end(), 0); if (size > currentItem.size()) { currentItem.setSize(size); needsUpdate = true; } if (needsUpdate) { currentItem.setRev(qMax(newItem.rev(), currentItem.rev()) + 1); currentItem.setAtime(QDateTime::currentDateTimeUtc()); // Only mark dirty when merged from application currentItem.setDirty(!connection()->context()->resource().isValid()); // Store all changes if (!currentItem.update()) { return failureResponse("Failed to store merged item"); } notify(currentItem, currentItem.collection(), changedParts); } sendResponse(currentItem, cmd.mergeModes()); return true; } bool AkAppend::sendResponse(const PimItem &item, Protocol::CreateItemCommand::MergeModes mergeModes) { if (mergeModes & Protocol::CreateItemCommand::Silent || mergeModes & Protocol::CreateItemCommand::None) { - auto resp = Protocol::FetchItemsResponsePtr::create(); - resp->setId(item.id()); - resp->setMTime(item.datetime()); - Handler::sendResponse(resp); + Protocol::FetchItemsResponse resp; + resp.setId(item.id()); + resp.setMTime(item.datetime()); + Handler::sendResponse(std::move(resp)); return true; } Protocol::ItemFetchScope fetchScope; fetchScope.setAncestorDepth(Protocol::ItemFetchScope::ParentAncestor); fetchScope.setFetch(Protocol::ItemFetchScope::AllAttributes | Protocol::ItemFetchScope::FullPayload | Protocol::ItemFetchScope::CacheOnly | Protocol::ItemFetchScope::Flags | Protocol::ItemFetchScope::GID | Protocol::ItemFetchScope::MTime | Protocol::ItemFetchScope::RemoteID | Protocol::ItemFetchScope::RemoteRevision | Protocol::ItemFetchScope::Size | Protocol::ItemFetchScope::Tags); fetchScope.setTagFetchScope({ "GID" }); ImapSet set; set.add(QVector() << item.id()); Scope scope; scope.setUidSet(set); FetchHelper fetchHelper(connection(), scope, fetchScope); if (!fetchHelper.fetchItems()) { return failureResponse("Failed to retrieve item"); } return true; } bool AkAppend::notify(const PimItem &item, bool seen, const Collection &collection) { DataStore::self()->notificationCollector()->itemAdded(item, seen, collection); if (PreprocessorManager::instance()->isActive()) { // enqueue the item for preprocessing PreprocessorManager::instance()->beginHandleItem(item, DataStore::self()); } return true; } bool AkAppend::notify(const PimItem &item, const Collection &collection, const QSet &changedParts) { if (!changedParts.isEmpty()) { DataStore::self()->notificationCollector()->itemChanged(item, changedParts, collection); } return true; } bool AkAppend::parseStream() { const auto &cmd = Protocol::cmdCast(m_command); // FIXME: The streaming/reading of all item parts can hold the transaction for // unnecessary long time -> should we wrap the PimItem into one transaction // and try to insert Parts independently? In case we fail to insert a part, // it's not a problem as it can be re-fetched at any time, except for attributes. DataStore *db = DataStore::self(); Transaction transaction(db, QStringLiteral("AKAPPEND")); ExternalPartStorageTransaction storageTrx; PimItem item; Collection parentCol; if (!buildPimItem(cmd, item, parentCol)) { return false; } if (cmd.mergeModes() == Protocol::CreateItemCommand::None) { if (!insertItem(cmd, item, parentCol)) { return false; } if (!transaction.commit()) { return failureResponse(QStringLiteral("Failed to commit transaction")); } storageTrx.commit(); } else { // Merging is always restricted to the same collection SelectQueryBuilder qb; qb.addValueCondition(PimItem::collectionIdColumn(), Query::Equals, parentCol.id()); Query::Condition rootCondition(Query::Or); Query::Condition mergeCondition(Query::And); if (cmd.mergeModes() & Protocol::CreateItemCommand::GID) { mergeCondition.addValueCondition(PimItem::gidColumn(), Query::Equals, item.gid()); } if (cmd.mergeModes() & Protocol::CreateItemCommand::RemoteID) { mergeCondition.addValueCondition(PimItem::remoteIdColumn(), Query::Equals, item.remoteId()); } rootCondition.addCondition(mergeCondition); // If an Item with matching RID but empty GID exists during GID merge, // merge into this item instead of creating a new one if (cmd.mergeModes() & Protocol::CreateItemCommand::GID && !item.remoteId().isEmpty()) { mergeCondition = Query::Condition(Query::And); mergeCondition.addValueCondition(PimItem::remoteIdColumn(), Query::Equals, item.remoteId()); mergeCondition.addValueCondition(PimItem::gidColumn(), Query::Equals, QStringLiteral("")); rootCondition.addCondition(mergeCondition); } qb.addCondition(rootCondition); if (!qb.exec()) { return failureResponse("Failed to query database for item"); } const QVector result = qb.result(); if (result.isEmpty()) { // No item with such GID/RID exists, so call AkAppend::insert() and behave // like if this was a new item if (!insertItem(cmd, item, parentCol)) { return false; } if (!transaction.commit()) { return failureResponse("Failed to commit transaction"); } storageTrx.commit(); } else if (result.count() == 1) { // Item with matching GID/RID combination exists, so merge this item into it // and send itemChanged() PimItem existingItem = result.at(0); if (!mergeItem(cmd, item, existingItem, parentCol)) { return false; } if (!transaction.commit()) { return failureResponse("Failed to commit transaction"); } storageTrx.commit(); } else { qCDebug(AKONADISERVER_LOG) << "Multiple merge candidates:"; for (const PimItem &item : result) { qCDebug(AKONADISERVER_LOG) << "\tID:" << item.id() << ", RID:" << item.remoteId() << ", GID:" << item.gid() << ", Collection:" << item.collection().name() << "(" << item.collectionId() << ")" << ", Resource:" << item.collection().resource().name() << "(" << item.collection().resourceId() << ")"; } // Nor GID or RID are guaranteed to be unique, so make sure we don't merge // something we don't want return failureResponse(QStringLiteral("Multiple merge candidates, aborting")); } } return successResponse(); } diff --git a/src/server/handler/fetchhelper.cpp b/src/server/handler/fetchhelper.cpp index b9c652755..2336447a4 100644 --- a/src/server/handler/fetchhelper.cpp +++ b/src/server/handler/fetchhelper.cpp @@ -1,748 +1,748 @@ /*************************************************************************** * Copyright (C) 2006-2009 by Tobias Koenig * * * * This program 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 program 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 General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "fetchhelper.h" #include "akonadi.h" #include "connection.h" #include "handler.h" #include "handlerhelper.h" #include "storage/selectquerybuilder.h" #include "storage/itemqueryhelper.h" #include "storage/itemretrievalmanager.h" #include "storage/itemretrievalrequest.h" #include "storage/parthelper.h" #include "storage/parttypehelper.h" #include "storage/transaction.h" #include "utils.h" #include "intervalcheck.h" #include "agentmanagerinterface.h" #include "dbusconnectionpool.h" #include "tagfetchhelper.h" #include "relationfetch.h" #include "akonadiserver_debug.h" #include #include #include #include #include #include #include using namespace Akonadi; using namespace Akonadi::Server; #define ENABLE_FETCH_PROFILING 0 #if ENABLE_FETCH_PROFILING #define BEGIN_TIMER(name) \ QElapsedTimer name##Timer; \ name##Timer.start(); #define END_TIMER(name) \ const double name##Elapsed = name##Timer.nsecsElapsed() / 1000000.0; #define PROF_INC(name) \ ++name; #else #define BEGIN_TIMER(name) #define END_TIMER(name) #define PROF_INC(name) #endif FetchHelper::FetchHelper(Connection *connection, const Scope &scope, const Protocol::ItemFetchScope &fetchScope) : FetchHelper(connection, connection->context(), scope, fetchScope) { } FetchHelper::FetchHelper(Connection *connection, CommandContext *context, const Scope &scope, const Protocol::ItemFetchScope &fetchScope) : mConnection(connection) , mContext(context) , mScope(scope) , mFetchScope(fetchScope) { std::fill(mItemQueryColumnMap, mItemQueryColumnMap + ItemQueryColumnCount, -1); } enum PartQueryColumns { PartQueryPimIdColumn, PartQueryTypeIdColumn, PartQueryDataColumn, PartQueryStorageColumn, PartQueryVersionColumn, PartQueryDataSizeColumn }; QSqlQuery FetchHelper::buildPartQuery(const QVector &partList, bool allPayload, bool allAttrs) { ///TODO: merge with ItemQuery QueryBuilder partQuery(PimItem::tableName()); if (!partList.isEmpty() || allPayload || allAttrs) { partQuery.addJoin(QueryBuilder::InnerJoin, Part::tableName(), PimItem::idFullColumnName(), Part::pimItemIdFullColumnName()); partQuery.addColumn(PimItem::idFullColumnName()); partQuery.addColumn(Part::partTypeIdFullColumnName()); partQuery.addColumn(Part::dataFullColumnName()); partQuery.addColumn(Part::storageFullColumnName()); partQuery.addColumn(Part::versionFullColumnName()); partQuery.addColumn(Part::datasizeFullColumnName()); partQuery.addSortColumn(PimItem::idFullColumnName(), Query::Descending); if (!partList.isEmpty() || allPayload || allAttrs) { Query::Condition cond(Query::Or); for (const QByteArray &b : qAsConst(partList)) { if (b.startsWith("PLD") || b.startsWith("ATR")) { cond.addValueCondition(Part::partTypeIdFullColumnName(), Query::Equals, PartTypeHelper::fromFqName(b).id()); } } if (allPayload || allAttrs) { partQuery.addJoin(QueryBuilder::InnerJoin, PartType::tableName(), Part::partTypeIdFullColumnName(), PartType::idFullColumnName()); if (allPayload) { cond.addValueCondition(PartType::nsFullColumnName(), Query::Equals, QStringLiteral("PLD")); } if (allAttrs) { cond.addValueCondition(PartType::nsFullColumnName(), Query::Equals, QStringLiteral("ATR")); } } partQuery.addCondition(cond); } ItemQueryHelper::scopeToQuery(mScope, mContext, partQuery); if (!partQuery.exec()) { throw HandlerException("Unable to list item parts"); } partQuery.query().next(); } return partQuery.query(); } QSqlQuery FetchHelper::buildItemQuery() { QueryBuilder itemQuery(PimItem::tableName()); int column = 0; #define ADD_COLUMN(colName, colId) { itemQuery.addColumn( colName ); mItemQueryColumnMap[colId] = column++; } ADD_COLUMN(PimItem::idFullColumnName(), ItemQueryPimItemIdColumn); if (mFetchScope.fetchRemoteId()) { ADD_COLUMN(PimItem::remoteIdFullColumnName(), ItemQueryPimItemRidColumn) } ADD_COLUMN(PimItem::mimeTypeIdFullColumnName(), ItemQueryMimeTypeIdColumn) ADD_COLUMN(PimItem::revFullColumnName(), ItemQueryRevColumn) if (mFetchScope.fetchRemoteRevision()) { ADD_COLUMN(PimItem::remoteRevisionFullColumnName(), ItemQueryRemoteRevisionColumn) } if (mFetchScope.fetchSize()) { ADD_COLUMN(PimItem::sizeFullColumnName(), ItemQuerySizeColumn) } if (mFetchScope.fetchMTime()) { ADD_COLUMN(PimItem::datetimeFullColumnName(), ItemQueryDatetimeColumn) } ADD_COLUMN(PimItem::collectionIdFullColumnName(), ItemQueryCollectionIdColumn) if (mFetchScope.fetchGID()) { ADD_COLUMN(PimItem::gidFullColumnName(), ItemQueryPimItemGidColumn) } #undef ADD_COLUMN itemQuery.addSortColumn(PimItem::idFullColumnName(), Query::Descending); ItemQueryHelper::scopeToQuery(mScope, mContext, itemQuery); if (mFetchScope.changedSince().isValid()) { itemQuery.addValueCondition(PimItem::datetimeFullColumnName(), Query::GreaterOrEqual, mFetchScope.changedSince().toUTC()); } if (!itemQuery.exec()) { throw HandlerException("Unable to list items"); } itemQuery.query().next(); return itemQuery.query(); } enum FlagQueryColumns { FlagQueryPimItemIdColumn, FlagQueryFlagIdColumn }; QSqlQuery FetchHelper::buildFlagQuery() { QueryBuilder flagQuery(PimItem::tableName()); flagQuery.addJoin(QueryBuilder::InnerJoin, PimItemFlagRelation::tableName(), PimItem::idFullColumnName(), PimItemFlagRelation::leftFullColumnName()); flagQuery.addColumn(PimItem::idFullColumnName()); flagQuery.addColumn(PimItemFlagRelation::rightFullColumnName()); ItemQueryHelper::scopeToQuery(mScope, mContext, flagQuery); flagQuery.addSortColumn(PimItem::idFullColumnName(), Query::Descending); if (!flagQuery.exec()) { throw HandlerException("Unable to retrieve item flags"); } flagQuery.query().next(); return flagQuery.query(); } enum TagQueryColumns { TagQueryItemIdColumn, TagQueryTagIdColumn, }; QSqlQuery FetchHelper::buildTagQuery() { QueryBuilder tagQuery(PimItem::tableName()); tagQuery.addJoin(QueryBuilder::InnerJoin, PimItemTagRelation::tableName(), PimItem::idFullColumnName(), PimItemTagRelation::leftFullColumnName()); tagQuery.addJoin(QueryBuilder::InnerJoin, Tag::tableName(), Tag::idFullColumnName(), PimItemTagRelation::rightFullColumnName()); tagQuery.addColumn(PimItem::idFullColumnName()); tagQuery.addColumn(Tag::idFullColumnName()); ItemQueryHelper::scopeToQuery(mScope, mContext, tagQuery); tagQuery.addSortColumn(PimItem::idFullColumnName(), Query::Descending); if (!tagQuery.exec()) { throw HandlerException("Unable to retrieve item tags"); } tagQuery.query().next(); return tagQuery.query(); } enum VRefQueryColumns { VRefQueryCollectionIdColumn, VRefQueryItemIdColumn }; QSqlQuery FetchHelper::buildVRefQuery() { QueryBuilder vRefQuery(PimItem::tableName()); vRefQuery.addJoin(QueryBuilder::LeftJoin, CollectionPimItemRelation::tableName(), CollectionPimItemRelation::rightFullColumnName(), PimItem::idFullColumnName()); vRefQuery.addColumn(CollectionPimItemRelation::leftFullColumnName()); vRefQuery.addColumn(CollectionPimItemRelation::rightFullColumnName()); ItemQueryHelper::scopeToQuery(mScope, mContext, vRefQuery); vRefQuery.addSortColumn(PimItem::idFullColumnName(), Query::Descending); if (!vRefQuery.exec()) { throw HandlerException("Unable to retrieve virtual references"); } vRefQuery.query().next(); return vRefQuery.query(); } bool FetchHelper::isScopeLocal(const Scope &scope) { // The only agent allowed to override local scope is the Baloo Indexer if (!mConnection->sessionId().startsWith("akonadi_indexing_agent")) { return false; } // Get list of all resources that own all items in the scope QueryBuilder qb(PimItem::tableName(), QueryBuilder::Select); qb.setDistinct(true); qb.addColumn(Resource::nameFullColumnName()); qb.addJoin(QueryBuilder::LeftJoin, Collection::tableName(), PimItem::collectionIdFullColumnName(), Collection::idFullColumnName()); qb.addJoin(QueryBuilder::LeftJoin, Resource::tableName(), Collection::resourceIdFullColumnName(), Resource::idFullColumnName()); ItemQueryHelper::scopeToQuery(scope, mContext, qb); if (mContext->resource().isValid()) { qb.addValueCondition(Resource::nameFullColumnName(), Query::NotEquals, mContext->resource().name()); } if (!qb.exec()) { throw HandlerException("Failed to query database"); return false; } // If there is more than one resource, i.e. this is a fetch from multiple // collections, then don't bother and just return FALSE. This case is aimed // specifically on Baloo, which fetches items from each collection independently, // so it will pass this check. QSqlQuery query = qb.query(); if (query.size() != 1) { return false; } query.next(); const QString resourceName = query.value(0).toString(); org::freedesktop::Akonadi::AgentManager manager(DBus::serviceName(DBus::Control), QStringLiteral("/AgentManager"), DBusConnectionPool::threadConnection()); const QString typeIdentifier = manager.agentInstanceType(resourceName); const QVariantMap properties = manager.agentCustomProperties(typeIdentifier); return properties.value(QStringLiteral("HasLocalStorage"), false).toBool(); } DataStore *FetchHelper::storageBackend() const { if (mConnection) { if (auto store = mConnection->storageBackend()) { return store; } } return DataStore::self(); } -bool FetchHelper::fetchItems(std::function &&itemCallback) +bool FetchHelper::fetchItems(std::function &&itemCallback) { BEGIN_TIMER(fetch) // retrieve missing parts // HACK: isScopeLocal() is a workaround for resources that have cache expiration // because when the cache expires, Baloo is not able to content of the items. So // we allow fetch of items that belong to local resources (like maildir) to ignore // cacheOnly and retrieve missing parts from the resource. However ItemRetriever // is painfully slow with many items and is generally designed to fetch a few // messages, not all of them. In the long term, we need a better way to do this. BEGIN_TIMER(itemRetriever) BEGIN_TIMER(scopeLocal) #if ENABLE_FETCH_PROFILING double scopeLocalElapsed = 0; #endif if (!mFetchScope.cacheOnly() || isScopeLocal(mScope)) { #if ENABLE_FETCH_PROFILING scopeLocalElapsed = scopeLocalTimer.elapsed(); #endif // trigger a collection sync if configured to do so triggerOnDemandFetch(); // Prepare for a call to ItemRetriever::exec(); // From a resource perspective the only parts that can be fetched are payloads. ItemRetriever retriever(mConnection); retriever.setScope(mScope); retriever.setRetrieveParts(mFetchScope.requestedPayloads()); retriever.setRetrieveFullPayload(mFetchScope.fullPayload()); retriever.setChangedSince(mFetchScope.changedSince()); if (!retriever.exec() && !mFetchScope.ignoreErrors()) { // There we go, retrieve the missing parts from the resource. if (mContext->resource().isValid()) { throw HandlerException(QStringLiteral("Unable to fetch item from backend (collection %1, resource %2) : %3") .arg(mContext->collectionId()) .arg(mContext->resource().id()) .arg(QString::fromLatin1(retriever.lastError()))); } else { throw HandlerException(QStringLiteral("Unable to fetch item from backend (collection %1) : %2") .arg(mContext->collectionId()) .arg(QString::fromLatin1(retriever.lastError()))); } } } END_TIMER(itemRetriever) BEGIN_TIMER(items) QSqlQuery itemQuery = buildItemQuery(); END_TIMER(items) // error if query did not find any item and scope is not listing items but // a request for a specific item if (!itemQuery.isValid()) { if (mFetchScope.ignoreErrors()) { return true; } switch (mScope.scope()) { case Scope::Uid: // fall through case Scope::Rid: // fall through case Scope::HierarchicalRid: // fall through case Scope::Gid: throw HandlerException("Item query returned empty result set"); break; default: break; } } // build part query if needed BEGIN_TIMER(parts) QSqlQuery partQuery(DataStore::self()->database()); if (!mFetchScope.requestedParts().isEmpty() || mFetchScope.fullPayload() || mFetchScope.allAttributes()) { partQuery = buildPartQuery(mFetchScope.requestedParts(), mFetchScope.fullPayload(), mFetchScope.allAttributes()); } END_TIMER(parts) // build flag query if needed BEGIN_TIMER(flags) QSqlQuery flagQuery(DataStore::self()->database()); if (mFetchScope.fetchFlags()) { flagQuery = buildFlagQuery(); } END_TIMER(flags) // build tag query if needed BEGIN_TIMER(tags) QSqlQuery tagQuery(DataStore::self()->database()); if (mFetchScope.fetchTags()) { tagQuery = buildTagQuery(); } END_TIMER(tags) BEGIN_TIMER(vRefs) QSqlQuery vRefQuery(DataStore::self()->database()); if (mFetchScope.fetchVirtualReferences()) { vRefQuery = buildVRefQuery(); } END_TIMER(vRefs) #if ENABLE_FETCH_PROFILING int itemsCount = 0; int flagsCount = 0; int partsCount = 0; int tagsCount = 0; int vRefsCount = 0; #endif BEGIN_TIMER(processing) QHash flagIdNameCache; QHash mimeTypeIdNameCache; QHash partTypeIdNameCache; while (itemQuery.isValid()) { PROF_INC(itemsCount) const qint64 pimItemId = extractQueryResult(itemQuery, ItemQueryPimItemIdColumn).toLongLong(); const int pimItemRev = extractQueryResult(itemQuery, ItemQueryRevColumn).toInt(); - auto response = Protocol::FetchItemsResponsePtr::create(); - response->setId(pimItemId); - response->setRevision(pimItemRev); + Protocol::FetchItemsResponse response; + response.setId(pimItemId); + response.setRevision(pimItemRev); const qint64 mimeTypeId = extractQueryResult(itemQuery, ItemQueryMimeTypeIdColumn).toLongLong(); auto mtIter = mimeTypeIdNameCache.find(mimeTypeId); if (mtIter == mimeTypeIdNameCache.end()) { mtIter = mimeTypeIdNameCache.insert(mimeTypeId, MimeType::retrieveById(mimeTypeId).name()); } - response->setMimeType(mtIter.value()); + response.setMimeType(mtIter.value()); if (mFetchScope.fetchRemoteId()) { - response->setRemoteId(extractQueryResult(itemQuery, ItemQueryPimItemRidColumn).toString()); + response.setRemoteId(extractQueryResult(itemQuery, ItemQueryPimItemRidColumn).toString()); } - response->setParentId(extractQueryResult(itemQuery, ItemQueryCollectionIdColumn).toLongLong()); + response.setParentId(extractQueryResult(itemQuery, ItemQueryCollectionIdColumn).toLongLong()); if (mFetchScope.fetchSize()) { - response->setSize(extractQueryResult(itemQuery, ItemQuerySizeColumn).toLongLong()); + response.setSize(extractQueryResult(itemQuery, ItemQuerySizeColumn).toLongLong()); } if (mFetchScope.fetchMTime()) { - response->setMTime(Utils::variantToDateTime(extractQueryResult(itemQuery, ItemQueryDatetimeColumn))); + response.setMTime(Utils::variantToDateTime(extractQueryResult(itemQuery, ItemQueryDatetimeColumn))); } if (mFetchScope.fetchRemoteRevision()) { - response->setRemoteRevision(extractQueryResult(itemQuery, ItemQueryRemoteRevisionColumn).toString()); + response.setRemoteRevision(extractQueryResult(itemQuery, ItemQueryRemoteRevisionColumn).toString()); } if (mFetchScope.fetchGID()) { - response->setGid(extractQueryResult(itemQuery, ItemQueryPimItemGidColumn).toString()); + response.setGid(extractQueryResult(itemQuery, ItemQueryPimItemGidColumn).toString()); } if (mFetchScope.fetchFlags()) { QVector flags; while (flagQuery.isValid()) { const qint64 id = flagQuery.value(FlagQueryPimItemIdColumn).toLongLong(); if (id > pimItemId) { flagQuery.next(); continue; } else if (id < pimItemId) { break; } const qint64 flagId = flagQuery.value(FlagQueryFlagIdColumn).toLongLong(); auto flagNameIter = flagIdNameCache.find(flagId); if (flagNameIter == flagIdNameCache.end()) { flagNameIter = flagIdNameCache.insert(flagId, Flag::retrieveById(flagId).name().toUtf8()); } flags << flagNameIter.value(); flagQuery.next(); } - response->setFlags(flags); + response.setFlags(flags); } if (mFetchScope.fetchTags()) { QVector tagIds; QVector tags; //We don't take the fetch scope into account yet. It's either id only or the full tag. const bool fullTagsRequested = !mFetchScope.tagFetchScope().isEmpty(); while (tagQuery.isValid()) { PROF_INC(tagsCount) const qint64 id = tagQuery.value(TagQueryItemIdColumn).toLongLong(); if (id > pimItemId) { tagQuery.next(); continue; } else if (id < pimItemId) { break; } tagIds << tagQuery.value(TagQueryTagIdColumn).toLongLong(); tagQuery.next(); } tags.reserve(tagIds.count()); if (!fullTagsRequested) { for (qint64 tagId : qAsConst(tagIds)) { Protocol::FetchTagsResponse resp; resp.setId(tagId); tags << resp; } } else { for (qint64 tagId : qAsConst(tagIds)) { - tags << *HandlerHelper::fetchTagsResponse(Tag::retrieveById(tagId)); + tags.push_back(HandlerHelper::fetchTagsResponse(Tag::retrieveById(tagId))); } } - response->setTags(tags); + response.setTags(tags); } if (mFetchScope.fetchVirtualReferences()) { QVector vRefs; while (vRefQuery.isValid()) { PROF_INC(vRefsCount) const qint64 id = vRefQuery.value(VRefQueryItemIdColumn).toLongLong(); if (id > pimItemId) { vRefQuery.next(); continue; } else if (id < pimItemId) { break; } vRefs << vRefQuery.value(VRefQueryCollectionIdColumn).toLongLong(); vRefQuery.next(); } - response->setVirtualReferences(vRefs); + response.setVirtualReferences(vRefs); } if (mFetchScope.fetchRelations()) { SelectQueryBuilder qb; Query::Condition condition; condition.setSubQueryMode(Query::Or); condition.addValueCondition(Relation::leftIdFullColumnName(), Query::Equals, pimItemId); condition.addValueCondition(Relation::rightIdFullColumnName(), Query::Equals, pimItemId); qb.addCondition(condition); qb.addGroupColumns(QStringList() << Relation::leftIdColumn() << Relation::rightIdColumn() << Relation::typeIdColumn() << Relation::remoteIdColumn()); if (!qb.exec()) { throw HandlerException("Unable to list item relations"); } QVector relations; const auto result = qb.result(); relations.reserve(result.size()); for (const Relation &rel : result) { - relations << *HandlerHelper::fetchRelationsResponse(rel); + relations.push_back(HandlerHelper::fetchRelationsResponse(rel));; } - response->setRelations(relations); + response.setRelations(relations); } if (mFetchScope.ancestorDepth() != Protocol::ItemFetchScope::NoAncestor) { - response->setAncestors(ancestorsForItem(response->parentId())); + response.setAncestors(ancestorsForItem(response.parentId())); } bool skipItem = false; QVector cachedParts; QVector parts; while (partQuery.isValid()) { PROF_INC(partsCount) const qint64 id = partQuery.value(PartQueryPimIdColumn).toLongLong(); if (id > pimItemId) { partQuery.next(); continue; } else if (id < pimItemId) { break; } const qint64 partTypeId = partQuery.value(PartQueryTypeIdColumn).toLongLong(); auto ptIter = partTypeIdNameCache.find(partTypeId); if (ptIter == partTypeIdNameCache.end()) { ptIter = partTypeIdNameCache.insert(partTypeId, PartTypeHelper::fullName(PartType::retrieveById(partTypeId)).toUtf8()); } Protocol::PartMetaData metaPart; Protocol::StreamPayloadResponse partData; partData.setPayloadName(ptIter.value()); metaPart.setName(ptIter.value()); metaPart.setVersion(partQuery.value(PartQueryVersionColumn).toInt()); metaPart.setSize(partQuery.value(PartQueryDataSizeColumn).toLongLong()); const QByteArray data = Utils::variantToByteArray(partQuery.value(PartQueryDataColumn)); if (mFetchScope.checkCachedPayloadPartsOnly()) { if (!data.isEmpty()) { cachedParts << ptIter.value(); } partQuery.next(); } else { if (mFetchScope.ignoreErrors() && data.isEmpty()) { //We wanted the payload, couldn't get it, and are ignoring errors. Skip the item. //This is not an error though, it's fine to have empty payload parts (to denote existing but not cached parts) qCDebug(AKONADISERVER_LOG) << "item" << id << "has an empty payload part in parttable for part" << metaPart.name(); skipItem = true; break; } metaPart.setStorageType(static_cast( partQuery.value(PartQueryStorageColumn).toInt())); if (data.isEmpty()) { partData.setData(QByteArray("")); } else { partData.setData(data); } partData.setMetaData(metaPart); if (mFetchScope.requestedParts().contains(ptIter.value()) || mFetchScope.fullPayload() || mFetchScope.allAttributes()) { parts.append(partData); } partQuery.next(); } } - response->setParts(parts); + response.setParts(parts); if (skipItem) { itemQuery.next(); continue; } if (mFetchScope.checkCachedPayloadPartsOnly()) { - response->setCachedParts(cachedParts); + response.setCachedParts(cachedParts); } if (itemCallback) { itemCallback(std::move(response)); } else { mConnection->sendResponse(std::move(response)); } itemQuery.next(); } END_TIMER(processing) // update atime (only if the payload was actually requested, otherwise a simple resource sync prevents cache clearing) BEGIN_TIMER(aTime) if (needsAccessTimeUpdate(mFetchScope.requestedParts()) || mFetchScope.fullPayload()) { updateItemAccessTime(); } END_TIMER(aTime) END_TIMER(fetch) #if ENABLE_FETCH_PROFILING qCDebug(AKONADISERVER_LOG) << "FetchHelper execution stats:"; qCDebug(AKONADISERVER_LOG) << "\tItems query:" << itemsElapsed << "ms," << itemsCount << " items in total"; qCDebug(AKONADISERVER_LOG) << "\tFlags query:" << flagsElapsed << "ms, " << flagsCount << " flags in total"; qCDebug(AKONADISERVER_LOG) << "\tParts query:" << partsElapsed << "ms, " << partsCount << " parts in total"; qCDebug(AKONADISERVER_LOG) << "\tTags query: " << tagsElapsed << "ms, " << tagsCount << " tags in total"; qCDebug(AKONADISERVER_LOG) << "\tVRefs query:" << vRefsElapsed << "ms, " << vRefsCount << " vRefs in total"; qCDebug(AKONADISERVER_LOG) << "\t------------"; qCDebug(AKONADISERVER_LOG) << "\tItem retriever:" << itemRetrieverElapsed << "ms (scope local:" << scopeLocalElapsed << "ms)"; qCDebug(AKONADISERVER_LOG) << "\tTotal query:" << (itemsElapsed + flagsElapsed + partsElapsed + tagsElapsed + vRefsElapsed) << "ms"; qCDebug(AKONADISERVER_LOG) << "\tTotal processing: " << processingElapsed << "ms"; qCDebug(AKONADISERVER_LOG) << "\tATime update:" << aTimeElapsed << "ms"; qCDebug(AKONADISERVER_LOG) << "\t============"; qCDebug(AKONADISERVER_LOG) << "\tTotal FETCH:" << fetchElapsed << "ms"; qCDebug(AKONADISERVER_LOG); qCDebug(AKONADISERVER_LOG); #endif return true; } bool FetchHelper::needsAccessTimeUpdate(const QVector &parts) { // TODO technically we should compare the part list with the cache policy of // the parent collection of the retrieved items, but that's kinda expensive // Only updating the atime if the full payload was requested is a good // approximation though. return parts.contains(AKONADI_PARAM_PLD_RFC822); } void FetchHelper::updateItemAccessTime() { Transaction transaction(storageBackend(), QStringLiteral("update atime")); QueryBuilder qb(PimItem::tableName(), QueryBuilder::Update); qb.setColumnValue(PimItem::atimeColumn(), QDateTime::currentDateTimeUtc()); ItemQueryHelper::scopeToQuery(mScope, mContext, qb); if (!qb.exec()) { qCWarning(AKONADISERVER_LOG) << "Unable to update item access time"; } else { transaction.commit(); } } void FetchHelper::triggerOnDemandFetch() { if (mContext->collectionId() <= 0 || mFetchScope.cacheOnly()) { return; } Collection collection = mContext->collection(); // HACK: don't trigger on-demand syncing if the resource is the one triggering it if (mConnection->sessionId() == collection.resource().name().toLatin1()) { return; } storageBackend()->activeCachePolicy(collection); if (!collection.cachePolicySyncOnDemand()) { return; } if (AkonadiServer::instance()->intervalChecker()) { AkonadiServer::instance()->intervalChecker()->requestCollectionSync(collection); } } QVector FetchHelper::ancestorsForItem(Collection::Id parentColId) { if (mFetchScope.ancestorDepth() == Protocol::ItemFetchScope::NoAncestor || parentColId == 0) { return QVector(); } const auto it = mAncestorCache.constFind(parentColId); if (it != mAncestorCache.cend()) { return *it; } QVector ancestors; Collection col = Collection::retrieveById(parentColId); const int depthNum = mFetchScope.ancestorDepth() == Protocol::ItemFetchScope::ParentAncestor ? 1 : INT_MAX; for (int i = 0; i < depthNum; ++i) { if (!col.isValid()) { Protocol::Ancestor ancestor; ancestor.setId(0); ancestors << ancestor; break; } Protocol::Ancestor ancestor; ancestor.setId(col.id()); ancestor.setRemoteId(col.remoteId()); ancestors << ancestor; col = col.parent(); } mAncestorCache.insert(parentColId, ancestors); return ancestors; } QVariant FetchHelper::extractQueryResult(const QSqlQuery &query, FetchHelper::ItemQueryColumns column) const { const int colId = mItemQueryColumnMap[column]; Q_ASSERT(colId >= 0); return query.value(colId); } diff --git a/src/server/handler/fetchhelper.h b/src/server/handler/fetchhelper.h index 29d8a302f..04edf7cda 100644 --- a/src/server/handler/fetchhelper.h +++ b/src/server/handler/fetchhelper.h @@ -1,98 +1,98 @@ /*************************************************************************** * Copyright (C) 2006-2009 by Tobias Koenig * * * * This program 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 program 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 General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef AKONADI_FETCHHELPER_H #define AKONADI_FETCHHELPER_H #include "storage/countquerybuilder.h" #include "storage/datastore.h" #include "storage/itemretriever.h" #include "commandcontext.h" #include #include #include #include class FetchHelperTest; namespace Akonadi { namespace Server { class AggregatedItemFetchScope; class Connection; class FetchHelper { public: FetchHelper(Connection *connection, const Scope &scope, const Protocol::ItemFetchScope &fetchScope); FetchHelper(Connection *connection, CommandContext *context, const Scope &scope, const Protocol::ItemFetchScope &fetchScope); - bool fetchItems(std::function &&callback = {}); + bool fetchItems(std::function &&callback = {}); private: enum ItemQueryColumns { ItemQueryPimItemIdColumn, ItemQueryPimItemRidColumn, ItemQueryMimeTypeIdColumn, ItemQueryRevColumn, ItemQueryRemoteRevisionColumn, ItemQuerySizeColumn, ItemQueryDatetimeColumn, ItemQueryCollectionIdColumn, ItemQueryPimItemGidColumn, ItemQueryColumnCount }; void updateItemAccessTime(); void triggerOnDemandFetch(); QSqlQuery buildItemQuery(); QSqlQuery buildPartQuery(const QVector &partList, bool allPayload, bool allAttrs); QSqlQuery buildFlagQuery(); QSqlQuery buildTagQuery(); QSqlQuery buildVRefQuery(); QVector ancestorsForItem(Collection::Id parentColId); static bool needsAccessTimeUpdate(const QVector &parts); QVariant extractQueryResult(const QSqlQuery &query, ItemQueryColumns column) const; bool isScopeLocal(const Scope &scope); DataStore *storageBackend() const; static QByteArray tagsToByteArray(const Tag::List &tags); static QByteArray relationsToByteArray(const Relation::List &relations); private: Connection *mConnection = nullptr; CommandContext *mContext = nullptr; QHash> mAncestorCache; Scope mScope; Protocol::ItemFetchScope mFetchScope; int mItemQueryColumnMap[ItemQueryColumnCount]; friend class ::FetchHelperTest; }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/handler/status.cpp b/src/server/handler/status.cpp index 762e98bb2..06622009e 100644 --- a/src/server/handler/status.cpp +++ b/src/server/handler/status.cpp @@ -1,52 +1,52 @@ /*************************************************************************** * Copyright (C) 2006 by Ingo Kloecker * * * * This program 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 program 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 General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "status.h" #include "connection.h" #include "handlerhelper.h" #include "storage/datastore.h" #include "storage/countquerybuilder.h" #include "storage/collectionstatistics.h" #include using namespace Akonadi; using namespace Akonadi::Server; bool Status::parseStream() { const auto &cmd = Protocol::cmdCast(m_command); const Collection col = HandlerHelper::collectionFromScope(cmd.collection(), connection()); if (!col.isValid()) { return failureResponse(QStringLiteral("No status for this folder")); } const CollectionStatistics::Statistics stats = CollectionStatistics::self()->statistics(col); if (stats.count == -1) { return failureResponse(QStringLiteral("Failed to query statistics.")); } - auto resp = Protocol::FetchCollectionStatsResponsePtr::create(); - resp->setCount(stats.count); - resp->setUnseen(stats.count - stats.read); - resp->setSize(stats.size); - return successResponse(resp); + Protocol::FetchCollectionStatsResponse resp; + resp.setCount(stats.count); + resp.setUnseen(stats.count - stats.read); + resp.setSize(stats.size); + return successResponse(std::move(resp)); } diff --git a/src/server/handler/store.cpp b/src/server/handler/store.cpp index b407f61cd..db981ee64 100644 --- a/src/server/handler/store.cpp +++ b/src/server/handler/store.cpp @@ -1,385 +1,385 @@ /*************************************************************************** * Copyright (C) 2006 by Tobias Koenig * * * * This program 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 program 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 General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "store.h" #include "connection.h" #include "handlerhelper.h" #include "storage/datastore.h" #include "storage/transaction.h" #include "storage/itemqueryhelper.h" #include "storage/selectquerybuilder.h" #include "storage/parthelper.h" #include "storage/dbconfig.h" #include "storage/itemretriever.h" #include "storage/parttypehelper.h" #include "storage/partstreamer.h" #include #include "akonadiserver_debug.h" #include #include using namespace Akonadi; using namespace Akonadi::Server; static bool payloadChanged(const QSet &changes) { for (const QByteArray &change : changes) { if (change.startsWith(AKONADI_PARAM_PLD)) { return true; } } return false; } bool Store::replaceFlags(const PimItem::List &item, const QSet &flags, bool &flagsChanged) { Flag::List flagList = HandlerHelper::resolveFlags(flags); DataStore *store = connection()->storageBackend(); if (!store->setItemsFlags(item, flagList, &flagsChanged)) { qCDebug(AKONADISERVER_LOG) << "Store::replaceFlags: Unable to replace flags"; return false; } return true; } bool Store::addFlags(const PimItem::List &items, const QSet &flags, bool &flagsChanged) { const Flag::List flagList = HandlerHelper::resolveFlags(flags); DataStore *store = connection()->storageBackend(); if (!store->appendItemsFlags(items, flagList, &flagsChanged)) { qCDebug(AKONADISERVER_LOG) << "Store::addFlags: Unable to add new item flags"; return false; } return true; } bool Store::deleteFlags(const PimItem::List &items, const QSet &flags, bool &flagsChanged) { DataStore *store = connection()->storageBackend(); QVector flagList; flagList.reserve(flags.size()); for (auto iter = flags.cbegin(), end = flags.cend(); iter != end; ++iter) { Flag flag = Flag::retrieveByName(QString::fromUtf8(*iter)); if (!flag.isValid()) { continue; } flagList.append(flag); } if (!store->removeItemsFlags(items, flagList, &flagsChanged)) { qCDebug(AKONADISERVER_LOG) << "Store::deleteFlags: Unable to remove item flags"; return false; } return true; } bool Store::replaceTags(const PimItem::List &item, const Scope &tags, bool &tagsChanged) { const Tag::List tagList = HandlerHelper::tagsFromScope(tags, connection()); if (!connection()->storageBackend()->setItemsTags(item, tagList, &tagsChanged)) { qCDebug(AKONADISERVER_LOG) << "Store::replaceTags: unable to replace tags"; return false; } return true; } bool Store::addTags(const PimItem::List &items, const Scope &tags, bool &tagsChanged) { const Tag::List tagList = HandlerHelper::tagsFromScope(tags, connection()); if (!connection()->storageBackend()->appendItemsTags(items, tagList, &tagsChanged)) { qCDebug(AKONADISERVER_LOG) << "Store::addTags: Unable to add new item tags"; return false; } return true; } bool Store::deleteTags(const PimItem::List &items, const Scope &tags, bool &tagsChanged) { const Tag::List tagList = HandlerHelper::tagsFromScope(tags, connection()); if (!connection()->storageBackend()->removeItemsTags(items, tagList, &tagsChanged)) { qCDebug(AKONADISERVER_LOG) << "Store::deleteTags: Unable to remove item tags"; return false; } return true; } bool Store::parseStream() { const auto &cmd = Protocol::cmdCast(m_command); //parseCommand(); DataStore *store = connection()->storageBackend(); Transaction transaction(store, QStringLiteral("STORE")); ExternalPartStorageTransaction storageTrx; // Set the same modification time for each item. QDateTime modificationtime = QDateTime::currentDateTimeUtc(); if (DbType::type(store->database()) != DbType::Sqlite) { // Remove milliseconds from the modificationtime. PSQL and MySQL don't // support milliseconds in DATETIME column, so FETCHed Items will report // time without milliseconds, while this command would return answer // with milliseconds modificationtime = modificationtime.addMSecs(-modificationtime.time().msec()); } // retrieve selected items SelectQueryBuilder qb; ItemQueryHelper::scopeToQuery(cmd.items(), connection()->context(), qb); if (!qb.exec()) { return failureResponse("Unable to retrieve items"); } PimItem::List pimItems = qb.result(); if (pimItems.isEmpty()) { return failureResponse("No items found"); } for (int i = 0; i < pimItems.size(); ++i) { if (cmd.oldRevision() > -1) { // check for conflicts if a resources tries to overwrite an item with dirty payload const PimItem &pimItem = pimItems.at(i); if (connection()->isOwnerResource(pimItem)) { if (pimItem.dirty()) { const QString error = QStringLiteral("[LRCONFLICT] Resource %1 tries to modify item %2 (%3) (in collection %4) with dirty payload, aborting STORE."); return failureResponse( error.arg(pimItem.collection().resource().name()) .arg(pimItem.id()) .arg(pimItem.remoteId()).arg(pimItem.collectionId())); } } // check and update revisions if (pimItems.at(i).rev() != (int) cmd.oldRevision()) { const QString error = QStringLiteral("[LLCONFLICT] Resource %1 tries to modify item %2 (%3) (in collection %4) with revision %5; the item was modified elsewhere and has revision %6, aborting STORE."); return failureResponse(error.arg(pimItem.collection().resource().name()) .arg(pimItem.id()) .arg(pimItem.remoteId()).arg(pimItem.collectionId()) .arg(cmd.oldRevision()).arg(pimItems.at(i).rev())); } } } PimItem &item = pimItems.first(); QSet changes; qint64 partSizes = 0; qint64 size = 0; bool flagsChanged = false; bool tagsChanged = false; if (cmd.modifiedParts() & Protocol::ModifyItemsCommand::AddedFlags) { if (!addFlags(pimItems, cmd.addedFlags(), flagsChanged)) { return failureResponse("Unable to add item flags"); } } if (cmd.modifiedParts() & Protocol::ModifyItemsCommand::RemovedFlags) { if (!deleteFlags(pimItems, cmd.removedFlags(), flagsChanged)) { return failureResponse("Unable to remove item flags"); } } if (cmd.modifiedParts() & Protocol::ModifyItemsCommand::Flags) { if (!replaceFlags(pimItems, cmd.flags(), flagsChanged)) { return failureResponse("Unable to reset flags"); } } if (flagsChanged) { changes << AKONADI_PARAM_FLAGS; } if (cmd.modifiedParts() & Protocol::ModifyItemsCommand::AddedTags) { if (!addTags(pimItems, cmd.addedTags(), tagsChanged)) { return failureResponse("Unable to add item tags"); } } if (cmd.modifiedParts() & Protocol::ModifyItemsCommand::RemovedTags) { if (!deleteTags(pimItems, cmd.removedTags(), tagsChanged)) { return failureResponse("Unabel to remove item tags"); } } if (cmd.modifiedParts() & Protocol::ModifyItemsCommand::Tags) { if (!replaceTags(pimItems, cmd.tags(), tagsChanged)) { return failureResponse("Unable to reset item tags"); } } if (tagsChanged) { changes << AKONADI_PARAM_TAGS; } if (item.isValid() && cmd.modifiedParts() & Protocol::ModifyItemsCommand::RemoteID) { if (item.remoteId() != cmd.remoteId()) { if (!connection()->isOwnerResource(item)) { return failureResponse("Only resources can modify remote identifiers"); } item.setRemoteId(cmd.remoteId()); changes << AKONADI_PARAM_REMOTEID; } } if (item.isValid() && cmd.modifiedParts() & Protocol::ModifyItemsCommand::GID) { if (item.gid() != cmd.gid()) { item.setGid(cmd.gid()); } changes << AKONADI_PARAM_GID; } if (item.isValid() && cmd.modifiedParts() & Protocol::ModifyItemsCommand::RemoteRevision) { if (item.remoteRevision() != cmd.remoteRevision()) { if (!connection()->isOwnerResource(item)) { return failureResponse("Only resources can modify remote revisions"); } item.setRemoteRevision(cmd.remoteRevision()); changes << AKONADI_PARAM_REMOTEREVISION; } } if (item.isValid() && !cmd.dirty()) { item.setDirty(false); } if (item.isValid() && cmd.modifiedParts() & Protocol::ModifyItemsCommand::Size) { size = cmd.itemSize(); changes << AKONADI_PARAM_SIZE; } if (item.isValid() && cmd.modifiedParts() & Protocol::ModifyItemsCommand::RemovedParts) { if (!cmd.removedParts().isEmpty()) { if (!store->removeItemParts(item, cmd.removedParts())) { return failureResponse("Unable to remove item parts"); } Q_FOREACH (const QByteArray &part, cmd.removedParts()) { changes.insert(part); } } } if (item.isValid() && cmd.modifiedParts() & Protocol::ModifyItemsCommand::Parts) { PartStreamer streamer(connection(), item); Q_FOREACH (const QByteArray &partName, cmd.parts()) { qint64 partSize = 0; if (!streamer.stream(true, partName, partSize)) { return failureResponse(streamer.error()); } changes.insert(partName); partSizes += partSize; } } if (item.isValid() && cmd.modifiedParts() & Protocol::ModifyItemsCommand::Attributes) { PartStreamer streamer(connection(), item); const Protocol::Attributes attrs = cmd.attributes(); for (auto iter = attrs.cbegin(), end = attrs.cend(); iter != end; ++iter) { bool changed = false; if (!streamer.streamAttribute(true, iter.key(), iter.value(), &changed)) { return failureResponse(streamer.error()); } if (changed) { changes.insert(iter.key()); } } } QDateTime datetime; if (!changes.isEmpty() || cmd.invalidateCache() || !cmd.dirty()) { // update item size if (pimItems.size() == 1 && (size > 0 || partSizes > 0)) { pimItems.first().setSize(qMax(size, partSizes)); } const bool onlyRemoteIdChanged = (changes.size() == 1 && changes.contains(AKONADI_PARAM_REMOTEID)); const bool onlyRemoteRevisionChanged = (changes.size() == 1 && changes.contains(AKONADI_PARAM_REMOTEREVISION)); const bool onlyRemoteIdAndRevisionChanged = (changes.size() == 2 && changes.contains(AKONADI_PARAM_REMOTEID) && changes.contains(AKONADI_PARAM_REMOTEREVISION)); const bool onlyFlagsChanged = (changes.size() == 1 && changes.contains(AKONADI_PARAM_FLAGS)); const bool onlyGIDChanged = (changes.size() == 1 && changes.contains(AKONADI_PARAM_GID)); // If only the remote id and/or the remote revision changed, we don't have to increase the REV, // because these updates do not change the payload and can only be done by the owning resource -> no conflicts possible const bool revisionNeedsUpdate = (!changes.isEmpty() && !onlyRemoteIdChanged && !onlyRemoteRevisionChanged && !onlyRemoteIdAndRevisionChanged && !onlyGIDChanged); // run update query and prepare change notifications for (int i = 0; i < pimItems.count(); ++i) { if (revisionNeedsUpdate) { pimItems[i].setRev(pimItems[i].rev() + 1); } PimItem &item = pimItems[i]; item.setDatetime(modificationtime); item.setAtime(modificationtime); if (!connection()->isOwnerResource(item) && payloadChanged(changes)) { item.setDirty(true); } if (!item.update()) { return failureResponse("Unable to write item changes into the database"); } if (cmd.invalidateCache()) { if (!store->invalidateItemCache(item)) { return failureResponse("Unable to invalidate item cache in the database"); } } // flags change notification went separatly during command parsing // GID-only changes are ignored to prevent resources from updating their storage when no actual change happened if (cmd.notify() && !changes.isEmpty() && !onlyFlagsChanged && !onlyGIDChanged) { // Don't send FLAGS notification in itemChanged changes.remove(AKONADI_PARAM_FLAGS); store->notificationCollector()->itemChanged(item, changes); } if (!cmd.noResponse()) { - auto resp = Protocol::ModifyItemsResponsePtr::create(); - resp->setId(item.id()); - resp->setNewRevision(item.rev()); - sendResponse(resp); + Protocol::ModifyItemsResponse resp; + resp.setId(item.id()); + resp.setNewRevision(item.rev()); + sendResponse(std::move(resp)); } } if (!transaction.commit()) { return failureResponse("Cannot commit transaction."); } // Always commit storage changes (deletion) after DB transaction storageTrx.commit(); datetime = modificationtime; } else { datetime = pimItems.first().datetime(); } - auto resp = Protocol::ModifyItemsResponsePtr::create(); - resp->setModificationDateTime(datetime); - return successResponse(resp); + Protocol::ModifyItemsResponse resp; + resp.setModificationDateTime(datetime); + return successResponse(std::move(resp)); } diff --git a/src/server/handler/tagfetchhelper.cpp b/src/server/handler/tagfetchhelper.cpp index a50798dad..7760dd997 100644 --- a/src/server/handler/tagfetchhelper.cpp +++ b/src/server/handler/tagfetchhelper.cpp @@ -1,158 +1,158 @@ /* Copyright (c) 2014 Daniel Vrátil 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 "tagfetchhelper.h" #include "handler.h" #include "connection.h" #include "utils.h" #include "storage/querybuilder.h" #include "storage/tagqueryhelper.h" using namespace Akonadi; using namespace Akonadi::Server; TagFetchHelper::TagFetchHelper(Connection *connection, const Scope &scope) : mConnection(connection) , mScope(scope) { } QSqlQuery TagFetchHelper::buildAttributeQuery() const { QueryBuilder qb(TagAttribute::tableName()); qb.addColumn(TagAttribute::tagIdFullColumnName()); qb.addColumn(TagAttribute::typeFullColumnName()); qb.addColumn(TagAttribute::valueFullColumnName()); qb.addSortColumn(TagAttribute::tagIdFullColumnName(), Query::Descending); qb.addJoin(QueryBuilder::InnerJoin, Tag::tableName(), TagAttribute::tagIdFullColumnName(), Tag::idFullColumnName()); TagQueryHelper::scopeToQuery(mScope, mConnection->context(), qb); if (!qb.exec()) { throw HandlerException("Unable to list tag attributes"); } qb.query().next(); return qb.query(); } QSqlQuery TagFetchHelper::buildAttributeQuery(qint64 id) { QueryBuilder qb(TagAttribute::tableName()); qb.addColumn(TagAttribute::tagIdColumn()); qb.addColumn(TagAttribute::typeColumn()); qb.addColumn(TagAttribute::valueColumn()); qb.addSortColumn(TagAttribute::tagIdColumn(), Query::Descending); qb.addValueCondition(TagAttribute::tagIdColumn(), Query::Equals, id); if (!qb.exec()) { throw HandlerException("Unable to list tag attributes"); } qb.query().next(); return qb.query(); } QSqlQuery TagFetchHelper::buildTagQuery() { QueryBuilder qb(Tag::tableName()); qb.addColumn(Tag::idFullColumnName()); qb.addColumn(Tag::gidFullColumnName()); qb.addColumn(Tag::parentIdFullColumnName()); qb.addJoin(QueryBuilder::InnerJoin, TagType::tableName(), Tag::typeIdFullColumnName(), TagType::idFullColumnName()); qb.addColumn(TagType::nameFullColumnName()); // Expose tag's remote ID only to resources if (mConnection->context()->resource().isValid()) { qb.addColumn(TagRemoteIdResourceRelation::remoteIdFullColumnName()); Query::Condition joinCondition; joinCondition.addValueCondition(TagRemoteIdResourceRelation::resourceIdFullColumnName(), Query::Equals, mConnection->context()->resource().id()); joinCondition.addColumnCondition(TagRemoteIdResourceRelation::tagIdFullColumnName(), Query::Equals, Tag::idFullColumnName()); qb.addJoin(QueryBuilder::LeftJoin, TagRemoteIdResourceRelation::tableName(), joinCondition); } qb.addSortColumn(Tag::idFullColumnName(), Query::Descending); TagQueryHelper::scopeToQuery(mScope, mConnection->context(), qb); if (!qb.exec()) { throw HandlerException("Unable to list tags"); } qb.query().next(); return qb.query(); } QMap TagFetchHelper::fetchTagAttributes(qint64 tagId) { QMap attributes; QSqlQuery attributeQuery = buildAttributeQuery(tagId); while (attributeQuery.isValid()) { attributes.insert(Utils::variantToByteArray(attributeQuery.value(1)), Utils::variantToByteArray(attributeQuery.value(2))); attributeQuery.next(); } return attributes; } bool TagFetchHelper::fetchTags() { QSqlQuery tagQuery = buildTagQuery(); QSqlQuery attributeQuery = buildAttributeQuery(); while (tagQuery.isValid()) { const qint64 tagId = tagQuery.value(0).toLongLong(); - auto response = Protocol::FetchTagsResponsePtr::create(); - response->setId(tagId); - response->setGid(Utils::variantToByteArray(tagQuery.value(1))); - response->setParentId(tagQuery.value(2).toLongLong()); - response->setType(Utils::variantToByteArray(tagQuery.value(3))); + Protocol::FetchTagsResponse response; + response.setId(tagId); + response.setGid(Utils::variantToByteArray(tagQuery.value(1))); + response.setParentId(tagQuery.value(2).toLongLong()); + response.setType(Utils::variantToByteArray(tagQuery.value(3))); if (mConnection->context()->resource().isValid()) { - response->setRemoteId(Utils::variantToByteArray(tagQuery.value(4))); + response.setRemoteId(Utils::variantToByteArray(tagQuery.value(4))); } QMap tagAttributes; while (attributeQuery.isValid()) { const qint64 id = attributeQuery.value(0).toLongLong(); if (id > tagId) { attributeQuery.next(); continue; } else if (id < tagId) { break; } tagAttributes.insert(Utils::variantToByteArray(attributeQuery.value(1)), Utils::variantToByteArray(attributeQuery.value(2))); attributeQuery.next(); } - response->setAttributes(tagAttributes); + response.setAttributes(tagAttributes); - mConnection->sendResponse(response); + mConnection->sendResponse(std::move(response)); tagQuery.next(); } return true; } diff --git a/src/server/handlerhelper.cpp b/src/server/handlerhelper.cpp index a6dcab6d5..2a542c6a5 100644 --- a/src/server/handlerhelper.cpp +++ b/src/server/handlerhelper.cpp @@ -1,419 +1,419 @@ /*************************************************************************** * Copyright (C) 2006 by Tobias Koenig * * * * This program 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 program 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 General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "handlerhelper.h" #include "storage/countquerybuilder.h" #include "storage/datastore.h" #include "storage/selectquerybuilder.h" #include "storage/collectionstatistics.h" #include "storage/queryhelper.h" #include "storage/collectionqueryhelper.h" #include "commandcontext.h" #include "handler.h" #include "connection.h" #include "utils.h" #include #include #include using namespace Akonadi; using namespace Akonadi::Server; Collection HandlerHelper::collectionFromIdOrName(const QByteArray &id) { // id is a number bool ok = false; qint64 collectionId = id.toLongLong(&ok); if (ok) { return Collection::retrieveById(collectionId); } // id is a path QString path = QString::fromUtf8(id); // ### should be UTF-7 for real IMAP compatibility const QStringList pathParts = path.split(QLatin1Char('/'), QString::SkipEmptyParts); Collection col; for (const QString &part : pathParts) { SelectQueryBuilder qb; qb.addValueCondition(Collection::nameColumn(), Query::Equals, part); if (col.isValid()) { qb.addValueCondition(Collection::parentIdColumn(), Query::Equals, col.id()); } else { qb.addValueCondition(Collection::parentIdColumn(), Query::Is, QVariant()); } if (!qb.exec()) { return Collection(); } Collection::List list = qb.result(); if (list.count() != 1) { return Collection(); } col = list.first(); } return col; } QString HandlerHelper::pathForCollection(const Collection &col) { QStringList parts; Collection current = col; while (current.isValid()) { parts.prepend(current.name()); current = current.parent(); } return parts.join(QLatin1Char('/')); } Protocol::CachePolicy HandlerHelper::cachePolicyResponse(const Collection &col) { Protocol::CachePolicy cachePolicy; cachePolicy.setInherit(col.cachePolicyInherit()); cachePolicy.setCacheTimeout(col.cachePolicyCacheTimeout()); cachePolicy.setCheckInterval(col.cachePolicyCheckInterval()); if (!col.cachePolicyLocalParts().isEmpty()) { cachePolicy.setLocalParts(col.cachePolicyLocalParts().split(QLatin1Char(' '))); } cachePolicy.setSyncOnDemand(col.cachePolicySyncOnDemand()); return cachePolicy; } -Protocol::FetchCollectionsResponsePtr HandlerHelper::fetchCollectionsResponse(const Collection &col) +Protocol::FetchCollectionsResponse HandlerHelper::fetchCollectionsResponse(const Collection &col) { QStringList mimeTypes; mimeTypes.reserve(col.mimeTypes().size()); Q_FOREACH (const MimeType &mt, col.mimeTypes()) { mimeTypes << mt.name(); } return fetchCollectionsResponse(col, col.attributes(), false, 0, QStack(), QStack(), false, mimeTypes); } -Protocol::FetchCollectionsResponsePtr HandlerHelper::fetchCollectionsResponse(const Collection &col, +Protocol::FetchCollectionsResponse HandlerHelper::fetchCollectionsResponse(const Collection &col, const CollectionAttribute::List &attrs, bool includeStatistics, int ancestorDepth, const QStack &ancestors, const QStack &ancestorAttributes, bool isReferenced, const QStringList &mimeTypes) { - auto response = Protocol::FetchCollectionsResponsePtr::create(); - response->setId(col.id()); - response->setParentId(col.parentId()); - response->setName(col.name()); - response->setMimeTypes(mimeTypes); - response->setRemoteId(col.remoteId()); - response->setRemoteRevision(col.remoteRevision()); - response->setResource(col.resource().name()); - response->setIsVirtual(col.isVirtual()); + Protocol::FetchCollectionsResponse response; + response.setId(col.id()); + response.setParentId(col.parentId()); + response.setName(col.name()); + response.setMimeTypes(mimeTypes); + response.setRemoteId(col.remoteId()); + response.setRemoteRevision(col.remoteRevision()); + response.setResource(col.resource().name()); + response.setIsVirtual(col.isVirtual()); if (includeStatistics) { const CollectionStatistics::Statistics stats = CollectionStatistics::self()->statistics(col); if (stats.count > -1) { Protocol::FetchCollectionStatsResponse statsResponse; statsResponse.setCount(stats.count); statsResponse.setUnseen(stats.count - stats.read); statsResponse.setSize(stats.size); - response->setStatistics(statsResponse); + response.setStatistics(statsResponse); } } if (!col.queryString().isEmpty()) { - response->setSearchQuery(col.queryString()); + response.setSearchQuery(col.queryString()); QVector searchCols; const QStringList searchColIds = col.queryCollections().split(QLatin1Char(' ')); searchCols.reserve(searchColIds.size()); for (const QString &searchColId : searchColIds) { searchCols << searchColId.toLongLong(); } - response->setSearchCollections(searchCols); + response.setSearchCollections(searchCols); } Protocol::CachePolicy cachePolicy = cachePolicyResponse(col); - response->setCachePolicy(cachePolicy); + response.setCachePolicy(cachePolicy); if (ancestorDepth) { QVector ancestorList = HandlerHelper::ancestorsResponse(ancestorDepth, ancestors, ancestorAttributes); - response->setAncestors(ancestorList); + response.setAncestors(ancestorList); } - response->setReferenced(isReferenced); - response->setEnabled(col.enabled()); - response->setDisplayPref(static_cast(col.displayPref())); - response->setSyncPref(static_cast(col.syncPref())); - response->setIndexPref(static_cast(col.indexPref())); + response.setReferenced(isReferenced); + response.setEnabled(col.enabled()); + response.setDisplayPref(static_cast(col.displayPref())); + response.setSyncPref(static_cast(col.syncPref())); + response.setIndexPref(static_cast(col.indexPref())); QMap ra; for (const CollectionAttribute &attr : attrs) { ra.insert(attr.type(), attr.value()); } - response->setAttributes(ra); + response.setAttributes(ra); return response; } QVector HandlerHelper::ancestorsResponse(int ancestorDepth, const QStack &_ancestors, const QStack &_ancestorsAttributes) { QVector rv; if (ancestorDepth > 0) { QStack ancestors(_ancestors); QStack ancestorAttributes(_ancestorsAttributes); for (int i = 0; i < ancestorDepth; ++i) { if (ancestors.isEmpty()) { Protocol::Ancestor ancestor; ancestor.setId(0); rv << ancestor; break; } const Collection c = ancestors.pop(); Protocol::Ancestor a; a.setId(c.id()); a.setRemoteId(c.remoteId()); a.setName(c.name()); if (!ancestorAttributes.isEmpty()) { QMap attrs; Q_FOREACH (const CollectionAttribute &attr, ancestorAttributes.pop()) { attrs.insert(attr.type(), attr.value()); } a.setAttributes(attrs); } rv << a; } } return rv; } -Protocol::FetchTagsResponsePtr HandlerHelper::fetchTagsResponse(const Tag &tag, +Protocol::FetchTagsResponse HandlerHelper::fetchTagsResponse(const Tag &tag, bool withRID, Connection *connection) { - auto response = Protocol::FetchTagsResponsePtr::create(); - response->setId(tag.id()); - response->setType(tag.tagType().name().toUtf8()); - response->setParentId(tag.parentId()); - response->setGid(tag.gid().toUtf8()); + Protocol::FetchTagsResponse response; + response.setId(tag.id()); + response.setType(tag.tagType().name().toUtf8()); + response.setParentId(tag.parentId()); + response.setGid(tag.gid().toUtf8()); if (withRID && connection) { // Fail silently if retrieving tag RID is not allowed in current context if (!connection->context()->resource().isValid()) { return response; } QueryBuilder qb(TagRemoteIdResourceRelation::tableName()); qb.addColumn(TagRemoteIdResourceRelation::remoteIdColumn()); qb.addValueCondition(TagRemoteIdResourceRelation::resourceIdColumn(), Query::Equals, connection->context()->resource().id()); qb.addValueCondition(TagRemoteIdResourceRelation::tagIdColumn(), Query::Equals, tag.id()); if (!qb.exec()) { throw HandlerException("Unable to query Tag Remote ID"); } QSqlQuery query = qb.query(); // No RID for this tag if (!query.next()) { return response; } - response->setRemoteId(Utils::variantToByteArray(query.value(0))); + response.setRemoteId(Utils::variantToByteArray(query.value(0))); } return response; } -Protocol::FetchRelationsResponsePtr HandlerHelper::fetchRelationsResponse(const Relation &relation) +Protocol::FetchRelationsResponse HandlerHelper::fetchRelationsResponse(const Relation &relation) { - auto resp = Protocol::FetchRelationsResponsePtr::create(); - resp->setLeft(relation.leftId()); - resp->setLeftMimeType(relation.left().mimeType().name().toUtf8()); - resp->setRight(relation.rightId()); - resp->setRightMimeType(relation.right().mimeType().name().toUtf8()); - resp->setType(relation.relationType().name().toUtf8()); + Protocol::FetchRelationsResponse resp; + resp.setLeft(relation.leftId()); + resp.setLeftMimeType(relation.left().mimeType().name().toUtf8()); + resp.setRight(relation.rightId()); + resp.setRightMimeType(relation.right().mimeType().name().toUtf8()); + resp.setType(relation.relationType().name().toUtf8()); return resp; } Flag::List HandlerHelper::resolveFlags(const QSet &flagNames) { Flag::List flagList; flagList.reserve(flagNames.size()); for (const QByteArray &flagName : flagNames) { const Flag flag = Flag::retrieveByNameOrCreate(QString::fromUtf8(flagName)); if (!flag.isValid()) { throw HandlerException("Unable to create flag"); } flagList.append(flag); } return flagList; } Tag::List HandlerHelper::resolveTagsByUID(const ImapSet &tags) { if (tags.isEmpty()) { return Tag::List(); } SelectQueryBuilder qb; QueryHelper::setToQuery(tags, Tag::idFullColumnName(), qb); if (!qb.exec()) { throw HandlerException("Unable to resolve tags"); } const Tag::List result = qb.result(); if (result.isEmpty()) { throw HandlerException("No tags found"); } return result; } Tag::List HandlerHelper::resolveTagsByGID(const QStringList &tagsGIDs) { Tag::List tagList; if (tagsGIDs.isEmpty()) { return tagList; } for (const QString &tagGID : tagsGIDs) { Tag::List tags = Tag::retrieveFiltered(Tag::gidColumn(), tagGID); Tag tag; if (tags.isEmpty()) { tag.setGid(tagGID); tag.setParentId(0); const TagType type = TagType::retrieveByNameOrCreate(QStringLiteral("PLAIN")); if (!type.isValid()) { throw HandlerException("Unable to create tag type"); } tag.setTagType(type); if (!tag.insert()) { throw HandlerException("Unable to create tag"); } } else if (tags.count() == 1) { tag = tags[0]; } else { // Should not happen throw HandlerException("Tag GID is not unique"); } tagList.append(tag); } return tagList; } Tag::List HandlerHelper::resolveTagsByRID(const QStringList &tagsRIDs, CommandContext *context) { Tag::List tags; if (tagsRIDs.isEmpty()) { return tags; } if (!context->resource().isValid()) { throw HandlerException("Tags can be resolved by their RID only in resource context"); } tags.reserve(tagsRIDs.size()); for (const QString &tagRID : tagsRIDs) { SelectQueryBuilder qb; Query::Condition cond; cond.addColumnCondition(Tag::idFullColumnName(), Query::Equals, TagRemoteIdResourceRelation::tagIdFullColumnName()); cond.addValueCondition(TagRemoteIdResourceRelation::resourceIdFullColumnName(), Query::Equals, context->resource().id()); qb.addJoin(QueryBuilder::LeftJoin, TagRemoteIdResourceRelation::tableName(), cond); if (DbType::type(DataStore::self()->database()) == DbType::Sqlite) { // FIXME: Workaround for an SQLite issue where "WHERE remoteId = 'rid-with-hyphen'" does // not match any rows, even if such row exists. This only seems to affect this table qb.addValueCondition(TagRemoteIdResourceRelation::remoteIdFullColumnName(), Query::Like, tagRID); } else { qb.addValueCondition(TagRemoteIdResourceRelation::remoteIdFullColumnName(), Query::Equals, tagRID); } if (!qb.exec()) { throw HandlerException("Unable to resolve tags"); } Tag tag; Tag::List results = qb.result(); if (results.isEmpty()) { // If the tag does not exist, we create a new one with GID matching RID Tag::List tags = resolveTagsByGID(QStringList() << tagRID); if (tags.count() != 1) { throw HandlerException("Unable to resolve tag"); } tag = tags[0]; TagRemoteIdResourceRelation rel; rel.setRemoteId(tagRID); rel.setTagId(tag.id()); rel.setResourceId(context->resource().id()); if (!rel.insert()) { throw HandlerException("Unable to create tag"); } } else if (results.count() == 1) { tag = results[0]; } else { throw HandlerException("Tag RID is not unique within this resource context"); } tags.append(tag); } return tags; } Collection HandlerHelper::collectionFromScope(const Scope &scope, Connection *connection) { if (scope.scope() == Scope::Invalid || scope.scope() == Scope::Gid) { throw HandlerException("Invalid collection scope"); } SelectQueryBuilder qb; CollectionQueryHelper::scopeToQuery(scope, connection, qb); if (!qb.exec()) { throw HandlerException("Failed to execute SQL query"); } const Collection::List c = qb.result(); if (c.isEmpty()) { return Collection(); } else if (c.count() == 1) { return c.at(0); } else { throw HandlerException("Query returned more than one reslut"); } } Tag::List HandlerHelper::tagsFromScope(const Scope &scope, Connection *connection) { if (scope.scope() == Scope::Invalid || scope.scope() == Scope::HierarchicalRid) { throw HandlerException("Invalid tag scope"); } if (scope.scope() == Scope::Uid) { return resolveTagsByUID(scope.uidSet()); } else if (scope.scope() == Scope::Gid) { return resolveTagsByGID(scope.gidSet()); } else if (scope.scope() == Scope::Rid) { return resolveTagsByRID(scope.ridSet(), connection->context()); } Q_ASSERT(false); return Tag::List(); } diff --git a/src/server/handlerhelper.h b/src/server/handlerhelper.h index cec19784f..77f7f25e0 100644 --- a/src/server/handlerhelper.h +++ b/src/server/handlerhelper.h @@ -1,133 +1,132 @@ /*************************************************************************** * Copyright (C) 2006 by Tobias Koenig * * * * This program 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 program 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 General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef AKONADIHANDLERHELPER_H #define AKONADIHANDLERHELPER_H #include "entities.h" #include #include #include namespace Akonadi { class Scope; class ImapSet; namespace Protocol { class Ancestor; class CachePolicy; class FetchCollectionsResponse; -using FetchCollectionsResponsePtr = QSharedPointer; class FetchTagsResponse; using FetchTagsResponsePtr = QSharedPointer; class FetchRelationsResponse; using FetchRelationsResponsePtr = QSharedPointer; } namespace Server { class CommandContext; class Connection; /** Helper functions for command handlers. */ class HandlerHelper { public: /** Returns the collection identified by the given id or path. */ static Collection collectionFromIdOrName(const QByteArray &id); /** Returns the full path for the given collection. */ static QString pathForCollection(const Collection &col); /** Returns the protocol representation of the cache policy of the given Collection object. */ static Protocol::CachePolicy cachePolicyResponse(const Collection &col); /** Returns the protocol representation of the given collection. Make sure DataStore::activeCachePolicy() has been called before to include the effective cache policy */ - static Protocol::FetchCollectionsResponsePtr fetchCollectionsResponse(const Collection &col); + static Protocol::FetchCollectionsResponse fetchCollectionsResponse(const Collection &col); /** Returns the protocol representation of the given collection. Make sure DataStore::activeCachePolicy() has been called before to include the effective cache policy */ - static Protocol::FetchCollectionsResponsePtr fetchCollectionsResponse(const Collection &col, + static Protocol::FetchCollectionsResponse fetchCollectionsResponse(const Collection &col, const CollectionAttribute::List &attributeList, bool includeStatistics = false, int ancestorDepth = 0, const QStack &ancestors = QStack(), const QStack &ancestorAttributes = QStack(), bool isReferenced = false, const QStringList &mimeTypes = QStringList()); /** Returns the protocol representation of a collection ancestor chain. */ static QVector ancestorsResponse(int ancestorDepth, const QStack &ancestors, const QStack &_ancestorsAttributes = QStack()); - static Protocol::FetchTagsResponsePtr fetchTagsResponse(const Tag &tag, + static Protocol::FetchTagsResponse fetchTagsResponse(const Tag &tag, bool withRID = false, Connection *connection = nullptr); - static Protocol::FetchRelationsResponsePtr fetchRelationsResponse(const Relation &relation); + static Protocol::FetchRelationsResponse fetchRelationsResponse(const Relation &relation); /** Converts a bytearray list of flag names into flag records. @throws HandlerException on errors during database operations */ static Flag::List resolveFlags(const QSet &flagNames); /** Converts a imap set of tags into tag records. @throws HandlerException on errors during database operations */ static Tag::List resolveTagsByUID(const ImapSet &tags); static Tag::List resolveTagsByGID(const QStringList &tagsGIDs); static Tag::List resolveTagsByRID(const QStringList &tagsRIDs, CommandContext *context); static Collection collectionFromScope(const Scope &scope, Connection *connection); static Tag::List tagsFromScope(const Scope &scope, Connection *connection); }; } // namespace Server } // namespace Akonadi #endif diff --git a/src/server/notificationsubscriber.cpp b/src/server/notificationsubscriber.cpp index aad646f30..ca740faa0 100644 --- a/src/server/notificationsubscriber.cpp +++ b/src/server/notificationsubscriber.cpp @@ -1,733 +1,734 @@ /* Copyright (c) 2015 Daniel Vrátil 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 "notificationsubscriber.h" #include "akonadiserver_debug.h" #include "notificationmanager.h" #include "collectionreferencemanager.h" #include "aggregatedfetchscope.h" #include "storage/querybuilder.h" #include "utils.h" #include #include #include #include #include using namespace Akonadi; using namespace Akonadi::Server; QMimeDatabase NotificationSubscriber::sMimeDatabase; #define TRACE_NTF(x) //#define TRACE_NTF(x) qCDebug(AKONADISERVER_LOG) << mSubscriber << x NotificationSubscriber::NotificationSubscriber(NotificationManager *manager) : QObject() , mManager(manager) , mSocket(nullptr) , mAllMonitored(false) , mExclusive(false) , mNotificationDebugging(false) { } NotificationSubscriber::NotificationSubscriber(NotificationManager *manager, quintptr socketDescriptor) : NotificationSubscriber(manager) { mSocket = new QLocalSocket(this); connect(mSocket, &QLocalSocket::readyRead, this, &NotificationSubscriber::handleIncomingData); connect(mSocket, &QLocalSocket::disconnected, this, &NotificationSubscriber::socketDisconnected); mSocket->setSocketDescriptor(socketDescriptor); const SchemaVersion schema = SchemaVersion::retrieveAll().first(); auto hello = Protocol::HelloResponsePtr::create(); hello->setServerName(QStringLiteral("Akonadi")); hello->setMessage(QStringLiteral("Not really IMAP server")); hello->setProtocolVersion(Protocol::version()); hello->setGeneration(schema.generation()); writeCommand(0, hello); } NotificationSubscriber::~NotificationSubscriber() { QMutexLocker locker(&mLock); if (mNotificationDebugging) { Q_EMIT notificationDebuggingChanged(false); } } void NotificationSubscriber::handleIncomingData() { while (mSocket->bytesAvailable() > (int) sizeof(qint64)) { QDataStream stream(mSocket); // Ignored atm qint64 tag = -1; stream >> tag; Protocol::CommandPtr cmd; try { cmd = Protocol::deserialize(mSocket); } catch (const Akonadi::ProtocolException &e) { qCWarning(AKONADISERVER_LOG) << "ProtocolException:" << e.what(); disconnectSubscriber(); return; } catch (const std::exception &e) { qCWarning(AKONADISERVER_LOG) << "Unknown exception:" << e.what(); disconnectSubscriber(); return; } if (cmd->type() == Protocol::Command::Invalid) { qCWarning(AKONADISERVER_LOG) << "Received an invalid command: resetting connection"; disconnectSubscriber(); return; } switch (cmd->type()) { case Protocol::Command::CreateSubscription: registerSubscriber(Protocol::cmdCast(cmd)); writeCommand(tag, Protocol::CreateSubscriptionResponsePtr::create()); break; case Protocol::Command::ModifySubscription: if (mSubscriber.isEmpty()) { qCWarning(AKONADISERVER_LOG) << "Received ModifySubscription command before RegisterSubscriber"; disconnectSubscriber(); return; } modifySubscription(Protocol::cmdCast(cmd)); writeCommand(tag, Protocol::ModifySubscriptionResponsePtr::create()); break; case Protocol::Command::Logout: disconnectSubscriber(); break; default: qCWarning(AKONADISERVER_LOG) << "Invalid command" << cmd->type() << "received by NotificationSubscriber" << mSubscriber; disconnectSubscriber(); break; } } } void NotificationSubscriber::socketDisconnected() { qCDebug(AKONADISERVER_LOG) << "Subscriber" << mSubscriber << "disconnected"; disconnectSubscriber(); } void NotificationSubscriber::disconnectSubscriber() { QMutexLocker locker(&mLock); auto changeNtf = Protocol::SubscriptionChangeNotificationPtr::create(); changeNtf->setSubscriber(mSubscriber); changeNtf->setSessionId(mSession); changeNtf->setOperation(Protocol::SubscriptionChangeNotification::Remove); mManager->slotNotify({ changeNtf }); disconnect(mSocket, &QLocalSocket::disconnected, this, &NotificationSubscriber::socketDisconnected); mSocket->close(); // Unregister ourselves from the aggergated collection fetch scope auto cfs = mManager->collectionFetchScope(); if (mCollectionFetchScope.fetchIdOnly()) { cfs->setFetchIdOnly(false); } if (mCollectionFetchScope.includeStatistics()) { cfs->setFetchStatistics(false); } const auto attrs = mCollectionFetchScope.attributes(); for (const auto &attr : attrs) { cfs->removeAttribute(attr); } mManager->forgetSubscriber(this); deleteLater(); } void NotificationSubscriber::registerSubscriber(const Protocol::CreateSubscriptionCommand &command) { QMutexLocker locker(&mLock); qCDebug(AKONADISERVER_LOG) << "Subscriber" << this << "identified as" << command.subscriberName(); mSubscriber = command.subscriberName(); mSession = command.session(); auto changeNtf = Protocol::SubscriptionChangeNotificationPtr::create(); changeNtf->setSubscriber(mSubscriber); changeNtf->setSessionId(mSession); changeNtf->setOperation(Protocol::SubscriptionChangeNotification::Add); mManager->slotNotify({ changeNtf }); } void NotificationSubscriber::modifySubscription(const Protocol::ModifySubscriptionCommand &command) { QMutexLocker locker(&mLock); const auto modifiedParts = command.modifiedParts(); #define START_MONITORING(type) \ (modifiedParts & Protocol::ModifySubscriptionCommand::ModifiedParts( \ Protocol::ModifySubscriptionCommand::type | Protocol::ModifySubscriptionCommand::Add)) #define STOP_MONITORING(type) \ (modifiedParts & Protocol::ModifySubscriptionCommand::ModifiedParts( \ Protocol::ModifySubscriptionCommand::type | Protocol::ModifySubscriptionCommand::Remove)) #define APPEND(set, newItems) \ Q_FOREACH (const auto &entity, newItems) { \ set.insert(entity); \ } #define REMOVE(set, items) \ Q_FOREACH (const auto &entity, items) { \ set.remove(entity); \ } if (START_MONITORING(Types)) { APPEND(mMonitoredTypes, command.startMonitoringTypes()) } if (STOP_MONITORING(Types)) { REMOVE(mMonitoredTypes, command.stopMonitoringTypes()) } if (START_MONITORING(Collections)) { APPEND(mMonitoredCollections, command.startMonitoringCollections()) } if (STOP_MONITORING(Collections)) { REMOVE(mMonitoredCollections, command.stopMonitoringCollections()) } if (START_MONITORING(Items)) { APPEND(mMonitoredItems, command.startMonitoringItems()) } if (STOP_MONITORING(Items)) { REMOVE(mMonitoredItems, command.stopMonitoringItems()) } if (START_MONITORING(Tags)) { APPEND(mMonitoredTags, command.startMonitoringTags()) } if (STOP_MONITORING(Tags)) { REMOVE(mMonitoredTags, command.stopMonitoringTags()) } if (START_MONITORING(Resources)) { APPEND(mMonitoredResources, command.startMonitoringResources()) } if (STOP_MONITORING(Resources)) { REMOVE(mMonitoredResources, command.stopMonitoringResources()) } if (START_MONITORING(MimeTypes)) { APPEND(mMonitoredMimeTypes, command.startMonitoringMimeTypes()) } if (STOP_MONITORING(MimeTypes)) { REMOVE(mMonitoredMimeTypes, command.stopMonitoringMimeTypes()) } if (START_MONITORING(Sessions)) { APPEND(mIgnoredSessions, command.startIgnoringSessions()) } if (STOP_MONITORING(Sessions)) { REMOVE(mIgnoredSessions, command.stopIgnoringSessions()) } if (modifiedParts & Protocol::ModifySubscriptionCommand::AllFlag) { mAllMonitored = command.allMonitored(); } if (modifiedParts & Protocol::ModifySubscriptionCommand::ExclusiveFlag) { mExclusive = command.isExclusive(); } if (modifiedParts & Protocol::ModifySubscriptionCommand::ItemFetchScope) { const auto newScope = command.itemFetchScope(); mManager->itemFetchScope()->apply(mItemFetchScope, newScope); mItemFetchScope = newScope; } if (modifiedParts & Protocol::ModifySubscriptionCommand::CollectionFetchScope) { const auto newScope = command.collectionFetchScope(); mManager->collectionFetchScope()->apply(mCollectionFetchScope, newScope); mCollectionFetchScope = newScope; } if (modifiedParts & Protocol::ModifySubscriptionCommand::TagFetchScope) { const auto newScope = command.tagFetchScope(); mManager->tagFetchScope()->apply(mTagFetchScope, newScope); mTagFetchScope = newScope; } if (mManager) { if (modifiedParts & Protocol::ModifySubscriptionCommand::Types) { // Did the caller just subscribed to subscription changes? if (command.startMonitoringTypes().contains(Protocol::ModifySubscriptionCommand::SubscriptionChanges)) { // If yes, then send them list of all existing subscribers Q_FOREACH (const NotificationSubscriber *subscriber, mManager->mSubscribers) { // Send them back to caller if (subscriber) { QMetaObject::invokeMethod(this, "notify", Qt::QueuedConnection, Q_ARG(Akonadi::Protocol::ChangeNotificationPtr, subscriber->toChangeNotification())); } } } if (command.startMonitoringTypes().contains(Protocol::ModifySubscriptionCommand::ChangeNotifications)) { if (!mNotificationDebugging) { mNotificationDebugging = true; Q_EMIT notificationDebuggingChanged(true); } } else if (command.stopMonitoringTypes().contains(Protocol::ModifySubscriptionCommand::ChangeNotifications)) { if (mNotificationDebugging) { mNotificationDebugging = false; Q_EMIT notificationDebuggingChanged(false); } } } // Emit subscription change notification auto changeNtf = toChangeNotification(); changeNtf->setOperation(Protocol::SubscriptionChangeNotification::Modify); mManager->slotNotify({ changeNtf }); } #undef START_MONITORING #undef STOP_MONITORING #undef APPEND #undef REMOVE } Protocol::SubscriptionChangeNotificationPtr NotificationSubscriber::toChangeNotification() const { // Assumes mLock being locked by caller auto ntf = Protocol::SubscriptionChangeNotificationPtr::create(); ntf->setSessionId(mSession); ntf->setSubscriber(mSubscriber); ntf->setOperation(Protocol::SubscriptionChangeNotification::Add); ntf->setCollections(mMonitoredCollections); ntf->setItems(mMonitoredItems); ntf->setTags(mMonitoredTags); ntf->setTypes(mMonitoredTypes); ntf->setMimeTypes(mMonitoredMimeTypes); ntf->setResources(mMonitoredResources); ntf->setIgnoredSessions(mIgnoredSessions); ntf->setAllMonitored(mAllMonitored); ntf->setExclusive(mExclusive); ntf->setItemFetchScope(mItemFetchScope); ntf->setCollectionFetchScope(mCollectionFetchScope); return ntf; } bool NotificationSubscriber::isCollectionMonitored(Entity::Id id) const { // Assumes mLock being locked by caller if (id < 0) { return false; } else if (mMonitoredCollections.contains(id)) { return true; } else if (mMonitoredCollections.contains(0)) { return true; } return false; } bool NotificationSubscriber::isMimeTypeMonitored(const QString &mimeType) const { // Assumes mLock being locked by caller const QMimeType mt = sMimeDatabase.mimeTypeForName(mimeType); if (mMonitoredMimeTypes.contains(mimeType)) { return true; } const QStringList lst = mt.aliases(); for (const QString &alias : lst) { if (mMonitoredMimeTypes.contains(alias)) { return true; } } return false; } bool NotificationSubscriber::isMoveDestinationResourceMonitored(const Protocol::ItemChangeNotification &msg) const { // Assumes mLock being locked by caller if (msg.operation() != Protocol::ItemChangeNotification::Move) { return false; } return mMonitoredResources.contains(msg.destinationResource()); } bool NotificationSubscriber::isMoveDestinationResourceMonitored(const Protocol::CollectionChangeNotification &msg) const { // Assumes mLock being locked by caller if (msg.operation() != Protocol::CollectionChangeNotification::Move) { return false; } return mMonitoredResources.contains(msg.destinationResource()); } bool NotificationSubscriber::acceptsItemNotification(const Protocol::ItemChangeNotification &msg) const { // Assumes mLock being locked by caller if (msg.items().isEmpty()) { return false; } if (CollectionReferenceManager::instance()->isReferenced(msg.parentCollection())) { //We always want notifications that affect the parent resource (like an item added to a referenced collection) const bool notificationForParentResource = (mSession == msg.resource()); const bool accepts = mExclusive || isCollectionMonitored(msg.parentCollection()) || isMoveDestinationResourceMonitored(msg) || notificationForParentResource; TRACE_NTF("ACCEPTS ITEM: parent col referenced" << "exclusive:" << mExclusive << "," << "parent monitored:" << isCollectionMonitored(msg.parentCollection()) << "," << "destination monitored:" << isMoveDestinationResourceMonitored(msg) << "," << "ntf for parent resource:" << notificationForParentResource << ":" << "ACCEPTED:" << accepts); return accepts; } if (mAllMonitored) { TRACE_NTF("ACCEPTS ITEM: all monitored"); return true; } if (!mMonitoredTypes.isEmpty() && !mMonitoredTypes.contains(Protocol::ModifySubscriptionCommand::ItemChanges)) { TRACE_NTF("ACCEPTS ITEM: REJECTED - Item changes not monitored"); return false; } // we have a resource or mimetype filter if (!mMonitoredResources.isEmpty() || !mMonitoredMimeTypes.isEmpty()) { if (mMonitoredResources.contains(msg.resource())) { TRACE_NTF("ACCEPTS ITEM: ACCEPTED - resource monitored"); return true; } if (isMoveDestinationResourceMonitored(msg)) { TRACE_NTF("ACCEPTS ITEM: ACCEPTED: move destination monitored"); return true; } Q_FOREACH (const auto &item, msg.items()) { - if (isMimeTypeMonitored(item->mimeType())) { + if (isMimeTypeMonitored(item.mimeType())) { TRACE_NTF("ACCEPTS ITEM: ACCEPTED - mimetype monitored"); return true; } } TRACE_NTF("ACCEPTS ITEM: REJECTED: resource nor mimetype monitored"); return false; } // we explicitly monitor that item or the collections it's in Q_FOREACH (const auto &item, msg.items()) { - if (mMonitoredItems.contains(item->id())) { + if (mMonitoredItems.contains(item.id())) { TRACE_NTF("ACCEPTS ITEM: ACCEPTED: item explicitly monitored"); return true; } } if (isCollectionMonitored(msg.parentCollection())) { TRACE_NTF("ACCEPTS ITEM: ACCEPTED: parent collection monitored"); return true; } if (isCollectionMonitored(msg.parentDestCollection())) { TRACE_NTF("ACCEPTS ITEM: ACCEPTED: destination collection monitored"); return true; } TRACE_NTF("ACCEPTS ITEM: REJECTED"); return false; } bool NotificationSubscriber::acceptsCollectionNotification(const Protocol::CollectionChangeNotification &msg) const { // Assumes mLock being locked by caller - const auto collection = msg.collection(); - if (!collection || collection->id() < 0) { + const auto &collection = msg.collection(); + if (collection.id() < 0) { return false; } // HACK: We need to dispatch notifications about disabled collections to SOME // agents (that's what we have the exclusive subscription for) - but because // querying each Collection from database would be expensive, we use the // metadata hack to transfer this information from NotificationCollector if (msg.metadata().contains("DISABLED") && (msg.operation() != Protocol::CollectionChangeNotification::Unsubscribe) && !msg.changedParts().contains("ENABLED")) { // Exclusive subscriber always gets it if (mExclusive) { return true; } //Deliver the notification if referenced from this session - if (CollectionReferenceManager::instance()->isReferenced(collection->id(), mSession)) { + if (CollectionReferenceManager::instance()->isReferenced(collection.id(), mSession)) { return true; } //Exclusive subscribers still want the notification - if (mExclusive && CollectionReferenceManager::instance()->isReferenced(collection->id())) { + if (mExclusive && CollectionReferenceManager::instance()->isReferenced(collection.id())) { return true; } //The session belonging to this monitor referenced or dereferenced the collection. We always want this notification. //The referencemanager no longer holds a reference, so we have to check this way. if (msg.changedParts().contains(AKONADI_PARAM_REFERENCED) && mSession == msg.sessionId()) { return true; } // If the collection is not referenced, monitored or the subscriber is not // exclusive (i.e. if we got here), then the subscriber does not care about // this one, so drop it return false; } if (mAllMonitored) { return true; } if (!mMonitoredTypes.isEmpty() && !mMonitoredTypes.contains(Protocol::ModifySubscriptionCommand::CollectionChanges)) { return false; } // we have a resource filter if (!mMonitoredResources.isEmpty()) { const bool resourceMatches = mMonitoredResources.contains(msg.resource()) || isMoveDestinationResourceMonitored(msg); // a bit hacky, but match the behaviour from the item case, // if resource is the only thing we are filtering on, stop here, and if the resource filter matched, of course if (mMonitoredMimeTypes.isEmpty() || resourceMatches) { return resourceMatches; } // else continue } // we explicitly monitor that colleciton, or all of them - if (isCollectionMonitored(collection->id())) { + if (isCollectionMonitored(collection.id())) { return true; } return isCollectionMonitored(msg.parentCollection()) || isCollectionMonitored(msg.parentDestCollection()); } bool NotificationSubscriber::acceptsTagNotification(const Protocol::TagChangeNotification &msg) const { // Assumes mLock being locked by caller - if (msg.tag()->id() < 0) { + if (msg.tag().id() < 0) { return false; } // Special handling for Tag removal notifications: When a Tag is removed, // a notification is emitted for each Resource that owns the tag (i.e. // each resource that owns a Tag RID - Tag RIDs are resource-specific). // Additionally then we send one more notification without any RID that is // destined for regular applications (which don't know anything about Tag RIDs) if (msg.operation() == Protocol::TagChangeNotification::Remove) { // HACK: Since have no way to determine which resource this NotificationSource // belongs to, we are abusing the fact that each resource ignores it's own // main session, which is called the same name as the resource. // If there are any ignored sessions, but this notification does not have // a specific resource set, then we ignore it, as this notification is // for clients, not resources (does not have tag RID) if (!mIgnoredSessions.isEmpty() && msg.resource().isEmpty()) { return false; } // If this source ignores a session (i.e. we assume it is a resource), // but this notification is for another resource, then we ignore it if (!msg.resource().isEmpty() && !mIgnoredSessions.contains(msg.resource())) { return false; } // Now we got here, which means that this notification either has empty // resource, i.e. it is destined for a client applications, or it's // destined for resource that we *think* (see the hack above) this // NotificationSource belongs too. Which means we approve this notification, // but it can still be discarded in the generic Tag notification filter // below } if (mAllMonitored) { return true; } if (!mMonitoredTypes.isEmpty() && !mMonitoredTypes.contains(Protocol::ModifySubscriptionCommand::TagChanges)) { return false; } if (mMonitoredTags.isEmpty()) { return true; } - if (mMonitoredTags.contains(msg.tag()->id())) { + if (mMonitoredTags.contains(msg.tag().id())) { return true; } return true; } bool NotificationSubscriber::acceptsRelationNotification(const Protocol::RelationChangeNotification &msg) const { // Assumes mLock being locked by caller Q_UNUSED(msg); if (mAllMonitored) { return true; } if (!mMonitoredTypes.isEmpty() && !mMonitoredTypes.contains(Protocol::ModifySubscriptionCommand::RelationChanges)) { return false; } return true; } bool NotificationSubscriber::acceptsSubscriptionNotification(const Protocol::SubscriptionChangeNotification &msg) const { // Assumes mLock being locked by caller Q_UNUSED(msg); // Unlike other types, subscription notifications must be explicitly enabled // by caller and are excluded from "monitor all" as well return mMonitoredTypes.contains(Protocol::ModifySubscriptionCommand::SubscriptionChanges); } bool NotificationSubscriber::acceptsDebugChangeNotification(const Protocol::DebugChangeNotification &msg) const { // Assumes mLock being locked by caller // We should never end up sending debug notification about a debug notification. // This could get very messy very quickly... Q_ASSERT(msg.notification()->type() != Protocol::Command::DebugChangeNotification); if (msg.notification()->type() == Protocol::Command::DebugChangeNotification) { return false; } // Unlike other types, debug change notifications must be explicitly enabled // by caller and are excluded from "monitor all" as well return mMonitoredTypes.contains(Protocol::ModifySubscriptionCommand::ChangeNotifications); } bool NotificationSubscriber::acceptsNotification(const Protocol::ChangeNotification &msg) const { // Assumes mLock being locked // Uninitialized subscriber gets nothing if (mSubscriber.isEmpty()) { return false; } // session is ignored // TODO: Should this afect SubscriptionChangeNotification and DebugChangeNotification? if (mIgnoredSessions.contains(msg.sessionId())) { return false; } switch (msg.type()) { case Protocol::Command::ItemChangeNotification: return acceptsItemNotification(static_cast(msg)); case Protocol::Command::CollectionChangeNotification: return acceptsCollectionNotification(static_cast(msg)); case Protocol::Command::TagChangeNotification: return acceptsTagNotification(static_cast(msg)); case Protocol::Command::RelationChangeNotification: return acceptsRelationNotification(static_cast(msg)); case Protocol::Command::SubscriptionChangeNotification: return acceptsSubscriptionNotification(static_cast(msg)); case Protocol::Command::DebugChangeNotification: return acceptsDebugChangeNotification(static_cast(msg)); default: qCDebug(AKONADISERVER_LOG) << "Received invalid change notification!"; return false; } } Protocol::CollectionChangeNotificationPtr NotificationSubscriber::customizeCollection(const Protocol::CollectionChangeNotificationPtr &ntf) { - const bool isReferencedFromSession = CollectionReferenceManager::instance()->isReferenced(ntf->collection()->id(), mSession); - if (isReferencedFromSession != ntf->collection()->referenced()) { + const bool isReferencedFromSession = CollectionReferenceManager::instance()->isReferenced(ntf->collection().id(), mSession); + if (isReferencedFromSession != ntf->collection().referenced()) { auto copy = Protocol::CollectionChangeNotificationPtr::create(*ntf); - copy->setCollection(Protocol::FetchCollectionsResponsePtr::create(*ntf->collection())); - copy->collection()->setReferenced(isReferencedFromSession); + auto copyCol = ntf->collection(); + copyCol.setReferenced(isReferencedFromSession); + copy->setCollection(std::move(copyCol)); return copy; } return ntf; } bool NotificationSubscriber::notify(const Protocol::ChangeNotificationPtr ¬ification) { // Guard against this object being deleted while we are waiting for the lock QPointer ptr(this); QMutexLocker locker(&mLock); if (!ptr) { return false; } if (acceptsNotification(*notification)) { auto ntf = notification; if (ntf->type() == Protocol::Command::CollectionChangeNotification) { ntf = customizeCollection(notification.staticCast()); } QMetaObject::invokeMethod(this, "writeNotification", Qt::QueuedConnection, Q_ARG(Akonadi::Protocol::ChangeNotificationPtr, ntf)); return true; } return false; } void NotificationSubscriber::writeNotification(const Protocol::ChangeNotificationPtr ¬ification) { // tag chosen by fair dice roll writeCommand(4, notification); } void NotificationSubscriber::writeCommand(qint64 tag, const Protocol::CommandPtr &cmd) { Q_ASSERT(QThread::currentThread() == thread()); QDataStream stream(mSocket); stream << tag; try { Protocol::serialize(mSocket, cmd); if (!mSocket->waitForBytesWritten()) { if (mSocket->state() == QLocalSocket::ConnectedState) { qCWarning(AKONADISERVER_LOG) << "Notification socket write timeout!"; } else { // client has disconnected, just discard the message } } } catch (const ProtocolException &e) { qCWarning(AKONADISERVER_LOG) << "Notification protocol exception:" << e.what(); } } diff --git a/src/server/storage/notificationcollector.cpp b/src/server/storage/notificationcollector.cpp index d58451502..2274162a9 100644 --- a/src/server/storage/notificationcollector.cpp +++ b/src/server/storage/notificationcollector.cpp @@ -1,580 +1,580 @@ /* Copyright (c) 2006 - 2007 Volker Krause 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 "notificationcollector.h" #include "storage/datastore.h" #include "storage/entity.h" #include "storage/collectionstatistics.h" #include "handlerhelper.h" #include "cachecleaner.h" #include "intervalcheck.h" #include "search/searchmanager.h" #include "akonadi.h" #include "handler/search.h" #include "notificationmanager.h" #include "aggregatedfetchscope.h" #include "selectquerybuilder.h" #include "handler/fetchhelper.h" #include "akonadiserver_debug.h" #include using namespace Akonadi; using namespace Akonadi::Server; NotificationCollector::NotificationCollector(DataStore *db) : mDb(db) { QObject::connect(db, &DataStore::transactionCommitted, [this]() { if (!mIgnoreTransactions) { dispatchNotifications(); } }); QObject::connect(db, &DataStore::transactionRolledBack, [this]() { if (!mIgnoreTransactions) { clear(); } }); } void NotificationCollector::itemAdded(const PimItem &item, bool seen, const Collection &collection, const QByteArray &resource) { SearchManager::instance()->scheduleSearchUpdate(); CollectionStatistics::self()->itemAdded(collection, item.size(), seen); itemNotification(Protocol::ItemChangeNotification::Add, item, collection, Collection(), resource); } void NotificationCollector::itemChanged(const PimItem &item, const QSet &changedParts, const Collection &collection, const QByteArray &resource) { SearchManager::instance()->scheduleSearchUpdate(); itemNotification(Protocol::ItemChangeNotification::Modify, item, collection, Collection(), resource, changedParts); } void NotificationCollector::itemsFlagsChanged(const PimItem::List &items, const QSet &addedFlags, const QSet &removedFlags, const Collection &collection, const QByteArray &resource) { int seenCount = (addedFlags.contains(AKONADI_FLAG_SEEN) || addedFlags.contains(AKONADI_FLAG_IGNORED) ? items.count() : 0); seenCount -= (removedFlags.contains(AKONADI_FLAG_SEEN) || removedFlags.contains(AKONADI_FLAG_IGNORED) ? items.count() : 0); CollectionStatistics::self()->itemsSeenChanged(collection, seenCount); itemNotification(Protocol::ItemChangeNotification::ModifyFlags, items, collection, Collection(), resource, QSet(), addedFlags, removedFlags); } void NotificationCollector::itemsTagsChanged(const PimItem::List &items, const QSet &addedTags, const QSet &removedTags, const Collection &collection, const QByteArray &resource) { itemNotification(Protocol::ItemChangeNotification::ModifyTags, items, collection, Collection(), resource, QSet(), QSet(), QSet(), addedTags, removedTags); } void NotificationCollector::itemsRelationsChanged(const PimItem::List &items, const Relation::List &addedRelations, const Relation::List &removedRelations, const Collection &collection, const QByteArray &resource) { itemNotification(Protocol::ItemChangeNotification::ModifyRelations, items, collection, Collection(), resource, QSet(), QSet(), QSet(), QSet(), QSet(), addedRelations, removedRelations); } void NotificationCollector::itemsMoved(const PimItem::List &items, const Collection &collectionSrc, const Collection &collectionDest, const QByteArray &sourceResource) { SearchManager::instance()->scheduleSearchUpdate(); itemNotification(Protocol::ItemChangeNotification::Move, items, collectionSrc, collectionDest, sourceResource); } void NotificationCollector::itemsRemoved(const PimItem::List &items, const Collection &collection, const QByteArray &resource) { itemNotification(Protocol::ItemChangeNotification::Remove, items, collection, Collection(), resource); } void NotificationCollector::itemsLinked(const PimItem::List &items, const Collection &collection) { itemNotification(Protocol::ItemChangeNotification::Link, items, collection, Collection(), QByteArray()); } void NotificationCollector::itemsUnlinked(const PimItem::List &items, const Collection &collection) { itemNotification(Protocol::ItemChangeNotification::Unlink, items, collection, Collection(), QByteArray()); } void NotificationCollector::collectionAdded(const Collection &collection, const QByteArray &resource) { if (auto cleaner = AkonadiServer::instance()->cacheCleaner()) { cleaner->collectionAdded(collection.id()); } if (auto checker = AkonadiServer::instance()->intervalChecker()) { checker->collectionAdded(collection.id()); } collectionNotification(Protocol::CollectionChangeNotification::Add, collection, collection.parentId(), -1, resource); } void NotificationCollector::collectionChanged(const Collection &collection, const QList &changes, const QByteArray &resource) { if (auto cleaner = AkonadiServer::instance()->cacheCleaner()) { cleaner->collectionChanged(collection.id()); } if (auto checker = AkonadiServer::instance()->intervalChecker()) { checker->collectionChanged(collection.id()); } if (changes.contains(AKONADI_PARAM_ENABLED) || changes.contains(AKONADI_PARAM_REFERENCED)) { CollectionStatistics::self()->invalidateCollection(collection); } collectionNotification(Protocol::CollectionChangeNotification::Modify, collection, collection.parentId(), -1, resource, changes.toSet()); } void NotificationCollector::collectionMoved(const Collection &collection, const Collection &source, const QByteArray &resource, const QByteArray &destResource) { if (auto cleaner = AkonadiServer::instance()->cacheCleaner()) { cleaner->collectionChanged(collection.id()); } if (auto checker = AkonadiServer::instance()->intervalChecker()) { checker->collectionChanged(collection.id()); } collectionNotification(Protocol::CollectionChangeNotification::Move, collection, source.id(), collection.parentId(), resource, QSet(), destResource); } void NotificationCollector::collectionRemoved(const Collection &collection, const QByteArray &resource) { if (auto cleaner = AkonadiServer::instance()->cacheCleaner()) { cleaner->collectionRemoved(collection.id()); } if (auto checker = AkonadiServer::instance()->intervalChecker()) { checker->collectionRemoved(collection.id()); } CollectionStatistics::self()->invalidateCollection(collection); collectionNotification(Protocol::CollectionChangeNotification::Remove, collection, collection.parentId(), -1, resource); } void NotificationCollector::collectionSubscribed(const Collection &collection, const QByteArray &resource) { if (auto cleaner = AkonadiServer::instance()->cacheCleaner()) { cleaner->collectionAdded(collection.id()); } if (auto checker = AkonadiServer::instance()->intervalChecker()) { checker->collectionAdded(collection.id()); } collectionNotification(Protocol::CollectionChangeNotification::Subscribe, collection, collection.parentId(), -1, resource, QSet()); } void NotificationCollector::collectionUnsubscribed(const Collection &collection, const QByteArray &resource) { if (auto cleaner = AkonadiServer::instance()->cacheCleaner()) { cleaner->collectionRemoved(collection.id()); } if (auto checker = AkonadiServer::instance()->intervalChecker()) { checker->collectionRemoved(collection.id()); } CollectionStatistics::self()->invalidateCollection(collection); collectionNotification(Protocol::CollectionChangeNotification::Unsubscribe, collection, collection.parentId(), -1, resource, QSet()); } void NotificationCollector::tagAdded(const Tag &tag) { tagNotification(Protocol::TagChangeNotification::Add, tag); } void NotificationCollector::tagChanged(const Tag &tag) { tagNotification(Protocol::TagChangeNotification::Modify, tag); } void NotificationCollector::tagRemoved(const Tag &tag, const QByteArray &resource, const QString &remoteId) { tagNotification(Protocol::TagChangeNotification::Remove, tag, resource, remoteId); } void NotificationCollector::relationAdded(const Relation &relation) { relationNotification(Protocol::RelationChangeNotification::Add, relation); } void NotificationCollector::relationRemoved(const Relation &relation) { relationNotification(Protocol::RelationChangeNotification::Remove, relation); } void NotificationCollector::clear() { mNotifications.clear(); } void NotificationCollector::setSessionId(const QByteArray &sessionId) { mSessionId = sessionId; } void NotificationCollector::itemNotification(Protocol::ItemChangeNotification::Operation op, const PimItem &item, const Collection &collection, const Collection &collectionDest, const QByteArray &resource, const QSet &parts) { PimItem::List items; items << item; itemNotification(op, items, collection, collectionDest, resource, parts); } void NotificationCollector::itemNotification(Protocol::ItemChangeNotification::Operation op, const PimItem::List &items, const Collection &collection, const Collection &collectionDest, const QByteArray &resource, const QSet &parts, const QSet &addedFlags, const QSet &removedFlags, const QSet &addedTags, const QSet &removedTags, const Relation::List &addedRelations, const Relation::List &removedRelations) { QMap > vCollections; if ((op == Protocol::ItemChangeNotification::Modify) || (op == Protocol::ItemChangeNotification::ModifyFlags) || (op == Protocol::ItemChangeNotification::ModifyTags) || (op == Protocol::ItemChangeNotification::ModifyRelations)) { vCollections = DataStore::self()->virtualCollections(items); } auto msg = Protocol::ItemChangeNotificationPtr::create(); msg->setSessionId(mSessionId); msg->setOperation(op); msg->setItemParts(parts); msg->setAddedFlags(addedFlags); msg->setRemovedFlags(removedFlags); msg->setAddedTags(addedTags); msg->setRemovedTags(removedTags); if (!addedRelations.isEmpty()) { QSet rels; Q_FOREACH (const Relation &rel, addedRelations) { rels.insert(Protocol::ItemChangeNotification::Relation(rel.leftId(), rel.rightId(), rel.relationType().name())); } msg->setAddedRelations(rels); } if (!removedRelations.isEmpty()) { QSet rels; Q_FOREACH (const Relation &rel, removedRelations) { rels.insert(Protocol::ItemChangeNotification::Relation(rel.leftId(), rel.rightId(), rel.relationType().name())); } msg->setRemovedRelations(rels); } if (collectionDest.isValid()) { QByteArray destResourceName; destResourceName = collectionDest.resource().name().toLatin1(); msg->setDestinationResource(destResourceName); } msg->setParentDestCollection(collectionDest.id()); - QVector ntfItems; + QVector ntfItems; Q_FOREACH (const PimItem &item, items) { - auto i = Protocol::FetchItemsResponsePtr::create(); - i->setId(item.id()); - i->setRemoteId(item.remoteId()); - i->setRemoteRevision(item.remoteRevision()); - i->setMimeType(item.mimeType().name()); - ntfItems.push_back(i); + Protocol::FetchItemsResponse i; + i.setId(item.id()); + i.setRemoteId(item.remoteId()); + i.setRemoteRevision(item.remoteRevision()); + i.setMimeType(item.mimeType().name()); + ntfItems.push_back(std::move(i)); } /* Notify all virtual collections the items are linked to. */ - QHash virtItems; + QHash virtItems; for (const auto &ntfItem : ntfItems) { - virtItems.insert(ntfItem->id(), ntfItem); + virtItems.insert(ntfItem.id(), std::move(ntfItem)); } auto iter = vCollections.constBegin(), endIter = vCollections.constEnd(); for (; iter != endIter; ++iter) { auto copy = Protocol::ItemChangeNotificationPtr::create(*msg); - QVector items; + QVector items; items.reserve(iter->size()); for (const auto &item : qAsConst(*iter)) { items.append(virtItems.value(item.id())); } copy->setItems(items); copy->setParentCollection(iter.key()); copy->setResource(resource); CollectionStatistics::self()->invalidateCollection(Collection::retrieveById(iter.key())); dispatchNotification(copy); } msg->setItems(ntfItems); Collection col; if (!collection.isValid()) { msg->setParentCollection(items.first().collection().id()); col = items.first().collection(); } else { msg->setParentCollection(collection.id()); col = collection; } QByteArray res = resource; if (res.isEmpty()) { if (col.resourceId() <= 0) { col = Collection::retrieveById(col.id()); } res = col.resource().name().toLatin1(); } msg->setResource(res); // Add and ModifyFlags are handled incrementally // (see itemAdded() and itemsFlagsChanged()) if (msg->operation() != Protocol::ItemChangeNotification::Add && msg->operation() != Protocol::ItemChangeNotification::ModifyFlags) { CollectionStatistics::self()->invalidateCollection(col); } dispatchNotification(msg); } void NotificationCollector::collectionNotification(Protocol::CollectionChangeNotification::Operation op, const Collection &collection, Collection::Id source, Collection::Id destination, const QByteArray &resource, const QSet &changes, const QByteArray &destResource) { auto msg = Protocol::CollectionChangeNotificationPtr::create(); msg->setOperation(op); msg->setSessionId(mSessionId); msg->setParentCollection(source); msg->setParentDestCollection(destination); msg->setDestinationResource(destResource); msg->setChangedParts(changes); auto msgCollection = HandlerHelper::fetchCollectionsResponse(collection); if (auto mgr = AkonadiServer::instance()->notificationManager()) { auto fetchScope = mgr->collectionFetchScope(); // Make sure we have all the data - if (!fetchScope->fetchIdOnly() && msgCollection->name().isEmpty()) { - const auto col = Collection::retrieveById(msgCollection->id()); + if (!fetchScope->fetchIdOnly() && msgCollection.name().isEmpty()) { + const auto col = Collection::retrieveById(msgCollection.id()); const auto mts = col.mimeTypes(); QStringList mimeTypes; mimeTypes.reserve(mts.size()); for (const auto &mt : mts) { mimeTypes.push_back(mt.name()); } msgCollection = HandlerHelper::fetchCollectionsResponse(col, {}, false, 0, {}, {}, false, mimeTypes); } // Get up-to-date statistics if (fetchScope->fetchStatistics()) { Collection col; - col.setId(msgCollection->id()); + col.setId(msgCollection.id()); const auto stats = CollectionStatistics::self()->statistics(col); - msgCollection->setStatistics(Protocol::FetchCollectionStatsResponse(stats.count, stats.count - stats.read, stats.size)); + msgCollection.setStatistics(Protocol::FetchCollectionStatsResponse(stats.count, stats.count - stats.read, stats.size)); } // Get attributes const auto requestedAttrs = fetchScope->attributes(); - auto msgColAttrs = msgCollection->attributes(); + auto msgColAttrs = msgCollection.attributes(); // TODO: This assumes that we have either none or all attributes in msgCollection if (msgColAttrs.isEmpty() && !requestedAttrs.isEmpty()) { SelectQueryBuilder qb; qb.addColumn(CollectionAttribute::typeFullColumnName()); qb.addColumn(CollectionAttribute::valueFullColumnName()); qb.addValueCondition(CollectionAttribute::collectionIdFullColumnName(), - Query::Equals, msgCollection->id()); + Query::Equals, msgCollection.id()); Query::Condition cond(Query::Or); for (const auto &attr : requestedAttrs) { cond.addValueCondition(CollectionAttribute::typeFullColumnName(), Query::Equals, attr); } qb.addCondition(cond); if (!qb.exec()) { qCWarning(AKONADISERVER_LOG) << "Failed to obtain collection attributes!"; } const auto attrs = qb.result(); for (const auto &attr : attrs) { msgColAttrs.insert(attr.type(), attr.value()); } - msgCollection->setAttributes(msgColAttrs); + msgCollection.setAttributes(msgColAttrs); } } - msg->setCollection(msgCollection); + msg->setCollection(std::move(msgCollection)); if (!collection.enabled()) { msg->addMetadata("DISABLED"); } QByteArray res = resource; if (res.isEmpty()) { res = collection.resource().name().toLatin1(); } msg->setResource(res); dispatchNotification(msg); } void NotificationCollector::tagNotification(Protocol::TagChangeNotification::Operation op, const Tag &tag, const QByteArray &resource, const QString &remoteId) { auto msg = Protocol::TagChangeNotificationPtr::create(); msg->setOperation(op); msg->setSessionId(mSessionId); msg->setResource(resource); auto msgTag = HandlerHelper::fetchTagsResponse(tag, false); - msgTag->setRemoteId(remoteId.toUtf8()); + msgTag.setRemoteId(remoteId.toUtf8()); if (auto mgr = AkonadiServer::instance()->notificationManager()) { auto fetchScope = mgr->tagFetchScope(); - if (!fetchScope->fetchIdOnly() && msgTag->gid().isEmpty()) { - msgTag = HandlerHelper::fetchTagsResponse(Tag::retrieveById(msgTag->id()), false); + if (!fetchScope->fetchIdOnly() && msgTag.gid().isEmpty()) { + msgTag = HandlerHelper::fetchTagsResponse(Tag::retrieveById(msgTag.id()), false); } const auto requestedAttrs = fetchScope->attributes(); - auto msgTagAttrs = msgTag->attributes(); + auto msgTagAttrs = msgTag.attributes(); if (msgTagAttrs.isEmpty() && !requestedAttrs.isEmpty()) { SelectQueryBuilder qb; qb.addColumn(TagAttribute::typeFullColumnName()); qb.addColumn(TagAttribute::valueFullColumnName()); - qb.addValueCondition(TagAttribute::tagIdFullColumnName(), Query::Equals, msgTag->id()); + qb.addValueCondition(TagAttribute::tagIdFullColumnName(), Query::Equals, msgTag.id()); Query::Condition cond(Query::Or); for (const auto &attr : requestedAttrs) { cond.addValueCondition(TagAttribute::typeFullColumnName(), Query::Equals, attr); } qb.addCondition(cond); if (!qb.exec()) { qCWarning(AKONADISERVER_LOG) << "Failed to obtain tag attributes!"; } const auto attrs = qb.result(); for (const auto &attr : attrs) { msgTagAttrs.insert(attr.type(), attr.value()); } - msgTag->setAttributes(msgTagAttrs); + msgTag.setAttributes(msgTagAttrs); } } - msg->setTag(msgTag); + msg->setTag(std::move(msgTag)); dispatchNotification(msg); } void NotificationCollector::relationNotification(Protocol::RelationChangeNotification::Operation op, const Relation &relation) { auto msg = Protocol::RelationChangeNotificationPtr::create(); msg->setOperation(op); msg->setSessionId(mSessionId); msg->setRelation(HandlerHelper::fetchRelationsResponse(relation)); dispatchNotification(msg); } void NotificationCollector::completeNotification(const Protocol::ChangeNotificationPtr &changeMsg) { if (changeMsg->type() == Protocol::Command::ItemChangeNotification) { const auto msg = changeMsg.staticCast(); const auto mgr = AkonadiServer::instance()->notificationManager(); if (mgr && msg->operation() != Protocol::ItemChangeNotification::Remove) { if (mDb->inTransaction()) { qCWarning(AKONADISERVER_LOG) << "FetchHelper requested from within a transaction, aborting, since this would deadlock!"; return; } auto fetchScope = mgr->itemFetchScope(); // NOTE: Checking and retrieving missing elements for each Item manually // here would require a complex code (and I'm too lazy), so instead we simply // feed the Items to FetchHelper and retrieve them all with the setup from // the aggregated fetch scope. The worst case is that we re-fetch everything // we already have, but that's stil better than the pre-ntf-payload situation QVector ids; const auto items = msg->items(); ids.reserve(items.size()); for (const auto &item : items) { - ids.push_back(item->id()); + ids.push_back(item.id()); } // Prevent transactions inside FetchHelper to recursively call our slot QScopedValueRollback ignoreTransactions(mIgnoreTransactions); mIgnoreTransactions = true; CommandContext context; auto scope = fetchScope->toFetchScope(); FetchHelper helper(Connection::self(), &context, Scope(ids), scope); - QVector fetchedItems; - std::function callback = [&fetchedItems](Protocol::CommandPtr cmd) { - fetchedItems.push_back(cmd.staticCast()); + QVector fetchedItems; + auto callback = [&fetchedItems](Protocol::FetchItemsResponse &&cmd) { + fetchedItems.push_back(std::move(cmd)); }; if (helper.fetchItems(std::move(callback))) { msg->setItems(fetchedItems); } else { qCWarning(AKONADISERVER_LOG) << "Failed to retrieve Items for notification!"; } } } } void NotificationCollector::dispatchNotification(const Protocol::ChangeNotificationPtr &msg) { if (!mDb || mDb->inTransaction()) { if (msg->type() == Protocol::Command::CollectionChangeNotification) { Protocol::CollectionChangeNotification::appendAndCompress(mNotifications, msg); } else { mNotifications.append(msg); } } else { completeNotification(msg); notify({msg}); } } void NotificationCollector::dispatchNotifications() { if (!mNotifications.isEmpty()) { for (auto &ntf : mNotifications) { completeNotification(ntf); } notify(std::move(mNotifications)); clear(); } } void NotificationCollector::notify(Protocol::ChangeNotificationList msgs) { if (auto mgr = AkonadiServer::instance()->notificationManager()) { QMetaObject::invokeMethod(mgr, "slotNotify", Qt::QueuedConnection, Q_ARG(Akonadi::Protocol::ChangeNotificationList, msgs)); } } diff --git a/src/server/storage/partstreamer.cpp b/src/server/storage/partstreamer.cpp index 1b4f60326..d2bb89c71 100644 --- a/src/server/storage/partstreamer.cpp +++ b/src/server/storage/partstreamer.cpp @@ -1,423 +1,423 @@ /* * Copyright (C) 2014 Daniel Vrátil * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include "partstreamer.h" #include "parthelper.h" #include "parttypehelper.h" #include "selectquerybuilder.h" #include "dbconfig.h" #include "connection.h" #include "capabilities_p.h" #include "akonadiserver_debug.h" #include #include #include #include #ifdef HAVE_UNISTD_H # include #endif #include #include using namespace Akonadi; using namespace Akonadi::Server; PartStreamer::PartStreamer(Connection *connection, const PimItem &pimItem) : mConnection(connection) , mItem(pimItem) { // Make sure the file_db_data path exists StandardDirs::saveDir("data", QStringLiteral("file_db_data")); } PartStreamer::~PartStreamer() { } QString PartStreamer::error() const { return mError; } Protocol::PartMetaData PartStreamer::requestPartMetaData(const QByteArray &partName) { { - auto resp = Protocol::StreamPayloadCommandPtr::create(); - resp->setPayloadName(partName); - resp->setRequest(Protocol::StreamPayloadCommand::MetaData); - mConnection->sendResponse(resp); + Protocol::StreamPayloadCommand resp; + resp.setPayloadName(partName); + resp.setRequest(Protocol::StreamPayloadCommand::MetaData); + mConnection->sendResponse(std::move(resp)); } const auto cmd = mConnection->readCommand(); if (!cmd->isValid() || Protocol::cmdCast(cmd).isError()) { mError = QStringLiteral("Client failed to provide part metadata"); return Protocol::PartMetaData(); } return Protocol::cmdCast(cmd).metaData(); } bool PartStreamer::streamPayload(Part &part, const QByteArray &partName) { Protocol::PartMetaData metaPart = requestPartMetaData(partName); if (metaPart.name().isEmpty()) { mError = QStringLiteral("Part name is empty"); return false; } part.setVersion(metaPart.version()); if (part.datasize() != metaPart.size()) { part.setDatasize(metaPart.size()); // Shortcut: if sizes differ, we don't need to compare data later no in order // to detect whether the part has changed mDataChanged = mDataChanged || (metaPart.size() != part.datasize()); } if (metaPart.storageType() == Protocol::PartMetaData::Foreign) { return streamForeignPayload(part, metaPart); } else if (part.datasize() > DbConfig::configuredDatabase()->sizeThreshold()) { //actual case when streaming storage is used: external payload is enabled, // data is big enough in a literal return streamPayloadToFile(part, metaPart); } else { return streamPayloadData(part, metaPart); } } bool PartStreamer::streamPayloadData(Part &part, const Protocol::PartMetaData &metaPart) { // If the part WAS external previously, remove data file if (part.storage() == Part::External) { ExternalPartStorage::self()->removePartFile( ExternalPartStorage::resolveAbsolutePath(part.data())); } // Request the actual data { - auto resp = Protocol::StreamPayloadCommandPtr::create(); - resp->setPayloadName(metaPart.name()); - resp->setRequest(Protocol::StreamPayloadCommand::Data); - mConnection->sendResponse(resp); + Protocol::StreamPayloadCommand resp; + resp.setPayloadName(metaPart.name()); + resp.setRequest(Protocol::StreamPayloadCommand::Data); + mConnection->sendResponse(std::move(resp)); } const auto cmd = mConnection->readCommand(); const auto &response = Protocol::cmdCast(cmd); if (!response.isValid() || response.isError()) { mError = QStringLiteral("Client failed to provide payload data"); qCCritical(AKONADISERVER_LOG) << mError; return false; } const QByteArray newData = response.data(); // only use the data size with intenral payload parts, for foreign parts // we use the size reported by client const auto newSize = (metaPart.storageType() == Protocol::PartMetaData::Internal) ? newData.size() : metaPart.size(); if (newSize != metaPart.size()) { mError = QStringLiteral("Payload size mismatch"); return false; } if (part.isValid()) { if (!mDataChanged) { mDataChanged = mDataChanged || (newData != part.data()); } PartHelper::update(&part, newData, newSize); } else { part.setData(newData); part.setDatasize(newSize); if (!part.insert()) { mError = QStringLiteral("Failed to insert part to database"); return false; } } return true; } bool PartStreamer::streamPayloadToFile(Part &part, const Protocol::PartMetaData &metaPart) { QByteArray origData; if (!mDataChanged && mCheckChanged) { origData = PartHelper::translateData(part); } QByteArray filename; if (part.isValid()) { if (part.storage() == Part::External) { // Part was external and is still external filename = part.data(); if (!filename.isEmpty()) { ExternalPartStorage::self()->removePartFile( ExternalPartStorage::resolveAbsolutePath(filename)); filename = ExternalPartStorage::updateFileNameRevision(filename); } else { // recover from data corruption filename = ExternalPartStorage::nameForPartId(part.id()); } } else { // Part wasn't external, but is now filename = ExternalPartStorage::nameForPartId(part.id()); } QFileInfo finfo(QString::fromUtf8(filename)); if (finfo.isAbsolute()) { filename = finfo.fileName().toUtf8(); } } part.setStorage(Part::External); part.setDatasize(metaPart.size()); part.setData(filename); if (part.isValid()) { if (!part.update()) { mError = QStringLiteral("Failed to update part in database"); return false; } } else { if (!part.insert()) { mError = QStringLiteral("Failed to insert part into database"); return false; } filename = ExternalPartStorage::nameForPartId(part.id()); part.setData(filename); if (!part.update()) { mError = QStringLiteral("Failed to update part in database"); return false; } } { - auto cmd = Protocol::StreamPayloadCommandPtr::create(); - cmd->setPayloadName(metaPart.name()); - cmd->setRequest(Protocol::StreamPayloadCommand::Data); - cmd->setDestination(QString::fromUtf8(filename)); - mConnection->sendResponse(cmd); + Protocol::StreamPayloadCommand cmd; + cmd.setPayloadName(metaPart.name()); + cmd.setRequest(Protocol::StreamPayloadCommand::Data); + cmd.setDestination(QString::fromUtf8(filename)); + mConnection->sendResponse(std::move(cmd)); } const auto cmd = mConnection->readCommand(); const auto &response = Protocol::cmdCast(cmd); if (!response.isValid() || response.isError()) { mError = QStringLiteral("Client failed to store payload into file"); qCCritical(AKONADISERVER_LOG) << mError; return false; } QFile file(ExternalPartStorage::resolveAbsolutePath(filename)); if (!file.exists()) { mError = QStringLiteral("External payload file does not exist"); qCCritical(AKONADISERVER_LOG) << mError; return false; } if (file.size() != metaPart.size()) { mError = QStringLiteral("Payload size mismatch"); qCDebug(AKONADISERVER_LOG) << mError << ", client advertised" << metaPart.size() << "bytes, but the file is" << file.size() << "bytes!"; return false; } if (mCheckChanged && !mDataChanged) { // This is invoked only when part already exists, data sizes match and // caller wants to know whether parts really differ mDataChanged = (origData != PartHelper::translateData(part)); } return true; } bool PartStreamer::streamForeignPayload(Part &part, const Protocol::PartMetaData &metaPart) { QByteArray origData; if (!mDataChanged && mCheckChanged) { origData = PartHelper::translateData(part); } { - auto cmd = Protocol::StreamPayloadCommandPtr::create(); - cmd->setPayloadName(metaPart.name()); - cmd->setRequest(Protocol::StreamPayloadCommand::Data); - mConnection->sendResponse(cmd); + Protocol::StreamPayloadCommand cmd; + cmd.setPayloadName(metaPart.name()); + cmd.setRequest(Protocol::StreamPayloadCommand::Data); + mConnection->sendResponse(std::move(cmd)); } const auto cmd = mConnection->readCommand(); const auto response = Protocol::cmdCast(cmd); if (!response.isValid() || response.isError()) { mError = QStringLiteral("Client failed to store payload into file"); qCCritical(AKONADISERVER_LOG) << mError; return false; } // If the part was previously external, clean up the data if (part.storage() == Part::External) { const QString filename = QString::fromUtf8(part.data()); ExternalPartStorage::self()->removePartFile( ExternalPartStorage::resolveAbsolutePath(filename)); } part.setStorage(Part::Foreign); part.setData(response.data()); if (part.isValid()) { if (!part.update()) { mError = QStringLiteral("Failed to update part in database"); return false; } } else { if (!part.insert()) { mError = QStringLiteral("Failed to insert part into database"); return false; } } const QString filename = QString::fromUtf8(response.data()); QFile file(filename); if (!file.exists()) { mError = QStringLiteral("Foreign payload file does not exist"); qCCritical(AKONADISERVER_LOG) << mError; return false; } if (file.size() != metaPart.size()) { mError = QStringLiteral("Payload size mismatch"); qCCritical(AKONADISERVER_LOG) << mError << ", client advertised" << metaPart.size() << "bytes, but the file size is" << file.size() << "bytes!"; return false; } if (mCheckChanged && !mDataChanged) { // This is invoked only when part already exists, data sizes match and // caller wants to know whether parts really differ mDataChanged = (origData != PartHelper::translateData(part)); } return true; } bool PartStreamer::preparePart(bool checkExists, const QByteArray &partName, Part &part) { mError.clear(); mDataChanged = false; const PartType partType = PartTypeHelper::fromFqName(partName); if (checkExists || mCheckChanged) { SelectQueryBuilder qb; qb.addValueCondition(Part::pimItemIdColumn(), Query::Equals, mItem.id()); qb.addValueCondition(Part::partTypeIdColumn(), Query::Equals, partType.id()); if (!qb.exec()) { mError = QStringLiteral("Unable to check item part existence"); return false; } const Part::List result = qb.result(); if (!result.isEmpty()) { part = result.at(0); } } // Shortcut: newly created parts are always "changed" if (!part.isValid()) { mDataChanged = true; } part.setPartType(partType); part.setPimItemId(mItem.id()); return true; } bool PartStreamer::stream(bool checkExists, const QByteArray &partName, qint64 &partSize, bool *changed) { mCheckChanged = (changed != nullptr); if (changed != nullptr) { *changed = false; } Part part; if (!preparePart(checkExists, partName, part)) { return false; } bool ok = streamPayload(part, partName); if (changed && ok && mCheckChanged) { *changed = mDataChanged; } partSize = part.datasize(); return ok; } bool PartStreamer::streamAttribute(bool checkExists, const QByteArray &_partName, const QByteArray &value, bool *changed) { mCheckChanged = (changed != nullptr); if (changed != nullptr) { *changed = false; } QByteArray partName; if (!_partName.startsWith("ATR:")) { partName = "ATR:" + _partName; } else { partName = _partName; } Part part; if (!preparePart(checkExists, partName, part)) { return false; } if (part.isValid()) { if (mCheckChanged) { if (PartHelper::translateData(part) != value) { mDataChanged = true; } } PartHelper::update(&part, value, value.size()); } else { const bool storeInFile = value.size() > DbConfig::configuredDatabase()->sizeThreshold(); part.setDatasize(value.size()); part.setVersion(0); if (storeInFile) { if (!part.insert()) { mError = QStringLiteral("Failed to store part in database"); return false; } PartHelper::update(&part, value, value.size()); } else { part.setData(value); if (!part.insert()) { mError = QStringLiteral("Failed to store part in database"); return false; } } } if (mCheckChanged) { *changed = mDataChanged; } return true; } diff --git a/src/server/tracer.cpp b/src/server/tracer.cpp index f9b9c57a5..dbf66fd4d 100644 --- a/src/server/tracer.cpp +++ b/src/server/tracer.cpp @@ -1,183 +1,183 @@ /*************************************************************************** * Copyright (C) 2006 by Tobias Koenig * * * * This program 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 program 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 General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "tracer.h" #include #include #include "traceradaptor.h" #include "dbustracer.h" #include "filetracer.h" #include "nulltracer.h" #include #include // #define DEFAULT_TRACER QLatin1String( "dbus" ) #define DEFAULT_TRACER QStringLiteral( "null" ) using namespace Akonadi::Server; Tracer *Tracer::mSelf = nullptr; Tracer::Tracer() : mTracerBackend(nullptr) , mSettings(new QSettings(Akonadi::StandardDirs::serverConfigFile(), QSettings::IniFormat)) { activateTracer(currentTracer()); new TracerAdaptor(this); QDBusConnection::sessionBus().registerObject(QStringLiteral("/tracing"), this, QDBusConnection::ExportAdaptors); } Tracer::~Tracer() { delete mTracerBackend; mTracerBackend = nullptr; } Tracer *Tracer::self() { if (!mSelf) { mSelf = new Tracer(); } return mSelf; } void Tracer::beginConnection(const QString &identifier, const QString &msg) { mMutex.lock(); mTracerBackend->beginConnection(identifier, msg); mMutex.unlock(); } void Tracer::endConnection(const QString &identifier, const QString &msg) { mMutex.lock(); mTracerBackend->endConnection(identifier, msg); mMutex.unlock(); } void Tracer::connectionInput(const QString &identifier, const QByteArray &msg) { mMutex.lock(); mTracerBackend->connectionInput(identifier, msg); mMutex.unlock(); } void Akonadi::Server::Tracer::connectionInput(const QString& identifier, qint64 tag, const Protocol::CommandPtr &cmd) { QByteArray msg; if (mTracerBackend->connectionFormat() == TracerInterface::Json) { QJsonObject json; json[QStringLiteral("tag")] = tag; Akonadi::Protocol::toJson(cmd.data(), json); QJsonDocument doc(json); msg = doc.toJson(QJsonDocument::Indented); } else { msg = QByteArray::number(tag) + ' ' + Protocol::debugString(cmd).toUtf8(); } connectionInput(identifier, msg); } void Tracer::connectionOutput(const QString &identifier, const QByteArray &msg) { mMutex.lock(); mTracerBackend->connectionOutput(identifier, msg); mMutex.unlock(); } -void Akonadi::Server::Tracer::connectionOutput(const QString& identifier, qint64 tag, const Protocol::CommandPtr &cmd) +void Tracer::connectionOutput(const QString &identifier, qint64 tag, const Protocol::CommandPtr &cmd) { QByteArray msg; if (mTracerBackend->connectionFormat() == TracerInterface::Json) { QJsonObject json; json[QStringLiteral("tag")] = tag; - Akonadi::Protocol::toJson(cmd.data(), json); - + Protocol::toJson(cmd.data(), json); QJsonDocument doc(json); msg = doc.toJson(QJsonDocument::Indented); } else { msg = QByteArray::number(tag) + ' ' + Protocol::debugString(cmd).toUtf8(); } connectionOutput(identifier, msg); } + void Tracer::signal(const QString &signalName, const QString &msg) { mMutex.lock(); mTracerBackend->signal(signalName, msg); mMutex.unlock(); } void Tracer::signal(const char *signalName, const QString &msg) { signal(QLatin1String(signalName), msg); } void Tracer::warning(const QString &componentName, const QString &msg) { mMutex.lock(); mTracerBackend->warning(componentName, msg); mMutex.unlock(); } void Tracer::error(const QString &componentName, const QString &msg) { mMutex.lock(); mTracerBackend->error(componentName, msg); mMutex.unlock(); } void Tracer::error(const char *componentName, const QString &msg) { error(QLatin1String(componentName), msg); } QString Tracer::currentTracer() const { QMutexLocker locker(&mMutex); return mSettings->value(QStringLiteral("Debug/Tracer"), DEFAULT_TRACER).toString(); } void Tracer::activateTracer(const QString &type) { QMutexLocker locker(&mMutex); delete mTracerBackend; mTracerBackend = nullptr; mSettings->setValue(QStringLiteral("Debug/Tracer"), type); mSettings->sync(); if (type == QLatin1String("file")) { const QString file = mSettings->value(QStringLiteral("Debug/File"), QStringLiteral("/dev/null")).toString(); mTracerBackend = new FileTracer(file); } else if (type == QLatin1String("null")) { mTracerBackend = new NullTracer(); } else { mTracerBackend = new DBusTracer(); } Q_ASSERT(mTracerBackend); } diff --git a/src/server/tracer.h b/src/server/tracer.h index c86ea81c1..3fcbe6e02 100644 --- a/src/server/tracer.h +++ b/src/server/tracer.h @@ -1,158 +1,178 @@ /*************************************************************************** * Copyright (C) 2006 by Tobias Koenig * * * * This program 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 program 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 General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef AKONADI_TRACER_H #define AKONADI_TRACER_H #include #include +#include +#include #include "tracerinterface.h" +#include "protocol_p.h" class QSettings; namespace Akonadi { namespace Protocol { class Command; using CommandPtr = QSharedPointer; } namespace Server { /** * The global tracer instance where all akonadi components can * send their tracing information to. * * The tracer will forward these information to the configured backends. */ class Tracer : public QObject, public TracerInterface { Q_OBJECT public: /** * Returns the global tracer instance. */ static Tracer *self(); /** * Destroys the global tracer instance. */ ~Tracer() override; + template + typename std::enable_if::value>::type + connectionOutput(const QString &identifier, qint64 tag, const T &cmd) { + QByteArray msg; + if (mTracerBackend->connectionFormat() == TracerInterface::Json) { + QJsonObject json; + json[QStringLiteral("tag")] = tag; + cmd.toJson(json); + QJsonDocument doc(json); + + msg = doc.toJson(QJsonDocument::Indented); + } else { + msg = QByteArray::number(tag) + ' ' + Protocol::debugString(cmd).toUtf8(); + } + connectionOutput(identifier, msg); + } + public Q_SLOTS: /** * This method is called whenever a new data (imap) connection to the akonadi server * is established. * * @param identifier The unique identifier for this connection. All input and output * messages for this connection will have the same identifier. * * @param msg A message specific string. */ void beginConnection(const QString &identifier, const QString &msg) override; /** * This method is called whenever a data (imap) connection to akonadi server is * closed. * * @param identifier The unique identifier of this connection. * @param msg A message specific string. */ void endConnection(const QString &identifier, const QString &msg) override; /** * This method is called whenever the akonadi server retrieves some data from the * outside. * * @param identifier The unique identifier of the connection on which the data * is retrieved. * @param msg A message specific string. */ void connectionInput(const QString &identifier, const QByteArray &msg) override; void connectionInput(const QString &identifier, qint64 tag, const Protocol::CommandPtr &cmd); /** * This method is called whenever the akonadi server sends some data out to a client. * * @param identifier The unique identifier of the connection on which the * data is send. * @param msg A message specific string. */ void connectionOutput(const QString &identifier, const QByteArray &msg) override; void connectionOutput(const QString &identifier, qint64 tag, const Protocol::CommandPtr &cmd); /** * This method is called whenever a dbus signal is emitted on the bus. * * @param signalName The name of the signal being sent. * @param msg A message specific string. */ void signal(const QString &signalName, const QString &msg) override; /** Convenience method with internal toLatin1 cast to compile with QT_NO_CAST_FROM_ASCII. */ void signal(const char *signalName, const QString &msg); /** * This method is called whenever a component wants to output a warning. */ void warning(const QString &componentName, const QString &msg) override; /** * This method is called whenever a component wants to output an error. */ void error(const QString &componentName, const QString &msg) override; /** * Convenience method for QT_NO_CAST_FROM_ASCII usage. */ void error(const char *componentName, const QString &msg); /** * Returns the currently activated tracer type. */ QString currentTracer() const; /** * Activates the given tracer type. */ void activateTracer(const QString &type); private: Tracer(); static Tracer *mSelf; TracerInterface *mTracerBackend; mutable QMutex mMutex; QScopedPointer mSettings; }; } // namespace Server } // namespace Akonadi #endif