comparison sat_frontends/quick_frontend/quick_chat.py @ 3254:6cf4bd6972c2

core, frontends: avatar refactoring: /!\ huge commit Avatar logic has been reworked around the IDENTITY plugin: plugins able to handle avatar or other identity related metadata (like nicknames) register to IDENTITY plugin in the same way as for other features like download/upload. Once registered, IDENTITY plugin will call them when suitable in order of priority, and handle caching. Methods to manage those metadata from frontend now use serialised data. For now `avatar` and `nicknames` are handled: - `avatar` is now a dict with `path` + metadata like `media_type`, instead of just a string path - `nicknames` is now a list of nicknames in order of priority. This list is never empty, and `nicknames[0]` should be the preferred nickname to use by frontends in most cases. In addition to contact specified nicknames, user set nickname (the one set in roster) is used in priority when available. Among the side changes done with this commit, there are: - a new `contactGet` bridge method to get roster metadata for a single contact - SatPresenceProtocol.send returns a Deferred to check when it has actually been sent - memory's methods to handle entities data now use `client` as first argument - metadata filter can be specified with `getIdentity` - `getAvatar` and `setAvatar` are now part of the IDENTITY plugin instead of XEP-0054 (and there signature has changed) - `isRoom` and `getBareOrFull` are now part of XEP-0045 plugin - jp avatar/get command uses `xdg-open` first when available for `--show` flag - `--no-cache` has been added to jp avatar/get and identity/get - jp identity/set has been simplified, explicit options (`--nickname` only for now) are used instead of `--field`. `--field` may come back in the future if necessary for extra data. - QuickContactList `SetContact` now handle None as a value, and doesn't use it to delete the metadata anymore - improved cache handling for `metadata` and `nicknames` in quick frontend - new `default` argument in QuickContactList `getCache`
author Goffi <goffi@goffi.org>
date Tue, 14 Apr 2020 21:00:33 +0200
parents 2556eb576aed
children be6d91572633
comparison
equal deleted inserted replaced
3253:1af840e84af7 3254:6cf4bd6972c2
26 from sat_frontends.tools import jid 26 from sat_frontends.tools import jid
27 import time 27 import time
28 28
29 29
30 log = getLogger(__name__) 30 log = getLogger(__name__)
31
32 try:
33 from locale import getlocale
34 except ImportError:
35 # FIXME: pyjamas workaround
36 getlocale = lambda x: (None, "utf-8")
37 31
38 32
39 ROOM_USER_JOINED = "ROOM_USER_JOINED" 33 ROOM_USER_JOINED = "ROOM_USER_JOINED"
40 ROOM_USER_LEFT = "ROOM_USER_LEFT" 34 ROOM_USER_LEFT = "ROOM_USER_LEFT"
41 ROOM_USER_MOVED = (ROOM_USER_JOINED, ROOM_USER_LEFT) 35 ROOM_USER_MOVED = (ROOM_USER_JOINED, ROOM_USER_LEFT)
144 time_format = "%c" if timestamp < self.parent.day_change else "%H:%M" 138 time_format = "%c" if timestamp < self.parent.day_change else "%H:%M"
145 return time.strftime(time_format, timestamp) 139 return time.strftime(time_format, timestamp)
146 140
147 @property 141 @property
148 def avatar(self): 142 def avatar(self):
149 """avatar full path or None if no avatar is found""" 143 """avatar data or None if no avatar is found"""
150 ret = self.host.getAvatar(self.from_jid, profile=self.profile) 144 entity = self.from_jid
151 return ret 145 contact_list = self.host.contact_lists[self.profile]
146 try:
147 return contact_list.getCache(entity, "avatar")
148 except (exceptions.NotFound, KeyError):
149 # we don't check the result as the avatar listener will be called
150 self.host.bridge.avatarGet(entity, True, self.profile)
151 return None
152 152
153 @property 153 @property
154 def encrypted(self): 154 def encrypted(self):
155 return self.extra.get("encrypted", False) 155 return self.extra.get("encrypted", False)
156 156
167 if self.parent.type == C.CHAT_GROUP or entity in list( 167 if self.parent.type == C.CHAT_GROUP or entity in list(
168 contact_list.getSpecials(C.CONTACT_SPECIAL_GROUP) 168 contact_list.getSpecials(C.CONTACT_SPECIAL_GROUP)
169 ): 169 ):
170 return entity.resource or "" 170 return entity.resource or ""
171 if entity.bare in contact_list: 171 if entity.bare in contact_list:
172 return ( 172
173 contact_list.getCache(entity, "nick") 173 try:
174 or contact_list.getCache(entity, "name") 174 nicknames = contact_list.getCache(entity, "nicknames")
175 or entity.node 175 except (exceptions.NotFound, KeyError):
176 or entity 176 # we check result as listener will be called
177 ) 177 self.host.bridge.identityGet(
178 entity.bare, ["nicknames"], True, self.profile)
179 return entity.node or entity
180
181 if nicknames:
182 return nicknames[0]
183 else:
184 return (
185 contact_list.getCache(entity, "name", default=None)
186 or entity.node
187 or entity
188 )
189
178 return entity.node or entity 190 return entity.node or entity
179 191
180 @property 192 @property
181 def status(self): 193 def status(self):
182 return self._status 194 return self._status
212 @property 224 @property
213 def attachments(self): 225 def attachments(self):
214 return self.extra.get(C.MESS_KEY_ATTACHMENTS) 226 return self.extra.get(C.MESS_KEY_ATTACHMENTS)
215 227
216 228
217 class MessageWidget(object): 229 class MessageWidget:
218 """Base classe for widgets""" 230 """Base classe for widgets"""
219 # This class does nothing and is only used to have a common ancestor 231 # This class does nothing and is only used to have a common ancestor
220 232
221 pass 233 pass
222 234
223 235
224 class Occupant(object): 236 class Occupant:
225 """Occupant metadata""" 237 """Occupant metadata"""
226 238
227 def __init__(self, parent, data, profile): 239 def __init__(self, parent, data, profile):
228 self.parent = parent 240 self.parent = parent
229 self.profile = profile 241 self.profile = profile
614 log_msg += _(" ({} messages)".format(size)) 626 log_msg += _(" ({} messages)".format(size))
615 log.debug(log_msg) 627 log.debug(log_msg)
616 628
617 if self.type == C.CHAT_ONE2ONE: 629 if self.type == C.CHAT_ONE2ONE:
618 special = self.host.contact_lists[self.profile].getCache( 630 special = self.host.contact_lists[self.profile].getCache(
619 self.target, C.CONTACT_SPECIAL, create_if_not_found=True 631 self.target, C.CONTACT_SPECIAL, create_if_not_found=True, default=None
620 ) 632 )
621 if special == C.CONTACT_SPECIAL_GROUP: 633 if special == C.CONTACT_SPECIAL_GROUP:
622 # we have a private conversation 634 # we have a private conversation
623 # so we need full jid for the history 635 # so we need full jid for the history
624 # (else we would get history from group itself) 636 # (else we would get history from group itself)
898 except KeyError: 910 except KeyError:
899 pass 911 pass
900 else: 912 else:
901 mess_data.status = status 913 mess_data.status = status
902 914
903 def onAvatar(self, entity, filename, profile): 915 def onAvatar(self, entity, avatar_data, profile):
904 if self.type == C.CHAT_GROUP: 916 if self.type == C.CHAT_GROUP:
905 if entity.bare == self.target: 917 if entity.bare == self.target:
906 try: 918 try:
907 self.occupants[entity.resource].update({"avatar": filename}) 919 self.occupants[entity.resource].update({"avatar": avatar_data})
908 except KeyError: 920 except KeyError:
909 # can happen for a message in history where the 921 # can happen for a message in history where the
910 # entity is not here anymore 922 # entity is not here anymore
911 pass 923 pass
912 924
913 for m in list(self.messages.values()): 925 for m in list(self.messages.values()):
914 if m.nick == entity.resource: 926 if m.nick == entity.resource:
915 for w in m.widgets: 927 for w in m.widgets:
916 w.update({"avatar": filename}) 928 w.update({"avatar": avatar_data})
917 else: 929 else:
918 if ( 930 if (
919 entity.bare == self.target.bare 931 entity.bare == self.target.bare
920 or entity.bare == self.host.profiles[profile].whoami.bare 932 or entity.bare == self.host.profiles[profile].whoami.bare
921 ): 933 ):
922 log.info("avatar updated for {}".format(entity)) 934 log.info("avatar updated for {}".format(entity))
923 for m in list(self.messages.values()): 935 for m in list(self.messages.values()):
924 if m.from_jid.bare == entity.bare: 936 if m.from_jid.bare == entity.bare:
925 for w in m.widgets: 937 for w in m.widgets:
926 w.update({"avatar": filename}) 938 w.update({"avatar": avatar_data})
927 939
928 940
929 quick_widgets.register(QuickChat) 941 quick_widgets.register(QuickChat)