- WebKitRenderer
added:
#KEY_PRESS:
#KEY_RELEASE:
#SCROLL_HORIZ:
#SCROLL_VERT:
#!/usr/bin/env python
# Event Manager for Uzbl
# Copyright (c) 2009-2010, Mason Larobina <mason.larobina@gmail.com>
# Copyright (c) 2009, Dieter Plaetinck <dieter@plaetinck.be>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
'''
E V E N T _ M A N A G E R . P Y
===============================
Event manager for uzbl written in python.
'''
import atexit
import imp
import logging
import os
import socket
import sys
import time
import weakref
import re
from collections import defaultdict
from functools import partial
from glob import glob
from itertools import count
from optparse import OptionParser
from select import select
from signal import signal, SIGTERM, SIGINT
from socket import socket, AF_UNIX, SOCK_STREAM
from traceback import format_exc
def xdghome(key, default):
'''Attempts to use the environ XDG_*_HOME paths if they exist otherwise
use $HOME and the default path.'''
xdgkey = "XDG_%s_HOME" % key
if xdgkey in os.environ.keys() and os.environ[xdgkey]:
return os.environ[xdgkey]
return os.path.join(os.environ['HOME'], default)
# `make install` will put the correct value here for your system
PREFIX = '/usr/local/'
# Setup xdg paths.
DATA_DIR = os.path.join(xdghome('DATA', '.local/share/'), 'uzbl/')
CACHE_DIR = os.path.join(xdghome('CACHE', '.cache/'), 'uzbl/')
# Define some globals.
SCRIPTNAME = os.path.basename(sys.argv[0])
def get_exc():
'''Format `format_exc` for logging.'''
return "\n%s" % format_exc().rstrip()
def expandpath(path):
'''Expand and realpath paths.'''
return os.path.realpath(os.path.expandvars(path))
def ascii(u):
'''Convert unicode strings into ascii for transmission over
ascii-only streams/sockets/devices.'''
return u.encode('utf-8')
def daemonize():
'''Daemonize the process using the Stevens' double-fork magic.'''
logger.info('entering daemon mode')
try:
if os.fork():
os._exit(0)
except OSError:
logger.critical(get_exc())
sys.exit(1)
os.chdir('/')
os.setsid()
os.umask(0)
try:
if os.fork():
os._exit(0)
except OSError:
logger.critical(get_exc())
sys.exit(1)
if sys.stdout.isatty():
sys.stdout.flush()
sys.stderr.flush()
devnull = '/dev/null'
stdin = file(devnull, 'r')
stdout = file(devnull, 'a+')
stderr = file(devnull, 'a+', 0)
os.dup2(stdin.fileno(), sys.stdin.fileno())
os.dup2(stdout.fileno(), sys.stdout.fileno())
os.dup2(stderr.fileno(), sys.stderr.fileno())
logger.info('entered daemon mode')
def make_dirs(path):
'''Make all basedirs recursively as required.'''
try:
dirname = os.path.dirname(path)
if not os.path.isdir(dirname):
logger.debug('creating directories %r' % dirname)
os.makedirs(dirname)
except OSError:
logger.error(get_exc())
class EventHandler(object):
'''Event handler class. Used to store args and kwargs which are merged
come time to call the callback with the event args and kwargs.'''
nextid = count().next
def __init__(self, plugin, event, callback, args, kwargs):
self.id = self.nextid()
self.plugin = plugin
self.event = event
self.callback = callback
self.args = args
self.kwargs = kwargs
def __repr__(self):
elems = ['id=%d' % self.id, 'event=%s' % self.event,
'callback=%r' % self.callback]
if self.args:
elems.append('args=%s' % repr(self.args))
if self.kwargs:
elems.append('kwargs=%s' % repr(self.kwargs))
elems.append('plugin=%s' % self.plugin.name)
return u'<handler(%s)>' % ', '.join(elems)
def call(self, uzbl, *args, **kwargs):
'''Execute the handler function and merge argument lists.'''
args = args + self.args
kwargs = dict(self.kwargs.items() + kwargs.items())
self.callback(uzbl, *args, **kwargs)
class Plugin(object):
'''Plugin module wrapper object.'''
# Special functions exported from the Plugin instance to the
# plugin namespace.
special_functions = ['require', 'export', 'export_dict', 'connect',
'connect_dict', 'logger', 'unquote', 'splitquoted']
def __init__(self, parent, name, path, plugin):
self.parent = parent
self.name = name
self.path = path
self.plugin = plugin
self.logger = get_logger('plugin.%s' % name)
# Weakrefs to all handlers created by this plugin
self.handlers = set([])
# Plugins init hook
init = getattr(plugin, 'init', None)
self.init = init if callable(init) else None
# Plugins optional after hook
after = getattr(plugin, 'after', None)
self.after = after if callable(after) else None
# Plugins optional cleanup hook
cleanup = getattr(plugin, 'cleanup', None)
self.cleanup = cleanup if callable(cleanup) else None
assert init or after or cleanup, "missing hooks in plugin"
# Export plugin's instance methods to plugin namespace
for attr in self.special_functions:
plugin.__dict__[attr] = getattr(self, attr)
def __repr__(self):
return u'<plugin(%r)>' % self.plugin
def export(self, uzbl, attr, object, prepend=True):
'''Attach `object` to `uzbl` instance. This is the preferred method
of sharing functionality, functions, data and objects between
plugins.
If the object is callable you may wish to turn the callable object
in to a meta-instance-method by prepending `uzbl` to the call stack.
You can change this behaviour with the `prepend` argument.
'''
assert attr not in uzbl.exports, "attr %r already exported by %r" %\
(attr, uzbl.exports[attr][0])
prepend = True if prepend and callable(object) else False
uzbl.__dict__[attr] = partial(object, uzbl) if prepend else object
uzbl.exports[attr] = (self, object, prepend)
uzbl.logger.info('exported %r to %r by plugin %r, prepended %r'
% (object, 'uzbl.%s' % attr, self.name, prepend))
def export_dict(self, uzbl, exports):
for (attr, object) in exports.items():
self.export(uzbl, attr, object)
def find_handler(self, event, callback, args, kwargs):
'''Check if a handler with the identical callback and arguments
exists and return it.'''
# Remove dead refs
self.handlers -= set(filter(lambda ref: not ref(), self.handlers))
# Find existing identical handler
for handler in [ref() for ref in self.handlers]:
if handler.event == event and handler.callback == callback \
and handler.args == args and handler.kwargs == kwargs:
return handler
def connect(self, uzbl, event, callback, *args, **kwargs):
'''Create an event handler object which handles `event` events.
Arguments passed to the connect function (`args` and `kwargs`) are
stored in the handler object and merged with the event arguments
come handler execution.
All handler functions must behave like a `uzbl` instance-method (that
means `uzbl` is prepended to the callback call arguments).'''
# Sanitise and check event name
event = event.upper().strip()
assert event and ' ' not in event
assert callable(callback), 'callback must be callable'
# Check if an identical handler already exists
handler = self.find_handler(event, callback, args, kwargs)
if not handler:
# Create a new handler
handler = EventHandler(self, event, callback, args, kwargs)
self.handlers.add(weakref.ref(handler))
self.logger.info('new %r' % handler)
uzbl.handlers[event].append(handler)
uzbl.logger.info('connected %r' % handler)
return handler
def connect_dict(self, uzbl, connects):
for (event, callback) in connects.items():
self.connect(uzbl, event, callback)
def require(self, plugin):
'''Check that plugin with name `plugin` has been loaded. Use this to
ensure that your plugins dependencies have been met.'''
assert plugin in self.parent.plugins, self.logger.critical(
'plugin %r required by plugin %r' (plugin, self.name))
@classmethod
def unquote(cls, s):
'''Removes quotation marks around strings if any and interprets
\\-escape sequences using `string_escape`'''
if s and s[0] == s[-1] and s[0] in ['"', "'"]:
s = s[1:-1]
return s.encode('utf-8').decode('string_escape').decode('utf-8')
_splitquoted = re.compile("( |\"(?:\\\\.|[^\"])*?\"|'(?:\\\\.|[^'])*?')")
@classmethod
def splitquoted(cls, text):
'''Splits string on whitespace while respecting quotations'''
return [cls.unquote(p) for p in cls._splitquoted.split(text) if p.strip()]
class Uzbl(object):
def __init__(self, parent, child_socket):
self.opts = opts
self.parent = parent
self.child_socket = child_socket
self.time = time.time()
self.pid = None
self.name = None
# Flag if the instance has raised the INSTANCE_START event.
self.instance_start = False
# Use name "unknown" until name is discovered.
self.logger = get_logger('uzbl-instance[]')
# Track plugin event handlers and exported functions.
self.exports = {}
self.handlers = defaultdict(list)
# Internal vars
self._depth = 0
self._buffer = ''
def __repr__(self):
return '<uzbl(%s)>' % ', '.join([
'pid=%s' % (self.pid if self.pid else "Unknown"),
'name=%s' % ('%r' % self.name if self.name else "Unknown"),
'uptime=%f' % (time.time()-self.time),
'%d exports' % len(self.exports.keys()),
'%d handlers' % sum([len(l) for l in self.handlers.values()])])
def init_plugins(self):
'''Call the init and after hooks in all loaded plugins for this
instance.'''
# Initialise each plugin with the current uzbl instance.
for plugin in self.parent.plugins.values():
if plugin.init:
self.logger.debug('calling %r plugin init hook' % plugin.name)
plugin.init(self)
# Allow plugins to use exported features of other plugins by calling an
# optional `after` function in the plugins namespace.
for plugin in self.parent.plugins.values():
if plugin.after:
self.logger.debug('calling %r plugin after hook'%plugin.name)
plugin.after(self)
def send(self, msg):
'''Send a command to the uzbl instance via the child socket
instance.'''
msg = msg.strip()
assert self.child_socket, "socket inactive"
if opts.print_events:
print ascii(u'%s<-- %s' % (' ' * self._depth, msg))
self.child_socket.send(ascii("%s\n" % msg))
def read(self):
'''Read data from the child socket and pass lines to the parse_msg
function.'''
try:
raw = unicode(self.child_socket.recv(8192), 'utf-8', 'ignore')
if not raw:
self.logger.debug('read null byte')
return self.close()
except:
self.logger.error(get_exc())
return self.close()
lines = (self._buffer + raw).split('\n')
self._buffer = lines.pop()
for line in filter(None, map(unicode.strip, lines)):
try:
self.parse_msg(line.strip())
except:
self.logger.error(get_exc())
self.logger.error('erroneous event: %r' % line)
def parse_msg(self, line):
'''Parse an incoming message from a uzbl instance. Event strings
will be parsed into `self.event(event, args)`.'''
# Split by spaces (and fill missing with nulls)
elems = (line.split(' ', 3) + ['',]*3)[:4]
# Ignore non-event messages.
if elems[0] != 'EVENT':
logger.info('non-event message: %r' % line)
if opts.print_events:
print '--- %s' % ascii(line)
return
# Check event string elements
(name, event, args) = elems[1:]
assert name and event, 'event string missing elements'
if not self.name:
self.name = name
self.logger = get_logger('uzbl-instance%s' % name)
self.logger.info('found instance name %r' % name)
assert self.name == name, 'instance name mismatch'
# Handle the event with the event handlers through the event method
self.event(event, args)
def event(self, event, *args, **kargs):
'''Raise an event.'''
event = event.upper()
if not opts.daemon_mode and opts.print_events:
elems = [event,]
if args: elems.append(unicode(args))
if kargs: elems.append(unicode(kargs))
print ascii(u'%s--> %s' % (' ' * self._depth, ' '.join(elems)))
if event == "INSTANCE_START" and args:
assert not self.instance_start, 'instance already started'
self.pid = int(args[0])
self.logger.info('found instance pid %r' % self.pid)
self.init_plugins()
elif event == "INSTANCE_EXIT":
self.logger.info('uzbl instance exit')
self.close()
if event not in self.handlers:
return
for handler in self.handlers[event]:
self._depth += 1
try:
handler.call(self, *args, **kargs)
except:
self.logger.error(get_exc())
self._depth -= 1
def close_connection(self, child_socket):
'''Close child socket and delete the uzbl instance created for that
child socket connection.'''
def close(self):
'''Close the client socket and call the plugin cleanup hooks.'''
self.logger.debug('called close method')
# Remove self from parent uzbls dict.
if self.child_socket in self.parent.uzbls:
self.logger.debug('removing self from uzbls list')
del self.parent.uzbls[self.child_socket]
try:
if self.child_socket:
self.logger.debug('closing child socket')
self.child_socket.close()
except:
self.logger.error(get_exc())
finally:
self.child_socket = None
# Call plugins cleanup hooks.
for plugin in self.parent.plugins.values():
if plugin.cleanup:
self.logger.debug('calling %r plugin cleanup hook'
% plugin.name)
plugin.cleanup(self)
logger.info('removed %r' % self)
class UzblEventDaemon(object):
def __init__(self):
self.opts = opts
self.server_socket = None
self._quit = False
# Hold uzbl instances
# {child socket: Uzbl instance, ..}
self.uzbls = {}
# Hold plugins
# {plugin name: Plugin instance, ..}
self.plugins = {}
# Register that the event daemon server has started by creating the
# pid file.
make_pid_file(opts.pid_file)
# Register a function to clean up the socket and pid file on exit.
atexit.register(self.quit)
# Add signal handlers.
for sigint in [SIGTERM, SIGINT]:
signal(sigint, self.quit)
# Load plugins into self.plugins
self.load_plugins(opts.plugins)
def load_plugins(self, plugins):
'''Load event manager plugins.'''
for path in plugins:
logger.debug('loading plugin %r' % path)
(dir, file) = os.path.split(path)
name = file[:-3] if file.lower().endswith('.py') else file
info = imp.find_module(name, [dir,])
module = imp.load_module(name, *info)
# Check if the plugin has a callable hook.
hooks = filter(callable, [getattr(module, attr, None) \
for attr in ['init', 'after', 'cleanup']])
assert hooks, "no hooks in plugin %r" % module
logger.debug('creating plugin instance for %r plugin' % name)
plugin = Plugin(self, name, path, module)
self.plugins[name] = plugin
logger.info('new %r' % plugin)
def create_server_socket(self):
'''Create the event manager daemon socket for uzbl instance duplex
communication.'''
# Close old socket.
self.close_server_socket()
sock = socket(AF_UNIX, SOCK_STREAM)
sock.bind(opts.server_socket)
sock.listen(5)
self.server_socket = sock
logger.debug('bound server socket to %r' % opts.server_socket)
def run(self):
'''Main event daemon loop.'''
logger.debug('entering main loop')
# Create and listen on the server socket
self.create_server_socket()
if opts.daemon_mode:
# Daemonize the process
daemonize()
# Update the pid file
make_pid_file(opts.pid_file)
try:
# Accept incoming connections and listen for incoming data
self.listen()
except:
if not self._quit:
logger.critical(get_exc())
# Clean up and exit
self.quit()
logger.debug('exiting main loop')
def listen(self):
'''Accept incoming connections and constantly poll instance sockets
for incoming data.'''
logger.info('listening on %r' % opts.server_socket)
# Count accepted connections
connections = 0
while (self.uzbls or not connections) or (not opts.auto_close):
socks = [self.server_socket] + self.uzbls.keys()
reads, _, errors = select(socks, [], socks, 1)
if self.server_socket in reads:
reads.remove(self.server_socket)
# Accept connection and create uzbl instance.
child_socket = self.server_socket.accept()[0]
self.uzbls[child_socket] = Uzbl(self, child_socket)
connections += 1
for uzbl in [self.uzbls[s] for s in reads]:
uzbl.read()
for uzbl in [self.uzbls[s] for s in errors]:
uzbl.logger.error('socket read error')
uzbl.close()
logger.info('auto closing')
def close_server_socket(self):
'''Close and delete the server socket.'''
try:
if self.server_socket:
logger.debug('closing server socket')
self.server_socket.close()
self.server_socket = None
if os.path.exists(opts.server_socket):
logger.info('unlinking %r' % opts.server_socket)
os.unlink(opts.server_socket)
except:
logger.error(get_exc())
def quit(self, sigint=None, *args):
'''Close all instance socket objects, server socket and delete the
pid file.'''
if sigint == SIGTERM:
logger.critical('caught SIGTERM, exiting')
elif sigint == SIGINT:
logger.critical('caught SIGINT, exiting')
elif not self._quit:
logger.debug('shutting down event manager')
self.close_server_socket()
for uzbl in self.uzbls.values():
uzbl.close()
del_pid_file(opts.pid_file)
if not self._quit:
logger.info('event manager shut down')
self._quit = True
def make_pid_file(pid_file):
'''Creates a pid file at `pid_file`, fails silently.'''
try:
logger.debug('creating pid file %r' % pid_file)
make_dirs(pid_file)
pid = os.getpid()
fileobj = open(pid_file, 'w')
fileobj.write('%d' % pid)
fileobj.close()
logger.info('created pid file %r with pid %d' % (pid_file, pid))
except:
logger.error(get_exc())
def del_pid_file(pid_file):
'''Deletes a pid file at `pid_file`, fails silently.'''
if os.path.isfile(pid_file):
try:
logger.debug('deleting pid file %r' % pid_file)
os.remove(pid_file)
logger.info('deleted pid file %r' % pid_file)
except:
logger.error(get_exc())
def get_pid(pid_file):
'''Reads a pid from pid file `pid_file`, fails None.'''
try:
logger.debug('reading pid file %r' % pid_file)
fileobj = open(pid_file, 'r')
pid = int(fileobj.read())
fileobj.close()
logger.info('read pid %d from pid file %r' % (pid, pid_file))
return pid
except (IOError, ValueError):
logger.error(get_exc())
return None
def pid_running(pid):
'''Checks if a process with a pid `pid` is running.'''
try:
os.kill(pid, 0)
except OSError:
return False
else:
return True
def term_process(pid):
'''Asks nicely then forces process with pid `pid` to exit.'''
try:
logger.info('sending SIGTERM to process with pid %r' % pid)
os.kill(pid, SIGTERM)
except OSError:
logger.error(get_exc())
logger.debug('waiting for process with pid %r to exit' % pid)
start = time.time()
while True:
if not pid_running(pid):
logger.debug('process with pid %d exit' % pid)
return True
if (time.time()-start) > 5:
logger.warning('process with pid %d failed to exit' % pid)
logger.info('sending SIGKILL to process with pid %d' % pid)
try:
os.kill(pid, SIGKILL)
except:
logger.critical(get_exc())
raise
if (time.time()-start) > 10:
logger.critical('unable to kill process with pid %d' % pid)
raise OSError
time.sleep(0.25)
def stop_action():
'''Stop the event manager daemon.'''
pid_file = opts.pid_file
if not os.path.isfile(pid_file):
logger.error('could not find running event manager with pid file %r'
% opts.pid_file)
return
pid = get_pid(pid_file)
if not pid_running(pid):
logger.debug('no process with pid %r' % pid)
del_pid_file(pid_file)
return
logger.debug('terminating process with pid %r' % pid)
term_process(pid)
del_pid_file(pid_file)
logger.info('stopped event manager process with pid %d' % pid)
def start_action():
'''Start the event manager daemon.'''
pid_file = opts.pid_file
if os.path.isfile(pid_file):
pid = get_pid(pid_file)
if pid_running(pid):
logger.error('event manager already started with pid %d' % pid)
return
logger.info('no process with pid %d' % pid)
del_pid_file(pid_file)
UzblEventDaemon().run()
def restart_action():
'''Restart the event manager daemon.'''
stop_action()
start_action()
def list_action():
'''List all the plugins that would be loaded in the current search
dirs.'''
names = {}
for plugin in opts.plugins:
(head, tail) = os.path.split(plugin)
if tail not in names:
names[tail] = plugin
for plugin in sorted(names.values()):
print plugin
if __name__ == "__main__":
parser = OptionParser('usage: %prog [options] {start|stop|restart|list}')
add = parser.add_option
add('-v', '--verbose',
dest='verbose', default=2, action='count',
help='increase verbosity')
add('-d', '--plugin-dir',
dest='plugin_dirs', action='append', metavar="DIR", default=[],
help='add extra plugin search dir, same as `-l "DIR/*.py"`')
add('-l', '--load-plugin',
dest='load_plugins', action='append', metavar="PLUGIN", default=[],
help='load plugin, loads before plugins in search dirs')
socket_location = os.path.join(CACHE_DIR, 'event_daemon')
add('-s', '--server-socket',
dest='server_socket', metavar="SOCKET", default=socket_location,
help='server AF_UNIX socket location')
add('-p', '--pid-file',
metavar="FILE", dest='pid_file',
help='pid file location, defaults to server socket + .pid')
add('-n', '--no-daemon',
dest='daemon_mode', action='store_false', default=True,
help='do not daemonize the process')
add('-a', '--auto-close',
dest='auto_close', action='store_true', default=False,
help='auto close after all instances disconnect')
add('-i', '--no-default-dirs',
dest='default_dirs', action='store_false', default=True,
help='ignore the default plugin search dirs')
add('-o', '--log-file',
dest='log_file', metavar='FILE',
help='write logging output to a file, defaults to server socket +'
' .log')
add('-q', '--quiet-events',
dest='print_events', action="store_false", default=True,
help="silence the printing of events to stdout")
(opts, args) = parser.parse_args()
opts.server_socket = expandpath(opts.server_socket)
# Set default pid file location
if not opts.pid_file:
opts.pid_file = "%s.pid" % opts.server_socket
else:
opts.pid_file = expandpath(opts.pid_file)
# Set default log file location
if not opts.log_file:
opts.log_file = "%s.log" % opts.server_socket
else:
opts.log_file = expandpath(opts.log_file)
# Logging setup
log_level = logging.CRITICAL - opts.verbose*10
# Console logging handler
ch = logging.StreamHandler()
ch.setLevel(max(log_level+10, 10))
ch.setFormatter(logging.Formatter(
'%(name)s: %(levelname)s: %(message)s'))
# File logging handler
fh = logging.FileHandler(opts.log_file, 'w', 'utf-8', 1)
fh.setLevel(max(log_level, 10))
fh.setFormatter(logging.Formatter(
'[%(created)f] %(name)s: %(levelname)s: %(message)s'))
# logging.getLogger wrapper which sets the levels and adds the
# file and console handlers automagically
def get_logger(name):
handlers = [ch, fh]
level = [max(log_level, 10),]
logger = logging.getLogger(name)
logger.setLevel(level[0])
for handler in handlers:
logger.addHandler(handler)
return logger
# Get main logger
logger = get_logger(SCRIPTNAME)
logger.info('logging to %r' % opts.log_file)
plugins = {}
# Load all `opts.load_plugins` into the plugins list
for path in opts.load_plugins:
path = expandpath(path)
matches = glob(path)
if not matches:
parser.error('cannot find plugin(s): %r' % path)
for plugin in matches:
(head, tail) = os.path.split(plugin)
if tail not in plugins:
logger.debug('found plugin: %r' % plugin)
plugins[tail] = plugin
else:
logger.debug('ignoring plugin: %r' % plugin)
# Add default plugin locations
if opts.default_dirs:
logger.debug('adding default plugin dirs to plugin dirs list')
opts.plugin_dirs += [os.path.join(DATA_DIR, 'plugins/'),
os.path.join(PREFIX, 'share/uzbl/examples/data/plugins/')]
else:
logger.debug('ignoring default plugin dirs')
# Load all plugins in `opts.plugin_dirs` into the plugins list
for dir in opts.plugin_dirs:
dir = expandpath(dir)
logger.debug('searching plugin dir: %r' % dir)
for plugin in glob(os.path.join(dir, '*.py')):
(head, tail) = os.path.split(plugin)
if tail not in plugins:
logger.debug('found plugin: %r' % plugin)
plugins[tail] = plugin
else:
logger.debug('ignoring plugin: %r' % plugin)
plugins = plugins.values()
# Check all the paths in the plugins list are files
for plugin in plugins:
if not os.path.isfile(plugin):
parser.error('plugin not a file: %r' % plugin)
if opts.auto_close: logger.debug('will auto close')
else: logger.debug('will not auto close')
if opts.daemon_mode: logger.debug('will daemonize')
else: logger.debug('will not daemonize')
opts.plugins = plugins
# init like {start|stop|..} daemon actions
daemon_actions = {'start': start_action, 'stop': stop_action,
'restart': restart_action, 'list': list_action}
if len(args) == 1:
action = args[0]
if action not in daemon_actions:
parser.error('invalid action: %r' % action)
elif not args:
logger.warning('no daemon action given, assuming %r' % 'start')
action = 'start'
else:
parser.error('invalid action argument: %r' % args)
logger.info('daemon action %r' % action)
# Do action
daemon_actions[action]()
logger.debug('process CPU time: %f' % time.clock())
# vi: set et ts=4: