diff src/browser/sat_browser/plugin_sec_otr.py @ 665:6a8a1103ad10 frontends_multi_profiles

browser_side: OTR uses 'profilePlugged', 'disconnect' and 'gotMenus' listeners
author souliane <souliane@mailoo.org>
date Tue, 03 Mar 2015 22:31:54 +0100
parents 8449a5db0602
children 849ffb24d5bf
line wrap: on
line diff
--- a/src/browser/sat_browser/plugin_sec_otr.py	Tue Mar 03 19:00:21 2015 +0100
+++ b/src/browser/sat_browser/plugin_sec_otr.py	Tue Mar 03 22:31:54 2015 +0100
@@ -359,6 +359,14 @@
         else:
             return self.contexts.get(other_jid, None)
 
+    def getContextsForBareUser(self, bare_jid):
+        """Get all the contexts for the users sharing the given bare JID.
+
+        @param bare_jid (jid.JID): bare JID
+        @return: list[Context]
+        """
+        return [context for other_jid, context in self.contexts.iteritems() if other_jid.bare == bare_jid]
+
     def fixResource(self, other_jid):
         """Return the full JID in case the resource of the given JID is missing.
 
@@ -383,6 +391,13 @@
         self.host.trigger.add("newMessageTrigger", self.newMessageTg, priority=TriggerManager.MAX_PRIORITY)
         self.host.trigger.add("sendMessageTrigger", self.sendMessageTg, priority=TriggerManager.MAX_PRIORITY)
 
+        # FIXME: workaround for a pyjamas issue: calling hash on a class method always return a different value if that method is defined directly within the class (with the "def" keyword)
+        self._profilePluggedListener = self.profilePluggedListener
+        self._gotMenusListener = self.gotMenusListener
+        # FIXME: these listeners are never removed, can't be removed by themselves (it modifies the list while looping), maybe need a 'one_shot' argument
+        self.host.addListener('profilePlugged', self._profilePluggedListener)
+        self.host.addListener('gotMenus', self._gotMenusListener)
+
     @classmethod
     def getInfoText(self, state=otr.context.STATE_PLAINTEXT, trust=''):
         """Get the widget info text for a certain message state and trust.
@@ -406,32 +421,45 @@
         else:
             return OTR.getInfoText(otrctx.state, otrctx.getCurrentTrust())
 
-    def inhibitMenus(self):
-        """Tell the caller which dynamic menus should be inhibited"""
-        return ["OTR"]  # menu categories name to inhibit
-
-    def extraMenus(self):
+    def gotMenusListener(self, menus, profile):
+        menus_to_delete = []
+        for menu in menus:
+            id_, type_, path, path_i18n = menu
+            if path[0] == 'OTR':
+                menus_to_delete.append(menu)
+        for menu in menus_to_delete:
+            menus.remove(menu)
         # FIXME: handle help strings too
-        return [(self._startRefresh, C.MENU_SINGLE, (MAIN_MENU, "Start / refresh"), (MAIN_MENU, D_("Start / refresh"))),
-                (self._endSession, C.MENU_SINGLE, (MAIN_MENU, "Stop encryption"), (MAIN_MENU, D_("Stop encryption"))),
-                (self._authenticate, C.MENU_SINGLE, (MAIN_MENU, "Authenticate correspondent"), (MAIN_MENU, D_("Authenticate correspondent"))),
-                (self._dropPrivkey, C.MENU_SINGLE, (MAIN_MENU, "Drop your private key"), (MAIN_MENU, D_("Drop your private key")))]
+        menus.extend([(self._startRefresh, C.MENU_SINGLE, (MAIN_MENU, "Start / refresh"), (MAIN_MENU, D_("Start / refresh"))),
+                      (self._endSession, C.MENU_SINGLE, (MAIN_MENU, "Stop encryption"), (MAIN_MENU, D_("Stop encryption"))),
+                      (self._authenticate, C.MENU_SINGLE, (MAIN_MENU, "Authenticate correspondent"), (MAIN_MENU, D_("Authenticate correspondent"))),
+                      (self._dropPrivkey, C.MENU_SINGLE, (MAIN_MENU, "Drop your private key"), (MAIN_MENU, D_("Drop your private key"))),
+                      ])
 
-    def profileConnected(self):
+    def profilePluggedListener(self, profile):
+        # FIXME: workaround for a pyjamas issue: calling hash on a class method always return a different value if that method is defined directly within the class (with the "def" keyword)
+        self._presenceListener = self.presenceListener
+        self._disconnectListener = self.disconnectListener
+        self.host.addListener('presence', self._presenceListener, [C.PROF_KEY_NONE])
+        # FIXME: this listener is never removed, can't be removed by itself (it modifies the list while looping), maybe need a 'one_shot' argument
+        self.host.addListener('disconnect', self._disconnectListener, [C.PROF_KEY_NONE])
+
         self.host.bridge.call('skipOTR', None)
         self.context_manager = ContextManager(self.host)
         # TODO: retrieve the encrypted private key from a HTML5 persistent storage,
         # decrypt it, parse it with otr.crypt.PK.parsePrivateKey(privkey) and
         # assign it to self.context_manager.account.privkey
 
-        # FIXME: workaround for a pyjamas issue: calling hash on a class method always return a different value if that method is defined directly within the class (with the "def" keyword)
-        self.presenceListener = self.onPresenceUpdate
-        self.host.addListener('presence', self.presenceListener, [C.PROF_KEY_NONE])
+    def disconnectListener(self, profile):
+        """Things to do just before the profile disconnection"""
+        self.host.removeListener('presence', self._presenceListener)
 
-    def profileDisconnected(self):
         for context in self.context_manager.contexts.values():
-            context.disconnect()
-        self.host.removeListener('presence', self.presenceListener)
+            context.disconnect()  # FIXME: no time to send the message before the profile has been disconnected
+
+    def presenceListener(self, entity, show, priority, statuses, profile):
+        if show == C.PRESENCE_UNAVAILABLE:
+            self.endSession(entity, disconnect=False)
 
     def newMessageTg(self, from_jid, msg, msg_type, to_jid, extra, profile):
         if msg_type != C.MESS_TYPE_CHAT:
@@ -477,27 +505,26 @@
         log.debug(u"sending message unencrypted")
         return True
 
-    def onPresenceUpdate(self, entity, show, priority, statuses, profile):
-        if show == C.PRESENCE_UNAVAILABLE:
-            self.endSession(entity, finish=True)
-
-    def endSession(self, other_jid, finish=False):
+    def endSession(self, other_jid, disconnect=True):
         """Finish or disconnect an OTR session
 
         @param other_jid (jid.JID): other JID
-        @param finish: if True, finish the session but do not disconnect it
-        @return: True if the session has been finished or disconnected, False if there was nothing to do
+        @param disconnect (bool): if False, finish the session but do not disconnect it
         """
         # checking for private key existence is not needed, context checking is enough
-        otrctx = self.context_manager.getContextForUser(other_jid, start=False)
-        if otrctx is None or otrctx.state == otr.context.STATE_PLAINTEXT:
-            if not finish:
-                self.host.newMessageHandler(unicode(other_jid), END_PLAIN_HAS_NOT.format(jid=other_jid), C.MESS_TYPE_INFO, unicode(self.host.whoami), {})
-            return
-        if finish:
-            otrctx.finish()
-        else:
-            otrctx.disconnect()
+        if other_jid.resource:
+            contexts = [self.context_manager.getContextForUser(other_jid, start=False)]
+        else:  # contact disconnected itself so we need to terminate the OTR session but the Chat panel lost its resource
+            contexts = self.context_manager.getContextsForBareUser(other_jid)
+        for otrctx in contexts:
+            if otrctx is None or otrctx.state == otr.context.STATE_PLAINTEXT:
+                if disconnect:
+                    self.host.newMessageHandler(unicode(other_jid), END_PLAIN_HAS_NOT.format(jid=other_jid), C.MESS_TYPE_INFO, unicode(self.host.whoami), {})
+                return
+            if disconnect:
+                otrctx.disconnect()
+            else:
+                otrctx.finish()
 
     # Menu callbacks