diff --git a/autotests/server/CMakeLists.txt b/autotests/server/CMakeLists.txt --- a/autotests/server/CMakeLists.txt +++ b/autotests/server/CMakeLists.txt @@ -85,6 +85,7 @@ add_server_test(parthelpertest.cpp) add_server_test(itemretrievertest.cpp) add_server_test(notificationsubscribertest.cpp) +add_server_test(notificationmanagertest.cpp) add_server_test(parttypehelpertest.cpp) add_server_test(collectionstatisticstest.cpp) add_server_test(aggregatedfetchscopetest.cpp) diff --git a/autotests/server/notificationmanagertest.cpp b/autotests/server/notificationmanagertest.cpp new file mode 100644 --- /dev/null +++ b/autotests/server/notificationmanagertest.cpp @@ -0,0 +1,157 @@ +/* + Copyright (c) 2019 David Faure + + 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 "aggregatedfetchscope.h" +#include "entities.h" +#include "notificationmanager.h" +#include "notificationsubscriber.h" + +#include +#include + +using namespace Akonadi; +using namespace Akonadi::Server; + +class TestableNotificationSubscriber : public NotificationSubscriber +{ +public: + TestableNotificationSubscriber(NotificationManager *manager) + : NotificationSubscriber(manager) + { + mSubscriber = "TestSubscriber"; + } + + using NotificationSubscriber::registerSubscriber; + using NotificationSubscriber::modifySubscription; + using NotificationSubscriber::disconnectSubscriber; + +}; + +class NotificationManagerTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void testAggregatedFetchScope() + { + NotificationManager manager(AkThread::NoThread); + QMetaObject::invokeMethod(&manager, "init", Qt::DirectConnection); + + // first subscriber, A + TestableNotificationSubscriber subscriberA(&manager); + Protocol::CreateSubscriptionCommand createCmd; + createCmd.setSession("session1"); + subscriberA.registerSubscriber(createCmd); + QVERIFY(!manager.tagFetchScope()->fetchIdOnly()); + QVERIFY(!manager.collectionFetchScope()->fetchIdOnly()); + QVERIFY(!manager.collectionFetchScope()->fetchStatistics()); + + // set A's subscription settings + Protocol::ModifySubscriptionCommand modifyCmd; + { + Protocol::TagFetchScope tagFetchScope; + tagFetchScope.setFetchIdOnly(true); + modifyCmd.setTagFetchScope(tagFetchScope); + + Protocol::CollectionFetchScope collectionFetchScope; + collectionFetchScope.setFetchIdOnly(true); + collectionFetchScope.setIncludeStatistics(true); + modifyCmd.setCollectionFetchScope(collectionFetchScope); + + Protocol::ItemFetchScope itemFetchScope; + itemFetchScope.setFetch(Protocol::ItemFetchScope::FullPayload); + itemFetchScope.setFetch(Protocol::ItemFetchScope::AllAttributes); + itemFetchScope.setFetch(Protocol::ItemFetchScope::Size); + itemFetchScope.setFetch(Protocol::ItemFetchScope::MTime); + itemFetchScope.setFetch(Protocol::ItemFetchScope::RemoteRevision); + itemFetchScope.setFetch(Protocol::ItemFetchScope::Flags); + itemFetchScope.setFetch(Protocol::ItemFetchScope::RemoteID); + itemFetchScope.setFetch(Protocol::ItemFetchScope::GID); + itemFetchScope.setFetch(Protocol::ItemFetchScope::Tags); + itemFetchScope.setFetch(Protocol::ItemFetchScope::Relations); + itemFetchScope.setFetch(Protocol::ItemFetchScope::VirtReferences); + modifyCmd.setItemFetchScope(itemFetchScope); + } + subscriberA.modifySubscription(modifyCmd); + QVERIFY(manager.tagFetchScope()->fetchIdOnly()); + QVERIFY(manager.collectionFetchScope()->fetchIdOnly()); + QVERIFY(manager.collectionFetchScope()->fetchStatistics()); + QVERIFY(manager.itemFetchScope()->fullPayload()); + QVERIFY(manager.itemFetchScope()->allAttributes()); + + // second subscriber, B + TestableNotificationSubscriber subscriberB(&manager); + subscriberB.registerSubscriber(createCmd); + QVERIFY(!manager.tagFetchScope()->fetchIdOnly()); // A and B don't agree, so: false + QVERIFY(!manager.collectionFetchScope()->fetchIdOnly()); + QVERIFY(manager.collectionFetchScope()->fetchStatistics()); // at least one - so still true + QVERIFY(manager.itemFetchScope()->fullPayload()); + QVERIFY(manager.itemFetchScope()->allAttributes()); + QVERIFY(manager.itemFetchScope()->fetchSize()); + QVERIFY(manager.itemFetchScope()->fetchMTime()); + QVERIFY(manager.itemFetchScope()->fetchRemoteRevision()); + QVERIFY(manager.itemFetchScope()->fetchFlags()); + QVERIFY(manager.itemFetchScope()->fetchRemoteId()); + QVERIFY(manager.itemFetchScope()->fetchGID()); + QVERIFY(manager.itemFetchScope()->fetchTags()); + QVERIFY(manager.itemFetchScope()->fetchRelations()); + QVERIFY(manager.itemFetchScope()->fetchVirtualReferences()); + + // give it the same settings + subscriberB.modifySubscription(modifyCmd); + QVERIFY(manager.tagFetchScope()->fetchIdOnly()); // now they agree + QVERIFY(manager.collectionFetchScope()->fetchIdOnly()); + QVERIFY(manager.collectionFetchScope()->fetchStatistics()); // no change for the "at least one" settings + + // revert B's settings, so we can check what happens when disconnecting + modifyCmd.setTagFetchScope(Protocol::TagFetchScope()); + modifyCmd.setCollectionFetchScope(Protocol::CollectionFetchScope()); + subscriberB.modifySubscription(modifyCmd); + QVERIFY(!manager.tagFetchScope()->fetchIdOnly()); + QVERIFY(!manager.collectionFetchScope()->fetchIdOnly()); + QVERIFY(manager.collectionFetchScope()->fetchStatistics()); + + // B goes away + subscriberB.disconnectSubscriber(); + QVERIFY(manager.tagFetchScope()->fetchIdOnly()); // B cleaned up after itself, so A can have id-only again + QVERIFY(manager.collectionFetchScope()->fetchIdOnly()); + QVERIFY(manager.collectionFetchScope()->fetchStatistics()); + + // A goes away + subscriberA.disconnectSubscriber(); + QVERIFY(!manager.collectionFetchScope()->fetchStatistics()); + QVERIFY(!manager.itemFetchScope()->fullPayload()); + QVERIFY(!manager.itemFetchScope()->allAttributes()); + QVERIFY(!manager.itemFetchScope()->fetchSize()); + QVERIFY(!manager.itemFetchScope()->fetchMTime()); + QVERIFY(!manager.itemFetchScope()->fetchRemoteRevision()); + QVERIFY(!manager.itemFetchScope()->fetchFlags()); + QVERIFY(!manager.itemFetchScope()->fetchRemoteId()); + QVERIFY(!manager.itemFetchScope()->fetchGID()); + QVERIFY(!manager.itemFetchScope()->fetchTags()); + QVERIFY(!manager.itemFetchScope()->fetchRelations()); + QVERIFY(!manager.itemFetchScope()->fetchVirtualReferences()); + } +}; + +AKTEST_MAIN(NotificationManagerTest) + +#include "notificationmanagertest.moc" diff --git a/src/server/akthread.h b/src/server/akthread.h --- a/src/server/akthread.h +++ b/src/server/akthread.h @@ -34,7 +34,8 @@ public: enum StartMode { AutoStart, - ManualStart + ManualStart, + NoThread // for unit-tests }; explicit AkThread(const QString &objectName, QThread::Priority priority = QThread::InheritPriority, @@ -52,6 +53,8 @@ virtual void init(); virtual void quit(); +private: + StartMode m_startMode; }; } diff --git a/src/server/akthread.cpp b/src/server/akthread.cpp --- a/src/server/akthread.cpp +++ b/src/server/akthread.cpp @@ -27,13 +27,15 @@ using namespace Akonadi::Server; AkThread::AkThread(const QString &objectName, StartMode startMode, QThread::Priority priority, QObject *parent) - : QObject(parent) + : QObject(parent), m_startMode(startMode) { setObjectName(objectName); - QThread *thread = new QThread(); - thread->setObjectName(objectName + QStringLiteral("-Thread")); - moveToThread(thread); - thread->start(priority); + if (startMode != NoThread) { + QThread *thread = new QThread(); + thread->setObjectName(objectName + QStringLiteral("-Thread")); + moveToThread(thread); + thread->start(priority); + } if (startMode == AutoStart) { startThread(); @@ -51,6 +53,7 @@ void AkThread::startThread() { + Q_ASSERT(m_startMode != NoThread); #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) const bool init = QMetaObject::invokeMethod(this, &AkThread::init, Qt::QueuedConnection); #else @@ -62,6 +65,8 @@ void AkThread::quitThread() { + if (m_startMode == NoThread) + return; qCDebug(AKONADISERVER_LOG) << "Shutting down" << objectName() << "..."; #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) const bool invoke = QMetaObject::invokeMethod(this, &AkThread::quit, Qt::QueuedConnection); @@ -81,18 +86,16 @@ void AkThread::init() { Q_ASSERT(thread() == QThread::currentThread()); - Q_ASSERT(thread() != qApp->thread()); } void AkThread::quit() { + Q_ASSERT(m_startMode != NoThread); Q_ASSERT(thread() == QThread::currentThread()); - Q_ASSERT(thread() != qApp->thread()); if (DataStore::hasDataStore()) { DataStore::self()->close(); } thread()->quit(); } - diff --git a/src/server/notificationmanager.h b/src/server/notificationmanager.h --- a/src/server/notificationmanager.h +++ b/src/server/notificationmanager.h @@ -47,7 +47,7 @@ Q_OBJECT public: - explicit NotificationManager(); + explicit NotificationManager(StartMode startMode = AutoStart); ~NotificationManager() override; void forgetSubscriber(NotificationSubscriber *subscriber); diff --git a/src/server/notificationmanager.cpp b/src/server/notificationmanager.cpp --- a/src/server/notificationmanager.cpp +++ b/src/server/notificationmanager.cpp @@ -32,6 +32,7 @@ #include #include +#include #include #include #include @@ -42,8 +43,8 @@ using namespace Akonadi; using namespace Akonadi::Server; -NotificationManager::NotificationManager() - : AkThread(QStringLiteral("NotificationManager")) +NotificationManager::NotificationManager(StartMode startMode) + : AkThread(QStringLiteral("NotificationManager"), startMode) , mTimer(nullptr) , mNotifyThreadPool(nullptr) , mDebugNotifications(0) diff --git a/src/server/notificationsubscriber.cpp b/src/server/notificationsubscriber.cpp --- a/src/server/notificationsubscriber.cpp +++ b/src/server/notificationsubscriber.cpp @@ -152,28 +152,23 @@ changeNtf->setOperation(Protocol::SubscriptionChangeNotification::Remove); mManager->slotNotify({ changeNtf }); - disconnect(mSocket, &QLocalSocket::disconnected, - this, &NotificationSubscriber::socketDisconnected); - mSocket->close(); + if (mSocket) { + disconnect(mSocket, &QLocalSocket::disconnected, + this, &NotificationSubscriber::socketDisconnected); + mSocket->close(); + } - // Unregister ourselves from the aggergated collection fetch scope + // Unregister ourselves from the aggregated 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); - } + cfs->apply(mCollectionFetchScope, Protocol::CollectionFetchScope()); cfs->removeSubscriber(); auto tfs = mManager->tagFetchScope(); + tfs->apply(mTagFetchScope, Protocol::TagFetchScope()); tfs->removeSubscriber(); auto ifs = mManager->itemFetchScope(); + ifs->apply(mItemFetchScope, Protocol::ItemFetchScope()); ifs->removeSubscriber(); mManager->forgetSubscriber(this);