changeset 763:ab851b46009c

plugin xep-0050 (ad-hoc commands): requesting part. first draft
author Goffi <goffi@goffi.org>
date Tue, 24 Dec 2013 15:43:52 +0100
parents aed7d99276b8
children d0e809014ea2
files src/plugins/plugin_xep_0050.py
diffstat 1 files changed, 135 insertions(+), 3 deletions(-) [+]
line wrap: on
line diff
--- 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@"):
         """