From 841914c050bc00a39d9313c05856568696e58a23 Mon Sep 17 00:00:00 2001 From: Brendan Taylor Date: Mon, 11 Apr 2011 19:59:51 -0600 Subject: move examples/data/scripts/uzbl-event-manager to bin/uzbl-event-manager --- Makefile | 2 +- bin/uzbl-event-manager | 991 +++++++++++++++++++++++++++++++ examples/data/scripts/uzbl-event-manager | 991 ------------------------------- 3 files changed, 992 insertions(+), 992 deletions(-) create mode 100755 bin/uzbl-event-manager delete mode 100755 examples/data/scripts/uzbl-event-manager diff --git a/Makefile b/Makefile index df87c9e..dad7e61 100644 --- a/Makefile +++ b/Makefile @@ -136,7 +136,7 @@ install-uzbl-core: all install-dirs install-uzbl-browser: install-dirs install -m755 bin/uzbl-browser $(INSTALLDIR)/bin/uzbl-browser - install -m755 examples/data/scripts/uzbl-event-manager $(INSTALLDIR)/bin/uzbl-event-manager + install -m755 bin/uzbl-event-manager $(INSTALLDIR)/bin/uzbl-event-manager mv $(INSTALLDIR)/bin/uzbl-browser $(INSTALLDIR)/bin/uzbl-browser.bak sed 's#^PREFIX=.*#PREFIX=$(RUN_PREFIX)#' < $(INSTALLDIR)/bin/uzbl-browser.bak > $(INSTALLDIR)/bin/uzbl-browser chmod 755 $(INSTALLDIR)/bin/uzbl-browser diff --git a/bin/uzbl-event-manager b/bin/uzbl-event-manager new file mode 100755 index 0000000..cb462c7 --- /dev/null +++ b/bin/uzbl-event-manager @@ -0,0 +1,991 @@ +#!/usr/bin/env python + +# Event Manager for Uzbl +# Copyright (c) 2009-2010, Mason Larobina +# Copyright (c) 2009, Dieter Plaetinck +# +# 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 . + +''' + +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'' % ', '.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'' % 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 '' % ', '.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: diff --git a/examples/data/scripts/uzbl-event-manager b/examples/data/scripts/uzbl-event-manager deleted file mode 100755 index cb462c7..0000000 --- a/examples/data/scripts/uzbl-event-manager +++ /dev/null @@ -1,991 +0,0 @@ -#!/usr/bin/env python - -# Event Manager for Uzbl -# Copyright (c) 2009-2010, Mason Larobina -# Copyright (c) 2009, Dieter Plaetinck -# -# 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 . - -''' - -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'' % ', '.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'' % 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 '' % ', '.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: -- cgit v1.2.3