--- 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