pvl/irk.py
author Tero Marttila <terom@paivola.fi>
Sun, 13 Jan 2013 01:52:49 +0200
changeset 116 89b7385d19ba
parent 111 4b96c153c113
child 121 4f16bf6365f1
permissions -rw-r--r--
pvl.irk: split off url/socket stuff into pvl.socket, clarify Irk __call__/__iter__ iterface
"""
    Irker client.
"""

import pvl.syslog.file # for stdin
import pvl.socket # for tcp

import optparse, sys

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

import 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")

    irker.add_option('--irker-notice',          action='store_true',
            help="Use irker NOTICE")

    return irker

def apply (options) :
    """
        Return Irker (XXX: target) from options.
    """
    
    # None -> stdout
    return Irker(options.irker, options.irker_notice)

class Irk (object) :
    """
        Irker JSON connection speaks JSON over a stream.

        TODO: timeouts?
    """

    PORT = 6659

    @classmethod
    def connect (cls, url) :
        """
            Connect to given URL string, or None -> stdout
        """

        if not url :
            # no read
            return cls(pvl.syslog.file.File(sys.stdout), recv=False)

        else :
            sock = pvl.socket.connect(url, port=cls.PORT)

            # just to make things a bit more exciting... and we really don't want to be blocking on our output..
            sock.setblocking(False)
            
            return cls(
                    pvl.socket.WriteStream(sock, buffer=None),
                    pvl.socket.ReadStream(sock)
            )

    def __init__ (self, send, recv=None) :
        """
            Use given file-like object (write, flush, fileno) for output.
        """

        self.send = send
        self.recv = recv
        
        log.debug("%s <-> %s", send, recv)

    def fileno (self) :
        """
            Return fd. Useful for detecting error conditions (connection lost).

            Only valid if self.recv is True.
        """

        return self.recv.fileno()

    def __call__ (self, **opts) :
        """
            Raises IOError on write errors.
        """

        log.debug("%s", opts)
        
        # write line + flush
        self.send(json.dumps(opts))
        
        # XXX: self.send.flush()

    def __iter__ (self) :
        """
            Yield JSON inputs from source.
        """

        for line in self.recv :
            # XXX: error handling?
            yield json.loads(line)

class IrkerTarget (object) :
    """
        A channel on an Irk connection.
    """

    def __init__ (self, irker, target, notice=False) :
        self.irker = irker
        self.target = target

        self._notice = notice
        
    def join (self) :
        log.info("%s", self)
        self.irker(to=str(self), privmsg='')

    def privmsg (self, *args) :
        for arg in args :
            log.info("%s: %s", self, arg)
            self.irker(to=str(self), privmsg=arg)

    def notice (self, *args) :
        for arg in args :
            log.info("%s: %s", self, arg)
            self.irker(to=str(self), notice=arg)

    def __call__ (self, *args) :
        # default msg policy
        if self._notice :
            return self.notice(*args)
        else :
            return self.privmsg(*args)

    def __str__ (self) :
        return self.target

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

    def __init__ (self, url=None, notice=False) :
        self.url = url
        self.targets = {}
        self.notice = notice
        
        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 __call__ (self, **opts) :
        """
            Send on current irker connection.

            TODO: handle errors and reconnect?
        """

        self.irk(**opts)

    def __getitem__ (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, notice=self.notice)
            self.targets[target].join()
            
        return self.targets[target]