pvl/irker.py
changeset 79 530c2aa73a97
parent 72 7bb07131c2b5
--- 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