"""
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")
irker.add_option('--irker-part', action='store_true',
help="Use irker PART")
return irker
def apply (options) :
"""
Return Irker (XXX: target) from options.
"""
# None -> stdout
return Irker(options.irker, options) # options.irker_*
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.
"""
if not self.recv :
# never going to be anything
return
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=None, part=None) :
self.irker = irker
self.target = target
self._notice = notice
self._part = part
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 part (self, msg='') :
log.info("%s: %s", self, msg)
if self._part :
self.irker(to=str(self), part=msg)
else :
log.warn("%s: no --irker-part", self)
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, options=None) :
"""
url - irker to connect to
options - irker_* configs
"""
self.url = url
self.targets = {}
self.options = options
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.options and self.options.irker_notice,
part = self.options and self.options.irker_part,
)
self.targets[target].join()
return self.targets[target]
def __delitem__ (self, target) :
"""
Unbind given target URL.
"""
target = self.targets.pop(target)
target.part()
def __iter__ (self) :
return iter(self.targets)