plugin xep-0050 (ad-hoc commands): requesting part. first draft
This commit is contained in:
parent
3857fff974
commit
edb1d7c76b
1 changed files with 135 additions and 3 deletions
|
|
@ -22,10 +22,11 @@ from twisted.words.protocols.jabber import jid
|
|||
from twisted.words.protocols.jabber import error as xmpp_error
|
||||
from twisted.words.xish import domish
|
||||
from twisted.internet import defer, reactor
|
||||
from wokkel import disco, iwokkel, data_form
|
||||
from wokkel import disco, iwokkel, data_form, compat
|
||||
from sat.core import exceptions
|
||||
from sat.memory.memory import Sessions
|
||||
from uuid import uuid4
|
||||
from sat.tools import xml_tools
|
||||
|
||||
from zope.interface import implements
|
||||
|
||||
|
|
@ -204,8 +205,13 @@ class XEP_0050(object):
|
|||
def __init__(self, host):
|
||||
info(_("plugin XEP-0050 initialization"))
|
||||
self.host = host
|
||||
self.requesting = {}
|
||||
self.requesting = Sessions()
|
||||
self.answering = {}
|
||||
host.bridge.addMethod("requestCommand", ".plugin", in_sign='ss', out_sign='s',
|
||||
method=self._requestCommandsList,
|
||||
async=True)
|
||||
self.__requesting_id = host.registerCallback(self._requestingEntity, with_data=True)
|
||||
host.importMenu("Service", "commands", self._commandsMenu, help_string=_("Execute ad-hoc commands"))
|
||||
|
||||
def getHandler(self, profile):
|
||||
return XEP_0050_handler(self)
|
||||
|
|
@ -219,12 +225,121 @@ class XEP_0050(object):
|
|||
except KeyError:
|
||||
pass
|
||||
|
||||
def _items2XMLUI(self, items):
|
||||
""" Convert discovery items to XMLUI dialog """
|
||||
# TODO: manage items on different jids
|
||||
form_ui = xml_tools.XMLUI("form", submit_id=self.__requesting_id)
|
||||
|
||||
form_ui.addText(_("Please select a command"), 'instructions')
|
||||
|
||||
options = [(item.nodeIdentifier, item.name) for item in items]
|
||||
form_ui.addList(options, "node")
|
||||
return form_ui
|
||||
|
||||
def _commandsAnswer2XMLUI(self, iq_elt, session_id, session_data):
|
||||
"""
|
||||
Convert command answer to an ui for frontend
|
||||
@param iq_elt: command result
|
||||
@param session_id: id of the session used with the frontend
|
||||
@param profile_key: %(doc_profile_key)s
|
||||
|
||||
"""
|
||||
command_elt = iq_elt.elements(NS_COMMANDS, "command").next()
|
||||
status = command_elt.getAttribute('status', XEP_0050.STATUS.EXECUTING)
|
||||
if status in [XEP_0050.STATUS.COMPLETED, XEP_0050.STATUS.CANCELED]:
|
||||
# the command session is finished, we purge our session
|
||||
del self.requesting[session_id]
|
||||
return None
|
||||
remote_session_id = command_elt.getAttribute('sessionid')
|
||||
if remote_session_id:
|
||||
session_data['remote_id'] = remote_session_id
|
||||
data_elt = command_elt.elements(data_form.NS_X_DATA, 'x').next()
|
||||
form = data_form.Form.fromElement(data_elt)
|
||||
return xml_tools.dataForm2XMLUI(form, self.__requesting_id, session_id=session_id)
|
||||
|
||||
def _requestingEntity(self, data, profile):
|
||||
"""
|
||||
request and entity and create XMLUI accordingly
|
||||
@param data: data returned by previous XMLUI (first one must come from self._commandsMenu)
|
||||
@param profile: %(doc_profile)s
|
||||
@return: callback dict result (with "xmlui" corresponding to the answering dialog, or empty if it's finished without error)
|
||||
|
||||
"""
|
||||
# TODO: cancel, prev and next are not managed
|
||||
# TODO: managed answerer errors
|
||||
# TODO: manage nodes with a non data form payload
|
||||
if "session_id" not in data:
|
||||
# we just had the jid, we now request it for the available commands
|
||||
session_id, session_data = self.requesting.newSession(profile=profile)
|
||||
entity = jid.JID(data[xml_tools.SAT_FORM_PREFIX+'jid'])
|
||||
session_data['jid'] = entity
|
||||
d = self.requestCommandsList(entity, profile)
|
||||
|
||||
def sendItems(xmlui):
|
||||
xmlui.setSessionId(session_id) # we need to keep track of the session
|
||||
return {'xmlui': xmlui.toXml()}
|
||||
|
||||
d.addCallback(sendItems)
|
||||
else:
|
||||
# we have started a several forms sessions
|
||||
try:
|
||||
session_data = self.requesting.profileGet(data["session_id"], profile)
|
||||
except KeyError:
|
||||
warning ("session id doesn't exist, session has probably expired")
|
||||
# TODO: send error dialog
|
||||
return defer.succeed({})
|
||||
session_id = data["session_id"]
|
||||
entity = session_data['jid']
|
||||
try:
|
||||
node = session_data['node']
|
||||
# node has already been received
|
||||
except KeyError:
|
||||
# it's the first time we know the node, we save it in session data
|
||||
node = session_data['node'] = data[xml_tools.SAT_FORM_PREFIX+'node']
|
||||
|
||||
client = self.host.getClient(profile)
|
||||
if not client:
|
||||
raise exceptions.ProfileUnknownError
|
||||
|
||||
# we request execute node's command
|
||||
iq_elt = compat.IQ(client.xmlstream, 'set')
|
||||
iq_elt['to'] = entity.full()
|
||||
command_elt = iq_elt.addElement("command", NS_COMMANDS)
|
||||
command_elt['node'] = session_data['node']
|
||||
command_elt['action'] = XEP_0050.ACTION.EXECUTE
|
||||
try:
|
||||
# remote_id is the XEP_0050 sessionid used by answering command
|
||||
# while session_id is our own session id used with the frontend
|
||||
command_elt['sessionid'] = session_data['remote_id']
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
command_elt.addChild(xml_tools.XMLUIResultToElt(data)) # We add the XMLUI result to the command payload
|
||||
d = iq_elt.send()
|
||||
d.addCallback(self._commandsAnswer2XMLUI, session_id, session_data)
|
||||
d.addCallback(lambda xmlui: {'xmlui': xmlui.toXml()} if xmlui is not None else {})
|
||||
|
||||
return d
|
||||
|
||||
def _commandsMenu(self, profile):
|
||||
""" First XMLUI activated by menu: ask for target jid
|
||||
@param profile: %(doc_profile)s
|
||||
|
||||
"""
|
||||
form_ui = xml_tools.XMLUI("form", submit_id=self.__requesting_id)
|
||||
form_ui.addText(_("Please enter target jid"), 'instructions')
|
||||
form_ui.changeLayout("pairs")
|
||||
form_ui.addLabel("jid")
|
||||
form_ui.addString("jid")
|
||||
return form_ui.toXml()
|
||||
|
||||
def _statusCallback(self, command_elt, session_data, action, node, profile):
|
||||
""" Ad-hoc command used to change the "show" part of status """
|
||||
actions = session_data.setdefault('actions',[])
|
||||
actions.append(action)
|
||||
|
||||
if len(actions) == 1:
|
||||
# it's our first request, we ask the desired new status
|
||||
status = XEP_0050.STATUS.EXECUTING
|
||||
form = data_form.Form('form', title=_('status selection'))
|
||||
show_options = [data_form.Option(name, label) for name, label in SHOWS.items()]
|
||||
|
|
@ -234,7 +349,8 @@ class XEP_0050(object):
|
|||
payload = form.toElement()
|
||||
note = None
|
||||
|
||||
elif len(actions) == 2: # we should have the answer here
|
||||
elif len(actions) == 2:
|
||||
# we should have the answer here
|
||||
try:
|
||||
x_elt = command_elt.elements(data_form.NS_X_DATA,'x').next()
|
||||
answer_form = data_form.Form.fromElement(x_elt)
|
||||
|
|
@ -259,6 +375,22 @@ class XEP_0050(object):
|
|||
|
||||
return (payload, status, None, note)
|
||||
|
||||
def _requestCommandsList(self, to_jid_s, profile_key):
|
||||
d = self.requestCommandsList(jid.JID(to_jid_s), profile_key)
|
||||
d.addCallback(lambda xmlui: xmlui.toXml())
|
||||
return d
|
||||
|
||||
def requestCommandsList(self, to_jid, profile_key):
|
||||
""" Request available commands
|
||||
@param to_jid: the entity answering the commands
|
||||
@param profile_key: %(doc_profile)s
|
||||
|
||||
"""
|
||||
client = self.host.getClient(profile_key)
|
||||
d = client.disco.requestItems(to_jid, NS_COMMANDS)
|
||||
d.addCallback(self._items2XMLUI)
|
||||
return d
|
||||
|
||||
def addAdHocCommand(self, callback, label, node="", features = None, timeout = 600, allowed_jids = None, allowed_groups = None,
|
||||
allowed_magics = None, forbidden_jids = None, forbidden_groups = None, profile_key="@NONE@"):
|
||||
"""
|
||||
|
|
|
|||
Loading…
Reference in a new issue