pvl/irker.py
author Tero Marttila <terom@fixme.fi>
Sat, 05 Jan 2013 01:36:30 +0200
changeset 71 11b267e1b2b0
parent 48 40ccb8d3c96e
child 72 7bb07131c2b5
permissions -rw-r--r--
pvl.irker: log.info on output
import optparse, sys

import logging; log = logging.getLogger('pvl.irker')

# 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-connect', metavar='URL',  default=connect,
            help="Irker daemon URL")

    irker.add_option('--irker-target', metavar='IRC',   default=target,
            help="Irker target URL")

    return irker

def apply (options, target=None) :
    """
        Return Irker (XXX: target) from options.
    """
    
    # XXX: None -> stdout
    irk = Irker(options.irker_connect)

    if options.irker_target :
        target = options.irker_target

    if target :
        target = irk.target(target)

    return irk, target

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) :
        try :
            s = socket.socket(af, st, proto)

        except socket.error as error :
            log.warning("%s:%s: socket: %s", host, port, error)
        
        log.info("%s", name)

        try :
            s.connect(addr)

        except socket.error as error :
            log.warning("%s:%s: connect: %s", host, port, error)
        
        return s

    else :
        raise Exception("Unable to connect: %s:%s" % (host, port))

class Irk (object) :
    """
        Irker connection.
    """

    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),
        'udp':  (socket.AF_INET, socket.SOCK_DGRAM),
        'unix': (socket.AF_UNIX, socket.SOCK_DGRAM),
    }

    def __init__ (self, url=None) :
        if url :
            self.url = urlparse.urlparse(url)
        else :
            self.url = None

        self.targets = {}
        
        self.irk = self.connect()
    
    def connect (self) :
        if not self.url :
            # XXX: not here, do this in apply()?
            return Irk(sys.stdout)

        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)

        # rejoin
        for target in self.targets :
            irk.join(target)

        return irk
    
    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)

            # XXX: reconnect?
            self.irk = self.connect()

    def target (self, target) :
        """
            Bind to given target URL, returning a callable 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