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 /libcontextsubscriber | |
parent | 23f77ad83b75a8a9256c6c72ca855ba1a358efd8 (diff) | |
parent | 3b6abe1576cc24652d91179161f0960ffb2c6ff3 (diff) |
Merge commit 'origin/master' into cdbqvariant
Diffstat (limited to 'libcontextsubscriber')
25 files changed, 521 insertions, 59 deletions
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 \ |