--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/pvl/irk.py Fri Jan 11 17:12:51 2013 +0200
@@ -0,0 +1,199 @@
+"""
+ Irker client.
+"""
+
+import optparse, sys
+
+import logging; log = logging.getLogger('pvl.irk')
+
+# proto
+import socket, json
+
+def parser (parser, connect='tcp://localhost/', target=None) :
+ """
+ Optparse option group.
+ """
+
+ irker = optparse.OptionGroup(parser, 'Irker output')
+
+ irker.add_option('--irker', metavar='URL', default=connect,
+ help="Irker daemon URL")
+
+ return irker
+
+def apply (options) :
+ """
+ Return Irker (XXX: target) from options.
+ """
+
+ # None -> stdout
+ return Irker(options.irker)
+
+def connect (host=None, port=None, family=socket.AF_UNSPEC, socktype=socket.SOCK_STREAM) :
+ """
+ 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
+
+ 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: %s" % (host, port, error))
+
+import urlparse
+
+class Irk (object) :
+ """
+ Irker JSON connection.
+
+ TODO: timeout?
+ """
+
+ PORT = 6659
+
+ SCHEME = {
+ 'tcp': (socket.AF_INET, socket.SOCK_STREAM),
+ 'udp': (socket.AF_INET, socket.SOCK_DGRAM),
+ '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)
+ else :
+ self.url = None
+
+ self.targets = {}
+
+ self.connect()
+
+ def connect (self) :
+ """
+ Connect, and fix up our targets.
+ """
+
+ self.irk = Irk.connect(self.url)
+
+ # rejoin
+ for target in self.targets.itervalues() :
+ target.join()
+
+ def send (self, **opts) :
+ """
+ Send on current irker connection.
+
+ TODO: handle errors and reconnect?
+ """
+
+ self.irk.send(**opts)
+
+ def target (self, target) :
+ """
+ Bind to given target URL, returning an IrkerTarget for sending messages.
+ """
+
+ if target not in self.targets :
+ self.targets[target] = IrkerTarget(self, target)
+ self.targets[target].join()
+
+ return self.targets[target]
+
+ __getitem__ = target