diff src/plugins/plugin_xep_0100.py @ 807:be4c5e24dab9

plugin XEP-0077, plugin XEP-0100, frontends: gateways have been entirely implemented in backend using the new refactored XMLUI and AdvancedListContainer. The now useless code has been removed from frontends.
author Goffi <goffi@goffi.org>
date Tue, 04 Feb 2014 18:26:03 +0100
parents bfabeedbf32e
children 743b757777d3
line wrap: on
line diff
--- a/src/plugins/plugin_xep_0100.py	Tue Feb 04 18:24:27 2014 +0100
+++ b/src/plugins/plugin_xep_0100.py	Tue Feb 04 18:26:03 2014 +0100
@@ -17,11 +17,13 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-from sat.core.i18n import _
-from logging import debug, info, error
+from sat.core.i18n import _, D_
+from sat.core import exceptions
+from sat.tools import xml_tools
+from logging import debug, info, warning, error
 from twisted.words.protocols.jabber import client as jabber_client, jid
 from twisted.words.protocols.jabber import error as jab_error
-import twisted.internet.error
+from twisted.internet import reactor, defer
 
 PLUGIN_INFO = {
     "name": "Gateways Plugin",
@@ -33,6 +35,22 @@
     "description": _("""Implementation of Gateways protocol""")
 }
 
+WARNING_MSG = D_(u"""Be careful ! Gateways allow you to use an external IM (legacy IM), so you can see your contact as XMPP contacts.
+But when you do this, all your messages go throught the external legacy IM server, it is a huge privacy issue (i.e.: all your messages throught the gateway can be monitored, recorded, analysed by the external server, most of time a private company).""")
+
+GATEWAY_TIMEOUT = 10 # time to wait before cancelling a gateway disco info, in seconds
+
+TYPE_DESCRIPTIONS = { 'irc': D_("Internet Relay Chat"),
+                      'xmpp': D_("XMPP"),
+                      'qq': D_("Tencent QQ"),
+                      'simple': D_("SIP/SIMPLE"),
+                      'icq': D_("ICQ"),
+                      'yahoo': D_("Yahoo! Messenger"),
+                      'gadu-gadu': D_("Gadu-Gadu"),
+                      'aim': D_("AOL Instant Messenger"),
+                      'msn': D_("Windows Live Messenger"),
+                    }
+
 
 class XEP_0100(object):
 
@@ -40,82 +58,155 @@
         info(_("Gateways plugin initialization"))
         self.host = host
         self.__gateways = {}  # dict used to construct the answer to findGateways. Key = target jid
-        host.bridge.addMethod("findGateways", ".plugin", in_sign='ss', out_sign='s', method=self.findGateways)
-        host.bridge.addMethod("gatewayRegister", ".plugin", in_sign='ssa(ss)s', out_sign='s', method=self.gatewayRegister)
+        host.bridge.addMethod("findGateways", ".plugin", in_sign='ss', out_sign='s', method=self._findGateways)
+        host.bridge.addMethod("gatewayRegister", ".plugin", in_sign='ss', out_sign='s', method=self._gatewayRegister)
+        self.__menu_id = host.registerCallback(self._gatewaysMenu, with_data=True)
+        self.__selected_id = host.registerCallback(self._gatewaySelectedCb, with_data=True)
+        host.importMenu((D_("Service"), D_("gateways")), self._gatewaysMenu, help_string=D_("Find gateways"))
+
+
+    def _gatewaysMenu(self, data, profile):
+        """ XMLUI activated by menu: return Gateways UI
+        @param profile: %(doc_profile)s
 
-    def __inc_handled_items(self, request_id, profile):
-        self.__gateways[request_id]['__handled_items'] += 1
+        """
+        client = self.host.getClient(profile)
+        try:
+            jid_ = jid.JID(data.get(xml_tools.formEscape('external_jid'), client.jid.host))
+        except RuntimeError:
+            raise exceptions.DataError(_("Invalid JID"))
+        d = self.findGateways(jid_, profile)
+        d.addCallback(self._gatewaysResult2XMLUI, jid_)
+        d.addCallback(lambda xmlui: {'xmlui': xmlui.toXml()})
+        return d
 
-        if self.__gateways[request_id]['__total_items'] == self.__gateways[request_id]['__handled_items']:
-            debug(_("All items checked for id [%s]") % str(request_id))
+    def _gatewaysResult2XMLUI(self, result, entity):
+        xmlui = xml_tools.XMLUI(title=_('Gateways manager (%s)') % entity.full())
+        xmlui.addText(_(WARNING_MSG))
+        xmlui.addDivider('dash')
+        adv_list = xmlui.changeContainer('advanced_list', columns=3, selectable='single', callback_id=self.__selected_id)
+        for success, gateway_data in result:
+            if not success:
+                fail_cond, disco_item = gateway_data
+                xmlui.addJid(disco_item.entity)
+                xmlui.addText(_('Failed (%s)') % fail_cond)
+                xmlui.addEmpty()
+            else:
+                jid_, data = gateway_data
+                for datum in data:
+                    identity, name = datum
+                    adv_list.setRowIndex(jid_.full())
+                    xmlui.addJid(jid_)
+                    xmlui.addText(name)
+                    xmlui.addText(self._getIdentityDesc(identity))
+        adv_list.end()
+        xmlui.addDivider('blank')
+        xmlui.changeContainer('advanced_list', columns=3)
+        xmlui.addLabel(_('Use external XMPP server'))
+        xmlui.addString('external_jid')
+        xmlui.addButton(self.__menu_id, _(u'Go !'), fields_back=('external_jid',))
+        return xmlui
+
+    def _gatewaySelectedCb(self, data, profile):
+        try:
+            target_jid = jid.JID(data['index'])
+        except (KeyError, RuntimeError):
+            warning(_("No gateway index selected"))
+            return {}
 
-            del self.__gateways[request_id]['__total_items']
-            del self.__gateways[request_id]['__handled_items']
-            self.host.actionResultExt(request_id, "DICT_DICT", self.__gateways[request_id], profile)
+        d = self.gatewayRegister(target_jid, profile)
+        d.addCallback(lambda xmlui: {'xmlui': xmlui.toXml()})
+        return d
+
+    def _getIdentityDesc(self, identity):
+        """ Return a human readable description of identity
+        @param identity: tuple as returned by Disco identities (category, type)
+
+        """
+        category, type_ = identity
+        if category != 'gateway':
+            error(_('INTERNAL ERROR: identity category should always be "gateway" in _getTypeString, got "%s"') % category)
+        try:
+            return _(TYPE_DESCRIPTIONS[type_])
+        except KeyError:
+            return _("Unknown IM")
 
-    def discoInfo(self, disco, entity, request_id, profile):
+    def _registrationSuccessful(self, jid_, profile):
+        """Called when in_band registration is ok, we must now follow the rest of procedure"""
+        debug(_("Registration successful, doing the rest"))
+        self.host.addContact(jid_, profile_key=profile)
+        self.host.setPresence(jid_, profile_key=profile)
+
+    def _gatewayRegister(self, target_jid_s, profile_key='@NONE@'):
+        d = self.gatewayRegister(jid.JID(target_jid_s), profile_key)
+        d.addCallback(lambda xmlui: xmlui.toXml())
+        return d
+
+    def gatewayRegister(self, target_jid, profile_key='@NONE@'):
+        """Register gateway using in-band registration, then log-in to gateway"""
+        profile = self.host.memory.getProfileName(profile_key)
+        assert(profile)
+        d = self.host.plugins["XEP-0077"].inBandRegister(target_jid, self._registrationSuccessful, profile)
+        return d
+
+    def _infosReceived(self, dl_result, items, target, client):
         """Find disco infos about entity, to check if it is a gateway"""
 
-        for identity in disco.identities:
-            if identity[0] == 'gateway':
-                print (_("Found gateway (%(jid)s): %(identity)s") % {'jid': entity.full(), 'identity': disco.identities[identity]})
-                self.__gateways[request_id][entity.full()] = {
-                    'name': disco.identities[identity],
-                    'type': identity[1]
-                }
-
-        self.__inc_handled_items(request_id, profile)
+        ret = []
+        for idx, (success, result) in enumerate(dl_result):
+            if not success:
+                if isinstance(result.value, defer.CancelledError):
+                    msg = _("Timeout")
+                else:
+                    try:
+                        msg = result.value.condition
+                    except AttibuteError:
+                        msg = str(result)
+                ret.append((success, (msg, items[idx])))
+            else:
+                entity = items[idx].entity
+                gateways = [(identity, result.identities[identity]) for identity in result.identities if identity[0] == 'gateway']
+                if gateways:
+                    info (_("Found gateway [%(jid)s]: %(identity_name)s") % {'jid': entity.full(), 'identity_name': ' - '.join([gateway[1] for gateway in gateways])})
+                    ret.append((success, (entity, gateways)))
+                else:
+                    info(_("Skipping [%(jid)s] which is not a gateway") % {'jid': entity.full()})
+        return ret
 
-    def discoInfoErr(self, failure, entity, request_id, profile):
-        """Something is going wrong with disco"""
-        failure.trap(jab_error.StanzaError, twisted.internet.error.ConnectionLost)
-        error(_("Error when discovering [%(jid)s]: %(error)s") % {'jid': entity.full(), 'error': failure.getErrorMessage()})
-        self.__inc_handled_items(request_id, profile)
+    def _itemsReceived(self, disco, target, client):
+        """Look for items with disco protocol, and ask infos for each one"""
 
-    def discoItems(self, disco, request_id, target, client):
-        """Look for items with disco protocol, and ask infos for each one"""
-        #FIXME: target is used as we can't find the original iq node (parent is None)
-        #       an other way would avoid this useless parameter (is there a way with wokkel ?)
         if len(disco._items) == 0:
             debug(_("No gateway found"))
-            self.host.actionResultExt(request_id, "DICT_DICT", {})
-            return
-
-        self.__gateways[request_id] = {'__total_items': len(disco._items), '__handled_items': 0, '__private__': {'target': target.full()}}
-        for item in disco._items:
-            #TODO: need to set a timeout for theses requests
-            debug(_("item found: %s"), item.name)
-            client.disco.requestInfo(item.entity).addCallback(self.discoInfo, entity=item.entity, request_id=request_id, profile=client.profile).addErrback(self.discoInfoErr, entity=item.entity, request_id=request_id, profile=client.profile)
+            return []
 
-    def discoItemsErr(self, failure, request_id, target, client):
-        """Something is going wrong with disco"""
-        error(_("Error when discovering [%(target)s]: %(condition)s") % {'target': target.full(), 'condition': unicode(failure.value)})
-        message_data = {"reason": "connection error", "message": _(u"Error while trying to discover %(target)s gateways: %(error_mess)s") % {'target': target.full(), 'error_mess': unicode(failure.value)}}
-        self.host.bridge.actionResult("ERROR", request_id, message_data)
+        _defers = []
+        for item in disco._items:
+            debug(_("item found: %s"), item.entity)
+            _defers.append(client.disco.requestInfo(item.entity))
+        dl = defer.DeferredList(_defers)
+        dl.addCallback(self._infosReceived, items=disco._items, target=target, client=client)
+        reactor.callLater(GATEWAY_TIMEOUT, dl.cancel)
+        return dl
 
-    def registrationSuccessful(self, target, profile):
-        """Called when in_band registration is ok, we must now follow the rest of procedure"""
-        debug(_("Registration successful, doing the rest"))
-        self.host.addContact(target, profile)
-        self.host.setPresence(target, profile)
 
-    def gatewayRegister(self, action, target, fields, profile_key='@DEFAULT@'):
-        """Register gateway using in-band registration, then log-in to gateway"""
+    def _findGateways(self, target_jid_s, profile_key):
+        target_jid = jid.JID(target_jid_s)
         profile = self.host.memory.getProfileName(profile_key)
-        assert(profile)  # FIXME: return an error here
-        if action == 'SUBMIT':
-            self.host.plugins["XEP-0077"].addTrigger(target, self.registrationSuccessful, profile)
-        return self.host.plugins["XEP-0077"].in_band_submit(action, target, fields, profile)
+        if not profile:
+            raise exceptions.ProfileUnknownError
+        d = self.findGateways(target_jid, profile)
+        d.addCallback(self._gatewaysResult2XMLUI, target_jid)
+        d.addCallback(lambda xmlui: xmlui.toXml())
+        return d
 
-    def findGateways(self, target, profile_key):
+
+    def findGateways(self, target, profile):
         """Find gateways in the target JID, using discovery protocol
-        Return an id used for retrieving the list of gateways
         """
-        profile = self.host.memory.getProfileName(profile_key)
-        client = self.host.getClient(profile_key)
+        client = self.host.getClient(profile)
         assert(client)
-        to_jid = jid.JID(target)
-        debug(_("find gateways (target = %(target)s, profile = %(profile)s)") % {'target': to_jid.full(), 'profile': profile})
-        request_id = self.host.get_next_id()
-        client.disco.requestItems(to_jid).addCallback(self.discoItems, request_id=request_id, target=to_jid, client=client).addErrback(self.discoItemsErr, request_id=request_id, target=to_jid, client=client)
-        return request_id
+        debug(_("find gateways (target = %(target)s, profile = %(profile)s)") % {'target': target.full(), 'profile': profile})
+        d = client.disco.requestItems(target)
+        d.addCallback(self._itemsReceived , target=target, client=client)
+        return d