Mercurial > libervia-backend
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) |