comparison 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
comparison
equal deleted inserted replaced
1264:60dfa2f5d61f 1265:e3a9ea76de35
19 19
20 from sat.core.i18n import _ 20 from sat.core.i18n import _
21 from sat.core.log import getLogger 21 from sat.core.log import getLogger
22 log = getLogger(__name__) 22 log = getLogger(__name__)
23 from sat_frontends.tools.jid import JID 23 from sat_frontends.tools.jid import JID
24 from sat_frontends.quick_frontend.quick_utils import unescapePrivate 24 from sat_frontends.quick_frontend import quick_widgets
25 from sat_frontends.quick_frontend.constants import Const as C 25 from sat_frontends.quick_frontend.constants import Const as C
26 26
27 27
28 class QuickChat(object): 28 class QuickChat(quick_widgets.QuickWidget):
29 29
30 def __init__(self, target, host, type_='one2one'): 30 def __init__(self, host, target, type_=C.CHAT_ONE2ONE, profiles=None):
31 self.target = target 31 """
32 self.host = host 32 @param type_: can be C.CHAT_ONE2ONE for single conversation or C.CHAT_GROUP for chat à la IRC
33 """
34
35 quick_widgets.QuickWidget.__init__(self, host, target, profiles=profiles)
36 assert type_ in (C.CHAT_ONE2ONE, C.CHAT_GROUP)
37 if type_ == C.CHAT_GROUP and target.resource:
38 raise ValueError("A group chat entity can't have a resource")
39 self.current_target = target
33 self.type = type_ 40 self.type = type_
34 self.id = "" 41 self.id = "" # FIXME: to be removed
35 self.nick = None 42 self.nick = None
36 self.occupants = set() 43 self.occupants = set()
37 44
38 def setType(self, type_): 45 def __str__(self):
39 """Set the type of the chat 46 return u"Chat Widget [target: {}, type: {}, profile: {}]".format(self.target, self.type, self.profile)
40 @param type: can be 'one2one' for single conversation or 'group' for chat à la IRC 47
41 """ 48 @staticmethod
42 self.type = type_ 49 def getWidgetHash(target, profile):
50 return (unicode(profile), target.bare)
51
52 @staticmethod
53 def getPrivateHash(target, profile):
54 """Get unique hash for private conversations
55
56 This method should be used with force_hash to get unique widget for private MUC conversations
57 """
58 return (unicode(profile), target)
59
60
61 def addTarget(self, target):
62 super(QuickChat, self).addTarget(target)
63 if target.resource:
64 self.current_target = target # FIXME: tmp, must use resource priority throught contactList instead
65
66 @property
67 def target(self):
68 if self.type == C.CHAT_GROUP:
69 return self.current_target.bare
70 return self.current_target
71
72 def manageMessage(self, entity, mess_type):
73 """Tell if this chat widget manage an entity and message type couple
74
75 @param entity (jid.JID): (full) jid of the sending entity
76 @param mess_type (str): message type as given by newMessage
77 @return (bool): True if this Chat Widget manage this couple
78 """
79 if self.type == C.CHAT_GROUP:
80 if mess_type == C.MESS_TYPE_GROUPCHAT and self.target == entity.bare:
81 return True
82 else:
83 if mess_type != C.MESS_TYPE_GROUPCHAT and entity in self.targets:
84 return True
85 return False
43 86
44 def setPresents(self, nicks): 87 def setPresents(self, nicks):
45 """Set the users presents in the contact list for a group chat 88 """Set the users presents in the contact list for a group chat
46 @param nicks: list of nicknames 89 @param nicks: list of nicknames
47 """ 90 """
48 log.debug (_("Adding users %s to room") % nicks) 91 log.debug (_("Adding users %s to room") % nicks)
49 if self.type != "group": 92 if self.type != C.CHAT_GROUP:
50 log.error (_("[INTERNAL] trying to set presents nicks for a non group chat window")) 93 log.error (_("[INTERNAL] trying to set presents nicks for a non group chat window"))
51 raise Exception("INTERNAL ERROR") #TODO: raise proper Exception here 94 raise Exception("INTERNAL ERROR") #TODO: raise proper Exception here
52 self.occupants.update(nicks) 95 self.occupants.update(nicks)
53 96
54 def replaceUser(self, nick, show_info=True): 97 def replaceUser(self, nick, show_info=True):
55 """Add user if it is not in the group list""" 98 """Add user if it is not in the group list"""
56 log.debug (_("Replacing user %s") % nick) 99 log.debug (_("Replacing user %s") % nick)
57 if self.type != "group": 100 if self.type != C.CHAT_GROUP:
58 log.error (_("[INTERNAL] trying to replace user for a non group chat window")) 101 log.error (_("[INTERNAL] trying to replace user for a non group chat window"))
59 raise Exception("INTERNAL ERROR") #TODO: raise proper Exception here 102 raise Exception("INTERNAL ERROR") #TODO: raise proper Exception here
60 len_before = len(self.occupants) 103 len_before = len(self.occupants)
61 self.occupants.add(nick) 104 self.occupants.add(nick)
62 if len_before != len(self.occupants) and show_info: 105 if len_before != len(self.occupants) and show_info:
63 self.printInfo("=> %s has joined the room" % nick) 106 self.printInfo("=> %s has joined the room" % nick)
64 107
65 def removeUser(self, nick, show_info=True): 108 def removeUser(self, nick, show_info=True):
66 """Remove a user from the group list""" 109 """Remove a user from the group list"""
67 log.debug(_("Removing user %s") % nick) 110 log.debug(_("Removing user %s") % nick)
68 if self.type != "group": 111 if self.type != C.CHAT_GROUP:
69 log.error (_("[INTERNAL] trying to remove user for a non group chat window")) 112 log.error (_("[INTERNAL] trying to remove user for a non group chat window"))
70 raise Exception("INTERNAL ERROR") #TODO: raise proper Exception here 113 raise Exception("INTERNAL ERROR") #TODO: raise proper Exception here
71 self.occupants.remove(nick) 114 self.occupants.remove(nick)
72 if show_info: 115 if show_info:
73 self.printInfo("<= %s has left the room" % nick) 116 self.printInfo("<= %s has left the room" % nick)
80 return unicode(self.nick) 123 return unicode(self.nick)
81 124
82 def changeUserNick(self, old_nick, new_nick): 125 def changeUserNick(self, old_nick, new_nick):
83 """Change nick of a user in group list""" 126 """Change nick of a user in group list"""
84 log.debug(_("Changing nick of user %(old_nick)s to %(new_nick)s") % {"old_nick": old_nick, "new_nick": new_nick}) 127 log.debug(_("Changing nick of user %(old_nick)s to %(new_nick)s") % {"old_nick": old_nick, "new_nick": new_nick})
85 if self.type != "group": 128 if self.type != C.CHAT_GROUP:
86 log.error (_("[INTERNAL] trying to change user nick for a non group chat window")) 129 log.error (_("[INTERNAL] trying to change user nick for a non group chat window"))
87 raise Exception("INTERNAL ERROR") #TODO: raise proper Exception here 130 raise Exception("INTERNAL ERROR") #TODO: raise proper Exception here
88 self.removeUser(old_nick, show_info=False) 131 self.removeUser(old_nick, show_info=False)
89 self.replaceUser(new_nick, show_info=False) 132 self.replaceUser(new_nick, show_info=False)
90 self.printInfo("%s is now known as %s" % (old_nick, new_nick)) 133 self.printInfo("%s is now known as %s" % (old_nick, new_nick))
91 134
92 def setSubject(self, subject): 135 def setSubject(self, subject):
93 """Set title for a group chat""" 136 """Set title for a group chat"""
94 log.debug(_("Setting subject to %s") % subject) 137 log.debug(_("Setting subject to %s") % subject)
95 if self.type != "group": 138 if self.type != C.CHAT_GROUP:
96 log.error (_("[INTERNAL] trying to set subject for a non group chat window")) 139 log.error (_("[INTERNAL] trying to set subject for a non group chat window"))
97 raise Exception("INTERNAL ERROR") #TODO: raise proper Exception here 140 raise Exception("INTERNAL ERROR") #TODO: raise proper Exception here
98 141
99 def afterHistoryPrint(self): 142 def afterHistoryPrint(self):
100 """Refresh or scroll down the focus after the history is printed""" 143 """Refresh or scroll down the focus after the history is printed"""
109 log.debug(_("now we print the history (%d messages)") % size) 152 log.debug(_("now we print the history (%d messages)") % size)
110 153
111 def onHistory(history): 154 def onHistory(history):
112 for line in history: 155 for line in history:
113 timestamp, from_jid, to_jid, message, _type, extra = line 156 timestamp, from_jid, to_jid, message, _type, extra = line
114 if ((self.type == 'group' and _type != 'groupchat') or 157 if ((self.type == C.CHAT_GROUP and _type != C.MESS_TYPE_GROUPCHAT) or
115 (self.type == 'one2one' and _type == 'groupchat')): 158 (self.type == C.CHAT_ONE2ONE and _type == C.MESS_TYPE_GROUPCHAT)):
116 continue 159 continue
117 self.printMessage(JID(from_jid), message, profile, timestamp) 160 self.printMessage(JID(from_jid), message, profile, timestamp)
118 self.afterHistoryPrint() 161 self.afterHistoryPrint()
119 162
120 def onHistoryError(err): 163 def onHistoryError(err):
121 log.error(_("Can't get history")) 164 log.error(_("Can't get history"))
122 165
123 if self.target.startswith(C.PRIVATE_PREFIX): 166 target = self.target.bare
124 target = unescapePrivate(self.target) 167
168 return self.host.bridge.getHistory(self.host.profiles[profile].whoami.bare, target, size, search=search, profile=profile, callback=onHistory, errback=onHistoryError)
169
170 def _get_nick(self, entity):
171 """Return nick of this entity when possible"""
172 if self.type == C.CHAT_GROUP:
173 return entity.resource
174 contact_list = self.host.contact_lists[self.profile]
175 if entity.bare in contact_list:
176 return contact_list.getCache(entity,'nick') or contact_list.getCache(entity,'name') or entity.node or entity
177 return entity.node or entity
178
179 def onPrivateCreated(self, widget):
180 """Method called when a new widget for private conversation (MUC) is created"""
181 raise NotImplementedError
182
183 def getOrCreatePrivateWidget(self, entity):
184 """Create a widget for private conversation, or get it if it already exists
185
186 @param entity: full jid of the target
187 """
188 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
189
190 def newMessage(self, from_jid, target, msg, type_, extra, profile):
191 if self.type == C.CHAT_GROUP and target.resource and type_ != C.MESS_TYPE_GROUPCHAT:
192 # we have a private message, we forward it to a private conversation widget
193 chat_widget = self.getOrCreatePrivateWidget(target)
194 chat_widget.newMessage(from_jid, target, msg, type_, extra, profile)
125 else: 195 else:
126 target = self.target.bare 196 timestamp = extra.get('archive')
127 197 if type_ == C.MESS_TYPE_INFO:
128 return self.host.bridge.getHistory(self.host.profiles[profile]['whoami'].bare, target, size, search=search, profile=profile, callback=onHistory, errback=onHistoryError) 198 self.printInfo(msg, timestamp=float(timestamp) if timestamp else None)
129 199 else:
130 def _get_nick(self, jid): 200 self.printMessage(from_jid, msg, profile, float(timestamp) if timestamp else None)
131 """Return nick of this jid when possible"""
132 if self.target.startswith(C.PRIVATE_PREFIX):
133 unescaped = unescapePrivate(self.target)
134 if jid.startswith(C.PRIVATE_PREFIX) or unescaped.bare == jid.bare:
135 return unescaped.resource
136 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)
137 201
138 def printMessage(self, from_jid, msg, profile, timestamp=None): 202 def printMessage(self, from_jid, msg, profile, timestamp=None):
139 """Print message in chat window. Must be implemented by child class""" 203 """Print message in chat window. Must be implemented by child class"""
140 jid = JID(from_jid) 204 jid = JID(from_jid)
141 nick = self._get_nick(jid) 205 nick = self._get_nick(jid)
142 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 206 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
143 if msg.startswith('/me '): 207 if msg.startswith('/me '):
144 self.printInfo('* %s %s' % (nick, msg[4:]), type_='me', timestamp=timestamp) 208 self.printInfo('* %s %s' % (nick, msg[4:]), type_='me', timestamp=timestamp)
145 return 209 return
146 return jid, nick, mymess 210 return jid, nick, mymess
147 211
163 def getGame(self, game_type): 227 def getGame(self, game_type):
164 """Return class managing the game type""" 228 """Return class managing the game type"""
165 #No need to raise an error as game are not mandatory 229 #No need to raise an error as game are not mandatory
166 log.warning(_('getGame is not implemented in this frontend')) 230 log.warning(_('getGame is not implemented in this frontend'))
167 231
168 def updateChatState(self, state, nick=None): 232 def updateChatState(self, from_jid, state):
169 """Set the chat state (XEP-0085) of the contact. Leave nick to None 233 """Set the chat state (XEP-0085) of the contact.
170 to set the state for a one2one conversation, or give a nickname or 234
171 C.ALL_OCCUPANTS to set the state of a participant within a MUC.
172 @param state: the new chat state 235 @param state: the new chat state
173 @param nick: None for one2one, the MUC user nick or ALL_OCCUPANTS
174 """ 236 """
175 raise NotImplementedError 237 raise NotImplementedError
176 238
239 quick_widgets.register(QuickChat)