diff options
author | Jean-Luc Lamadon <jean-luc.lamadon@nokia.com> | 2011-01-05 15:17:16 +0200 |
---|---|---|
committer | Jean-Luc Lamadon <jean-luc.lamadon@nokia.com> | 2011-01-05 15:17:16 +0200 |
commit | 671f50a6a5f5a6f8861c01167dfd20d949a82813 (patch) | |
tree | 880d3f8219639874273b45f04ba2ebd1359b8074 | |
parent | e931b6601c5290b8302943a146336f56a92c1983 (diff) | |
parent | 08fd4537cd3a4c82ab40f8dcbc2b753c23135867 (diff) |
Merge branch 'properly_block'
25 files changed, 508 insertions, 22 deletions
diff --git a/configure.ac b/configure.ac index 1e83e83a..1160952a 100644 --- a/configure.ac +++ b/configure.ac @@ -61,6 +61,7 @@ AC_CONFIG_FILES([ libcontextsubscriber/customer-tests/testplugins/timeplugin1/Makefile libcontextsubscriber/customer-tests/testplugins/timeplugin2/Makefile libcontextsubscriber/customer-tests/forward-compatible/Makefile + libcontextsubscriber/customer-tests/waitforsubs/Makefile libcontextsubscriber/doc/Makefile libcontextsubscriber/man/Makefile libcontextsubscriber/src/Makefile diff --git a/debian/libcontextsubscriber-tests.install b/debian/libcontextsubscriber-tests.install index 51c93c97..bdcb6351 100644 --- a/debian/libcontextsubscriber-tests.install +++ b/debian/libcontextsubscriber-tests.install @@ -9,5 +9,7 @@ usr/share/libcontextsubscriber-tests/asynchronicity/slowfast.context usr/share/libcontextsubscriber-tests/registry/*.py usr/share/libcontextsubscriber-tests/pluginchanging/pluginchanging.py usr/share/libcontextsubscriber-tests/pluginchanging/time*.context.temp +usr/share/libcontextsubscriber-tests/waitforsubscription/context-provide.context usr/lib/contextkit/subscriber-test-plugins/contextsubscribertime*.so +usr/lib/libcontextsubscriber-tests/waitforsubscriptiontests usr/bin/check-version usr/lib/contextkit/subscriber-check-version diff --git a/libcontextsubscriber/.gitignore b/libcontextsubscriber/.gitignore index 5f2d18c3..81aff450 100644 --- a/libcontextsubscriber/.gitignore +++ b/libcontextsubscriber/.gitignore @@ -39,6 +39,7 @@ coverage/ /customer-tests/testplugins/contextsubscribertime1.so /customer-tests/testplugins/contextsubscribertime2.so +/customer-tests/waitforsubs/waitforsubscriptiontests /customer-tests/coverage-build/Makefile.coverage # Other binaries diff --git a/libcontextsubscriber/customer-tests/Makefile.am b/libcontextsubscriber/customer-tests/Makefile.am index 99fca067..1c581d4b 100644 --- a/libcontextsubscriber/customer-tests/Makefile.am +++ b/libcontextsubscriber/customer-tests/Makefile.am @@ -1,4 +1,4 @@ -SUBDIRS = update-contextkit-providers testplugins forward-compatible +SUBDIRS = update-contextkit-providers testplugins forward-compatible waitforsubs libcontextsubscribertestsdir = $(datadir)/libcontextsubscriber-tests dist_libcontextsubscribertests_DATA = tests.xml diff --git a/libcontextsubscriber/customer-tests/runTests.sh b/libcontextsubscriber/customer-tests/runTests.sh index 97645849..f74975aa 100755 --- a/libcontextsubscriber/customer-tests/runTests.sh +++ b/libcontextsubscriber/customer-tests/runTests.sh @@ -1,7 +1,8 @@ #!/bin/bash -e cd $(dirname $0) -DIRS="commander subscription asynchronicity registry pluginchanging" +DIRS_PYTHON="commander subscription asynchronicity registry pluginchanging" +DIRS_CHECK="waitforsubs" . ./env.sh make -C ../../libcontextprovider/src @@ -24,7 +25,7 @@ make -C testplugins if pkg-config contextprovider-1.0 || [ -e ../../libcontextprovider/src/.libs/libcontextprovider.so ] then - for dir in $DIRS; do + for dir in $DIRS_PYTHON; do echo "Running tests in $dir" cd $dir for file in *.py; do @@ -32,6 +33,10 @@ then done cd .. done + for dir in $DIRS_CHECK; do + echo "Running tests in $dir" + make -C $dir check-customer + done else echo "libcontextprovider is not installed nor built" exit 1 diff --git a/libcontextsubscriber/customer-tests/testplugins/timeplugin.cpp b/libcontextsubscriber/customer-tests/testplugins/timeplugin.cpp index b4df1c4d..dbb86022 100644 --- a/libcontextsubscriber/customer-tests/testplugins/timeplugin.cpp +++ b/libcontextsubscriber/customer-tests/testplugins/timeplugin.cpp @@ -59,6 +59,14 @@ void TimePlugin::unsubscribe(QSet<QString> keys) timer.stop(); } +void TimePlugin::blockUntilReady() +{ +} + +void TimePlugin::blockUntilSubscribed(const QString& key) +{ +} + void TimePlugin::onTimeout() { contextDebug() << "Timeout"; diff --git a/libcontextsubscriber/customer-tests/testplugins/timeplugin.h b/libcontextsubscriber/customer-tests/testplugins/timeplugin.h index cd4f044c..051548b0 100644 --- a/libcontextsubscriber/customer-tests/testplugins/timeplugin.h +++ b/libcontextsubscriber/customer-tests/testplugins/timeplugin.h @@ -46,6 +46,8 @@ public: explicit TimePlugin(); virtual void subscribe(QSet<QString> keys); virtual void unsubscribe(QSet<QString> keys); + virtual void blockUntilReady(); + virtual void blockUntilSubscribed(const QString& key); private Q_SLOTS: void onTimeout(); diff --git a/libcontextsubscriber/customer-tests/tests.xml b/libcontextsubscriber/customer-tests/tests.xml index f4402f10..f852363d 100644 --- a/libcontextsubscriber/customer-tests/tests.xml +++ b/libcontextsubscriber/customer-tests/tests.xml @@ -103,6 +103,9 @@ requirement="" timeout="20"> <step expected_result="0">cd /usr/share/libcontextsubscriber-tests/forward-compatible ; ./test.sh ;</step> </case> + <case name="waitforsubscription" description="waitForSubscription[AndBlock] functionalities" requirement="" timeout="50"> + <step expected_result="0">. /tmp/session_bus_address.user;export CONTEXT_PROVIDERS=/usr/share/libcontextsubscriber-tests/waitforsubscription/ ;cd /usr/lib/libcontextsubscriber-tests/;./waitforsubscriptiontests</step> + </case> <environments> <scratchbox>false</scratchbox> <hardware>true</hardware> diff --git a/libcontextsubscriber/customer-tests/waitforsubs/Makefile.am b/libcontextsubscriber/customer-tests/waitforsubs/Makefile.am new file mode 100644 index 00000000..f35eacd6 --- /dev/null +++ b/libcontextsubscriber/customer-tests/waitforsubs/Makefile.am @@ -0,0 +1,28 @@ +testdir = $(libdir)/libcontextsubscriber-tests +test_PROGRAMS = waitforsubscriptiontests + +testdatadir = $(datadir)/libcontextsubscriber-tests/waitforsubscription +testdata_DATA = context-provide.context + +waitforsubscriptiontests_SOURCES = \ + testwaitforsubscription.cpp \ + testwaitforsubscription.h + +check-customer: $(test_PROGRAMS) + ./.libs/waitforsubscriptiontests + +AM_CXXFLAGS = $(QtCore_CFLAGS) $(QtTest_CFLAGS) -I$(srcdir)/../../src \ + -I$(top_srcdir)/common + +LIBS += $(QtCore_LIBS) $(QtTest_LIBS) +waitforsubscriptiontests_LDADD = ../../src/libcontextsubscriber.la $(top_builddir)/common/libcommon.la + +../../src/libcontextsubscriber.la: FORCE + $(MAKE) -C ../../src libcontextsubscriber.la + +.PHONY: FORCE + +# moccing +nodist_waitforsubscriptiontests_SOURCES = mocs.cpp +QT_TOMOC = $(filter %.h, $(waitforsubscriptiontests_SOURCES)) +include $(top_srcdir)/am/qt.am diff --git a/libcontextsubscriber/customer-tests/waitforsubs/context-provide.context b/libcontextsubscriber/customer-tests/waitforsubs/context-provide.context new file mode 100644 index 00000000..4ce177c9 --- /dev/null +++ b/libcontextsubscriber/customer-tests/waitforsubs/context-provide.context @@ -0,0 +1,23 @@ +<?xml version="1.0"?> +<provider bus="session" service="com.nokia.test"> + <key name="Test.Prop"> + <type> + string + </type> + </key> + <key name="Test.Prop2"> + <type> + string + </type> + </key> + <key name="Test.Prop3"> + <type> + string + </type> + </key> + <key name="Test.Prop4"> + <type> + string + </type> + </key> +</provider> diff --git a/libcontextsubscriber/customer-tests/waitforsubs/testwaitforsubscription.cpp b/libcontextsubscriber/customer-tests/waitforsubs/testwaitforsubscription.cpp new file mode 100644 index 00000000..668157d9 --- /dev/null +++ b/libcontextsubscriber/customer-tests/waitforsubs/testwaitforsubscription.cpp @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2008-2010 Nokia Corporation. + * + * Contact: Marius Vollmer <marius.vollmer@nokia.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * version 2.1 as published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include "testwaitforsubscription.h" + +#include "contextproperty.h" +#include "sconnect.h" + +#include <QtTest/QtTest> +#include <QDebug> +#include <QProcess> + +#include <stdlib.h> + +WaitForSubscriptionTests::WaitForSubscriptionTests() + : providerStarted(false), isReadyToRead(false) +{ +} + +void WaitForSubscriptionTests::initTestCase() +{ + setenv("CONTEXT_PROVIDERS", ".", 0); + provider = new QProcess(); + provider->start("context-provide", + QStringList() << "--session" + << "com.nokia.test" + << "string" << "Test.Prop" << "someValue" + << "string" << "Test.Prop2" << "someOther" + << "string" << "Test.Prop3" << "thirdValue" + << "string" << "Test.Prop4" << "fourthValue"); + // Record whether the client was successfully started + sconnect(provider, SIGNAL(readyReadStandardOutput()), + this, SLOT(readStandardOutput())); + + providerStarted = provider->waitForStarted(); + + while (!isReadyToRead) { + QCoreApplication::processEvents(QEventLoop::AllEvents); + } +} + +void WaitForSubscriptionTests::cleanupTestCase() +{ + if (providerStarted) { + provider->kill(); + provider->waitForFinished(); + } + delete provider; +} + +void WaitForSubscriptionTests::waitAndBlockExisting() +{ + QVERIFY(providerStarted); + + ContextProperty prop("Test.Prop"); + Helper h; + QTimer::singleShot(0, &h, SLOT(onTimeout())); + + prop.waitForSubscription(true); + + // The event loop hasn't spinned... + QVERIFY(!h.processed); + + // And the property should have a value + QCOMPARE(prop.value(), QVariant(QString("someValue"))); + + // For test sanity; check that we *did* schedule the event correctly + QTest::qWait(100); + QVERIFY(h.processed); +} + +void WaitForSubscriptionTests::waitAndBlockNonExisting() +{ + QVERIFY(providerStarted); + + ContextProperty prop("Test.NotThereAtAll"); + Helper h; + QTimer::singleShot(0, &h, SLOT(onTimeout())); + + prop.waitForSubscription(true); + + // The event loop hasn't spinned... + QVERIFY(!h.processed); + + // And the property shouldn't have a value + QCOMPARE(prop.value(), QVariant()); + + // For test sanity; check that we *did* schedule the event correctly + QTest::qWait(100); + QVERIFY(h.processed); +} + +void WaitForSubscriptionTests::waitAndSpinExisting() +{ + QVERIFY(providerStarted); + + // Use a different property here, to make sure the previous test doesn't + // affect this one. + ContextProperty prop("Test.Prop2"); + Helper h; + QTimer::singleShot(0, &h, SLOT(onTimeout())); + + prop.waitForSubscription(); + + // The event loop has spinned... + QVERIFY(h.processed); + + // And the property should have a value + QCOMPARE(prop.value(), QVariant(QString("someOther"))); +} + +void WaitForSubscriptionTests::waitAndSpinNonExisting() +{ + QVERIFY(providerStarted); + + ContextProperty prop("Test.NotThereAtAll"); + Helper h; + QTimer::singleShot(0, &h, SLOT(onTimeout())); + + prop.waitForSubscription(); + + // The event loop hasn't spinned, since we realize so early that "we cannot + // ever hope to subscribe to this property". + QVERIFY(!h.processed); + + // And the property shouldn't have a value + QCOMPARE(prop.value(), QVariant()); + + // For test sanity; check that we *did* schedule the event correctly + QTest::qWait(100); + QVERIFY(h.processed); +} + +void WaitForSubscriptionTests::waitAndBlockSubscribed() +{ + QVERIFY(providerStarted); + + ContextProperty prop("Test.Prop3"); + + // Wait until the property has a value... + QTime timer; + timer.start(); + while (prop.value().isNull() && timer.elapsed() < 5000) + QTest::qWait(100); + + QCOMPARE(prop.value(), QVariant(QString("thirdValue"))); + + Helper h; + QTimer::singleShot(0, &h, SLOT(onTimeout())); + + prop.waitForSubscription(true); + + // The event loop hasn't spinned... + QVERIFY(!h.processed); + + // And the property should have a value + QCOMPARE(prop.value(), QVariant(QString("thirdValue"))); + + // For test sanity; check that we *did* schedule the event correctly + QTest::qWait(100); + QVERIFY(h.processed); +} + +void WaitForSubscriptionTests::waitAndSpinSubscribed() +{ + QVERIFY(providerStarted); + + ContextProperty prop("Test.Prop4"); + + // Wait until the property has a value... + QTime timer; + timer.start(); + while (prop.value().isNull() && timer.elapsed() < 5000) + QTest::qWait(100); + + QCOMPARE(prop.value(), QVariant(QString("fourthValue"))); + + Helper h; + QTimer::singleShot(0, &h, SLOT(onTimeout())); + + prop.waitForSubscription(); + + // The event loop hasn't spinned since the property was already subscribed + QVERIFY(!h.processed); + + + // For test sanity; check that we *did* schedule the event correctly + QTest::qWait(100); + QVERIFY(h.processed); +} + +void WaitForSubscriptionTests::readStandardOutput() +{ + isReadyToRead = true; +} + +QTEST_MAIN(WaitForSubscriptionTests); diff --git a/libcontextsubscriber/customer-tests/waitforsubs/testwaitforsubscription.h b/libcontextsubscriber/customer-tests/waitforsubs/testwaitforsubscription.h new file mode 100644 index 00000000..4f658241 --- /dev/null +++ b/libcontextsubscriber/customer-tests/waitforsubs/testwaitforsubscription.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2008-2010 Nokia Corporation. + * + * Contact: Marius Vollmer <marius.vollmer@nokia.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * version 2.1 as published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include <QObject> + +class QProcess; + +class WaitForSubscriptionTests : public QObject +{ + Q_OBJECT + + public: + WaitForSubscriptionTests(); +public Q_SLOTS: + void readStandardOutput(); + +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + + void waitAndBlockExisting(); + void waitAndBlockNonExisting(); + + void waitAndSpinExisting(); + void waitAndSpinNonExisting(); + + void waitAndBlockSubscribed(); + void waitAndSpinSubscribed(); + +private: + QProcess* provider; + bool providerStarted; + bool isReadyToRead; +}; + +class Helper : public QObject +{ + Q_OBJECT +public: + bool processed; + Helper() : processed(false) + { + } + +public Q_SLOTS: + void onTimeout() + { + processed = true; + } +}; diff --git a/libcontextsubscriber/src/contextkitplugin.cpp b/libcontextsubscriber/src/contextkitplugin.cpp index 2f038d40..84e5a53f 100644 --- a/libcontextsubscriber/src/contextkitplugin.cpp +++ b/libcontextsubscriber/src/contextkitplugin.cpp @@ -88,6 +88,7 @@ ContextKitPlugin::ContextKitPlugin(const QDBusConnection bus, const QString& bus managerInterface(0), connection(new QDBusConnection(bus)), busName(busName), + newProtocol(true), defaultNewProtocol(true) { reset(); @@ -114,7 +115,7 @@ void ContextKitPlugin::reset() subscriberInterface = 0; delete(managerInterface); managerInterface = 0; - newProtocol = false; + newProtocol = defaultNewProtocol; // Disconnect the ValueChanged signal for all keys (object paths) connection->disconnect(busName, "", propertyIName, "ValueChanged", this, SLOT(onNewValueChanged(QList<QVariant>,quint64,QDBusMessage))); @@ -124,14 +125,6 @@ void ContextKitPlugin::reset() /// appears. void ContextKitPlugin::onProviderAppeared() { - // It is possible that this function is called and we have a Subscribe call - // in progress. This happens when things happen in the following order: - // 1. the subscriber is started - // 2. the subscriber optimistically sends the Subscribe call - // 3. the provider is started (quick enough to handle the Subscribe call) - // 4. providerListener notices that the provider was started - // In this case, the plugin is in "ready" state already. - contextDebug() << "Provider appeared:" << busName; reset(); @@ -232,7 +225,7 @@ void ContextKitPlugin::subscribe(QSet<QString> keys) // previous async call. (We emit "ready" when handling // GetSubscriber. "Ready" is not queued, and the above // layer can call subscribe when handling it.) - + pendingKeys.insert(key); QMetaObject::invokeMethod(this, "newSubscribe", Qt::QueuedConnection, Q_ARG(QString, key)); } else { @@ -242,6 +235,13 @@ void ContextKitPlugin::subscribe(QSet<QString> keys) void ContextKitPlugin::newSubscribe(const QString& key) { + if (pendingKeys.contains(key) == false) { + // this key was already handled, probably because + // waitForSubscriptionAndBlock forced the subscription to happen. + return; + } + pendingKeys.remove(key); + QString objectPath = keyToPath(key); // Store the "object path -> key" mapping so that we can transform // back when a valueChanged signal comes over D-Bus. (Note the @@ -259,6 +259,7 @@ void ContextKitPlugin::newSubscribe(const QString& key) SLOT(onNewValueChanged(QList<QVariant>,quint64,QDBusMessage))); PendingSubscribeWatcher *psw = new PendingSubscribeWatcher(pc, key, this); + pendingWatchers.insert(key, psw); sconnect(psw, SIGNAL(subscribeFinished(QString)), this, @@ -271,6 +272,14 @@ void ContextKitPlugin::newSubscribe(const QString& key) SIGNAL(valueChanged(QString,TimedValue)), this, SIGNAL(valueChanged(QString,TimedValue))); + sconnect(psw, + SIGNAL(subscribeFinished(QString)), + this, + SLOT(removePendingWatcher(const QString&))); + sconnect(psw, + SIGNAL(subscribeFailed(QString,QString)), + this, + SLOT(removePendingWatcher(const QString&))); } @@ -370,6 +379,31 @@ void ContextKitPlugin::onNewValueChanged(QList<QVariant> value, } } +void ContextKitPlugin::blockUntilReady() +{ + // This will result in emitting ready() immediately; we don't really block. + // This optimistic plugin is in "ready" state even if it's not sure whether + // the provider is running. + onProviderAppeared(); +} + +void ContextKitPlugin::blockUntilSubscribed(const QString& key) +{ + // Force the subscriptions (that were scheduled) to happen now + while (newProtocol && pendingKeys.size() > 0) { + QString key = *(pendingKeys.constBegin()); + newSubscribe(key); + } + if (pendingWatchers.contains(key)) { + pendingWatchers.value(key)->waitForFinished(); + } +} + +void ContextKitPlugin::removePendingWatcher(const QString& key) +{ + pendingWatchers.remove(key); +} + PendingSubscribeWatcher::PendingSubscribeWatcher(const QDBusPendingCall &call, const QString &key, QObject * parent) : diff --git a/libcontextsubscriber/src/contextkitplugin.h b/libcontextsubscriber/src/contextkitplugin.h index d1aab54b..eed4bdc0 100644 --- a/libcontextsubscriber/src/contextkitplugin.h +++ b/libcontextsubscriber/src/contextkitplugin.h @@ -71,6 +71,8 @@ public: void subscribe(QSet<QString> keys); void unsubscribe(QSet<QString> keys); void setDefaultNewProtocol(bool s); + void blockUntilReady(); + void blockUntilSubscribed(const QString& key); Q_SIGNALS: #ifdef DOXYGEN_ONLY @@ -93,6 +95,7 @@ private Q_SLOTS: void onProviderAppeared(); void onProviderDisappeared(); void newSubscribe(const QString& key); + void removePendingWatcher(const QString& key); private: static QString keyToPath(QString key); @@ -114,6 +117,8 @@ private: QHash<QString, QString> objectPathToKey; + QHash<QString, PendingSubscribeWatcher*> pendingWatchers; + QSet<QString> pendingKeys; }; QVariant demarshallValue(const QVariant &v); diff --git a/libcontextsubscriber/src/contextproperty.cpp b/libcontextsubscriber/src/contextproperty.cpp index 3a8f33c3..3ed377b9 100644 --- a/libcontextsubscriber/src/contextproperty.cpp +++ b/libcontextsubscriber/src/contextproperty.cpp @@ -307,6 +307,25 @@ void ContextProperty::waitForSubscription() const } } +/// Suspends the execution of the current thread until subcription is complete +/// for this context property. Spins the event loop if \a block is false, and +/// blocks (e.g., select / poll with a socket) if \a block is true. Calling this +/// function while the subscription is not in progress (because it has completed +/// already or because the property is currently unsubscribed) does nothing. +/// Calling this function with \a block = true is only allowed for +/// ContextProperty objects associated with the main thread, and calling this +/// function is only allowed in the main thread. +void ContextProperty::waitForSubscription(bool block) const +{ + if (!block) { + waitForSubscription(); + return; + } + if (!priv->subscribed) + return; + priv->handle->blockUntilSubscribed(); +} + /// Sets all of the ContextProperty instances immune to 'external /// commanding'. This is only intended to be used by the Context /// Commander itself, so that it can use ContextProperties without diff --git a/libcontextsubscriber/src/contextproperty.h b/libcontextsubscriber/src/contextproperty.h index e4848177..824097b7 100644 --- a/libcontextsubscriber/src/contextproperty.h +++ b/libcontextsubscriber/src/contextproperty.h @@ -47,7 +47,8 @@ public: void subscribe () const; void unsubscribe () const; - void waitForSubscription () const; + void waitForSubscription() const; + void waitForSubscription(bool block) const; static void ignoreCommander(); static void setTypeCheck(bool typeCheck); diff --git a/libcontextsubscriber/src/iproviderplugin.h b/libcontextsubscriber/src/iproviderplugin.h index 811af127..49f0c48d 100644 --- a/libcontextsubscriber/src/iproviderplugin.h +++ b/libcontextsubscriber/src/iproviderplugin.h @@ -40,6 +40,8 @@ class IProviderPlugin : public QObject public: virtual void subscribe(QSet<QString> keys) = 0; virtual void unsubscribe(QSet<QString> keys) = 0; + virtual void blockUntilReady() = 0; + virtual void blockUntilSubscribed(const QString& key) = 0; Q_SIGNALS: void ready(); diff --git a/libcontextsubscriber/src/propertyhandle.cpp b/libcontextsubscriber/src/propertyhandle.cpp index 3289d897..f8172a25 100644 --- a/libcontextsubscriber/src/propertyhandle.cpp +++ b/libcontextsubscriber/src/propertyhandle.cpp @@ -286,6 +286,20 @@ void PropertyHandle::onValueChanged() } } +void PropertyHandle::blockUntilSubscribed() +{ + // Call blockUntilSubscribed once per each provider in pendingSubscriptions. + // Making the call might or might not result in removing the provider from + // pendingSubscriptions (depending on whether some events on the way are + // queued or not), so make no assumptions on that. + QSet<Provider*> pendingSubscriptionsCopy = pendingSubscriptions; + while (pendingSubscriptionsCopy.size() > 0) { + Provider* provider = *(pendingSubscriptionsCopy.constBegin()); + provider->blockUntilSubscribed(myKey); + pendingSubscriptionsCopy.remove(provider); + } +} + const ContextPropertyInfo* PropertyHandle::info() const { return myInfo; diff --git a/libcontextsubscriber/src/propertyhandle.h b/libcontextsubscriber/src/propertyhandle.h index b74fcc7b..220829f3 100644 --- a/libcontextsubscriber/src/propertyhandle.h +++ b/libcontextsubscriber/src/propertyhandle.h @@ -58,6 +58,8 @@ public: static void ignoreCommander(); static void setTypeCheck(bool typeCheck); + void blockUntilSubscribed(); + Q_SIGNALS: void valueChanged(); diff --git a/libcontextsubscriber/src/provider.cpp b/libcontextsubscriber/src/provider.cpp index 5c3d8f47..a38110bf 100644 --- a/libcontextsubscriber/src/provider.cpp +++ b/libcontextsubscriber/src/provider.cpp @@ -110,7 +110,8 @@ namespace ContextSubscriber { /// Stores the passed plugin name and construction paramater, then /// moves into the main thread and queues a constructPlugin call. Provider::Provider(const ContextProviderInfo& providerInfo) - : plugin(0), pluginState(INITIALIZING), providerInfo(providerInfo) + : plugin(0), pluginState(INITIALIZING), providerInfo(providerInfo), + subscribeLock(QMutex::Recursive), pluginConstructed(false) { // Move the PropertyHandle (and all children) to main thread. moveToThread(QCoreApplication::instance()->thread()); @@ -125,6 +126,10 @@ Provider::Provider(const ContextProviderInfo& providerInfo) /// up with the name of the function). void Provider::constructPlugin() { + if (pluginConstructed) + return; + pluginConstructed = true; + contextDebug() << F_PLUGINS; if (providerInfo.plugin == "contextkit-dbus") { plugin = contextKitPluginFactory(providerInfo.constructionString); @@ -192,16 +197,16 @@ void Provider::constructPlugin() sconnect(plugin, SIGNAL(failed(QString)), this, SLOT(onPluginFailed(QString))); - // The following signals are queued, because a plugin might emit - // subscribeFinished() right in the subscribe() call. + // The following signals (as well as valueChanged) can be emitted in + // subscribe() of the plugin. Here we utilize the fact that our mutexes are + // recursive. qRegisterMetaType<TimedValue>("TimedValue"); sconnect(plugin, SIGNAL(subscribeFinished(QString, TimedValue)), - this, SLOT(onPluginSubscribeFinished(QString, TimedValue)), - Qt::QueuedConnection); + this, SLOT(onPluginSubscribeFinished(QString, TimedValue))); sconnect(plugin, SIGNAL(subscribeFinished(QString)), - this, SLOT(onPluginSubscribeFinished(QString)), Qt::QueuedConnection); + this, SLOT(onPluginSubscribeFinished(QString))); sconnect(plugin, SIGNAL(subscribeFailed(QString, QString)), - this, SLOT(onPluginSubscribeFailed(QString, QString)), Qt::QueuedConnection); + this, SLOT(onPluginSubscribeFailed(QString, QString))); } /// Updates \c pluginState to \c READY and requests subscription for @@ -210,6 +215,10 @@ void Provider::onPluginReady() { contextDebug(); + // Ignore the signal if the plugin is already in the ready state. + if (pluginState == READY) + return; + QMutexLocker lock(&subscribeLock); // Renew the subscriptions (if any). // Renewing happens when a provider has disappeared and now it appeared again. @@ -392,6 +401,33 @@ void Provider::onPluginValueChanged(QString key, QVariant newValue) contextDebug() << "Received a property not subscribed to:" << key; } +void Provider::blockUntilSubscribed(const QString& key) +{ + // This function might be called before the plugin is constructed (since + // it's constructed in a queued way). If so, construct it now. + constructPlugin(); + + if (plugin == 0) // we couldn't construct a plugin + { + return; + } + + // Maybe the plugin hasn't had time to emit ready() yet. Force the plugin + // to be ready, then. + + // When this is called, the plugin waits until it's ready, and emits the + // ready() signal (the connection is not queued). As a result, we + // handleSubscribes() and that calls subscribe(). Or, the plugin emits + // failed(), we handleSubscribes(), and don't call anything. + plugin->blockUntilReady(); + + if (pluginState == READY) { + // And tell the plugin to block until the subscription of this key is + // complete. + plugin->blockUntilSubscribed(key); + } +} + TimedValue Provider::get(const QString &key) const { return values.value(key, TimedValue(QVariant())); diff --git a/libcontextsubscriber/src/provider.h b/libcontextsubscriber/src/provider.h index 6722ef94..789e2796 100644 --- a/libcontextsubscriber/src/provider.h +++ b/libcontextsubscriber/src/provider.h @@ -52,6 +52,8 @@ public: TimedValue get(const QString &key) const; void clearValues(); + void blockUntilSubscribed(const QString& key); + Q_SIGNALS: void subscribeFinished(Provider *provider, QString key); void valueChanged(QString key); @@ -84,6 +86,7 @@ private: QSet<QString> subscribedKeys; ///< The keys that should be currently subscribed to QMap<QString, TimedValue> values; ///< A cache of values already received from the plugin + bool pluginConstructed; }; } // end namespace diff --git a/libcontextsubscriber/unit-tests/propertyhandle/provider.h b/libcontextsubscriber/unit-tests/propertyhandle/provider.h index 64380996..e87e5024 100644 --- a/libcontextsubscriber/unit-tests/propertyhandle/provider.h +++ b/libcontextsubscriber/unit-tests/propertyhandle/provider.h @@ -44,6 +44,7 @@ public: void unsubscribe(const QString &key); TimedValue get(const QString &key) const; void clearValues(); + void blockUntilSubscribed(const QString& key); Q_SIGNALS: void subscribeFinished(QString key); diff --git a/libcontextsubscriber/unit-tests/propertyhandle/testpropertyhandle.cpp b/libcontextsubscriber/unit-tests/propertyhandle/testpropertyhandle.cpp index d9627776..8bb6ca1f 100644 --- a/libcontextsubscriber/unit-tests/propertyhandle/testpropertyhandle.cpp +++ b/libcontextsubscriber/unit-tests/propertyhandle/testpropertyhandle.cpp @@ -162,6 +162,10 @@ void Provider::unsubscribe(const QString& key) unsubscribeProviderNames << myName; } +void Provider::blockUntilSubscribed(const QString& key) +{ +} + void Provider::clearValues() { cachedValue = TimedValue(QVariant()); diff --git a/libcontextsubscriber/unit-tests/provider/contextkitplugin.h b/libcontextsubscriber/unit-tests/provider/contextkitplugin.h index 5b491db9..9fdc5349 100644 --- a/libcontextsubscriber/unit-tests/provider/contextkitplugin.h +++ b/libcontextsubscriber/unit-tests/provider/contextkitplugin.h @@ -44,6 +44,8 @@ class ContextKitPlugin : public IProviderPlugin public: void subscribe(QSet<QString> keys); void unsubscribe(QSet<QString> keys); + void blockUntilReady(); + void blockUntilSubscribed(const QString& key); Q_SIGNALS: void ready(); diff --git a/libcontextsubscriber/unit-tests/provider/testprovider.cpp b/libcontextsubscriber/unit-tests/provider/testprovider.cpp index 09c7a071..6ed5f7a7 100644 --- a/libcontextsubscriber/unit-tests/provider/testprovider.cpp +++ b/libcontextsubscriber/unit-tests/provider/testprovider.cpp @@ -133,6 +133,14 @@ void ContextKitPlugin::unsubscribe(QSet<QString> keys) unsubscribeRequested += keys; } +void ContextKitPlugin::blockUntilSubscribed(const QString& key) +{ +} + +void ContextKitPlugin::blockUntilReady() +{ +} + // // Definition of testcases // @@ -314,7 +322,6 @@ void ProviderUnitTests::pluginValueChanges() provider->subscribe("test.key1"); provider->callAllMethodsInQueue(); Q_EMIT pluginInstances[conStr]->subscribeFinished("test.key1"); - QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents); // signal delivery is queued QSignalSpy spy(provider, SIGNAL(valueChanged(QString))); Q_EMIT pluginInstances[conStr]->valueChanged("test.key1", QVariant(42)); |