diff frontends/src/quick_frontend/quick_chat.py @ 1265:e3a9ea76de35 frontends_multi_profiles

quick_frontend, primitivus: multi-profiles refactoring part 1 (big commit, sorry :p): This refactoring allow primitivus to manage correctly several profiles at once, with various other improvments: - profile_manager can now plug several profiles at once, requesting password when needed. No more profile plug specific method is used anymore in backend, instead a "validated" key is used in actions - Primitivus widget are now based on a common "PrimitivusWidget" classe which mainly manage the decoration so far - all widgets are treated in the same way (contactList, Chat, Progress, etc), no more chat_wins specific behaviour - widgets are created in a dedicated manager, with facilities to react on new widget creation or other events - quick_frontend introduce a new QuickWidget class, which aims to be as generic and flexible as possible. It can manage several targets (jids or something else), and several profiles - each widget class return a Hash according to its target. For example if given a target jid and a profile, a widget class return a hash like (target.bare, profile), the same widget will be used for all resources of the same jid - better management of CHAT_GROUP mode for Chat widgets - some code moved from Primitivus to QuickFrontend, the final goal is to have most non backend code in QuickFrontend, and just graphic code in subclasses - no more (un)escapePrivate/PRIVATE_PREFIX - contactList improved a lot: entities not in roster and special entities (private MUC conversations) are better managed - resources can be displayed in Primitivus, and their status messages - profiles are managed in QuickFrontend with dedicated managers This is work in progress, other frontends are broken. Urwid SàText need to be updated. Most of features of Primitivus should work as before (or in a better way ;))
author Goffi <goffi@goffi.org>
date Wed, 10 Dec 2014 19:00:09 +0100
parents f0c9b149ed99
children faa1129559b8
line wrap: on
line diff
--- a/frontends/src/quick_frontend/quick_chat.py	Wed Dec 10 18:37:14 2014 +0100
+++ b/frontends/src/quick_frontend/quick_chat.py	Wed Dec 10 19:00:09 2014 +0100
@@ -21,32 +21,75 @@
 from sat.core.log import getLogger
 log = getLogger(__name__)
 from sat_frontends.tools.jid  import JID
-from sat_frontends.quick_frontend.quick_utils import unescapePrivate
+from sat_frontends.quick_frontend import quick_widgets
 from sat_frontends.quick_frontend.constants import Const as C
 
 
-class QuickChat(object):
+class QuickChat(quick_widgets.QuickWidget):
+
+    def __init__(self, host, target, type_=C.CHAT_ONE2ONE, profiles=None):
+        """
+        @param type_: can be C.CHAT_ONE2ONE for single conversation or C.CHAT_GROUP for chat à la IRC
+        """
 
-    def __init__(self, target, host, type_='one2one'):
-        self.target = target
-        self.host = host
+        quick_widgets.QuickWidget.__init__(self, host, target, profiles=profiles)
+        assert type_ in (C.CHAT_ONE2ONE, C.CHAT_GROUP)
+        if type_ == C.CHAT_GROUP and target.resource:
+            raise ValueError("A group chat entity can't have a resource")
+        self.current_target = target
         self.type = type_
-        self.id = ""
+        self.id = "" # FIXME: to be removed
         self.nick = None
         self.occupants = set()
 
-    def setType(self, type_):
-        """Set the type of the chat
-        @param type: can be 'one2one' for single conversation or 'group' for chat à la IRC
+    def __str__(self):
+        return u"Chat Widget [target: {}, type: {}, profile: {}]".format(self.target, self.type, self.profile)
+
+    @staticmethod
+    def getWidgetHash(target, profile):
+        return (unicode(profile), target.bare)
+
+    @staticmethod
+    def getPrivateHash(target, profile):
+        """Get unique hash for private conversations
+
+        This method should be used with force_hash to get unique widget for private MUC conversations
         """
-        self.type = type_
+        return (unicode(profile), target)
+
+
+    def addTarget(self, target):
+        super(QuickChat, self).addTarget(target)
+        if target.resource:
+            self.current_target = target # FIXME: tmp, must use resource priority throught contactList instead
+
+    @property
+    def target(self):
+        if self.type == C.CHAT_GROUP:
+            return self.current_target.bare
+        return self.current_target
+
+    def manageMessage(self, entity, mess_type):
+        """Tell if this chat widget manage an entity and message type couple
+
+        @param entity (jid.JID): (full) jid of the sending entity
+        @param mess_type (str): message type as given by newMessage
+        @return (bool): True if this Chat Widget manage this couple
+        """
+        if self.type == C.CHAT_GROUP:
+            if mess_type == C.MESS_TYPE_GROUPCHAT and self.target == entity.bare:
+                return True
+        else:
+            if mess_type != C.MESS_TYPE_GROUPCHAT and entity in self.targets:
+                return True
+        return False
 
     def setPresents(self, nicks):
         """Set the users presents in the contact list for a group chat
         @param nicks: list of nicknames
         """
         log.debug (_("Adding users %s to room") % nicks)
-        if self.type != "group":
+        if self.type != C.CHAT_GROUP:
             log.error (_("[INTERNAL] trying to set presents nicks for a non group chat window"))
             raise Exception("INTERNAL ERROR") #TODO: raise proper Exception here
         self.occupants.update(nicks)
@@ -54,7 +97,7 @@
     def replaceUser(self, nick, show_info=True):
         """Add user if it is not in the group list"""
         log.debug (_("Replacing user %s") % nick)
-        if self.type != "group":
+        if self.type != C.CHAT_GROUP:
             log.error (_("[INTERNAL] trying to replace user for a non group chat window"))
             raise Exception("INTERNAL ERROR") #TODO: raise proper Exception here
         len_before = len(self.occupants)
@@ -65,7 +108,7 @@
     def removeUser(self, nick, show_info=True):
         """Remove a user from the group list"""
         log.debug(_("Removing user %s") % nick)
-        if self.type != "group":
+        if self.type != C.CHAT_GROUP:
             log.error (_("[INTERNAL] trying to remove user for a non group chat window"))
             raise Exception("INTERNAL ERROR") #TODO: raise proper Exception here
         self.occupants.remove(nick)
@@ -82,7 +125,7 @@
     def changeUserNick(self, old_nick, new_nick):
         """Change nick of a user in group list"""
         log.debug(_("Changing nick of user %(old_nick)s to %(new_nick)s") % {"old_nick": old_nick, "new_nick": new_nick})
-        if self.type != "group":
+        if self.type != C.CHAT_GROUP:
             log.error (_("[INTERNAL] trying to change user nick for a non group chat window"))
             raise Exception("INTERNAL ERROR") #TODO: raise proper Exception here
         self.removeUser(old_nick, show_info=False)
@@ -92,7 +135,7 @@
     def setSubject(self, subject):
         """Set title for a group chat"""
         log.debug(_("Setting subject to %s") % subject)
-        if self.type != "group":
+        if self.type != C.CHAT_GROUP:
             log.error (_("[INTERNAL] trying to set subject for a non group chat window"))
             raise Exception("INTERNAL ERROR") #TODO: raise proper Exception here
 
@@ -111,8 +154,8 @@
         def onHistory(history):
             for line in history:
                 timestamp, from_jid, to_jid, message, _type, extra = line
-                if ((self.type == 'group' and _type != 'groupchat') or
-                   (self.type == 'one2one' and _type == 'groupchat')):
+                if ((self.type == C.CHAT_GROUP and _type != C.MESS_TYPE_GROUPCHAT) or
+                   (self.type == C.CHAT_ONE2ONE and _type == C.MESS_TYPE_GROUPCHAT)):
                     continue
                 self.printMessage(JID(from_jid), message, profile, timestamp)
             self.afterHistoryPrint()
@@ -120,26 +163,47 @@
         def onHistoryError(err):
             log.error(_("Can't get history"))
 
-        if self.target.startswith(C.PRIVATE_PREFIX):
-            target = unescapePrivate(self.target)
-        else:
-            target = self.target.bare
+        target = self.target.bare
+
+        return self.host.bridge.getHistory(self.host.profiles[profile].whoami.bare, target, size, search=search, profile=profile, callback=onHistory, errback=onHistoryError)
 
-        return self.host.bridge.getHistory(self.host.profiles[profile]['whoami'].bare, target, size, search=search, profile=profile, callback=onHistory, errback=onHistoryError)
+    def _get_nick(self, entity):
+        """Return nick of this entity when possible"""
+        if self.type == C.CHAT_GROUP:
+            return entity.resource
+        contact_list = self.host.contact_lists[self.profile]
+        if entity.bare in contact_list:
+            return contact_list.getCache(entity,'nick') or contact_list.getCache(entity,'name') or entity.node or entity
+        return entity.node or entity
+
+    def onPrivateCreated(self, widget):
+        """Method called when a new widget for private conversation (MUC) is created"""
+        raise NotImplementedError
 
-    def _get_nick(self, jid):
-        """Return nick of this jid when possible"""
-        if self.target.startswith(C.PRIVATE_PREFIX):
-            unescaped = unescapePrivate(self.target)
-            if jid.startswith(C.PRIVATE_PREFIX) or unescaped.bare == jid.bare:
-                return unescaped.resource
-        return jid.resource if self.type == "group" else (self.host.contact_list.getCache(jid,'nick') or self.host.contact_list.getCache(jid,'name') or jid.node)
+    def getOrCreatePrivateWidget(self, entity):
+        """Create a widget for private conversation, or get it if it already exists
+
+        @param entity: full jid of the target
+        """
+        return self.host.widgets.getOrCreateWidget(QuickChat, entity, type_=C.CHAT_ONE2ONE, force_hash=self.getPrivateHash(self.profile, entity), on_new_widget=self.onPrivateCreated, profile=self.profile) # we force hash to have a new widget, not this one again
+
+    def newMessage(self, from_jid, target, msg, type_, extra, profile):
+        if self.type == C.CHAT_GROUP and target.resource and type_ != C.MESS_TYPE_GROUPCHAT:
+            # we have a private message, we forward it to a private conversation widget
+            chat_widget = self.getOrCreatePrivateWidget(target)
+            chat_widget.newMessage(from_jid, target, msg, type_, extra, profile)
+        else:
+            timestamp = extra.get('archive')
+            if type_ == C.MESS_TYPE_INFO:
+                self.printInfo(msg, timestamp=float(timestamp) if timestamp else None)
+            else:
+                self.printMessage(from_jid, msg, profile, float(timestamp) if timestamp else None)
 
     def printMessage(self, from_jid, msg, profile, timestamp=None):
         """Print message in chat window. Must be implemented by child class"""
         jid = JID(from_jid)
         nick = self._get_nick(jid)
-        mymess = (jid.resource == self.nick) if self.type == "group" else (jid.bare == self.host.profiles[profile]['whoami'].bare) #mymess = True if message comes from local user
+        mymess = (jid.resource == self.nick) if self.type == C.CHAT_GROUP else (jid.bare == self.host.profiles[profile].whoami.bare) #mymess = True if message comes from local user
         if msg.startswith('/me '):
             self.printInfo('* %s %s' % (nick, msg[4:]), type_='me', timestamp=timestamp)
             return
@@ -165,12 +229,11 @@
         #No need to raise an error as game are not mandatory
         log.warning(_('getGame is not implemented in this frontend'))
 
-    def updateChatState(self, state, nick=None):
-        """Set the chat state (XEP-0085) of the contact. Leave nick to None
-        to set the state for a one2one conversation, or give a nickname or
-        C.ALL_OCCUPANTS to set the state of a participant within a MUC.
+    def updateChatState(self, from_jid, state):
+        """Set the chat state (XEP-0085) of the contact.
+
         @param state: the new chat state
-        @param nick: None for one2one, the MUC user nick or ALL_OCCUPANTS
         """
         raise NotImplementedError
 
+quick_widgets.register(QuickChat)