2006-01-20 13:23:38 +00:00
## $Id: commands.py,v 1.11 2005/11/30 17:03:11 normanr Exp $
2005-04-26 18:45:54 +00:00
## Ad-Hoc Command manager
## Mike Albon (c) 5th January 2005
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2, or (at your option)
## any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
""" This module is a ad-hoc command processor for xmpppy. It uses the plug-in mechanism like most of the core library. It depends on a DISCO browser manager.
There are 3 classes here , a command processor Commands like the Browser , and a command template plugin Command , and an example command .
To use this module :
Instansiate the module with the parent transport and disco browser manager as parameters .
' Plug in ' commands using the command template .
The command feature must be added to existing disco replies where neccessary .
What it supplies :
Automatic command registration with the disco browser manager .
Automatic listing of commands in the public command list .
A means of handling requests , by redirection though the command manager .
"""
2006-01-18 21:03:29 +00:00
from protocol import *
from client import PlugIn
2005-04-26 18:45:54 +00:00
class Commands ( PlugIn ) :
2005-10-07 14:53:46 +00:00
""" Commands is an ancestor of PlugIn and can be attached to any session.
2005-04-26 18:45:54 +00:00
The commands class provides a lookup and browse mechnism . It follows the same priciple of the Browser class , for Service Discovery to provide the list of commands , it adds the ' list ' disco type to your existing disco handler function .
How it works :
The commands are added into the existing Browser on the correct nodes . When the command list is built the supplied discovery handler function needs to have a ' list ' option in type . This then gets enumerated , all results returned as None are ignored .
The command executed is then called using it ' s Execute method. All session management is handled by the command itself.
"""
2005-10-07 14:53:46 +00:00
def __init__ ( self , browser ) :
2005-04-26 18:45:54 +00:00
""" Initialises class and sets up local variables """
PlugIn . __init__ ( self )
DBG_LINE = ' commands '
self . _exported_methods = [ ]
self . _handlers = { ' ' : { } }
2005-10-07 14:53:46 +00:00
self . _browser = browser
2005-04-26 18:45:54 +00:00
2005-10-07 14:53:46 +00:00
def plugin ( self , owner ) :
2005-04-26 18:45:54 +00:00
""" Makes handlers within the session """
# Plug into the session and the disco manager
# We only need get and set, results are not needed by a service provider, only a service user.
owner . RegisterHandler ( ' iq ' , self . _CommandHandler , typ = ' set ' , ns = NS_COMMANDS )
owner . RegisterHandler ( ' iq ' , self . _CommandHandler , typ = ' get ' , ns = NS_COMMANDS )
2005-10-07 14:53:46 +00:00
self . _browser . setDiscoHandler ( self . _DiscoHandler , node = NS_COMMANDS , jid = ' ' )
2005-04-26 18:45:54 +00:00
def plugout ( self ) :
""" Removes handlers from the session """
# unPlug from the session and the disco manager
self . _owner . UnregisterHandler ( ' iq ' , self_CommandHandler , ns = NS_COMMANDS )
2005-10-07 14:53:46 +00:00
for jid in self . _handlers :
self . _browser . delDiscoHandler ( self . _DiscoHandler , node = NS_COMMANDS )
2005-04-26 18:45:54 +00:00
def _CommandHandler ( self , conn , request ) :
""" The internal method to process the routing of command execution requests """
# This is the command handler itself.
# We must:
# Pass on command execution to command handler
# (Do we need to keep session details here, or can that be done in the command?)
jid = str ( request . getTo ( ) )
try :
node = request . getTagAttr ( ' command ' , ' node ' )
except :
conn . send ( Error ( request , ERR_BAD_REQUEST ) )
raise NodeProcessed
if self . _handlers . has_key ( jid ) :
if self . _handlers [ jid ] . has_key ( node ) :
self . _handlers [ jid ] [ node ] [ ' execute ' ] ( conn , request )
else :
2005-10-07 14:53:46 +00:00
conn . send ( Error ( request , ERR_ITEM_NOT_FOUND ) )
2005-04-26 18:45:54 +00:00
raise NodeProcessed
elif self . _handlers [ ' ' ] . has_key ( node ) :
2005-10-07 14:53:46 +00:00
self . _handlers [ ' ' ] [ node ] [ ' execute ' ] ( conn , request )
2005-04-26 18:45:54 +00:00
else :
2007-06-03 14:07:54 +00:00
conn . send ( Error ( request , ERR_ITEM_NOT_FOUND ) )
2005-04-26 18:45:54 +00:00
raise NodeProcessed
def _DiscoHandler ( self , conn , request , typ ) :
""" The internal method to process service discovery requests """
# This is the disco manager handler.
2005-10-07 14:53:46 +00:00
if typ == ' items ' :
# We must:
# Generate a list of commands and return the list
# * This handler does not handle individual commands disco requests.
# Pseudo:
# Enumerate the 'item' disco of each command for the specified jid
# Build responce and send
# To make this code easy to write we add an 'list' disco type, it returns a tuple or 'none' if not advertised
list = [ ]
items = [ ]
jid = str ( request . getTo ( ) )
# Get specific jid based results
if self . _handlers . has_key ( jid ) :
for each in self . _handlers [ jid ] . keys ( ) :
items . append ( ( jid , each ) )
else :
# Get generic results
for each in self . _handlers [ ' ' ] . keys ( ) :
items . append ( ( ' ' , each ) )
if items != [ ] :
for each in items :
i = self . _handlers [ each [ 0 ] ] [ each [ 1 ] ] [ ' disco ' ] ( conn , request , ' list ' )
if i != None :
list . append ( Node ( tag = ' item ' , attrs = { ' jid ' : i [ 0 ] , ' node ' : i [ 1 ] , ' name ' : i [ 2 ] } ) )
iq = request . buildReply ( ' result ' )
2005-10-09 10:04:17 +00:00
if request . getQuerynode ( ) : iq . setQuerynode ( request . getQuerynode ( ) )
2005-10-07 14:53:46 +00:00
iq . setQueryPayload ( list )
conn . send ( iq )
else :
conn . send ( Error ( request , ERR_ITEM_NOT_FOUND ) )
raise NodeProcessed
elif typ == ' info ' :
2006-01-20 13:23:38 +00:00
return { ' ids ' : [ { ' category ' : ' automation ' , ' type ' : ' command-list ' } ] , ' features ' : [ ] }
2005-04-26 18:45:54 +00:00
def addCommand ( self , name , cmddisco , cmdexecute , jid = ' ' ) :
""" The method to call if adding a new command to the session, the requred parameters of cmddisco and cmdexecute are the methods to enable that command to be executed """
# This command takes a command object and the name of the command for registration
# We must:
# Add item into disco
# Add item into command list
if not self . _handlers . has_key ( jid ) :
self . _handlers [ jid ] = { }
2005-10-07 14:53:46 +00:00
self . _browser . setDiscoHandler ( self . _DiscoHandler , node = NS_COMMANDS , jid = jid )
2005-04-26 18:45:54 +00:00
if self . _handlers [ jid ] . has_key ( name ) :
raise NameError , ' Command Exists '
else :
self . _handlers [ jid ] [ name ] = { ' disco ' : cmddisco , ' execute ' : cmdexecute }
# Need to add disco stuff here
self . _browser . setDiscoHandler ( cmddisco , node = name , jid = jid )
def delCommand ( self , name , jid = ' ' ) :
""" Removed command from the session """
# This command takes a command object and the name used for registration
# We must:
# Remove item from disco
# Remove item from command list
if not self . _handlers . has_key ( jid ) :
raise NameError , ' Jid not found '
if not self . _handlers [ jid ] . has_key ( name ) :
raise NameError , ' Command not found '
else :
#Do disco removal here
command = self . getCommand ( name , jid ) [ ' disco ' ]
del self . _handlers [ jid ] [ name ]
self . _browser . delDiscoHandler ( command , node = name , jid = jid )
def getCommand ( self , name , jid = ' ' ) :
""" Returns the command tuple """
# This gets the command object with name
# We must:
# Return item that matches this name
if not self . _handlers . has_key ( jid ) :
raise NameError , ' Jid not found '
elif not self . _handlers [ jid ] . has_key ( name ) :
raise NameError , ' Command not found '
else :
return self . _handlers [ jid ] [ name ]
class Command_Handler_Prototype ( PlugIn ) :
2005-05-07 10:57:40 +00:00
""" This is a prototype command handler, as each command uses a disco method
and execute method you can implement it any way you like , however this is
my first attempt at making a generic handler that you can hang process
stages on too . There is an example command below .
2005-04-26 18:45:54 +00:00
The parameters are as follows :
name : the name of the command within the jabber environment
description : the natural language description
discofeatures : the features supported by the command
initial : the initial command in the from of { ' execute ' : commandname }
All stages set the ' actions ' dictionary for each session to represent the possible options available .
"""
name = ' examplecommand '
2005-10-07 14:53:46 +00:00
count = 0
2005-04-26 18:45:54 +00:00
description = ' an example command '
discofeatures = [ NS_COMMANDS , NS_DATA ]
# This is the command template
2005-10-07 14:53:46 +00:00
def __init__ ( self , jid = ' ' ) :
2005-04-26 18:45:54 +00:00
""" Set up the class """
PlugIn . __init__ ( self )
DBG_LINE = ' command '
self . sessioncount = 0
self . sessions = { }
# Disco information for command list pre-formatted as a tuple
2006-01-20 13:23:38 +00:00
self . discoinfo = { ' ids ' : [ { ' category ' : ' automation ' , ' type ' : ' command-node ' , ' name ' : self . description } ] , ' features ' : self . discofeatures }
2005-10-07 14:53:46 +00:00
self . _jid = jid
2005-04-26 18:45:54 +00:00
2005-10-07 14:53:46 +00:00
def plugin ( self , owner ) :
2005-04-26 18:45:54 +00:00
""" Plug command into the commands class """
# The owner in this instance is the Command Processor
2005-10-07 14:53:46 +00:00
self . _commands = owner
self . _owner = owner . _owner
self . _commands . addCommand ( self . name , self . _DiscoHandler , self . Execute , jid = self . _jid )
2005-04-26 18:45:54 +00:00
2005-10-07 14:53:46 +00:00
def plugout ( self ) :
2005-04-26 18:45:54 +00:00
""" Remove command from the commands class """
2005-10-07 14:53:46 +00:00
self . _commands . delCommand ( name , self . _jid )
2005-04-26 18:45:54 +00:00
def getSessionID ( self ) :
""" Returns an id for the command session """
2005-10-07 14:53:46 +00:00
self . count = self . count + 1
2005-04-26 18:45:54 +00:00
return ' cmd- %s - %d ' % ( self . name , self . count )
def Execute ( self , conn , request ) :
""" The method that handles all the commands, and routes them to the correct method for that stage. """
# New request or old?
try :
session = request . getTagAttr ( ' command ' , ' sessionid ' )
except :
session = None
2005-10-07 14:53:46 +00:00
try :
action = request . getTagAttr ( ' command ' , ' action ' )
except :
action = None
if action == None : action = ' execute '
2005-04-26 18:45:54 +00:00
# Check session is in session list
if self . sessions . has_key ( session ) :
if self . sessions [ session ] [ ' jid ' ] == request . getFrom ( ) :
2005-10-07 14:53:46 +00:00
# Check action is vaild
if self . sessions [ session ] [ ' actions ' ] . has_key ( action ) :
2005-04-26 18:45:54 +00:00
# Execute next action
2005-10-07 14:53:46 +00:00
self . sessions [ session ] [ ' actions ' ] [ action ] ( conn , request )
2005-04-26 18:45:54 +00:00
else :
# Stage not presented as an option
2005-10-07 14:53:46 +00:00
self . _owner . send ( Error ( request , ERR_BAD_REQUEST ) )
raise NodeProcessed
2005-04-26 18:45:54 +00:00
else :
# Jid and session don't match. Go away imposter
2005-10-07 14:53:46 +00:00
self . _owner . send ( Error ( request , ERR_BAD_REQUEST ) )
raise NodeProcessed
2005-04-26 18:45:54 +00:00
elif session != None :
# Not on this sessionid you won't.
2005-10-07 14:53:46 +00:00
self . _owner . send ( Error ( request , ERR_BAD_REQUEST ) )
raise NodeProcessed
2005-04-26 18:45:54 +00:00
else :
# New session
2005-10-07 14:53:46 +00:00
self . initial [ action ] ( conn , request )
2005-04-26 18:45:54 +00:00
def _DiscoHandler ( self , conn , request , type ) :
""" The handler for discovery events """
if type == ' list ' :
return ( request . getTo ( ) , self . name , self . description )
elif type == ' items ' :
return [ ]
elif type == ' info ' :
return self . discoinfo
class TestCommand ( Command_Handler_Prototype ) :
2005-05-07 10:57:40 +00:00
""" Example class. You should read source if you wish to understate how it works.
Generally , it presents a " master " that giudes user through to calculate something .
"""
2005-04-26 18:45:54 +00:00
name = ' testcommand '
description = ' a noddy example command '
2005-10-07 14:53:46 +00:00
def __init__ ( self , jid = ' ' ) :
2005-05-07 10:57:40 +00:00
""" Init internal constants. """
2005-10-07 14:53:46 +00:00
Command_Handler_Prototype . __init__ ( self , jid )
2005-04-26 18:45:54 +00:00
self . pi = 3.14
self . initial = { ' execute ' : self . cmdFirstStage }
def cmdFirstStage ( self , conn , request ) :
2005-05-07 10:57:40 +00:00
""" Determine """
2005-04-26 18:45:54 +00:00
# This is the only place this should be repeated as all other stages should have SessionIDs
try :
session = request . getTagAttr ( ' command ' , ' sessionid ' )
except :
session = None
if session == None :
session = self . getSessionID ( )
sessions [ session ] = { ' jid ' : request . getFrom ( ) , ' actions ' : { ' cancel ' : self . cmdCancel , ' next ' : self . cmdSecondStage } , ' data ' : { ' type ' : None } }
# As this is the first stage we only send a form
reply = request . buildReply ( ' result ' )
form = DataForm ( title = ' Select type of operation ' , data = [ ' Use the combobox to select the type of calculation you would like to do, then click Next ' , DataField ( name = ' calctype ' , label = ' Calculation Type ' , value = sessions [ session ] [ ' data ' ] [ ' type ' ] , options = [ [ ' circlediameter ' , ' Calculate the Diameter of a circle ' ] , [ ' circlearea ' , ' Calculate the area of a circle ' ] ] , typ = ' list-single ' , required = 1 ) ] )
replypayload = [ Node ( ' actions ' , attrs = { ' execute ' : ' next ' } , payload = [ Node ( ' next ' ) ] ) , form ]
2008-01-20 23:24:03 +00:00
reply . addChild ( name = ' command ' , attrs = { ' xmlns ' : NS_COMMAND , ' node ' : request . getTagAttr ( ' command ' , ' node ' ) , ' sessionid ' : session , ' status ' : ' executing ' } , payload = replypayload )
2005-04-26 18:45:54 +00:00
self . _owner . send ( reply )
raise NodeProcessed
def cmdSecondStage ( self , conn , request ) :
2005-10-07 14:53:46 +00:00
form = DataForm ( node = result . getTag ( name = ' command ' ) . getTag ( name = ' x ' , namespace = NS_DATA ) )
2005-04-26 18:45:54 +00:00
sessions [ request . getTagAttr ( ' command ' , ' sessionid ' ) ] [ ' data ' ] [ ' type ' ] = form . getField ( ' calctype ' )
sessions [ request . getTagAttr ( ' command ' , ' sessionid ' ) ] [ ' actions ' ] = { ' cancel ' : self . cmdCancel , None : self . cmdThirdStage , ' previous ' : cmdFirstStage }
# The form generation is split out to another method as it may be called by cmdThirdStage
self . cmdSecondStageReply ( conn , request )
def cmdSecondStageReply ( self , conn , request ) :
reply = request . buildReply ( ' result ' )
form = DataForm ( title = ' Enter the radius ' , data = [ ' Enter the radius of the circle (numbers only) ' , DataField ( label = ' Radius ' , name = ' radius ' , typ = ' text-single ' ) ] )
replypayload = [ Node ( ' actions ' , attrs = { ' execute ' : ' complete ' } , payload = [ Node ( ' complete ' ) , Node ( ' prev ' ) ] ) , form ]
2008-01-20 23:24:03 +00:00
reply . addChild ( name = ' command ' , attrs = { ' xmlns ' : NS_COMMAND , ' node ' : request . getTagAttr ( ' command ' , ' node ' ) , ' sessionid ' : request . getTagAttr ( ' command ' , ' sessionid ' ) , ' status ' : ' executing ' } , payload = replypayload )
2005-04-26 18:45:54 +00:00
self . _owner . send ( reply )
raise NodeProcessed
def cmdThirdStage ( self , conn , request ) :
2005-10-07 14:53:46 +00:00
form = DataForm ( node = result . getTag ( name = ' command ' ) . getTag ( name = ' x ' , namespace = NS_DATA ) )
2005-04-26 18:45:54 +00:00
try :
num = float ( form . getField ( ' radius ' ) )
except :
self . cmdSecondStageReply ( conn , request )
if sessions [ request . getTagAttr ( ' command ' , ' sessionid ' ) ] [ ' data ' ] [ ' type ' ] == ' circlearea ' :
result = num * ( pi * * 2 )
else :
result = num * 2 * pi
reply = result . buildReply ( request )
form = DataForm ( typ = ' result ' , data = [ DataField ( label = ' result ' , name = ' result ' , value = result ) ] )
2008-01-20 23:24:03 +00:00
reply . addChild ( name = ' command ' , attrs = { ' xmlns ' : NS_COMMAND , ' node ' : request . getTagAttr ( ' command ' , ' node ' ) , ' sessionid ' : request . getTagAttr ( ' command ' , ' sessionid ' ) , ' status ' : ' completed ' } , payload = form )
2005-04-26 18:45:54 +00:00
self . _owner . send ( reply )
raise NodeProcessed
def cmdCancel ( self , conn , request ) :
reply = request . buildReply ( ' result ' )
2008-01-20 23:24:03 +00:00
reply . addChild ( name = ' command ' , attrs = { ' xmlns ' : NS_COMMAND , ' node ' : request . getTagAttr ( ' command ' , ' node ' ) , ' sessionid ' : request . getTagAttr ( ' command ' , ' sessionid ' ) , ' status ' : ' cancelled ' } )
2005-04-26 18:45:54 +00:00
self . _owner . send ( reply )
del sessions [ request . getTagAttr ( ' command ' , ' sessionid ' ) ]