From b8bdb5c1a2dcd36083d48389df229388be328a65 Mon Sep 17 00:00:00 2001 From: Konstantin Stepanov Date: Sun, 17 Oct 2010 16:18:37 +0300 Subject: [PATCH] Initial commit --- .gitignore | 2 + dbuscron.py | 47 ++++++++++++++++++++++ dbuscron/__init__.py | 8 ++++ dbuscron/bus.py | 104 +++++++++++++++++++++++++++++++++++++++++++++++++ dbuscron/command.py | 65 +++++++++++++++++++++++++++++++ dbuscron/daemonize.py | 57 +++++++++++++++++++++++++++ dbuscron/parser.py | 80 +++++++++++++++++++++++++++++++++++++ 7 files changed, 363 insertions(+) create mode 100644 .gitignore create mode 100755 dbuscron.py create mode 100644 dbuscron/__init__.py create mode 100644 dbuscron/bus.py create mode 100644 dbuscron/command.py create mode 100644 dbuscron/daemonize.py create mode 100644 dbuscron/parser.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..34fcef1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.pyo +*.pyc diff --git a/dbuscron.py b/dbuscron.py new file mode 100755 index 0000000..a2bfb17 --- /dev/null +++ b/dbuscron.py @@ -0,0 +1,47 @@ +#!/usr/bin/python +# +# bus type sender interface path member destination args command +# +# Examples for N900: +# +# Headphones unplugged: +# S signal * org.freedesktop.Hal.Manager /org/freedesktop/Hal/Manager DeviceRemoved * * echo Headphones unplugged; +# +# Call recieved: +# S signal * com.nokia.csd.Call /com/nokia/csd/call Coming * * echo $DBUS_ARG1 is calling +# + +import sys + +if __name__ == '__main__': + + daemon = (len(sys.argv) < 2) or (sys.argv[1] != '-f') + if daemon: + from dbuscron.daemonize import daemonize + daemonize( + pidfile='/var/run/dbuscron.pid', + logfile='/var/log/dbuscron.log' + ) + + from dbuscron import DbusBus, DbusRuleMatcher, Command, Commands, CrontabParser + + bus = DbusBus() + commands = Commands() + crontab = CrontabParser('/etc/dbuscrontab') + + for rule, cmd in crontab: + matcher = DbusRuleMatcher(**rule) + command = Command(cmd) + matcher.register() + commands.add(matcher, command) + + commands.environ = crontab.environ + bus.attach_handler(commands.handler) + + try: + bus.listen() + except KeyboardInterrupt: + sys.exit(2) + +# vim: ts=8 sts=4 sw=4 et + diff --git a/dbuscron/__init__.py b/dbuscron/__init__.py new file mode 100644 index 0000000..c01666e --- /dev/null +++ b/dbuscron/__init__.py @@ -0,0 +1,8 @@ + +from dbuscron.bus import DbusRuleMatcher, DbusBus +from dbuscron.command import Command, Commands +from dbuscron.daemonize import daemonize +from dbuscron.parser import CrontabParser + +__all__ = ['DbusRuleMatcher', 'DbusBus', 'Command', 'Commands', 'daemonize', 'CrontabParser'] + diff --git a/dbuscron/bus.py b/dbuscron/bus.py new file mode 100644 index 0000000..300c215 --- /dev/null +++ b/dbuscron/bus.py @@ -0,0 +1,104 @@ + +import dbus + +def get_dbus_message_type(message): + return message.__class__.__name__.lower()[0:-7] + +class DbusBus(object): + __bus = None + __system_bus = None + __session_bus = None + + def __new__(cls): + if not cls.__bus: + cls.__bus = super(DbusBus, cls).__new__(cls) + return cls.__bus + + @property + def system(self): + if not self.__system_bus: + self.__system_bus = dbus.SystemBus() + return self.__system_bus + + @property + def session(self): + if not self.__session_bus: + self.__session_bus = dbus.SessionBus() + return self.__session_bus + + def attach_handler(self, handler): + if self.__system_bus: + self.__system_bus.add_message_filter(handler) + if self.__session_bus: + self.__session_bus.add_message_filter(handler) + + def listen(self): + from dbus.mainloop.glib import DBusGMainLoop + from gobject import MainLoop + DBusGMainLoop(set_as_default=True) + loop = MainLoop() + loop.run() + +class DbusRuleMatcher(object): + def __init__(self, bus_=None, type_=None, sender_=None, interface_=None, path_=None, member_=None, destination_=None, args_=[]): + self._bus = bus_ + self._type = type_ + self._sender = sender_ + self._interface = interface_ + self._path = path_ + self._member = member_ + self._destination = destination_ + self._args = args_ + + def register(self): + rule = str(self) + if rule: + self._bus.add_match_string(str(self)) + + def __str__(self): + rule = [] + for key in ['type', 'sender', 'interface', 'path', 'member', 'destination']: + value = getattr(self, '_'+key) + if value is not None: + rule.append("%s='%s'" % (key, value)) + + if self._args: + for i, arg in enumerate(self._args): + rule.append("arg%d%s='%s'" % (i, 'path' if arg.startswith('/') else '', arg)) + + return ','.join(rule) + + def match(self, bus, message): + + if self._bus not in (None, bus): + return False + + if self._type is not None: + type_ = get_dbus_message_type(message) + if self._type != type_: + return False + + if self._sender not in (None, message.get_sender()): + return False + + if self._interface not in (None, message.get_interface()): + return False + + if self._path not in (None, message.get_path()): + return False + + if self._member not in (None, message.get_member()): + return False + + if self._destination not in (None, message.get_destination()): + return False + + args_ = message.get_args_list() + for i, arg in enumerate(args_): + if i >= len(self._args): + break + if self._args[i] not in (None, arg): + return False + + return True + diff --git a/dbuscron/command.py b/dbuscron/command.py new file mode 100644 index 0000000..e7d8c68 --- /dev/null +++ b/dbuscron/command.py @@ -0,0 +1,65 @@ + +import os + +class Command(object): + def __init__(self, cmd): + self.__value = cmd + if self.is_shell_cmd: + self.__file = os.environ.get('SHELL', '/bin/sh') + self.__args = [self.__file, '-c', self.__value] + else: + self.__args = cmd.split(' ') + self.__file = self.__args[0] + + def __call__(self, bus, message, environ): + + args_list = message.get_args_list() + env = dict() + env.update(environ) + env.update(dict( + (('DBUS_ARG%d' % i, str(args_list[i])) for i in range(0, len(args_list))), + DBUS_ARGN = str(len(args_list)), + DBUS_SENDER = str(message.get_sender()), + DBUS_DEST = str(message.get_destination()), + DBUS_IFACE = str(message.get_interface()), + DBUS_PATH = str(message.get_path()), + DBUS_MEMBER = str(message.get_member()), + DBUS_BUS = bus.__class__.__name__.lower()[0:-3], + DBUS_TYPE = get_dbus_message_type(message) + )) + result = os.spawnvpe(os.P_WAIT, self.__file, self.__args, env) + return result + + @property + def is_shell_cmd(self): + for c in '|><$&;{}': + if c in self.__value: + return True + return False + + def __str__(self): + return self.__value + +class Commands(object): + __commands = {} + __environ = {} + + def _get_environ(self): + return self.__environ + + def _set_environ(self, value): + self.__environ = dict() + self.__environ.update(os.environ) + self.__environ.update(value) + + environ = property(_get_environ, _set_environ) + + def handler(self, bus, message): + for rule, command in self.__commands.iteritems(): + if rule.match(bus, message): + command(bus, message, self.__environ) + return + + def add(self, matcher, command): + self.__commands[matcher] = command + diff --git a/dbuscron/daemonize.py b/dbuscron/daemonize.py new file mode 100644 index 0000000..4096289 --- /dev/null +++ b/dbuscron/daemonize.py @@ -0,0 +1,57 @@ +import os, sys + +def daemonize(logfile=None, errfile=None, pidfile=None): + devnull = os.devnull if hasattr(os, 'devnull') else '/dev/null' + + initwd = os.getcwd() + def absolutize(path): + if path.startswith('/'): + return path + return os.path.join(initwd, path) + + try: + if os.fork() == 0: + os.setsid() + if os.fork() == 0: + os.chdir('/') + os.umask(0) + else: + os._exit(0) + else: + os._exit(0) + except OSError, e: + raise Exception('Failed daemonization: %s' % str(e)) + + for i in range(0, 3): + os.close(i) + + os.open(devnull, os.O_RDWR) + + def open_trunc(fname): + f = os.open(fname, \ + os.O_WRONLY|os.O_CREAT) + os.ftruncate(f, 0) + return f + + def open_or_dup(fname, fd=None): + if fname: + return open_trunc(absolutize(fname)) + elif fd: + os.dup2(*fd) + + open_or_dup(logfile, (0, 1)) + open_or_dup(errfile, (1, 2)) + + pid = os.getpid() + if pidfile: + pidfile = absolutize(pidfile) + fd = open_trunc(pidfile) + os.write(fd, str(pid)) + os.close(fd) + + def remove_pidfile(): + os.unlink(pidfile) + sys.exitfunc = remove_pidfile + + return pid + diff --git a/dbuscron/parser.py b/dbuscron/parser.py new file mode 100644 index 0000000..6dcd6cb --- /dev/null +++ b/dbuscron/parser.py @@ -0,0 +1,80 @@ +from __future__ import with_statement +import re +from dbuscron.bus import DbusBus + +try: + from itertools import product +except ImportError: + def product(*args): + if args: + head, tail = args[0], args[1:] + for h in head: + for t in product(*tail): + yield (h,) + t + + else: + yield () + +class CrontabParser(object): + __fields_sep = re.compile(r'\s+') + __envvar_sep = re.compile(r'\s*=\s*') + __fields = [ + 'bus_', + 'type_', + 'sender_', + 'interface_', + 'path_', + 'member_', + 'destination_', + 'args_', + #'command' + ] + + def __init__(self, fname): + self.__bus = DbusBus() + self.__filename = fname + self.__environ = dict() + + @property + def environ(self): + return self.__environ + + def __iter__(self): + # bus type sender interface path member destination args command + with open(self.__filename) as f: + for line in f: + line = line.strip() + + if not line or line.startswith('#'): + continue + + parts = self.__fields_sep.split(line, 8) + if len(parts) < 9: + parts = self.__envvar_sep(line, 1) + if len(parts) == 2: + self.__environ[parts[0]] = parts[1] + continue + + rule = [(None,), (None,), (None,), (None,), (None,), (None,), (None,), (None,)] + + for p in range(1, 8): + if parts[p] != '*': + rule[p] = parts[p].split(',') + + command = parts[8] + + if parts[0] == '*' or parts[0] == 'S,s' or parts[0] == 's,S': + rule[0] = (self.__bus.system, self.__bus.session) + elif parts[0] == 's': + rule[0] = (self.__bus.session,) + elif parts[0] == 'S': + rule[0] = (self.__bus.system,) + + for r in product(*rule): + if r[7]: + r[7] = r[7].split(';') + ruled = dict() + for i, f in enumerate(self.__fields): + ruled[f] = r[i] + yield ruled, command + -- 1.7.9.5