terom@48: import optparse, sys terom@48: terom@48: import logging; log = logging.getLogger('pvl.irker') terom@48: terom@48: # proto terom@48: import socket, json terom@48: terom@48: def parser (parser, connect='tcp://localhost/', target=None) : terom@48: """ terom@48: Optparse option group. terom@48: """ terom@48: terom@48: irker = optparse.OptionGroup(parser, 'Irker output') terom@48: terom@48: irker.add_option('--irker-connect', metavar='URL', default=connect, terom@48: help="Irker daemon URL") terom@48: terom@48: irker.add_option('--irker-target', metavar='IRC', default=target, terom@48: help="Irker target URL") terom@48: terom@48: return irker terom@48: terom@48: def apply (options, target=None) : terom@48: """ terom@48: Return Irker (XXX: target) from options. terom@48: """ terom@48: terom@48: # XXX: None -> stdout terom@48: irk = Irker(options.irker_connect) terom@48: terom@48: if options.irker_target : terom@48: target = options.irker_target terom@48: terom@48: if target : terom@48: target = irk.target(target) terom@48: terom@48: return irk, target terom@48: terom@48: def connect (host=None, port=None, family=socket.AF_UNSPEC, socktype=socket.SOCK_STREAM) : terom@48: for af, st, proto, name, addr in socket.getaddrinfo(host, port, family, socktype) : terom@48: try : terom@48: s = socket.socket(af, st, proto) terom@48: terom@48: except socket.error as error : terom@48: log.warning("%s:%s: socket: %s", host, port, error) terom@48: terom@48: log.info("%s", name) terom@48: terom@48: try : terom@48: s.connect(addr) terom@48: terom@48: except socket.error as error : terom@48: log.warning("%s:%s: connect: %s", host, port, error) terom@48: terom@48: return s terom@48: terom@48: else : terom@48: raise Exception("Unable to connect: %s:%s" % (host, port)) terom@48: terom@48: class Irk (object) : terom@48: """ terom@48: Irker connection. terom@48: """ terom@48: terom@48: PORT = 6659 terom@48: terom@48: @classmethod terom@48: def socket (cls, socket) : terom@48: return cls(socket.makefile('w')) terom@48: terom@48: def __init__ (self, file) : terom@48: self.file = file terom@48: terom@48: log.debug("%s", file) terom@48: terom@48: def send (self, **opts) : terom@71: log.debug("%s", opts) terom@71: terom@48: json.dump(opts, self.file) terom@48: self.file.write('\n') terom@48: self.file.flush() terom@48: terom@48: def join (self, to) : terom@71: log.info("%s", to) terom@48: self.send(to=to, privmsg='') terom@48: terom@48: def privmsg (self, to, *args) : terom@48: for arg in args : terom@71: log.info("%s: %s", to, arg) terom@48: self.send(to=to, privmsg=arg) terom@48: terom@48: import urlparse terom@48: import functools terom@48: terom@48: class Irker (object) : terom@48: """ terom@48: Reconnecting irker. terom@48: terom@48: XXX: reconnect with state, or just crash and burn to be restarted? terom@48: """ terom@48: terom@48: SCHEME = { terom@48: 'tcp': (socket.AF_INET, socket.SOCK_STREAM), terom@48: 'udp': (socket.AF_INET, socket.SOCK_DGRAM), terom@48: 'unix': (socket.AF_UNIX, socket.SOCK_DGRAM), terom@48: } terom@48: terom@48: def __init__ (self, url=None) : terom@48: if url : terom@48: self.url = urlparse.urlparse(url) terom@48: else : terom@48: self.url = None terom@48: terom@48: self.targets = {} terom@48: terom@48: self.irk = self.connect() terom@48: terom@48: def connect (self) : terom@48: if not self.url : terom@48: # XXX: not here, do this in apply()? terom@48: return Irk(sys.stdout) terom@48: terom@48: family, socktype = self.SCHEME[self.url.scheme] terom@48: terom@48: if family == socket.AF_UNIX : terom@48: raise Exception("unix:// is not supported") terom@48: else : terom@48: # inet terom@48: s = connect(self.url.hostname, self.url.port or Irk.PORT, family=family, socktype=socktype) terom@48: terom@48: irk = Irk.socket(s) terom@48: terom@48: # rejoin terom@48: for target in self.targets : terom@48: irk.join(target) terom@48: terom@48: return irk terom@48: terom@48: def _target (self, target, *args) : terom@48: try : terom@48: for msg in args : terom@48: self.irk.send(to=target, privmsg=msg) terom@48: except IOError as ex : terom@48: log.warning("lost irk: %s", ex) terom@48: terom@48: # XXX: reconnect? terom@48: self.irk = self.connect() terom@48: terom@48: def target (self, target) : terom@48: """ terom@48: Bind to given target URL, returning a callable for sending messages. terom@48: """ terom@48: terom@48: if target in self.targets : terom@48: _target = self.targets[target] terom@48: else : terom@48: self.irk.join(target) terom@48: _target = self.targets[target] = functools.partial(self._target, target) terom@48: terom@48: return _target terom@48: terom@48: