diff -r 8deb7d308d18 -r 530c2aa73a97 pvl/irker.py --- a/pvl/irker.py Thu Jan 10 20:03:33 2013 +0200 +++ b/pvl/irker.py Thu Jan 10 20:03:44 2013 +0200 @@ -22,76 +22,60 @@ Return Irker (XXX: target) from options. """ - if options.irker : - # XXX: None -> stdout - irk = Irker(options.irker) - else : - irk = None - - return irk + # None -> stdout + return Irker(options.irker) def connect (host=None, port=None, family=socket.AF_UNSPEC, socktype=socket.SOCK_STREAM) : - for af, st, proto, name, addr in socket.getaddrinfo(host, port, family, socktype) : + """ + Return a TCP/UDP socket connected to the given host/port using getaddrinfo. + + TODO: timeout? + """ + + log.debug("%s:%s: %s/%s", host, port, family, socktype) + + if host : + flags = socket.AI_CANONNAME + else : + flags = 0 + + addrinfo = socket.getaddrinfo(host, port, family, socktype, 0, flags) + + if not addrinfo : + raise Exception("getaddrinfo: %s:%s: no results" % (host, port)) + + for af, st, proto, name, addr in addrinfo : try : s = socket.socket(af, st, proto) except socket.error as error : log.warning("%s:%s: socket: %s", host, port, error) + continue - log.info("%s", name) - try : s.connect(addr) except socket.error as error : log.warning("%s:%s: connect: %s", host, port, error) + continue + + log.info("%s", name) return s else : - raise Exception("Unable to connect: %s:%s" % (host, port)) + raise Exception("Unable to connect: %s:%s: %s" % (host, port, error)) + +import urlparse class Irk (object) : """ - Irker connection. + Irker JSON connection. + + TODO: timeout? """ PORT = 6659 - - @classmethod - def socket (cls, socket) : - return cls(socket.makefile('w')) - - def __init__ (self, file) : - self.file = file - - log.debug("%s", file) - - def send (self, **opts) : - log.debug("%s", opts) - - json.dump(opts, self.file) - self.file.write('\n') - self.file.flush() - - def join (self, to) : - log.info("%s", to) - self.send(to=to, privmsg='') - - def privmsg (self, to, *args) : - for arg in args : - log.info("%s: %s", to, arg) - self.send(to=to, privmsg=arg) - -import urlparse -import functools - -class Irker (object) : - """ - Reconnecting irker. - - XXX: reconnect with state, or just crash and burn to be restarted? - """ SCHEME = { 'tcp': (socket.AF_INET, socket.SOCK_STREAM), @@ -99,6 +83,74 @@ 'unix': (socket.AF_UNIX, socket.SOCK_DGRAM), } + @classmethod + def connect (cls, url) : + """ + Connect to given urllib URL, or None -> stdout + """ + + if not url : + return cls(sys.stdout) + + family, socktype = cls.SCHEME[url.scheme] + + if family == socket.AF_UNIX : + raise Exception("unix:// is not supported") + else : + # inet + sock = connect(url.hostname, url.port or cls.PORT, family=family, socktype=socktype) + + return cls(sock.makefile('w')) + + def __init__ (self, file) : + """ + Use given file-like object for output. + """ + + self.file = file + + log.debug("%s", file) + + def send (self, **opts) : + """ + Raises IOError on write errors. + """ + + log.debug("%s", opts) + + # write line + flush + json.dump(opts, self.file) + self.file.write('\n') + self.file.flush() + +class IrkerTarget (object) : + """ + A channel on an Irk connection. + """ + + def __init__ (self, irker, target) : + self.irker = irker + self.target = target + + def join (self) : + log.info("%s", self) + self.irker.send(to=str(self), privmsg='') + + def privmsg (self, *args) : + for arg in args : + log.info("%s: %s", self, arg) + self.irker.send(to=str(self), privmsg=arg) + + __call__ = privmsg + + def __str__ (self) : + return self.target + +class Irker (object) : + """ + Reconnecting irker. + """ + def __init__ (self, url=None) : if url : self.url = urlparse.urlparse(url) @@ -107,50 +159,37 @@ self.targets = {} - self.irk = self.connect() + self.connect() def connect (self) : - if not self.url : - # XXX: not here, do this in apply()? - return Irk(sys.stdout) + """ + Connect, and fix up our targets. + """ - family, socktype = self.SCHEME[self.url.scheme] - - if family == socket.AF_UNIX : - raise Exception("unix:// is not supported") - else : - # inet - s = connect(self.url.hostname, self.url.port or Irk.PORT, family=family, socktype=socktype) - - irk = Irk.socket(s) + self.irk = Irk.connect(self.url) # rejoin - for target in self.targets : - irk.join(target) - - return irk + for target in self.targets.itervalues() : + target.join() - def _target (self, target, *args) : - try : - for msg in args : - self.irk.send(to=target, privmsg=msg) - except IOError as ex : - log.warning("lost irk: %s", ex) + def send (self, **opts) : + """ + Send on current irker connection. - # XXX: reconnect? - self.irk = self.connect() + TODO: handle errors and reconnect? + """ + + self.irk.send(**opts) def target (self, target) : """ - Bind to given target URL, returning a callable for sending messages. + Bind to given target URL, returning an IrkerTarget for sending messages. """ - if target in self.targets : - _target = self.targets[target] - else : - self.irk.join(target) - _target = self.targets[target] = functools.partial(self._target, target) - - return _target + if target not in self.targets : + self.targets[target] = IrkerTarget(self, target) + self.targets[target].join() + + return self.targets[target] - + __getitem__ = target