aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJean-Luc Lamadon <jean-luc.lamadon@nokia.com>2011-01-05 15:17:16 +0200
committerJean-Luc Lamadon <jean-luc.lamadon@nokia.com>2011-01-05 15:17:16 +0200
commit671f50a6a5f5a6f8861c01167dfd20d949a82813 (patch)
tree880d3f8219639874273b45f04ba2ebd1359b8074
parente931b6601c5290b8302943a146336f56a92c1983 (diff)
parent08fd4537cd3a4c82ab40f8dcbc2b753c23135867 (diff)
Merge branch 'properly_block'
-rw-r--r--configure.ac1
-rw-r--r--debian/libcontextsubscriber-tests.install2
-rw-r--r--libcontextsubscriber/.gitignore1
-rw-r--r--libcontextsubscriber/customer-tests/Makefile.am2
-rwxr-xr-xlibcontextsubscriber/customer-tests/runTests.sh9
-rw-r--r--libcontextsubscriber/customer-tests/testplugins/timeplugin.cpp8
-rw-r--r--libcontextsubscriber/customer-tests/testplugins/timeplugin.h2
-rw-r--r--libcontextsubscriber/customer-tests/tests.xml3
-rw-r--r--libcontextsubscriber/customer-tests/waitforsubs/Makefile.am28
-rw-r--r--libcontextsubscriber/customer-tests/waitforsubs/context-provide.context23
-rw-r--r--libcontextsubscriber/customer-tests/waitforsubs/testwaitforsubscription.cpp215
-rw-r--r--libcontextsubscriber/customer-tests/waitforsubs/testwaitforsubscription.h68
-rw-r--r--libcontextsubscriber/src/contextkitplugin.cpp54
-rw-r--r--libcontextsubscriber/src/contextkitplugin.h5
-rw-r--r--libcontextsubscriber/src/contextproperty.cpp19
-rw-r--r--libcontextsubscriber/src/contextproperty.h3
-rw-r--r--libcontextsubscriber/src/iproviderplugin.h2
-rw-r--r--libcontextsubscriber/src/propertyhandle.cpp14
-rw-r--r--libcontextsubscriber/src/propertyhandle.h2
-rw-r--r--libcontextsubscriber/src/provider.cpp50
-rw-r--r--libcontextsubscriber/src/provider.h3
-rw-r--r--libcontextsubscriber/unit-tests/propertyhandle/provider.h1
-rw-r--r--libcontextsubscriber/unit-tests/propertyhandle/testpropertyhandle.cpp4
-rw-r--r--libcontextsubscriber/unit-tests/provider/contextkitplugin.h2
-rw-r--r--libcontextsubscriber/unit-tests/provider/testprovider.cpp9
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));