aboutsummaryrefslogtreecommitdiffhomepage
path: root/bin
diff options
context:
space:
mode:
authorGravatar Brendan Taylor <whateley@gmail.com>2011-04-11 19:59:51 -0600
committerGravatar Brendan Taylor <whateley@gmail.com>2011-04-11 19:59:51 -0600
commit841914c050bc00a39d9313c05856568696e58a23 (patch)
tree802747fe0388b230e02b0c49f9f946532629b404 /bin
parent3384703009e8190c8ad50821a4ace94c03733488 (diff)
move examples/data/scripts/uzbl-event-manager to bin/uzbl-event-manager
Diffstat (limited to 'bin')
-rwxr-xr-xbin/uzbl-event-manager991
1 files changed, 991 insertions, 0 deletions
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 <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: