# HG changeset patch # User Goffi # Date 1387896232 -3600 # Node ID ab851b46009c95c605ac0e8b74c8e3d6c7f77bd5 # Parent aed7d99276b88d3a862927ab6c7fd0a9b4e02be6 plugin xep-0050 (ad-hoc commands): requesting part. first draft diff -r aed7d99276b8 -r ab851b46009c src/plugins/plugin_xep_0050.py --- a/src/plugins/plugin_xep_0050.py Tue Dec 24 15:43:22 2013 +0100 +++ b/src/plugins/plugin_xep_0050.py Tue Dec 24 15:43:52 2013 +0100 @@ -22,10 +22,11 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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@"): """