diff options
author | Gergely Risko <gergely+context@risko.hu> | 2009-09-16 16:46:22 +0300 |
---|---|---|
committer | Gergely Risko <gergely+context@risko.hu> | 2009-09-16 16:46:22 +0300 |
commit | 7cf44bded328c27c6cbe18300ed379c4d706bc16 (patch) | |
tree | 72c7aaeb1d39365c11d9921303bdb8cc8a927fb0 | |
parent | 23f77ad83b75a8a9256c6c72ca855ba1a358efd8 (diff) | |
parent | 3b6abe1576cc24652d91179161f0960ffb2c6ff3 (diff) |
Merge commit 'origin/master' into cdbqvariant
35 files changed, 588 insertions, 114 deletions
diff --git a/configure.ac b/configure.ac index 865567e6..ef97e23a 100644 --- a/configure.ac +++ b/configure.ac @@ -62,6 +62,9 @@ AC_CONFIG_FILES([ libcontextsubscriber/contextsubscriber-1.0.pc libcontextsubscriber/customer-tests/Makefile libcontextsubscriber/customer-tests/update-contextkit-providers/Makefile + libcontextsubscriber/customer-tests/testplugins/Makefile + libcontextsubscriber/customer-tests/testplugins/timeplugin1/Makefile + libcontextsubscriber/customer-tests/testplugins/timeplugin2/Makefile libcontextsubscriber/doc/Makefile libcontextsubscriber/man/Makefile libcontextsubscriber/src/Makefile diff --git a/libcontextprovider/src/contextc.cpp b/libcontextprovider/src/contextc.cpp index be98e5d7..394eb361 100644 --- a/libcontextprovider/src/contextc.cpp +++ b/libcontextprovider/src/contextc.cpp @@ -57,6 +57,10 @@ using namespace ContextProvider; keys ("Battery.OnBattery", "Battery.ChargePercentage") and sets their respective values. + Note: If the provider used other D-Bus bindings than QDBus, the + service name ("org.test.provider") needs to be unique, i.e., the + provider process should not register it itself. + \section Callbacks The context_provider_install_key function and context_provider_install_group function diff --git a/libcontextprovider/src/service.cpp b/libcontextprovider/src/service.cpp index 0895a976..618e7c98 100644 --- a/libcontextprovider/src/service.cpp +++ b/libcontextprovider/src/service.cpp @@ -36,7 +36,7 @@ namespace ContextProvider { This library implements the provider side of the Context Framework. It has both a C++ and a C interface, so you can choose which you - prefer. + prefer. For the documentation of the C API, see \ref CApi. The C++ interface consists mainly of the three classes Service, Property, and Group in the namespace ContextProvider. They are @@ -233,12 +233,15 @@ void Service::setValue(const QString &key, const QVariant &val) priv->backend->manager()->setKeyValue(key, val); } -/// Controls te service registration on dbus. If register service is set to -/// true (by default) the service while be registered on dbus. Set to false -// if you want to reuse an existing service (ie. provided by piece of code). -void Service::setRegisterService(bool r) +/// Set (override) the dbus \a connection to use by the Service. When the +/// dbus connection is specified using this function the service is NOT +/// automatically registered on the bus. It's ok to call this function many times +/// as long as the connection name is the same all the time. It's NOT +/// okay to call this function more than one time with connections with +/// different names. +void Service::setConnection(const QDBusConnection &connection) { - priv->backend->setRegisterService(r); + priv->backend->setConnection(connection); } /// Start the Service again after it has been stopped. All clients diff --git a/libcontextprovider/src/service.h b/libcontextprovider/src/service.h index 517b4e56..f5c5b5d1 100644 --- a/libcontextprovider/src/service.h +++ b/libcontextprovider/src/service.h @@ -53,7 +53,7 @@ public: void setAsDefault(); void setValue(const QString &key, const QVariant &val); - void setRegisterService(bool reg); + void setConnection(const QDBusConnection &connection); private: class ServicePrivate *priv; diff --git a/libcontextprovider/src/servicebackend.cpp b/libcontextprovider/src/servicebackend.cpp index d8004af5..8bbe93d7 100644 --- a/libcontextprovider/src/servicebackend.cpp +++ b/libcontextprovider/src/servicebackend.cpp @@ -47,8 +47,9 @@ struct ServiceBackendPrivate { QString busName; Manager *manager; QDBusConnection *connection; + QDBusConnection *implicitConnection; int refCount; - bool registerService; + bool registeredService; }; /// Creates new ServiceBackend. The backend automatically creates it's Manager. @@ -63,8 +64,9 @@ ServiceBackend::ServiceBackend(QDBusConnection::BusType busType, const QString & priv->busName = busName; priv->manager = new Manager(); priv->connection = NULL; + priv->implicitConnection = NULL; priv->refCount = 0; - priv->registerService = true; + priv->registeredService = false; } /// Destroys the ServiceBackend. The backend is stopped. @@ -74,6 +76,7 @@ ServiceBackend::~ServiceBackend() { contextDebug() << F_SERVICE_BACKEND << F_DESTROY << "Destroying Service"; stop(); + delete priv->implicitConnection; delete priv; if (ServiceBackend::defaultServiceBackend == this) ServiceBackend::defaultServiceBackend = NULL; @@ -93,26 +96,29 @@ void ServiceBackend::setValue(const QString &key, const QVariant &val) priv->manager->setKeyValue(key, val); } -/// Controls te service registration on dbus. If register service is set to -/// true (by default) the service while be registered on dbus. Set to false -/// if you want to reuse an existing service (ie. provided by piece of code). -/// This function will fail if requested behavior is different from the current -/// behavior and the service is already running. -void ServiceBackend::setRegisterService(bool r) +/// Set (override) the dbus \a connection to use by the ServiceBackend. When the +/// dbus connection is specified using this function the service is NOT +/// automatically registered on the bus. It's ok to call this function many times +/// as long as the connection name is the same all the time. It's NOT +/// okay to call this function more than one time with connections with +/// different names. +void ServiceBackend::setConnection(const QDBusConnection &connection) { - if (priv->registerService == r) + if (priv->connection && priv->connection->name() == connection.name()) { + // Silently exit return; - - // Complain ONLY when actually trying to change value. This is important - // for some black-box operation when you have no clue whetver you're starting - // the service or just reusing it (ie. plugins). + } if (priv->connection) { - contextWarning() << F_SERVICE_BACKEND << "Trying to set service registration while service running"; + contextWarning() << F_SERVICE_BACKEND << "Trying to change connection while service backed running!"; return; - } else { - priv->registerService = r; } + + if (priv->implicitConnection && connection.name() != priv->implicitConnection->name()) { + contextWarning() << F_SERVICE_BACKEND << "Connection was already set/forced with different name!"; + return; + } else + priv->implicitConnection = new QDBusConnection(connection); } /// Start the ServiceBackend again after it has been stopped. All clients @@ -123,15 +129,21 @@ bool ServiceBackend::start() if (priv->connection) return false; - priv->connection = new QDBusConnection(QDBusConnection::connectToBus(priv->busType, priv->busName)); - ManagerAdaptor *managerAdaptor = new ManagerAdaptor(priv->manager, priv->connection); + if (priv->implicitConnection == NULL) { + priv->connection = new QDBusConnection(QDBusConnection::connectToBus(priv->busType, priv->busName)); - // Register service - if (priv->registerService && !priv->connection->registerService(priv->busName)) { - contextCritical() << F_SERVICE_BACKEND << "Failed to register service with name" << priv->busName; - stop(); - return false; - } + // Register service + if (!priv->connection->registerService(priv->busName)) { + contextCritical() << F_SERVICE_BACKEND << "Failed to register service with name" << priv->busName; + stop(); + return false; + } + + priv->registeredService = true; + } else + priv->connection = new QDBusConnection(*priv->implicitConnection); + + ManagerAdaptor *managerAdaptor = new ManagerAdaptor(priv->manager, priv->connection); // Register object if (managerAdaptor && !priv->connection->registerObject("/org/freedesktop/ContextKit/Manager", priv->manager)) { @@ -153,11 +165,13 @@ void ServiceBackend::stop() // Unregister priv->connection->unregisterObject("/org/freedesktop/ContextKit/Manager"); - priv->connection->unregisterService(priv->busName); + if (priv->registeredService) + priv->connection->unregisterService(priv->busName); // Dealloc delete priv->connection; priv->connection = NULL; + priv->registeredService = false; } /// If the service is running, stop and start it again. diff --git a/libcontextprovider/src/servicebackend.h b/libcontextprovider/src/servicebackend.h index 1c1d6ba3..fc7cd02b 100644 --- a/libcontextprovider/src/servicebackend.h +++ b/libcontextprovider/src/servicebackend.h @@ -49,7 +49,7 @@ public: void stop(); void restart(); - void setRegisterService(bool reg); + void setConnection(const QDBusConnection &connection); void setAsDefault(); void setValue(const QString &key, const QVariant &val); Manager *manager(); diff --git a/libcontextprovider/unit-tests/service/servicebackend.h b/libcontextprovider/unit-tests/service/servicebackend.h index c11fc7da..c230ba0b 100644 --- a/libcontextprovider/unit-tests/service/servicebackend.h +++ b/libcontextprovider/unit-tests/service/servicebackend.h @@ -43,7 +43,7 @@ public: static ServiceBackend *defaultService; void setAsDefault(); - void setRegisterService(bool reg); + void setConnection(const QDBusConnection &connection); void ref(); void unref(); diff --git a/libcontextprovider/unit-tests/service/serviceunittest.cpp b/libcontextprovider/unit-tests/service/serviceunittest.cpp index fcf6dcba..59c69233 100644 --- a/libcontextprovider/unit-tests/service/serviceunittest.cpp +++ b/libcontextprovider/unit-tests/service/serviceunittest.cpp @@ -61,7 +61,7 @@ void ServiceBackend::setAsDefault() defaultService = this; } -void ServiceBackend::setRegisterService(bool reg) +void ServiceBackend::setConnection(const QDBusConnection &connection) { } diff --git a/libcontextprovider/unit-tests/servicebackend/servicebackendunittest.cpp b/libcontextprovider/unit-tests/servicebackend/servicebackendunittest.cpp index 9665cd94..66ba3574 100644 --- a/libcontextprovider/unit-tests/servicebackend/servicebackendunittest.cpp +++ b/libcontextprovider/unit-tests/servicebackend/servicebackendunittest.cpp @@ -33,7 +33,9 @@ struct ContextProvider::ServiceBackendPrivate { QString busName; Manager *manager; QDBusConnection *connection; + QDBusConnection *implicitConnection; int refCount; + bool registeredService; }; QString *lastKey = NULL; @@ -83,6 +85,7 @@ private slots: void refCouting(); void manager(); void startAndStop(); + void setConnection(); private: ServiceBackend *serviceBackend; @@ -160,6 +163,11 @@ void ServiceBackendUnitTest::setValue() QCOMPARE(lastValue->toInt(), 99); } +void ServiceBackendUnitTest::setConnection() +{ + serviceBackend->setConnection(QDBusConnection::sessionBus()); + QCOMPARE(serviceBackend->priv->implicitConnection->name(), QDBusConnection::sessionBus().name()); +} #include "servicebackendunittest.moc" QTEST_MAIN(ServiceBackendUnitTest); diff --git a/libcontextsubscriber/cli/commandwatcher.cpp b/libcontextsubscriber/cli/commandwatcher.cpp index a4841d96..c4b8d11e 100644 --- a/libcontextsubscriber/cli/commandwatcher.cpp +++ b/libcontextsubscriber/cli/commandwatcher.cpp @@ -17,7 +17,6 @@ CommandWatcher::CommandWatcher(int commandfd, QMap<QString, ContextProperty*> *properties, QObject *parent) : QObject(parent), commandfd(commandfd), properties(properties) { - fcntl(commandfd, F_SETFL, O_NONBLOCK); commandNotifier = new QSocketNotifier(commandfd, QSocketNotifier::Read, this); sconnect(commandNotifier, SIGNAL(activated(int)), this, SLOT(onActivated())); help(); @@ -29,6 +28,7 @@ void CommandWatcher::onActivated() static QByteArray commandBuffer = ""; static char buf[1024]; int readSize; + fcntl(commandfd, F_SETFL, O_NONBLOCK); while ((readSize = read(commandfd, &buf, 1024)) > 0) commandBuffer += QByteArray(buf, readSize); @@ -61,12 +61,14 @@ void CommandWatcher::help() qDebug() << " type KEY - get the info()->type for a key"; qDebug() << " plugin KEY - get the info()->plugin for a key"; qDebug() << " constructionstring KEY - get the info()->constructionstring for a key"; + qDebug() << " flush - write FLUSHED to stderr and stdout"; qDebug() << "Any prefix of a command can be used as an abbreviation"; } void CommandWatcher::interpret(const QString& command) const { QTextStream out(stdout); + QTextStream err(stderr); if (command == "") { help(); } else { @@ -74,7 +76,7 @@ void CommandWatcher::interpret(const QString& command) const QString commandName = args[0]; args.pop_front(); - if (args.size() == 0) { + if (args.size() == 0 && !QString("flush").startsWith(commandName)) { help(); return; } @@ -163,6 +165,11 @@ void CommandWatcher::interpret(const QString& command) const out << "constructionstring: " << properties->value(key)->info()->constructionString() << endl; else qDebug() << "no such key:" << key; + } else if (QString("flush").startsWith(commandName)) { + out << "FLUSHED" << endl; + out.flush(); + err << "FLUSHED" << endl; + err.flush(); } else help(); } diff --git a/libcontextsubscriber/customer-tests/Makefile.am b/libcontextsubscriber/customer-tests/Makefile.am index 8eed733d..8ec2e565 100644 --- a/libcontextsubscriber/customer-tests/Makefile.am +++ b/libcontextsubscriber/customer-tests/Makefile.am @@ -1,4 +1,5 @@ -SUBDIRS = update-contextkit-providers +SUBDIRS = update-contextkit-providers testplugins +CHECKSUBDIRS = update-contextkit-providers libcontextsubscribertestsdir = $(datadir)/libcontextsubscriber-tests libcontextsubscribertests_DATA = tests.xml @@ -6,7 +7,7 @@ libcontextsubscribertests_DATA = tests.xml check-customer: $(MAKE) -C $(top_srcdir) all ./runTests.sh - for i in $(SUBDIRS); do $(MAKE) -C $$i $@; done + for i in $(CHECKSUBDIRS); do $(MAKE) -C $$i $@; done CLEANFILES = *.pyc diff --git a/libcontextsubscriber/customer-tests/asynchronicity/asynchronicity.py b/libcontextsubscriber/customer-tests/asynchronicity/asynchronicity.py index 11180b58..d1de94af 100755 --- a/libcontextsubscriber/customer-tests/asynchronicity/asynchronicity.py +++ b/libcontextsubscriber/customer-tests/asynchronicity/asynchronicity.py @@ -32,16 +32,9 @@ import os import signal import re import time - import unittest from subprocess import Popen, PIPE - -def timeoutHandler(signum, frame): - raise Exception('tests has been running for too long') - -class Callable: - def __init__(self, anycallable): - self.__call__ = anycallable +from cltool import CLTool class Asynchronous(unittest.TestCase): def startProvider(busname, args): @@ -51,15 +44,7 @@ class Asynchronous(unittest.TestCase): print >>ret.stdin, "info()" ret.stdout.readline().rstrip() return ret - startProvider = Callable(startProvider) - - def wanted(name, type, value): - return "%s = %s:%s" % (name, type, value) - wanted = Callable(wanted) - - def wantedUnknown(name): - return "%s is Unknown" % (name) - wantedUnknown = Callable(wantedUnknown) + startProvider = staticmethod(startProvider) #SetUp def setUp(self): @@ -97,20 +82,19 @@ class Asynchronous(unittest.TestCase): """ # check the fast property - self.context_client = Popen(["context-listen", "test.fast", "test.slow"], - stdin=PIPE, stdout=PIPE, stderr=PIPE) + self.context_client = CLTool("context-listen", "test.fast", "test.slow") - got = self.context_client.stdout.readline().rstrip() - self.assertEqual(got, - self.wanted("test.fast", "int", "42"), - "Bad value for the fast property") + self.assert_(self.context_client.expect(CLTool.STDOUT, + CLTool.wanted("test.fast", "int", "42"), + 1), # timeout == 1 second + "Bad value for the fast property, wanted 42, communication:") fast_time = time.time() # check the slow property - got = self.context_client.stdout.readline().rstrip() - self.assertEqual(got, - self.wanted("test.slow", "int", "42"), - "Bad value for the slow property") + self.assert_(self.context_client.expect(CLTool.STDOUT, + CLTool.wanted("test.slow", "int", "42"), + 10), # timeout == 10 second max, but 3 is enough usually + "Bad value for the slow property, wanted 42, communication:") slow_time = time.time() self.assert_(slow_time - fast_time > 2.0, @@ -129,9 +113,6 @@ def runTests(): result = unittest.TextTestRunner(verbosity=2).run(suiteInstallation) return len(result.errors + result.failures) - if __name__ == "__main__": sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 1) - signal.signal(signal.SIGALRM, timeoutHandler) - signal.alarm(10) sys.exit(runTests()) diff --git a/libcontextsubscriber/customer-tests/common/cltool.py b/libcontextsubscriber/customer-tests/common/cltool.py new file mode 100644 index 00000000..c80ce355 --- /dev/null +++ b/libcontextsubscriber/customer-tests/common/cltool.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python +## +## Copyright (C) 2008, 2009 Nokia. All rights reserved. +## +## 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 + +import re +import time +from subprocess import Popen, PIPE + +class CLTool: + STDOUT = 1 + STDERR = 2 + def __init__(self, *cline): + self.__process = Popen(cline, stdin=PIPE, stdout=PIPE, stderr=PIPE) + self.__io = [] + + def send(self, string): + self.__io.append((0, string)) + print >>self.__process.stdin, string + + def expect(self, fileno, exp_str, timeout): + stream = 0 + if fileno == self.STDOUT: stream = self.__process.stdout + if fileno == self.STDERR: stream = self.__process.stderr + if stream == 0: return False + + print >>self.__process.stdin, "flush" + cur_str = "" + start_time = time.time() + while True: + line = stream.readline().rstrip() + if line == "FLUSHED": + if re.match(exp_str, cur_str): + return True + else: + time.sleep(0.1) + if time.time() - start_time > timeout: + self.printio() + print "Expected:", exp_str + print "Received before the timeout:\n", cur_str + return False + print >>self.__process.stdin, "flush" + else: + cur_str += line + "\n" + self.__io.append((fileno, line)) + + def printio(self): + print + print '----------------------------------------------------' + for line in self.__io: + if line[0] == 0: + print "[IN] <<<", line[1] + if line[0] == 1: + print "[OU] >>>", line[1] + if line[0] == 2: + print "[ER] >>>", line[1] + print '----------------------------------------------------' + + def wanted(name, type, value): + return "%s = %s:%s$" % (name, type, value) + wanted = staticmethod(wanted) + + def wantedUnknown(name): + return "%s is Unknown" % (name) + wantedUnknown = staticmethod(wantedUnknown) diff --git a/libcontextsubscriber/customer-tests/env.sh b/libcontextsubscriber/customer-tests/env.sh new file mode 100644 index 00000000..31a65d57 --- /dev/null +++ b/libcontextsubscriber/customer-tests/env.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +export PYTHONPATH="`pwd`/common/" +export CONTEXT_PROVIDERS=. +export LD_LIBRARY_PATH=../../src/.libs:../../../libcontextprovider/src/.libs +export PATH=$PATH:../../../python:../../cli:../../reg-cli diff --git a/libcontextsubscriber/customer-tests/pluginchanging/pluginchanging.py b/libcontextsubscriber/customer-tests/pluginchanging/pluginchanging.py new file mode 100755 index 00000000..70a04149 --- /dev/null +++ b/libcontextsubscriber/customer-tests/pluginchanging/pluginchanging.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python +## +## This file is part of ContextKit. +## +## Copyright (C) 2009 Nokia. All rights reserved. +## +## 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 +## +## +## Requires python2.5-gobject and python2.5-dbus +## +import sys +import unittest +import os +import string +from subprocess import Popen, PIPE +import time +import signal + +def proc_kill(pid): + os.system('../common/rec-kill.sh %d' % pid) + +def timeoutHandler(signum, frame): + raise Exception('tests have been running for too long') + +def stdoutRead (object,lines): + list = [] + for i in range(lines): + list.append(object.stdout.readline().rstrip()) + return list + +class Subscription(unittest.TestCase): + + def setUp(self): + os.environ["CONTEXT_PROVIDERS"] = "." + # We need 2 plugins which are in separate directories. + os.environ["CONTEXT_SUBSCRIBER_PLUGINS"] = "." + os.system('cp ../testplugins/timeplugin1/.libs/libcontextsubscribertime1.so* .') + os.system('cp ../testplugins/timeplugin2/.libs/libcontextsubscribertime2.so* .') + + + self.context_client = Popen(["context-listen","Test.Time"],stdin=PIPE,stdout=PIPE,stderr=PIPE) + + def tearDown(self): + proc_kill(self.context_client.pid) + os.remove('time.context') + os.system('rm libcontextsubscribertime*.so*') + + def testChangingPlugin(self): + + # Copy the declaration file, declaring libcontextsubscribertime1 plugin. + os.system('cp time1.context.temp time.context.temp') + os.system('mv time.context.temp time.context') + #print "now reading" + actual = self.context_client.stdout.readline().rstrip() + #print actual + + # The client got a value provided by the libcontextsubscribertime1 + self.assertEqual(actual.startswith("Test.Time = QString:Time1: "), True, "Got: %s" % actual) + + # Modify the registry so that the key is now provided by libcontextsubscribertime2 + os.system('cp time2.context.temp time.context.temp') + os.system('mv time.context.temp time.context') + + # Assert that the client starts getting the value from the correct plugin + # (not necessarily the first one) + actual = self.context_client.stdout.readline().rstrip() + actual = self.context_client.stdout.readline().rstrip() + + self.assertEqual(actual.startswith("Test.Time = QString:Time2: "), True, "Got: %s" % actual) + +if __name__ == "__main__": + sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 1) + signal.signal(signal.SIGALRM, timeoutHandler) + signal.alarm(30) + unittest.main() diff --git a/libcontextsubscriber/customer-tests/pluginchanging/time1.context.temp b/libcontextsubscriber/customer-tests/pluginchanging/time1.context.temp new file mode 100644 index 00000000..d29592fe --- /dev/null +++ b/libcontextsubscriber/customer-tests/pluginchanging/time1.context.temp @@ -0,0 +1,4 @@ +<?xml version="1.0"?> +<provider xmlns="http://contextkit.freedesktop.org/Provider" plugin="/libcontextsubscribertime1" constructionString="time"> + <key name="Test.Time"><type>STRING</type></key> +</provider> diff --git a/libcontextsubscriber/customer-tests/pluginchanging/time2.context.temp b/libcontextsubscriber/customer-tests/pluginchanging/time2.context.temp new file mode 100644 index 00000000..c8a2757c --- /dev/null +++ b/libcontextsubscriber/customer-tests/pluginchanging/time2.context.temp @@ -0,0 +1,4 @@ +<?xml version="1.0"?> +<provider xmlns="http://contextkit.freedesktop.org/Provider" plugin="/libcontextsubscribertime2" constructionString="time"> + <key name="Test.Time"><type>STRING</type></key> +</provider> diff --git a/libcontextsubscriber/customer-tests/runTests.sh b/libcontextsubscriber/customer-tests/runTests.sh index 82efc04f..c723a495 100755 --- a/libcontextsubscriber/customer-tests/runTests.sh +++ b/libcontextsubscriber/customer-tests/runTests.sh @@ -1,12 +1,11 @@ #!/bin/bash -DIRS="commander subscription asynchronicity registry" +cd $(dirname $0) +DIRS="commander subscription asynchronicity registry pluginchanging" +. ./env.sh if pkg-config contextprovider-1.0 || [ -e ../../libcontextprovider/src/.libs/libcontextprovider.so ] then - export CONTEXT_PROVIDERS=. - export LD_LIBRARY_PATH=../../src/.libs:../../../libcontextprovider/src/.libs - export PATH=$PATH:../../../python:../../cli:../../reg-cli for dir in $DIRS; do cd $dir diff --git a/libcontextsubscriber/customer-tests/testplugins/Makefile.am b/libcontextsubscriber/customer-tests/testplugins/Makefile.am new file mode 100644 index 00000000..38b401c6 --- /dev/null +++ b/libcontextsubscriber/customer-tests/testplugins/Makefile.am @@ -0,0 +1,2 @@ +SUBDIRS = timeplugin1 timeplugin2 +EXTRA_DIST = timeplugin.cpp timeplugin.h diff --git a/libcontextsubscriber/customer-tests/testplugins/timeplugin.cpp b/libcontextsubscriber/customer-tests/testplugins/timeplugin.cpp new file mode 100644 index 00000000..6e3c4df0 --- /dev/null +++ b/libcontextsubscriber/customer-tests/testplugins/timeplugin.cpp @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2009 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 "timeplugin.h" +#include "sconnect.h" + +#include "logging.h" + +#include <QDateTime> + +/// The factory method for constructing the IPropertyProvider instance. +IProviderPlugin* pluginFactory(QString /*constructionString*/) +{ + // Note: it's the caller's responsibility to delete the plugin if + // needed. + return new ContextSubscriberTime::TimePlugin(); +} + +namespace ContextSubscriberTime { + +TimePlugin::TimePlugin() +{ + contextDebug(); + prefix = TIME_PLUGIN_PREFIX; + timer.setInterval(2000); + sconnect(&timer, SIGNAL(timeout()), this, SLOT(onTimeout())); + QMetaObject::invokeMethod(this, "ready", Qt::QueuedConnection); +} + +void TimePlugin::subscribe(QSet<QString> keys) +{ + contextDebug() << keys; + foreach(const QString& key, keys) { + emit subscribeFinished(key); + } + timer.start(); +} + +void TimePlugin::unsubscribe(QSet<QString> keys) +{ + timer.stop(); +} + +void TimePlugin::onTimeout() +{ + contextDebug() << "Timeout"; + emit valueChanged("Test.Time", QDateTime::currentDateTime().toString().prepend(prefix)); +} + +} // end namespace diff --git a/libcontextsubscriber/customer-tests/testplugins/timeplugin.h b/libcontextsubscriber/customer-tests/testplugins/timeplugin.h new file mode 100644 index 00000000..3a51fb5a --- /dev/null +++ b/libcontextsubscriber/customer-tests/testplugins/timeplugin.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2009 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 + * + */ + +/* +This is a test plugin for customer tests. +*/ + +#ifndef TIMEPLUGIN_H +#define TIMEPLUGIN_H + +#include "iproviderplugin.h" // For IProviderPlugin definition +#include <QTimer> + +using ContextSubscriber::IProviderPlugin; + +extern "C" { + IProviderPlugin* pluginFactory(QString constructionString); +} + +namespace ContextSubscriberTime +{ + +class TimePlugin : public IProviderPlugin +{ + Q_OBJECT + +public: + explicit TimePlugin(); + virtual void subscribe(QSet<QString> keys); + virtual void unsubscribe(QSet<QString> keys); + +private slots: + void onTimeout(); + +private: + QTimer timer; + QString prefix; +}; +} + +#endif diff --git a/libcontextsubscriber/customer-tests/testplugins/timeplugin1/.gitignore b/libcontextsubscriber/customer-tests/testplugins/timeplugin1/.gitignore new file mode 100644 index 00000000..d79521dc --- /dev/null +++ b/libcontextsubscriber/customer-tests/testplugins/timeplugin1/.gitignore @@ -0,0 +1,2 @@ +timeplugin.cpp +timeplugin.h diff --git a/libcontextsubscriber/customer-tests/testplugins/timeplugin1/Makefile.am b/libcontextsubscriber/customer-tests/testplugins/timeplugin1/Makefile.am new file mode 100644 index 00000000..723dfd06 --- /dev/null +++ b/libcontextsubscriber/customer-tests/testplugins/timeplugin1/Makefile.am @@ -0,0 +1,30 @@ +lib_LTLIBRARIES = libcontextsubscribertime1.la +libcontextsubscribertime1_la_SOURCES = timeplugin.cpp timeplugin.h + +BUILT_SOURCES = $(libcontextsubscribertime1_la_SOURCES) +timeplugin.cpp: ../timeplugin.cpp + ln -sf $< $@ +timeplugin.h: ../timeplugin.h + ln -sf $< $@ + +clean-local: + rm -f timeplugin.cpp timeplugin.h + +AM_CXXFLAGS = -I$(top_srcdir)/common \ + -I$(srcdir)/../../../src $(QtCore_CFLAGS) \ + $(QtDBus_CFLAGS) \ + '-DCONTEXT_LOG_MODULE_NAME="time1plugin"' \ + '-DTIME_PLUGIN_PREFIX="Time1: "' + +$(top_builddir)/common/libcommon.a: + $(MAKE) -C $(top_builddir)/common libcommon.a + +LIBS += $(CDB_LIBS) $(QtCore_LIBS) $(QtDBus_LIBS) +libcontextsubscribertime1_la_LIBADD=$(top_builddir)/common/libcommon.la + +.PHONY: $(top_builddir)/common/libcommon.la + +# moccing +nodist_libcontextsubscribertime1_la_SOURCES = mocs.cpp +QT_TOMOC = $(filter %.h, $(libcontextsubscribertime1_la_SOURCES)) +include $(top_srcdir)/am/qt.am diff --git a/libcontextsubscriber/customer-tests/testplugins/timeplugin2/.gitignore b/libcontextsubscriber/customer-tests/testplugins/timeplugin2/.gitignore new file mode 100644 index 00000000..d79521dc --- /dev/null +++ b/libcontextsubscriber/customer-tests/testplugins/timeplugin2/.gitignore @@ -0,0 +1,2 @@ +timeplugin.cpp +timeplugin.h diff --git a/libcontextsubscriber/customer-tests/testplugins/timeplugin2/Makefile.am b/libcontextsubscriber/customer-tests/testplugins/timeplugin2/Makefile.am new file mode 100644 index 00000000..6e1f27bc --- /dev/null +++ b/libcontextsubscriber/customer-tests/testplugins/timeplugin2/Makefile.am @@ -0,0 +1,30 @@ +lib_LTLIBRARIES = libcontextsubscribertime2.la +libcontextsubscribertime2_la_SOURCES = timeplugin.cpp timeplugin.h + +BUILT_SOURCES = $(libcontextsubscribertime2_la_SOURCES) +timeplugin.cpp: ../timeplugin.cpp + ln -sf $< $@ +timeplugin.h: ../timeplugin.h + ln -sf $< $@ + +clean-local: + rm -f timeplugin.cpp timeplugin.h + +AM_CXXFLAGS = -I$(top_srcdir)/common \ + -I$(srcdir)/../../../src $(QtCore_CFLAGS) \ + $(QtDBus_CFLAGS) \ + '-DCONTEXT_LOG_MODULE_NAME="time2plugin"' \ + '-DTIME_PLUGIN_PREFIX="Time2: "' + +$(top_builddir)/common/libcommon.a: + $(MAKE) -C $(top_builddir)/common libcommon.a + +LIBS += $(CDB_LIBS) $(QtCore_LIBS) $(QtDBus_LIBS) +libcontextsubscribertime2_la_LIBADD=$(top_builddir)/common/libcommon.la + +.PHONY: $(top_builddir)/common/libcommon.la + +# moccing +nodist_libcontextsubscribertime2_la_SOURCES = mocs.cpp +QT_TOMOC = $(filter %.h, $(libcontextsubscribertime2_la_SOURCES)) +include $(top_srcdir)/am/qt.am diff --git a/libcontextsubscriber/src/Makefile.am b/libcontextsubscriber/src/Makefile.am index 051c7b92..b790c4dd 100644 --- a/libcontextsubscriber/src/Makefile.am +++ b/libcontextsubscriber/src/Makefile.am @@ -20,6 +20,7 @@ includecontextsubscriber_HEADERS = contextproperty.h \ AM_CXXFLAGS = -I$(top_srcdir)/common \ $(QtCore_CFLAGS) $(QtXml_CFLAGS) $(QtDBus_CFLAGS) \ + '-DDEFAULT_CONTEXT_SUBSCRIBER_PLUGINS="@libdir@/contextkit/subscriber-plugins"' \ '-DDEFAULT_CONTEXT_PROVIDERS="@datadir@/contextkit/providers/"' \ '-DDEFAULT_CONTEXT_CORE_DECLARATIONS="@datadir@/contextkit/core.context"' \ '-DCONTEXT_LOG_MODULE_NAME="libcontextsubscriber"' @@ -27,7 +28,7 @@ AM_CXXFLAGS = -I$(top_srcdir)/common \ $(top_builddir)/common/libcommon.la: $(MAKE) -C $(top_builddir)/common libcommon.la -LIBS += $(CDB_LIBS) $(QtCore_LIBS) $(QtXml_LIBS) $(QtDBus_LIBS) +LIBS += $(CDB_LIBS) $(QtCore_LIBS) $(QtXml_LIBS) $(QtDBus_LIBS) libcontextsubscriber_la_LIBADD=$(top_builddir)/common/libcommon.la .PHONY: $(top_builddir)/common/libcommon.la @@ -40,3 +41,6 @@ include $(top_srcdir)/am/qt.am # because if you change configure parameter DEFAULT_CONTEXT_PROVIDERS, # you should do a recompile infocdbbackend.lo infoxmlbackend.lo: Makefile + +# and the same for DEFAULT_CONTEXT_SUBSCRIBER_PLUGINS +provider.lo: Makefile diff --git a/libcontextsubscriber/src/contextpropertyinfo.cpp b/libcontextsubscriber/src/contextpropertyinfo.cpp index 1e23f2db..30cfd8b7 100644 --- a/libcontextsubscriber/src/contextpropertyinfo.cpp +++ b/libcontextsubscriber/src/contextpropertyinfo.cpp @@ -303,29 +303,48 @@ void ContextPropertyInfo::onKeyDataChanged(const QString& key) if (key != keyName) return; + // Update caches and store old values + QString oldType = cachedType; QString newType = InfoBackend::instance()->typeForKey(keyName); - if (cachedType != newType) { + cachedType = newType; - if (cachedType == "") + cachedDoc = InfoBackend::instance()->docForKey(keyName); + + QString oldPlugin = cachedPlugin; + QString oldConstructionString = cachedConstructionString; + QString newPlugin = InfoBackend::instance()->pluginForKey(keyName); + QString newConstructionString = InfoBackend::instance()->constructionStringForKey(keyName); + cachedPlugin = newPlugin; + cachedConstructionString = newConstructionString; + + // Release the lock before emitting the signals; otherwise + // listeners trying to access cached values would create a + // deadlock. + lock.unlock(); + + // Emit the needed signals + if (oldType != newType) { + + if (oldType == "") emit existsChanged(true); if (newType == "") emit existsChanged(false); - cachedType = newType; emit typeChanged(cachedType); } - cachedDoc = InfoBackend::instance()->docForKey(keyName); - - // TBD: obsolete the providerChanged signal and add pluginChanged or sth? - QString newPlugin = InfoBackend::instance()->pluginForKey(keyName); - if (cachedPlugin == "contextkit-dbus" || newPlugin == "contextkit-dbus") { - cachedPlugin = newPlugin; - cachedConstructionString = InfoBackend::instance()->constructionStringForKey(keyName); - QString newProvider = ""; - if (newPlugin == "contextkit-dbus") { - newProvider = cachedConstructionString.split(":").last(); + // TBD: obsolete the providerChanged & providerDBusTypeChanged signals? + if (oldPlugin != newPlugin || oldConstructionString != newConstructionString) { + if (oldPlugin == "contextkit-dbus" || newPlugin == "contextkit-dbus") { + QString newProvider = ""; + if (newPlugin == "contextkit-dbus") { + newProvider = cachedConstructionString.split(":").last(); + } + emit providerChanged(newProvider); + // Note: we don't emit providerDBusTypeChanged any + // more. It would be cumbersome, and there's no real use + // case for listening to it. } - emit providerChanged(newProvider); + emit pluginChanged(newPlugin, newConstructionString); } } diff --git a/libcontextsubscriber/src/contextpropertyinfo.h b/libcontextsubscriber/src/contextpropertyinfo.h index 3ba6b255..e83aebb9 100644 --- a/libcontextsubscriber/src/contextpropertyinfo.h +++ b/libcontextsubscriber/src/contextpropertyinfo.h @@ -85,6 +85,14 @@ signals: /// signal you can wait (watch) for various keys to become available. /// \param exists The new state of the key. void existsChanged(bool exists); + + /// Emitted when the libcontextsubscriber plugin providing the key + /// changes, or the construction parameter to give to the plugin + /// changes.. The \a plugin is the name of the new plugin + /// providing the key and the \a constructionString is the new + /// construction parameter to give to the plugin. + void pluginChanged(QString plugin, QString constructionString); + }; #endif // CONTEXTPROPERTYINFO_H diff --git a/libcontextsubscriber/src/iproviderplugin.h b/libcontextsubscriber/src/iproviderplugin.h index 2cfef845..dcd4342c 100644 --- a/libcontextsubscriber/src/iproviderplugin.h +++ b/libcontextsubscriber/src/iproviderplugin.h @@ -27,6 +27,11 @@ namespace ContextSubscriber { +/* This is not a public API of ContextKit, please do not write third + * party plugins for the ContextKit client library without first + * contacting us. + */ + class IProviderPlugin : public QObject { Q_OBJECT @@ -42,6 +47,8 @@ signals: void valueChanged(QString key, QVariant value); }; +typedef IProviderPlugin* (*PluginFactoryFunc)(QString constructionString); + } #endif diff --git a/libcontextsubscriber/src/loggingfeatures.h b/libcontextsubscriber/src/loggingfeatures.h index 86f71c25..05a0d276 100644 --- a/libcontextsubscriber/src/loggingfeatures.h +++ b/libcontextsubscriber/src/loggingfeatures.h @@ -26,5 +26,6 @@ #define F_XML (ContextFeature("xml")) #define F_CDB (ContextFeature("cdb")) #define F_DESTROY (ContextFeature("destroy")) +#define F_PLUGINS (ContextFeature("plugins")) #endif diff --git a/libcontextsubscriber/src/propertyhandle.cpp b/libcontextsubscriber/src/propertyhandle.cpp index f1592659..cef5c406 100644 --- a/libcontextsubscriber/src/propertyhandle.cpp +++ b/libcontextsubscriber/src/propertyhandle.cpp @@ -83,6 +83,12 @@ PropertyHandle::PropertyHandle(const QString& key) // done before calling updateProvider. myInfo = new ContextPropertyInfo(myKey, this); + // Start listening to changes in property introspection (e.g., added to registry, plugin changes) + sconnect(myInfo, SIGNAL(existsChanged(bool)), + this, SLOT(updateProvider())); + sconnect(myInfo, SIGNAL(pluginChanged(QString, QString)), + this, SLOT(updateProvider())); + // Start listening for the context commander, and also initiate a // NameHasOwner check. @@ -103,9 +109,6 @@ PropertyHandle::PropertyHandle(const QString& key) // Otherwise, delay connecting to the provider until we know // whether commander is present. - // Start listening to changes in property registry (e.g., new keys, keys removed) - sconnect(ContextRegistryInfo::instance(), SIGNAL(keysChanged(const QStringList&)), - this, SLOT(updateProvider())); // Move the PropertyHandle (and all children) to main thread. moveToThread(QCoreApplication::instance()->thread()); @@ -127,6 +130,7 @@ void PropertyHandle::setTypeCheck(bool typeCheck) void PropertyHandle::updateProvider() { Provider *newProvider; + contextDebug() << F_PLUGINS; if (commandingEnabled && commanderListener->isServicePresent() == DBusNameListener::Present) { // If commander is present it should be able to override the @@ -139,6 +143,7 @@ void PropertyHandle::updateProvider() if (myInfo->exists()) { // If myInfo knows the current provider which should be // connected to, connect to it. + contextDebug() << F_PLUGINS << "Key exists"; newProvider = Provider::instance(myInfo->plugin(), myInfo->constructionString()); } else { @@ -146,6 +151,8 @@ void PropertyHandle::updateProvider() // This way, we can still continue communicating with the // provider even though the key is no longer in the // registry. + contextDebug() << F_PLUGINS << "Key doesn't exist -> keep old provider info"; + newProvider = myProvider; if (newProvider == 0) { diff --git a/libcontextsubscriber/src/provider.cpp b/libcontextsubscriber/src/provider.cpp index c5e8b931..dcf745fc 100644 --- a/libcontextsubscriber/src/provider.cpp +++ b/libcontextsubscriber/src/provider.cpp @@ -25,10 +25,12 @@ #include "sconnect.h" #include "contextkitplugin.h" #include "logging.h" +#include "loggingfeatures.h" #include <QTimer> #include <QMutexLocker> #include <QCoreApplication> #include <QThread> +#include <QLibrary> namespace ContextSubscriber { @@ -36,6 +38,11 @@ namespace ContextSubscriber { \class IProviderPlugin \brief Interface for provider plugins. + Note: this interface is private, currently it is not advised to use + it and create ContextKit subscriber plugins on your own, we can and + will change this interface anytime in the future even between small + bugfix releases. + Every Provider instance contains exactly one plugin (pointer) with this interface which is constructed on initialization time and never change after that. This way the concrete protocol (dbus, shared @@ -45,7 +52,14 @@ namespace ContextSubscriber { unsubscribe calls (on the wire) using the \c subscribe and \c unsubscribe methods. - The plugin can fail or became ready anytime because of things + When the plugin is constructed, it should emit the signal ready() + when it is ready to take in subscriptions. However, the signal + ready() should not be emitted in the plugin constructor. If the + plugin is able to take in subscriptions immediately, you can use + QMetaObject::invokeMethod with QueuedConnection to emit the signal + when the main loop is entered the next time. + + The plugin can fail or became ready again anytime because of things happening on the wire inside the plugin (socket closed, dbus service appears/disappears). Whenever the plugin has new information about this it should emit the signal \c ready or \c failed accordingly. @@ -109,9 +123,43 @@ Provider::Provider(const QString &plugin, const QString &constructionString) /// up with the name of the function). void Provider::constructPlugin() { + contextDebug() << F_PLUGINS; if (pluginName == "contextkit-dbus") { plugin = contextKitPluginFactory(constructionString); - } else ; // FIXME: implement plugin system in the else branch + } + else if (pluginName.startsWith("/")) { // Require the plugin name to start with / + // Enable overriding the plugin location with an environment variable + const char *pluginPath = getenv("CONTEXT_SUBSCRIBER_PLUGINS"); + if (! pluginPath) + pluginPath = DEFAULT_CONTEXT_SUBSCRIBER_PLUGINS; + + QString pluginFilename(pluginPath); + // Allow pluginPath to have a trailing / or not + if (pluginFilename.endsWith("/")) { + pluginFilename.chop(1); + } + + pluginFilename.append(pluginName); + + QLibrary library(pluginFilename); + library.load(); + + if (library.isLoaded()) { + PluginFactoryFunc factory = (PluginFactoryFunc) library.resolve("pluginFactory"); + if (factory) { + contextDebug() << "Resolved factory function"; + plugin = factory(constructionString); + } else { + contextCritical() << "Error resolving function pluginFactory from plugin" << pluginFilename; + } + } + else { + contextCritical() << "Error loading plugin" << pluginFilename << ":" << library.errorString(); + } + } + else { + contextCritical() << "Illegal plugin name" << pluginName << ", doesn't start with /"; + } if (plugin == 0) { pluginState = FAILED; diff --git a/libcontextsubscriber/unit-tests/propertyhandle/contextpropertyinfo.h b/libcontextsubscriber/unit-tests/propertyhandle/contextpropertyinfo.h index a53f6d46..61cdcb45 100644 --- a/libcontextsubscriber/unit-tests/propertyhandle/contextpropertyinfo.h +++ b/libcontextsubscriber/unit-tests/propertyhandle/contextpropertyinfo.h @@ -50,6 +50,7 @@ signals: void providerDBusTypeChanged(QDBusConnection::BusType newBusType); void typeChanged(QString newType); void existsChanged(bool exists); + void pluginChanged(QString, QString); public: // For the test program diff --git a/libcontextsubscriber/unit-tests/provider/Makefile.am b/libcontextsubscriber/unit-tests/provider/Makefile.am index 8d28ea53..936b9a92 100644 --- a/libcontextsubscriber/unit-tests/provider/Makefile.am +++ b/libcontextsubscriber/unit-tests/provider/Makefile.am @@ -9,7 +9,9 @@ COVERAGE_FILES = provider.cpp # do the testing, coverage, etc. stuff # tests.am is using +=, so we have to set a value here for these four always -AM_CXXFLAGS = $(QtDBus_CFLAGS) +AM_CXXFLAGS = $(QtDBus_CFLAGS) \ + '-DDEFAULT_CONTEXT_SUBSCRIBER_PLUGINS="@libdir@/contextkit/subscriber-plugins"' + AM_LDFLAGS = $(QtDBus_LIBS) # copy these files from the real source FROM_SOURCE = provider.cpp provider.h iproviderplugin.h \ diff --git a/spec/core.context b/spec/core.context index dccb80a3..f5569677 100644 --- a/spec/core.context +++ b/spec/core.context @@ -291,26 +291,6 @@ A boolean indicating whether or not the device is visible to other Bluetooth devices when they search for others. </doc> </key> - <key name="Bluetooth.TrafficOut"> - <type>percentage</type> - <doc> -A rough indication of the current outgoing traffic rate over -Bluetooth, in percent of the maximum possible rate. - </doc> - </key> - <key name="Bluetooth.TrafficIn"> - <type>percentage</type> - <doc> -A rough indication of the current incoming traffic rate over -Bluetooth, in percent of the maximum possible rate. - </doc> - </key> - <key name="Bluetooth.SignalStrength"> - <type>percentage</type> - <doc> -The strength of the Bluetooth radio connection. - </doc> - </key> <doc> Cellular -------- |