Mercurial > libervia-backend
comparison sat_frontends/quick_frontend/quick_contact_list.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 | 142ecb7f6338 |
children | be6d91572633 |
comparison
equal
deleted
inserted
replaced
3253:1af840e84af7 | 3254:6cf4bd6972c2 |
---|---|
103 "General", | 103 "General", |
104 profile_key=profile, | 104 profile_key=profile, |
105 callback=self._showOfflineContacts, | 105 callback=self._showOfflineContacts, |
106 ) | 106 ) |
107 | 107 |
108 # FIXME: workaround for a pyjamas issue: calling hash on a class method always | 108 self.host.addListener("presence", self.onPresenceUpdate, [self.profile]) |
109 # return a different value if that method is defined directly within the | 109 self.host.addListener("nicknames", self.onNicknamesUpdate, [self.profile]) |
110 # class (with the "def" keyword) | 110 self.host.addListener("notification", self.onNotification, [self.profile]) |
111 self.presenceListener = self.onPresenceUpdate | 111 # onNotification only updates the entity, so we can re-use it |
112 self.host.addListener("presence", self.presenceListener, [self.profile]) | 112 self.host.addListener("notificationsClear", self.onNotification, [self.profile]) |
113 self.nickListener = self.onNickUpdate | |
114 self.host.addListener("nick", self.nickListener, [self.profile]) | |
115 self.notifListener = self.onNotification | |
116 self.host.addListener("notification", self.notifListener, [self.profile]) | |
117 # notifListener only update the entity, so we can re-use it | |
118 self.host.addListener("notificationsClear", self.notifListener, [self.profile]) | |
119 | 113 |
120 @property | 114 @property |
121 def whoami(self): | 115 def whoami(self): |
122 return self.host.profiles[self.profile].whoami | 116 return self.host.profiles[self.profile].whoami |
123 | 117 |
162 """ | 156 """ |
163 return set( | 157 return set( |
164 [ | 158 [ |
165 entity | 159 entity |
166 for entity in self._roster | 160 for entity in self._roster |
167 if self.getCache(entity, C.PRESENCE_SHOW) is not None | 161 if self.getCache(entity, C.PRESENCE_SHOW, default=None) is not None |
168 ] | 162 ] |
169 ) | 163 ) |
170 | 164 |
171 @property | 165 @property |
172 def roster_entities_by_group(self): | 166 def roster_entities_by_group(self): |
253 self.host.bridge.getContacts(self.profile, callback=self._gotContacts) | 247 self.host.bridge.getContacts(self.profile, callback=self._gotContacts) |
254 | 248 |
255 def fill(self): | 249 def fill(self): |
256 handler.fill(self.profile) | 250 handler.fill(self.profile) |
257 | 251 |
258 def getCache(self, entity, name=None, bare_default=True, create_if_not_found=False): | 252 def getCache( |
253 self, entity, name=None, bare_default=True, create_if_not_found=False, | |
254 default=Exception): | |
259 """Return a cache value for a contact | 255 """Return a cache value for a contact |
260 | 256 |
261 @param entity(jid.JID): entity of the contact from who we want data | 257 @param entity(jid.JID): entity of the contact from who we want data |
262 (resource is used if given) | 258 (resource is used if given) |
263 if a resource specific information is requested: | 259 if a resource specific information is requested: |
270 the requested resource. | 266 the requested resource. |
271 If False, None is returned if no value is found for the requested resource. | 267 If False, None is returned if no value is found for the requested resource. |
272 If None, bare_default will be set to False if entity is in a room, True else | 268 If None, bare_default will be set to False if entity is in a room, True else |
273 @param create_if_not_found(bool): if True, create contact if it's not found | 269 @param create_if_not_found(bool): if True, create contact if it's not found |
274 in cache | 270 in cache |
271 @param default(object): value to return when name is not found in cache | |
272 if Exception is used, a KeyError will be returned | |
273 otherwise, the given value will be used | |
275 @return: full cache if no name is given, or value of "name", or None | 274 @return: full cache if no name is given, or value of "name", or None |
276 @raise NotFound: entity not found in cache | 275 @raise NotFound: entity not found in cache |
276 @raise KeyError: name not found in cache | |
277 """ | 277 """ |
278 # FIXME: resource handling need to be reworked | 278 # FIXME: resource handling need to be reworked |
279 # FIXME: bare_default work for requesting full jid to get bare jid, | 279 # FIXME: bare_default work for requesting full jid to get bare jid, |
280 # but not the other way | 280 # but not the other way |
281 # e.g.: if we have set an avatar for user@server.tld/resource | 281 # e.g.: if we have set an avatar for user@server.tld/resource |
289 cache = self._cache[entity.bare] | 289 cache = self._cache[entity.bare] |
290 else: | 290 else: |
291 raise exceptions.NotFound | 291 raise exceptions.NotFound |
292 | 292 |
293 if name is None: | 293 if name is None: |
294 if default is not Exception: | |
295 raise exceptions.InternalError( | |
296 "default value can only Exception when name is not specified" | |
297 ) | |
294 # full cache is requested | 298 # full cache is requested |
295 return cache | 299 return cache |
296 | 300 |
297 if name in ("status", C.PRESENCE_STATUSES, C.PRESENCE_PRIORITY, C.PRESENCE_SHOW): | 301 if name in ("status", C.PRESENCE_STATUSES, C.PRESENCE_PRIORITY, C.PRESENCE_SHOW): |
298 # these data are related to the resource | 302 # these data are related to the resource |
311 return cache[C.PRESENCE_STATUSES].get(C.PRESENCE_STATUSES_DEFAULT, "") | 315 return cache[C.PRESENCE_STATUSES].get(C.PRESENCE_STATUSES_DEFAULT, "") |
312 | 316 |
313 elif entity.resource: | 317 elif entity.resource: |
314 try: | 318 try: |
315 return cache[C.CONTACT_RESOURCES][entity.resource][name] | 319 return cache[C.CONTACT_RESOURCES][entity.resource][name] |
316 except KeyError: | 320 except KeyError as e: |
317 if bare_default is None: | 321 if bare_default is None: |
318 bare_default = not self.isRoom(entity.bare) | 322 bare_default = not self.isRoom(entity.bare) |
319 if not bare_default: | 323 if not bare_default: |
320 return None | 324 if default is Exception: |
325 raise e | |
326 else: | |
327 return default | |
321 | 328 |
322 try: | 329 try: |
323 return cache[name] | 330 return cache[name] |
324 except KeyError: | 331 except KeyError as e: |
325 return None | 332 if default is Exception: |
333 raise e | |
334 else: | |
335 return default | |
326 | 336 |
327 def setCache(self, entity, name, value): | 337 def setCache(self, entity, name, value): |
328 """Set or update value for one data in cache | 338 """Set or update value for one data in cache |
329 | 339 |
330 @param entity(JID): entity to update | 340 @param entity(JID): entity to update |
331 @param name(unicode): value to set or update | 341 @param name(str): value to set or update |
332 """ | 342 """ |
333 self.setContact(entity, attributes={name: value}) | 343 self.setContact(entity, attributes={name: value}) |
334 | 344 |
335 def getFullJid(self, entity): | 345 def getFullJid(self, entity): |
336 """Get full jid from a bare jid | 346 """Get full jid from a bare jid |
389 @param entity(jid.JID): jid of the special entity | 399 @param entity(jid.JID): jid of the special entity |
390 if the jid is full, will be added to special extras | 400 if the jid is full, will be added to special extras |
391 @param special_type: one of special type (e.g. C.CONTACT_SPECIAL_GROUP) | 401 @param special_type: one of special type (e.g. C.CONTACT_SPECIAL_GROUP) |
392 @return (bool): True if entity is from this special type | 402 @return (bool): True if entity is from this special type |
393 """ | 403 """ |
394 return self.getCache(entity, C.CONTACT_SPECIAL) == special_type | 404 return self.getCache(entity, C.CONTACT_SPECIAL, default=None) == special_type |
395 | 405 |
396 def setSpecial(self, entity, special_type): | 406 def setSpecial(self, entity, special_type): |
397 """Set special flag on an entity | 407 """Set special flag on an entity |
398 | 408 |
399 @param entity(jid.JID): jid of the special entity | 409 @param entity(jid.JID): jid of the special entity |
415 for entity in self._specials: | 425 for entity in self._specials: |
416 if bare and entity.resource: | 426 if bare and entity.resource: |
417 continue | 427 continue |
418 if ( | 428 if ( |
419 special_type is not None | 429 special_type is not None |
420 and self.getCache(entity, C.CONTACT_SPECIAL) != special_type | 430 and self.getCache(entity, C.CONTACT_SPECIAL, default=None) != special_type |
421 ): | 431 ): |
422 continue | 432 continue |
423 yield entity | 433 yield entity |
424 | 434 |
425 def disconnect(self): | 435 def disconnect(self): |
441 | 451 |
442 def setContact(self, entity, groups=None, attributes=None, in_roster=False): | 452 def setContact(self, entity, groups=None, attributes=None, in_roster=False): |
443 """Add a contact to the list if it doesn't exist, else update it. | 453 """Add a contact to the list if it doesn't exist, else update it. |
444 | 454 |
445 This method can be called with groups=None for the purpose of updating | 455 This method can be called with groups=None for the purpose of updating |
446 the contact's attributes (e.g. nickname). In that case, the groups | 456 the contact's attributes (e.g. nicknames). In that case, the groups |
447 attribute must not be set to the default group but ignored. If not, | 457 attribute must not be set to the default group but ignored. If not, |
448 you may move your contact from its actual group(s) to the default one. | 458 you may move your contact from its actual group(s) to the default one. |
449 | 459 |
450 None value for 'groups' has a different meaning than [None] | 460 None value for 'groups' has a different meaning than [None] |
451 which is for the default group. | 461 which is for the default group. |
452 | 462 |
453 @param entity (jid.JID): entity to add or replace | 463 @param entity (jid.JID): entity to add or replace |
454 if entity is a full jid, attributes will be cached in for the full jid only | 464 if entity is a full jid, attributes will be cached in for the full jid only |
455 @param groups (list): list of groups or None to ignore the groups membership. | 465 @param groups (list): list of groups or None to ignore the groups membership. |
456 @param attributes (dict): attibutes of the added jid or to update | 466 @param attributes (dict): attibutes of the added jid or to update |
457 if attribute value is None, it will be removed | |
458 @param in_roster (bool): True if contact is from roster | 467 @param in_roster (bool): True if contact is from roster |
459 """ | 468 """ |
460 if attributes is None: | 469 if attributes is None: |
461 attributes = {} | 470 attributes = {} |
462 | 471 |
504 del attributes[C.CONTACT_SPECIAL] | 513 del attributes[C.CONTACT_SPECIAL] |
505 self._specials.remove(entity) | 514 self._specials.remove(entity) |
506 else: | 515 else: |
507 self._specials.add(entity) | 516 self._specials.add(entity) |
508 cache[C.CONTACT_MAIN_RESOURCE] = None | 517 cache[C.CONTACT_MAIN_RESOURCE] = None |
509 if 'nick' in cache: | 518 if 'nicknames' in cache: |
510 del cache['nick'] | 519 del cache['nicknames'] |
511 | 520 |
512 # now the attributes we keep in cache | 521 # now the attributes we keep in cache |
513 # XXX: if entity is a full jid, we store the value for the resource only | 522 # XXX: if entity is a full jid, we store the value for the resource only |
514 cache_attr = ( | 523 cache_attr = ( |
515 cache[C.CONTACT_RESOURCES].setdefault(entity.resource, {}) | 524 cache[C.CONTACT_RESOURCES].setdefault(entity.resource, {}) |
516 if entity.resource | 525 if entity.resource |
517 else cache | 526 else cache |
518 ) | 527 ) |
519 for attribute, value in attributes.items(): | 528 for attribute, value in attributes.items(): |
520 if value is None: | 529 if attribute == "nicknames" and self.isSpecial( |
521 # XXX: pyjamas hack: we need to use pop instead of del | 530 entity, C.CONTACT_SPECIAL_GROUP |
522 try: | 531 ): |
523 cache_attr[attribute].pop(value) | 532 # we don't want to keep nicknames for MUC rooms |
524 except KeyError: | 533 # FIXME: this is here as plugin XEP-0054 can link resource's nick |
525 pass | 534 # with bare jid which in the case of MUC |
526 else: | 535 # set the nick for the whole MUC |
527 if attribute == "nick" and self.isSpecial( | 536 # resulting in bad name displayed in some frontends |
528 entity, C.CONTACT_SPECIAL_GROUP | 537 # FIXME: with plugin XEP-0054 + plugin identity refactoring, this |
529 ): | 538 # may not be needed anymore… |
530 # we don't want to keep nick for MUC rooms | 539 continue |
531 # FIXME: this is here as plugin XEP-0054 can link resource's nick | 540 cache_attr[attribute] = value |
532 # with bare jid which in the case of MUC | |
533 # set the nick for the whole MUC | |
534 # resulting in bad name displayed in some frontends | |
535 continue | |
536 cache_attr[attribute] = value | |
537 | 541 |
538 # we can update the display if needed | 542 # we can update the display if needed |
539 if self.entityVisible(entity_bare): | 543 if self.entityVisible(entity_bare): |
540 # if the contact was not visible, we need to add a widget | 544 # if the contact was not visible, we need to add a widget |
541 # else we just update id | 545 # else we just update id |
552 @param check_resource (bool): True if resource must be significant | 556 @param check_resource (bool): True if resource must be significant |
553 @return (bool): True if that contact should be showed in the list | 557 @return (bool): True if that contact should be showed in the list |
554 """ | 558 """ |
555 try: | 559 try: |
556 show = self.getCache(entity, C.PRESENCE_SHOW) | 560 show = self.getCache(entity, C.PRESENCE_SHOW) |
557 except exceptions.NotFound: | 561 except (exceptions.NotFound, KeyError): |
558 return False | 562 return False |
559 | 563 |
560 if check_resource: | 564 if check_resource: |
561 selected = self._selected | 565 selected = self._selected |
562 else: | 566 else: |
680 update_type = C.UPDATE_MODIFY if was_visible else C.UPDATE_ADD | 684 update_type = C.UPDATE_MODIFY if was_visible else C.UPDATE_ADD |
681 self.update([entity], update_type, self.profile) | 685 self.update([entity], update_type, self.profile) |
682 elif was_visible: | 686 elif was_visible: |
683 self.update([entity], C.UPDATE_DELETE, self.profile) | 687 self.update([entity], C.UPDATE_DELETE, self.profile) |
684 | 688 |
685 def onNickUpdate(self, entity, new_nick, profile): | 689 def onNicknamesUpdate(self, entity, nicknames, profile): |
686 """Update entity's nick | 690 """Update entity's nicknames |
687 | 691 |
688 @param entity(jid.JID): entity updated | 692 @param entity(jid.JID): entity updated |
689 @param new_nick(unicode): new nick of the entity | 693 @param nicknames(list[unicode]): nicknames of the entity |
690 @param profile: %(doc_profile)s | 694 @param profile: %(doc_profile)s |
691 """ | 695 """ |
692 assert profile == self.profile | 696 assert profile == self.profile |
693 self.setCache(entity, "nick", new_nick) | 697 self.setCache(entity, "nicknames", nicknames) |
694 | 698 |
695 def onNotification(self, entity, notif, profile): | 699 def onNotification(self, entity, notif, profile): |
696 """Update entity with notification | 700 """Update entity with notification |
697 | 701 |
698 @param entity(jid.JID): entity updated | 702 @param entity(jid.JID): entity updated |
1091 @param type_(unicode, None): update type, may be: | 1095 @param type_(unicode, None): update type, may be: |
1092 - C.UPDATE_DELETE: entity deleted | 1096 - C.UPDATE_DELETE: entity deleted |
1093 - C.UPDATE_MODIFY: entity updated | 1097 - C.UPDATE_MODIFY: entity updated |
1094 - C.UPDATE_ADD: entity added | 1098 - C.UPDATE_ADD: entity added |
1095 - C.UPDATE_SELECTION: selection modified | 1099 - C.UPDATE_SELECTION: selection modified |
1100 - C.UPDATE_STRUCTURE: organisation of items is modified (not items | |
1101 themselves) | |
1096 or None for undefined update | 1102 or None for undefined update |
1097 Note that events correspond to addition, modification and deletion | 1103 Note that events correspond to addition, modification and deletion |
1098 of items on the whole contact list. If the contact is visible or not | 1104 of items on the whole contact list. If the contact is visible or not |
1099 has no influence on the type_. | 1105 has no influence on the type_. |
1100 @param profile(unicode, None): profile concerned with the update | 1106 @param profile(unicode, None): profile concerned with the update |