#!/usr/bin/python
import sys
import os
import datetime
import time
import select
import logging
import libxml2
import getpass
import ConfigParser
from subprocess import Popen, PIPE
from optparse import *
from M2Crypto.SSL import Context

# pubsub import must come first because it overloads part of the
# StanzaProcessor class
from ligo.lvalert import pubsub

from pyxmpp.all import JID,Iq,Presence,Message,StreamError,TLSSettings
from pyxmpp.jabber.all import Client
from pyxmpp.jabber.simple import send_message
from pyxmpp.interface import implements
from pyxmpp.interfaces import *

"""
A tool to listen for events on a pubsub node 
"""

__version__ = "$Revision$"
__date__ = "$Date$"
__name__ = "lvalert_listen"
__Id__ = "$Id$"
__title__ = "LIGO-Virgo Alert Administration"


#################################################################
# help message
usage = """\
%prog [options]
-----------------------------------------------------------------

  A tool to listen to the pubsub service of openfire at
  lvalert.phys.uwm.edu. LSC-Virgo members can activate their accounts 
  on this server by completing the form at
  
    https://www.lsc-group.phys.uwm.edu/cgi-bin/jabber-acct.cgi 

  and typing your password.

  Before using this program to listen to a node, you must subscribe to
  the node using lvalert_admin. Then you will receive any events that are
  published to that node by doing:

  %prog --username albert.einstein --password secret

  When an event is published to the node, a message will be printed to
  the window where the listener is running. To see the event, run

  %prog --username albert.einstein --password secret --show

  The owner (person who creates the node by default) can delete and
  publish information to the node. The owner can also add other
  publishers to the node. Configuration and management of nodes and
  subscriptions are handled with lvalert_admin. 
  
  Others can subscribe to any existing node. Run 

  lvalert_admin --help

  to find out how to manage your subscriptions. 

  It is also possible to specify actions to be taken upon receipt of a
  message from a given node using a config-file to specify the program
  to run when the message is received via different nodes. The payload
  of the message is piped to the command via stdin.  A sample
  config-file called example.ini might look like

  [lvalert_test]
  executible=./mycounter

  then running

  lvalert_listen --username albert.einstein --password secret --config-file example.ini

  would result in ./mycounter being executed and the output printed to
  screen whenever a message was received via the lvalert_test node. 

  Alternatively, instead of the name of an executable, you could
  indicate either "stdout" or "-" (withouth quotes) and the alert
  payload will be written to standard output.
"""

#################################################################
def parse_command_line():
  """
  Parser function dedicated
  """
  parser = OptionParser( usage=usage, \
      version= "%prog CVS\n" +
      "$Id$\n" +
      "$Name$\n")

  #username and password
  parser.add_option("-a","--username",action="store",type="string",\
      default="", help="the username of the publisher or listener" )
  parser.add_option("-b","--password",action="store",type="string",\
      default="", help="the password of the publisher or listener" )
  parser.add_option("-s","--server",action="store",type="string",\
      default="lvalert.phys.uwm.edu", help="the pubsub server" )
  parser.add_option("-r","--resource",action="store",type="string",\
      default="listener", help="resource to use in JID" )
  parser.add_option("-c","--config-file",action="store",type="string",\
      default=None, help="config file with list of actions" )

  parser.add_option("-S","--show",action="store_true",\
      default=False, help="print the payload to stdout" )

  parser.add_option("-n","--node",action="store",type="string",\
      default=None, help="name of the node on the pubsub server" )

  # debugging options
  parser.add_option("-v","--verbose",action="store_true",\
      default=False, help="be verbose as you process the request" )
  parser.add_option("-g","--debug",action="store_true",\
      default=False, help="should  print out lots of information" )
  
  (options,args) = parser.parse_args()

  return options, sys.argv[1:]
  
# ============================================================================
# -- get command line arguments
opts, args = parse_command_line()

class LVAlertHandler(object):
    """Provides the actions taken when an event arrives.
    """

    implements(IMessageHandlersProvider)
    
    def __init__(self, client, actions):
        """Just remember who created this."""
        self.client = client
        self.actions = actions
    
    def get_message_handlers(self):
        """Return list of (message_type, message_handler) tuples.

        The handlers returned will be called when matching message is received
        in a client session."""
        return [
            (None, self.message),
            ]

    def message(self,stanza):
        """Message handler for the component.

        Prints a message with the time an alert is received. If the
        --show option is set, then it will also print the contents of
        the alert.

        :returns: `True` to indicate, that the stanza should not be processed
        any further."""
        e=self.get_entry(stanza)
        n=self.get_node(stanza)
        if e:
            if n in self.actions:
                action = self.actions[n]
                if action == 'stdout' or action == '-':
                    sys.stdout.write(str(e))
                else:
                    p = Popen([self.actions[n]], stdin=PIPE, stdout=PIPE)
                    print p.communicate(e)[0]
                sys.stdout.flush()
            else:
                print "Payload received at %s" % (datetime.datetime.now().ctime())
                if opts.show:
                    print u'%s' % (e,),
        return True

    def get_node(self,stanza):
        c = stanza.xmlnode.children
        c = c.children
        if c:
            return c.prop("node")

    def get_entry(self,stanza):
        c = stanza.xmlnode.children
        while c:
            try:
                if c.name=="event":
                    return c.getContent()
            except libxml2.treeError:
                pass
            c = c.next
        return None

class MyClient(Client):
    def __init__(self, jid, password,actions):
        # if bare JID is provided add a resource -- it is required
        if not jid.resource:
            jid=JID(jid.node, jid.domain, "listener")

        # we require a TLS connection
        #  Specify sslv3 to get around Sun Java SSL bug handling session ticket
        #  https://rt.phys.uwm.edu/Ticket/Display.html?id=1825
        #  http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6728126
        t=TLSSettings(require=True,verify_peer=False, ctx=Context('sslv3'))

        # setup client with provided connection information
        # and identity data
        Client.__init__(self, jid, password, \
            auth_methods=["sasl:GSSAPI","sasl:PLAIN"], tls_settings=t,keepalive=30)

        # add the separate components
        self.interface_providers = [
            LVAlertHandler(self,actions),
            ]

    def stream_state_changed(self,state,arg):
        """This one is called when the state of stream connecting the
        component to a server changes. This will usually be used to
        let the user know what is going on."""
        if opts.verbose:
            print "*** State changed: %s %r ***" % (state,arg)
        else:
            pass

# add a logger so that we can see what's going
if opts.debug:
    logger=logging.getLogger()
    logger.addHandler(logging.StreamHandler())
    logger.setLevel(logging.DEBUG)

# debug the memory
libxml2.debugMemory(1)

# set up handlers for each node
actions={}
if opts.config_file:
    cp=ConfigParser.ConfigParser()
    cp.read(opts.config_file)
    for node in cp.sections():
        try:
            actions[node] = cp.get(node,'executible')
        except ConfigParser.NoOptionError:
            actions[node] = cp.get(node,'executable')

# set up the stream
myjid=JID(opts.username+"@"+opts.server+"/"+opts.resource)
if not opts.password:
    mypassword = getpass.getpass()
else:
    mypassword = opts.password
s=MyClient(myjid,mypassword,actions)

if opts.verbose:
    print "connecting..."
s.connect()

if opts.verbose:
    print "listening for message..."
try:
    s.loop(1)
except KeyboardInterrupt:
    print u"disconnecting..."
    s.disconnect()

# vi: sts=4 et sw=4
