Mercurial > libervia-backend
comparison frontends/src/quick_frontend/quick_chat.py @ 1955:633b5c21aefd
backend, frontend: messages refactoring (huge commit, not finished):
/!\ database schema has been modified, do a backup before updating
message have been refactored, here are the main changes:
- languages are now handled
- all messages have an uid (internal to SàT)
- message updating is anticipated
- subject is now first class
- new naming scheme is used newMessage => messageNew, getHistory => historyGet, sendMessage => messageSend
- minimal compatibility refactoring in quick_frontend/Primitivus, better refactoring should follow
- threads handling
- delayed messages are saved into history
- info messages may also be saved in history (e.g. to keep track of people joining/leaving a room)
- duplicate messages should be avoided
- historyGet return messages in right order, no need to sort again
- plugins have been updated to follow new features, some of them need to be reworked (e.g. OTR)
- XEP-0203 (Delayed Delivery) is now fully handled in core, the plugin just handle disco and creation of a delay element
- /!\ jp and Libervia are currently broken, as some features of Primitivus
It has been put in one huge commit to avoid breaking messaging between changes.
This is the main part of message refactoring, other commits will follow to take profit of the new features/behaviour.
author | Goffi <goffi@goffi.org> |
---|---|
date | Tue, 24 May 2016 22:11:04 +0200 |
parents | 2daf7b4c6756 |
children | a2bc5089c2eb |
comparison
equal
deleted
inserted
replaced
1943:ccfe45302a5c | 1955:633b5c21aefd |
---|---|
23 from sat_frontends.tools import jid | 23 from sat_frontends.tools import jid |
24 from sat_frontends.quick_frontend import quick_widgets | 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 from collections import OrderedDict | 26 from collections import OrderedDict |
27 from datetime import datetime | 27 from datetime import datetime |
28 from time import time | |
29 | 28 |
30 try: | 29 try: |
31 # FIXME: to be removed when an acceptable solution is here | 30 # FIXME: to be removed when an acceptable solution is here |
32 unicode('') # XXX: unicode doesn't exist in pyjamas | 31 unicode('') # XXX: unicode doesn't exist in pyjamas |
33 except (TypeError, AttributeError): # Error raised is not the same depending on pyjsbuild options | 32 except (TypeError, AttributeError): # Error raised is not the same depending on pyjsbuild options |
50 self.type = type_ | 49 self.type = type_ |
51 self.id = "" # FIXME: to be removed | 50 self.id = "" # FIXME: to be removed |
52 self.nick = None | 51 self.nick = None |
53 self.games = {} # key=game name (unicode), value=instance of quick_games.RoomGame | 52 self.games = {} # key=game name (unicode), value=instance of quick_games.RoomGame |
54 | 53 |
55 if type_ == C.CHAT_ONE2ONE: | 54 self.historyPrint(profile=self.profile) |
56 self.historyPrint(profile=self.profile) | |
57 | |
58 # FIXME: has been introduced to temporarily fix http://bugs.goffi.org/show_bug.cgi?id=12 | |
59 self.initialised = False | |
60 | 55 |
61 def __str__(self): | 56 def __str__(self): |
62 return u"Chat Widget [target: {}, type: {}, profile: {}]".format(self.target, self.type, self.profile) | 57 return u"Chat Widget [target: {}, type: {}, profile: {}]".format(self.target, self.type, self.profile) |
63 | 58 |
64 @staticmethod | 59 @staticmethod |
65 def getWidgetHash(target, profile): | 60 def getWidgetHash(target, profiles): |
66 return (unicode(profile), target.bare) | 61 profile = profiles[0] |
62 return (profile, target.bare) | |
67 | 63 |
68 @staticmethod | 64 @staticmethod |
69 def getPrivateHash(target, profile): | 65 def getPrivateHash(target, profile): |
70 """Get unique hash for private conversations | 66 """Get unique hash for private conversations |
71 | 67 |
97 | 93 |
98 def manageMessage(self, entity, mess_type): | 94 def manageMessage(self, entity, mess_type): |
99 """Tell if this chat widget manage an entity and message type couple | 95 """Tell if this chat widget manage an entity and message type couple |
100 | 96 |
101 @param entity (jid.JID): (full) jid of the sending entity | 97 @param entity (jid.JID): (full) jid of the sending entity |
102 @param mess_type (str): message type as given by newMessage | 98 @param mess_type (str): message type as given by messageNew |
103 @return (bool): True if this Chat Widget manage this couple | 99 @return (bool): True if this Chat Widget manage this couple |
104 """ | 100 """ |
105 if self.type == C.CHAT_GROUP: | 101 if self.type == C.CHAT_GROUP: |
106 if mess_type == C.MESS_TYPE_GROUPCHAT and self.target == entity.bare: | 102 if mess_type == C.MESS_TYPE_GROUPCHAT and self.target == entity.bare: |
107 return True | 103 return True |
110 return True | 106 return True |
111 return False | 107 return False |
112 | 108 |
113 def addUser(self, nick): | 109 def addUser(self, nick): |
114 """Add user if it is not in the group list""" | 110 """Add user if it is not in the group list""" |
115 if not self.initialised: | |
116 return # FIXME: tmp fix for bug 12, do not flood the room with the messages when we've just entered it | |
117 self.printInfo("=> %s has joined the room" % nick) | 111 self.printInfo("=> %s has joined the room" % nick) |
118 | 112 |
119 def removeUser(self, nick): | 113 def removeUser(self, nick): |
120 """Remove a user from the group list""" | 114 """Remove a user from the group list""" |
121 self.printInfo("<= %s has left the room" % nick) | 115 self.printInfo("<= %s has left the room" % nick) |
139 """Refresh or scroll down the focus after the history is printed""" | 133 """Refresh or scroll down the focus after the history is printed""" |
140 pass | 134 pass |
141 | 135 |
142 def historyPrint(self, size=C.HISTORY_LIMIT_DEFAULT, search='', profile='@NONE@'): | 136 def historyPrint(self, size=C.HISTORY_LIMIT_DEFAULT, search='', profile='@NONE@'): |
143 """Print the current history | 137 """Print the current history |
138 | |
144 @param size (int): number of messages | 139 @param size (int): number of messages |
145 @param search (str): pattern to filter the history results | 140 @param search (str): pattern to filter the history results |
146 @param profile (str): %(doc_profile)s | 141 @param profile (str): %(doc_profile)s |
147 """ | 142 """ |
148 log_msg = _(u"now we print the history") | 143 log_msg = _(u"now we print the history") |
150 log_msg += _(u" (%d messages)" % size) | 145 log_msg += _(u" (%d messages)" % size) |
151 log.debug(log_msg) | 146 log.debug(log_msg) |
152 | 147 |
153 target = self.target.bare | 148 target = self.target.bare |
154 | 149 |
155 def onHistory(history): | 150 def _historyGetCb(history): |
156 self.initialised = True # FIXME: tmp fix for bug 12 | |
157 day_format = "%A, %d %b %Y" # to display the day change | 151 day_format = "%A, %d %b %Y" # to display the day change |
158 previous_day = datetime.now().strftime(day_format) | 152 previous_day = datetime.now().strftime(day_format) |
159 for line in history: | 153 for data in history: |
160 timestamp, from_jid, to_jid, message, type_, extra = line # FIXME: extra is unused ! | 154 uid, timestamp, from_jid, to_jid, message, subject, type_, extra = data # FIXME: extra is unused ! |
161 if ((self.type == C.CHAT_GROUP and type_ != C.MESS_TYPE_GROUPCHAT) or | 155 if ((self.type == C.CHAT_GROUP and type_ != C.MESS_TYPE_GROUPCHAT) or |
162 (self.type == C.CHAT_ONE2ONE and type_ == C.MESS_TYPE_GROUPCHAT)): | 156 (self.type == C.CHAT_ONE2ONE and type_ == C.MESS_TYPE_GROUPCHAT)): |
163 continue | 157 continue |
164 message_day = datetime.fromtimestamp(float(timestamp or time())).strftime(day_format) | 158 message_day = datetime.fromtimestamp(timestamp).strftime(day_format) |
165 if previous_day != message_day: | 159 if previous_day != message_day: |
166 self.printDayChange(message_day) | 160 self.printDayChange(message_day) |
167 previous_day = message_day | 161 previous_day = message_day |
168 extra["timestamp"] = timestamp | 162 extra["timestamp"] = timestamp |
169 self.newMessage(jid.JID(from_jid), target, message, type_, extra, profile) | 163 self.messageNew(uid, timestamp, jid.JID(from_jid), target, message, subject, type_, extra, profile) |
170 self.afterHistoryPrint() | 164 self.afterHistoryPrint() |
171 | 165 |
172 def onHistoryError(err): | 166 def _historyGetEb(err): |
173 log.error(_("Can't get history")) | 167 log.error(_("Can't get history")) |
174 | 168 |
175 self.initialised = False # FIXME: tmp fix for bug 12, here needed for :history and :search commands | 169 self.host.bridge.historyGet(unicode(self.host.profiles[profile].whoami.bare), unicode(target), size, True, search, profile, callback=_historyGetCb, errback=_historyGetEb) |
176 self.host.bridge.getHistory(unicode(self.host.profiles[profile].whoami.bare), unicode(target), size, True, search, profile, callback=onHistory, errback=onHistoryError) | |
177 | 170 |
178 def _get_nick(self, entity): | 171 def _get_nick(self, entity): |
179 """Return nick of this entity when possible""" | 172 """Return nick of this entity when possible""" |
180 contact_list = self.host.contact_lists[self.profile] | 173 contact_list = self.host.contact_lists[self.profile] |
181 if self.type == C.CHAT_GROUP or entity in contact_list.getSpecialExtras(C.CONTACT_SPECIAL_GROUP): | 174 if self.type == C.CHAT_GROUP or entity in contact_list.getSpecialExtras(C.CONTACT_SPECIAL_GROUP): |
193 | 186 |
194 @param entity: full jid of the target | 187 @param entity: full jid of the target |
195 """ | 188 """ |
196 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 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 |
197 | 190 |
198 def newMessage(self, from_jid, target, msg, type_, extra, profile): | 191 def messageNew(self, uid, timestamp, from_jid, target, msg, subject, type_, extra, profile): |
192 try: | |
193 msg = msg.itervalues().next() # FIXME: tmp fix until message refactoring is finished (msg is now a dict) | |
194 except StopIteration: | |
195 log.warning(u"No message found (uid: {})".format(uid)) | |
196 msg = '' | |
199 if self.type == C.CHAT_GROUP and target.resource and type_ != C.MESS_TYPE_GROUPCHAT: | 197 if self.type == C.CHAT_GROUP and target.resource and type_ != C.MESS_TYPE_GROUPCHAT: |
200 # we have a private message, we forward it to a private conversation widget | 198 # we have a private message, we forward it to a private conversation widget |
201 chat_widget = self.getOrCreatePrivateWidget(target) | 199 chat_widget = self.getOrCreatePrivateWidget(target) |
202 chat_widget.newMessage(from_jid, target, msg, type_, extra, profile) | 200 chat_widget.messageNew(uid, timestamp, from_jid, target, msg, subject, type_, extra, profile) |
203 return | 201 return |
204 try: | |
205 timestamp = float(extra['timestamp']) | |
206 except KeyError: | |
207 timestamp = None | |
208 | |
209 if not self.initialised and self.type == C.CHAT_ONE2ONE: | |
210 return # FIXME: tmp fix for bug 12, do not display the first one2one message which is already in the local history | |
211 | 202 |
212 if type_ == C.MESS_TYPE_INFO: | 203 if type_ == C.MESS_TYPE_INFO: |
213 self.printInfo(msg, extra=extra) | 204 self.printInfo(msg, extra=extra) |
214 else: | 205 else: |
215 self.initialised = True # FIXME: tmp fix for bug 12, do not discard any message from now | |
216 | |
217 nick = self._get_nick(from_jid) | 206 nick = self._get_nick(from_jid) |
218 if msg.startswith('/me '): | 207 if msg.startswith('/me '): |
219 self.printInfo('* %s %s' % (nick, msg[4:]), type_='me', extra=extra) | 208 self.printInfo('* {} {}'.format(nick, msg[4:]), type_='me', extra=extra) |
220 else: | 209 else: |
221 # my_message is True if message comes from local user | 210 # my_message is True if message comes from local user |
222 my_message = (from_jid.resource == self.nick) if self.type == C.CHAT_GROUP else (from_jid.bare == self.host.profiles[profile].whoami.bare) | 211 my_message = (from_jid.resource == self.nick) if self.type == C.CHAT_GROUP else (from_jid.bare == self.host.profiles[profile].whoami.bare) |
223 self.printMessage(nick, my_message, msg, timestamp, extra, profile) | 212 self.printMessage(nick, my_message, msg, timestamp, extra, profile) |
224 if timestamp: | 213 # FIXME: to be checked/removed after message refactoring |
225 self.afterHistoryPrint() | 214 # if timestamp: |
215 self.afterHistoryPrint() | |
226 | 216 |
227 def printMessage(self, nick, my_message, message, timestamp, extra=None, profile=C.PROF_KEY_NONE): | 217 def printMessage(self, nick, my_message, message, timestamp, extra=None, profile=C.PROF_KEY_NONE): |
228 """Print message in chat window. | 218 """Print message in chat window. |
229 | 219 |
230 @param nick (unicode): author nick | 220 @param nick (unicode): author nick |
231 @param my_message (boolean): True if profile is the author | 221 @param my_message (boolean): True if profile is the author |
232 @param message (unicode): message content | 222 @param message (unicode): message content |
233 @param extra (dict): extra data | 223 @param extra (dict): extra data |
234 """ | 224 """ |
235 if not timestamp: | 225 # FIXME: check/remove this if necessary (message refactoring) |
236 # XXX: do not send notifications for each line of the history being displayed | 226 # if not timestamp: |
237 # FIXME: this must be changed in the future if the timestamp is passed with | 227 # # XXX: do not send notifications for each line of the history being displayed |
238 # all messages and not only with the messages coming from the history. | 228 # # FIXME: this must be changed in the future if the timestamp is passed with |
239 self.notify(nick, message) | 229 # # all messages and not only with the messages coming from the history. |
230 self.notify(nick, message) | |
240 | 231 |
241 def printInfo(self, msg, type_='normal', extra=None): | 232 def printInfo(self, msg, type_='normal', extra=None): |
242 """Print general info. | 233 """Print general info. |
243 | 234 |
244 @param msg (unicode): message to print | 235 @param msg (unicode): message to print |