diff options
author | Akos PASZTORY <ext-akos.pasztory@nokia.com> | 2009-05-14 14:31:13 +0300 |
---|---|---|
committer | Gergely Risko <ext-risko.gergely@nokia.com> | 2009-05-26 15:17:19 +0300 |
commit | 9efba78ba665662fb73cf810c6b7e146a992fd16 (patch) | |
tree | 3ae2f2fb72ef283800b34a9bb423f1cbebe6fd2a /python | |
parent | 338e88b325d0dbf3df8bb271bc0d168c21ce1ee6 (diff) |
Bringing the python stuff alive.
Moved from tools/flexiprovider to python/ContextKit, added into the build
infrastructure and adapted scripts.
Diffstat (limited to 'python')
-rw-r--r-- | python/ContextKit/CTypesHelpers.py (renamed from python/CTypesHelpers.py) | 0 | ||||
-rw-r--r-- | python/ContextKit/ContextProvider.py (renamed from python/ContextProvider.py) | 0 | ||||
-rw-r--r-- | python/ContextKit/__init__.py | 0 | ||||
-rw-r--r-- | python/ContextKit/flexiprovider.py | 209 | ||||
-rw-r--r-- | python/Makefile.am | 11 | ||||
-rw-r--r-- | python/README | 29 | ||||
-rwxr-xr-x | python/complex.py | 22 | ||||
-rwxr-xr-x | python/context-provide | 39 | ||||
-rwxr-xr-x | python/context-rlwrap | 35 | ||||
-rwxr-xr-x | python/simple.py | 12 |
10 files changed, 357 insertions, 0 deletions
diff --git a/python/CTypesHelpers.py b/python/ContextKit/CTypesHelpers.py index bada4e0d..bada4e0d 100644 --- a/python/CTypesHelpers.py +++ b/python/ContextKit/CTypesHelpers.py diff --git a/python/ContextProvider.py b/python/ContextKit/ContextProvider.py index 6e8f61fd..6e8f61fd 100644 --- a/python/ContextProvider.py +++ b/python/ContextKit/ContextProvider.py diff --git a/python/ContextKit/__init__.py b/python/ContextKit/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/python/ContextKit/__init__.py diff --git a/python/ContextKit/flexiprovider.py b/python/ContextKit/flexiprovider.py new file mode 100644 index 00000000..fd9a56ec --- /dev/null +++ b/python/ContextKit/flexiprovider.py @@ -0,0 +1,209 @@ +#!/usr/bin/python +import os +import sys +import tempfile +import subprocess +import glib, gobject +import dbus, dbus.service, dbus.mainloop.glib + +def pkgconfig(*args): + """Runs `pkg-config $args` and returns the Popen object, augmented + with an `output' attribute containing stdout.""" + cmd = ['pkg-config'] + list(args) + process = subprocess.Popen(cmd, stdout=subprocess.PIPE) + process.output = process.communicate()[0].strip() + return process + +# The following kludge is needed if we want this to be usable without +# installing libcontextprovider. If pkg-config reports it as +# uninstalled, we extend PYTHONPATH, then we try to import the module. +# If that fails, extend LD_LIBRARY_PATH and re-exec ourselves. + +if pkgconfig('--exists', 'contextprovider-1.0').returncode != 0 or \ + pkgconfig('--exists', 'duivaluespace-1.0').returncode != 0: + raise RuntimeError("You need to make pkg-config find " + "contextprovider-1.0 and duivaluespace-1.0 " + "somehow.\nTry setting $PKG_CONFIG_PATH.") + +if pkgconfig('--uninstalled', 'contextprovider-1.0').returncode == 0: + sys.path.append(pkgconfig('--variable=pythondir', 'contextprovider-1.0').output) +try: + import ContextProvider as CP +except ImportError: + raise +except: + # Failed, probably because LD_LIBRARY_PATH is not right. Set it and + # re-exec ourselves. To avoid an infinite loop, we try this only + # when LD_LIBRARY_PATH doesn't yet contain what we want to add. + libdir = pkgconfig('--variable=libdir', 'contextprovider-1.0').output + ldpath = [d for d in os.environ.get('LD_LIBRARY_PATH', '').split(':') if d != ''] + if libdir in ldpath: + raise + ldpath += [libdir, libdir + '/.libs'] + env = dict(os.environ) + env.update(LD_LIBRARY_PATH=':'.join(ldpath)) + os.execve(sys.argv[0], sys.argv, env) + +class _property(object): + """Kind-of a template for property types.""" + def __init__(self, datatype, default): + self.datatype = datatype + self.default = default + self.name = None + def __call__(self, name, initial_value=None): + """Clones self, setting its name and initial value.""" + if initial_value is None: + initial_value = self.default + prop = _property(self.datatype, initial_value) + prop.name = name + return prop + +# Pre-defined types and their defaults. +INT = _property('INT', 0) +STRING = _property('STRING', '') +DOUBLE = _property('DOUBLE', 0.0) +TRUTH = _property('TRUTH', False) + +def xmlfor(busname='ctx.flexiprovider', bus='session', *props): + """Generates a provider .xml document for the given properties + (which are instances of _property).""" + xml = ['<?xml version="1.0"?>\n' + '<provider bus="%s" service="%s">' % (bus, busname)] + for p in props: + node, _, key = p.name.partition('.') + xml.append(' <node name="%s">\n' + ' <key name="%s" type="%s">\n' + ' <doc>A phony but very flexible property.</doc>\n' + ' </key>\n' + ' </node>' % (node, key, p.datatype)) + xml.append('</provider>') + return '\n'.join(xml) + +# Location of the update-context-providers script, initialized lazily +# in the function below. +U_C_P = None +def update_context_providers(xml, dir='.'): + """Runs the update-context-provider script in $dir after writing + $xml to a temporary file.""" + global U_C_P + if U_C_P is None: + U_C_P = '' + if pkgconfig('--uninstalled', 'duivaluespace-1.0').returncode == 0: + U_C_P = '%s/registry/' % pkgconfig('--variable=pcfiledir', + 'duivaluespace-1.0').output + U_C_P += 'update-context-providers' + tmpf = tempfile.NamedTemporaryFile('w+b', -1, '.xml', 'flexi', dir) + print >>tmpf, xml + tmpf.flush() + subprocess.call([U_C_P, dir]) + +class Flexiprovider(object): + def stdin_ready(self, fd, cond): + if cond & (glib.IO_ERR | glib.IO_HUP): + self.loop.quit() + return False + # We assume that stdin is line-buffered (ie. readline() doesn't + # block (too much)). It's true if it's a tty. If piping from + # another program, do an entire line. + # TODO maybe we should rather fix ourselves + l = sys.stdin.readline().strip() + if l == '': + self.loop.quit() + return False + try: + # TODO something more sophisticated? + code = compile(l, '<input from stdin>', 'single') + eval(code, {}, dict(INT=INT, + STRING=STRING, + TRUTH=TRUTH, + DOUBLE=DOUBLE, + set=self.set, + get=self.get, + reset=self.reset, + quit=self.loop.quit, + add=self.add_and_update, + info=self.info)) + except: + # We are ignoring errors. + import traceback + traceback.print_exc() + return True + + def __init__(self, properties, name='ctx.flexiprovider', bus='session'): + self.busname = name + self.bus = bus + self.loop = gobject.MainLoop() + self.props = {} + self.values = {} + # Hook into libcontextprovider. + self.setters = dict(INT=CP.ContextProvider.set_integer, + STRING=CP.ContextProvider.set_string, + DOUBLE=CP.ContextProvider.set_double, + TRUTH=CP.ContextProvider.set_boolean) + self.subscribed_cb = CP.ContextProvider.SUBSCRIPTION_CHANGED_CALLBACK(lambda x, y: None) + CP.ContextProvider.init(dict(session=0, system=1)[self.bus], self.busname) + # Add properties and set the initial values. + for p in properties: + self.addproperty(p) + self.update_providers() + dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) + + def get(self, prop): + return self.values.get(prop, None) + + def set(self, prop, val): + if prop not in self.props: + return + self.values[prop] = val + if val is None: + CP.ContextProvider.set_null(prop) + else: + self.setters[self.props[prop].datatype](prop, val) + + def reset(self, prop): + if prop not in self.props: + return + self.set(prop, self.props[prop].default) + + def addproperty(self, prop): + self.props[prop.name] = prop + self.values[prop.name] = prop.default + CP.ContextProvider.install_key(prop.name, 0, self.subscribed_cb, None) + self.reset(prop.name) + + def add_and_update(self, prop): + self.addproperty(prop) + self.update_providers() + + def update_providers(self): + update_context_providers(xmlfor(self.busname, self.bus, *self.props.values())) + + def info(self): + """Dumps information about currently provided properties.""" + for p in self.props.values(): + print p.datatype.ljust(8), p.name.ljust(20), \ + self.values[p.name], '(', p.default, ')' + + def interactive(self): + """Runs the provider interactively, accepting the following + commands from the standard input (note that this is Python + syntax, and it gets eval()ed): + + set(property, value): sets the given property + get(property): prints the value of the property + reset(property): resets the property + add(propertydesc): adds a new property + info(): prints properties and their values + quit(): stops the whole thing + + The provider will stop also if stdin is closed. + """ + glib.io_add_watch(sys.stdin.fileno(), + glib.IO_IN | glib.IO_HUP | glib.IO_ERR, + self.stdin_ready) + self.loop.run() + + def run(self): + self.loop.run() + +__all__ = ('INT', 'STRING', 'DOUBLE', 'TRUTH', 'Flexiprovider') diff --git a/python/Makefile.am b/python/Makefile.am new file mode 100644 index 00000000..c716a0b0 --- /dev/null +++ b/python/Makefile.am @@ -0,0 +1,11 @@ +dist_bin_SCRIPTS = context-provide \ + context-rlwrap + +pkgpython_PYTHON = ContextKit/flexiprovider.py \ + ContextKit/ContextProvider.py \ + ContextKit/CTypesHelpers.py \ + ContextKit/__init__.py + +EXTRA_DIST = README \ + simple.py \ + complex.py
\ No newline at end of file diff --git a/python/README b/python/README new file mode 100644 index 00000000..64d0ddc9 --- /dev/null +++ b/python/README @@ -0,0 +1,29 @@ +These directories contain a simple Python binding for the +libcontextprovider library and a test provider tool. + +Flexiprovider is a Python module/tool for testing Context Framework +clients. It allows to define properties and provides means to change +their values during runtime. + +There are two examples: `simple.py' and `complex.py', which you may use +to get a clue how to write your own provider. The `context-provide' +script allows you to start up a provider from the command line, without +writing any Python code. + +The interactive mode supports the following commands on the standard +input (note that this is Python syntax, and it gets eval()ed): + + set(property, value): sets the given property + get(property): prints the value of the property + reset(property): resets the property + add(propertydesc): adds a new property, see simple.py for + examples of propertydesc + info(): prints properties and their values + quit(): stops the whole thing + +The provider will stop also if stdin is closed. + +You may use `context-rlwrap' as illustrated below to enjoy a more +friendlier readline interface: + + context-rlwrap ./provide.py my.fake.provider diff --git a/python/complex.py b/python/complex.py new file mode 100755 index 00000000..2adf166e --- /dev/null +++ b/python/complex.py @@ -0,0 +1,22 @@ +#!/usr/bin/python +"""A more complex provider with a horroristic theme.""" + +from ContextKit.flexiprovider import * +import glib + +count = 0 +def there_are_more(): + global count + count += 1 + fp.set('Zombie.Count', count) + if count == 2: + fp.add_and_update(STRING('You.Shout')) + fp.set('You.Shout', 'omg zombies!') + if count == 5: + fp.set('Life.isInDanger', True) + return True + +glib.timeout_add_seconds(2, there_are_more) +fp = Flexiprovider([INT('Zombie.Count'), TRUTH('Life.isInDanger')], + 'omg.zombies', 'session') +fp.run() diff --git a/python/context-provide b/python/context-provide new file mode 100755 index 00000000..4a3b5610 --- /dev/null +++ b/python/context-provide @@ -0,0 +1,39 @@ +#!/usr/bin/python +"""context-provide -- start up a provider from the command line + +Usage: context-provide [BUSTYPE:]PROVIDERNAME [TYPE NAME INITVALUE ...] + +Starts up a Flexiprovider with the given PROVIDERNAME, serving +properties specified in the arguments. TYPE is one of 'int', 'string', +'double', 'truth'. BUSTYPE is either 'system', 'session' (or void, +defaulting to the latter). +""" + +import sys +from ContextKit.flexiprovider import * + +types = dict(int=(INT, int), + truth=(TRUTH, bool), + string=(STRING, str), + double=(DOUBLE, float)) +properties = [] + +if len(sys.argv) < 2: + print __doc__ + sys.exit(1) +sys.argv.pop(0) +busaddress = sys.argv.pop(0).split(':') +if len(busaddress) == 1: + busaddress.insert(0, 'session') +if busaddress[0] not in ('session', 'system'): + sys.exit(2) +while len(sys.argv) >= 3: + datatype, name, initvalue = sys.argv[:3] + del sys.argv[:3] + if datatype not in types: + continue + cookiecutter, conversion = types[datatype] + properties.append(cookiecutter(name, conversion(initvalue))) + +provider = Flexiprovider(properties, busaddress[1], busaddress[0]) +provider.interactive() diff --git a/python/context-rlwrap b/python/context-rlwrap new file mode 100755 index 00000000..96702559 --- /dev/null +++ b/python/context-rlwrap @@ -0,0 +1,35 @@ +#!/usr/bin/python +"""context-rlwrap -- a simple readline wrapper + +Usage: context-rlwrap [some other program and args] + +Wraps the plain stdin-based input mechanism of the given program into a +convenient readline-based one. +""" + +import os +import sys +import signal +import readline + +if len(sys.argv) < 2: + sys.exit(__doc__) +rfd, wfd = os.pipe() +pid = os.fork() +if pid == 0: + os.close(wfd) + os.dup2(rfd, 0) + sys.argv.pop(0) + os.execv(sys.argv[0], sys.argv) +else: + def childied(n, f): + p, ec = os.waitpid(pid, 0) + sys.exit(ec) + signal.signal(signal.SIGCHLD, childied) + os.close(rfd) + try: + while True: + l = raw_input() + os.write(wfd, l + '\n') + except (KeyboardInterrupt, SystemExit): raise + except: pass diff --git a/python/simple.py b/python/simple.py new file mode 100755 index 00000000..a74cecdf --- /dev/null +++ b/python/simple.py @@ -0,0 +1,12 @@ +#!/usr/bin/python +""" +A very simple example of the Flexiprovider. Exports three properties +initially. +""" + +from ContextKit.flexiprovider import * + +Flexiprovider([INT('location.altitude'), + STRING('my.name', 'flexi'), + TRUTH('is.out.there')], + 'ctx.flexi.provider', 'session').interactive() |