Mercurial > libervia-backend
comparison sat/memory/memory.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 | ae09989e9feb |
children | a3639d6d9643 |
comparison
equal
deleted
inserted
replaced
3253:1af840e84af7 | 3254:6cf4bd6972c2 |
---|---|
647 for entity_jid, entity_data in profile_cache.items(): | 647 for entity_jid, entity_data in profile_cache.items(): |
648 for resource, resource_data in entity_data.items(): | 648 for resource, resource_data in entity_data.items(): |
649 full_jid = copy.copy(entity_jid) | 649 full_jid = copy.copy(entity_jid) |
650 full_jid.resource = resource | 650 full_jid.resource = resource |
651 try: | 651 try: |
652 presence_data = self.getEntityDatum(full_jid, "presence", profile_key) | 652 presence_data = self.getEntityDatum(client, full_jid, "presence") |
653 except KeyError: | 653 except KeyError: |
654 continue | 654 continue |
655 entities_presence.setdefault(entity_jid, {})[ | 655 entities_presence.setdefault(entity_jid, {})[ |
656 resource or "" | 656 resource or "" |
657 ] = presence_data | 657 ] = presence_data |
665 @param show: show status | 665 @param show: show status |
666 @param priority: priority | 666 @param priority: priority |
667 @param statuses: dictionary of statuses | 667 @param statuses: dictionary of statuses |
668 @param profile_key: %(doc_profile_key)s | 668 @param profile_key: %(doc_profile_key)s |
669 """ | 669 """ |
670 client = self.host.getClient(profile_key) | |
670 presence_data = PresenceTuple(show, priority, statuses) | 671 presence_data = PresenceTuple(show, priority, statuses) |
671 self.updateEntityData( | 672 self.updateEntityData( |
672 entity_jid, "presence", presence_data, profile_key=profile_key | 673 client, entity_jid, "presence", presence_data |
673 ) | 674 ) |
674 if entity_jid.resource and show != C.PRESENCE_UNAVAILABLE: | 675 if entity_jid.resource and show != C.PRESENCE_UNAVAILABLE: |
675 # If a resource is available, bare jid should not have presence information | 676 # If a resource is available, bare jid should not have presence information |
676 try: | 677 try: |
677 self.delEntityDatum(entity_jid.userhostJID(), "presence", profile_key) | 678 self.delEntityDatum(client, entity_jid.userhostJID(), "presence") |
678 except (KeyError, exceptions.UnknownEntityError): | 679 except (KeyError, exceptions.UnknownEntityError): |
679 pass | 680 pass |
680 | 681 |
681 ## Resources ## | 682 ## Resources ## |
682 | 683 |
722 available = [] | 723 available = [] |
723 for resource in self.getAllResources(client, entity_jid): | 724 for resource in self.getAllResources(client, entity_jid): |
724 full_jid = copy.copy(entity_jid) | 725 full_jid = copy.copy(entity_jid) |
725 full_jid.resource = resource | 726 full_jid.resource = resource |
726 try: | 727 try: |
727 presence_data = self.getEntityDatum(full_jid, "presence", client.profile) | 728 presence_data = self.getEntityDatum(client, full_jid, "presence") |
728 except KeyError: | 729 except KeyError: |
729 log.debug("Can't get presence data for {}".format(full_jid)) | 730 log.debug("Can't get presence data for {}".format(full_jid)) |
730 else: | 731 else: |
731 if presence_data.show != C.PRESENCE_UNAVAILABLE: | 732 if presence_data.show != C.PRESENCE_UNAVAILABLE: |
732 available.append(resource) | 733 available.append(resource) |
760 priority_resources = [] | 761 priority_resources = [] |
761 for resource in resources: | 762 for resource in resources: |
762 full_jid = copy.copy(entity_jid) | 763 full_jid = copy.copy(entity_jid) |
763 full_jid.resource = resource | 764 full_jid.resource = resource |
764 try: | 765 try: |
765 presence_data = self.getEntityDatum(full_jid, "presence", client.profile) | 766 presence_data = self.getEntityDatum(client, full_jid, "presence") |
766 except KeyError: | 767 except KeyError: |
767 log.debug("No presence information for {}".format(full_jid)) | 768 log.debug("No presence information for {}".format(full_jid)) |
768 continue | 769 continue |
769 priority_resources.append((resource, presence_data.priority)) | 770 priority_resources.append((resource, presence_data.priority)) |
770 try: | 771 try: |
810 full_jid = copy.copy(bare_jid) | 811 full_jid = copy.copy(bare_jid) |
811 full_jid.resource = resource | 812 full_jid.resource = resource |
812 yield full_jid | 813 yield full_jid |
813 | 814 |
814 def updateEntityData( | 815 def updateEntityData( |
815 self, entity_jid, key, value, silent=False, profile_key=C.PROF_KEY_NONE | 816 self, client, entity_jid, key, value, silent=False |
816 ): | 817 ): |
817 """Set a misc data for an entity | 818 """Set a misc data for an entity |
818 | 819 |
819 If key was registered with setSignalOnUpdate, a signal will be sent to frontends | 820 If key was registered with setSignalOnUpdate, a signal will be sent to frontends |
820 @param entity_jid: JID of the entity, C.ENTITY_ALL_RESOURCES for all resources of | 821 @param entity_jid: JID of the entity, C.ENTITY_ALL_RESOURCES for all resources of |
821 all entities, C.ENTITY_ALL for all entities (all resources + bare jids) | 822 all entities, C.ENTITY_ALL for all entities (all resources + bare jids) |
822 @param key: key to set (eg: C.ENTITY_TYPE) | 823 @param key: key to set (eg: C.ENTITY_TYPE) |
823 @param value: value for this key (eg: C.ENTITY_TYPE_MUC) | 824 @param value: value for this key (eg: C.ENTITY_TYPE_MUC) |
824 @param silent(bool): if True, doesn't send signal to frontend, even if there is a | 825 @param silent(bool): if True, doesn't send signal to frontend, even if there is a |
825 signal flag (see setSignalOnUpdate) | 826 signal flag (see setSignalOnUpdate) |
826 @param profile_key: %(doc_profile_key)s | 827 """ |
827 """ | |
828 client = self.host.getClient(profile_key) | |
829 profile_cache = self._getProfileCache(client) | 828 profile_cache = self._getProfileCache(client) |
830 if entity_jid in (C.ENTITY_ALL_RESOURCES, C.ENTITY_ALL): | 829 if entity_jid in (C.ENTITY_ALL_RESOURCES, C.ENTITY_ALL): |
831 entities = self.getAllEntitiesIter(client, entity_jid == C.ENTITY_ALL) | 830 entities = self.getAllEntitiesIter(client, entity_jid == C.ENTITY_ALL) |
832 else: | 831 else: |
833 entities = (entity_jid,) | 832 entities = (entity_jid,) |
837 jid_.resource, {} | 836 jid_.resource, {} |
838 ) | 837 ) |
839 | 838 |
840 entity_data[key] = value | 839 entity_data[key] = value |
841 if key in self._key_signals and not silent: | 840 if key in self._key_signals and not silent: |
842 if not isinstance(value, str): | 841 self.host.bridge.entityDataUpdated( |
843 log.error( | 842 jid_.full(), |
844 "Setting a non string value ({}) for a key ({}) which has a signal flag".format( | 843 key, |
845 value, key | 844 data_format.serialise(value), |
846 ) | 845 client.profile |
847 ) | 846 ) |
848 else: | 847 |
849 self.host.bridge.entityDataUpdated( | 848 def delEntityDatum(self, client, entity_jid, key): |
850 jid_.full(), key, value, self.getProfileName(profile_key) | |
851 ) | |
852 | |
853 def delEntityDatum(self, entity_jid, key, profile_key): | |
854 """Delete a data for an entity | 849 """Delete a data for an entity |
855 | 850 |
856 @param entity_jid: JID of the entity, C.ENTITY_ALL_RESOURCES for all resources of all entities, | 851 @param entity_jid: JID of the entity, C.ENTITY_ALL_RESOURCES for all resources of all entities, |
857 C.ENTITY_ALL for all entities (all resources + bare jids) | 852 C.ENTITY_ALL for all entities (all resources + bare jids) |
858 @param key: key to delete (eg: C.ENTITY_TYPE) | 853 @param key: key to delete (eg: C.ENTITY_TYPE) |
859 @param profile_key: %(doc_profile_key)s | |
860 | 854 |
861 @raise exceptions.UnknownEntityError: if entity is not in cache | 855 @raise exceptions.UnknownEntityError: if entity is not in cache |
862 @raise KeyError: key is not in cache | 856 @raise KeyError: key is not in cache |
863 """ | 857 """ |
864 client = self.host.getClient(profile_key) | |
865 profile_cache = self._getProfileCache(client) | 858 profile_cache = self._getProfileCache(client) |
866 if entity_jid in (C.ENTITY_ALL_RESOURCES, C.ENTITY_ALL): | 859 if entity_jid in (C.ENTITY_ALL_RESOURCES, C.ENTITY_ALL): |
867 entities = self.getAllEntitiesIter(client, entity_jid == C.ENTITY_ALL) | 860 entities = self.getAllEntitiesIter(client, entity_jid == C.ENTITY_ALL) |
868 else: | 861 else: |
869 entities = (entity_jid,) | 862 entities = (entity_jid,) |
882 continue # we ignore KeyError when deleting keys from several entities | 875 continue # we ignore KeyError when deleting keys from several entities |
883 else: | 876 else: |
884 raise e | 877 raise e |
885 | 878 |
886 def _getEntitiesData(self, entities_jids, keys_list, profile_key): | 879 def _getEntitiesData(self, entities_jids, keys_list, profile_key): |
880 client = self.host.getClient(profile_key) | |
887 ret = self.getEntitiesData( | 881 ret = self.getEntitiesData( |
888 [jid.JID(jid_) for jid_ in entities_jids], keys_list, profile_key | 882 client, [jid.JID(jid_) for jid_ in entities_jids], keys_list |
889 ) | 883 ) |
890 return {jid_.full(): data for jid_, data in ret.items()} | 884 return { |
891 | 885 jid_.full(): {k: data_format.serialise(v) for k,v in data.items()} |
892 def getEntitiesData(self, entities_jids, keys_list=None, profile_key=C.PROF_KEY_NONE): | 886 for jid_, data in ret.items() |
887 } | |
888 | |
889 def getEntitiesData(self, client, entities_jids, keys_list=None): | |
893 """Get a list of cached values for several entities at once | 890 """Get a list of cached values for several entities at once |
894 | 891 |
895 @param entities_jids: jids of the entities, or empty list for all entities in cache | 892 @param entities_jids: jids of the entities, or empty list for all entities in cache |
896 @param keys_list (iterable,None): list of keys to get, None for everything | 893 @param keys_list (iterable,None): list of keys to get, None for everything |
897 @param profile_key: %(doc_profile_key)s | 894 @param profile_key: %(doc_profile_key)s |
914 entity_data[key] = entity_cache_data[key] | 911 entity_data[key] = entity_cache_data[key] |
915 except KeyError: | 912 except KeyError: |
916 continue | 913 continue |
917 return entity_data | 914 return entity_data |
918 | 915 |
919 client = self.host.getClient(profile_key) | |
920 profile_cache = self._getProfileCache(client) | 916 profile_cache = self._getProfileCache(client) |
921 ret_data = {} | 917 ret_data = {} |
922 if entities_jids: | 918 if entities_jids: |
923 for entity in entities_jids: | 919 for entity in entities_jids: |
924 try: | 920 try: |
935 full_jid.resource = resource | 931 full_jid.resource = resource |
936 ret_data[full_jid] = fillEntityData(entity_cache_data) | 932 ret_data[full_jid] = fillEntityData(entity_cache_data) |
937 | 933 |
938 return ret_data | 934 return ret_data |
939 | 935 |
940 def getEntityData(self, entity_jid, keys_list=None, profile_key=C.PROF_KEY_NONE): | 936 def _getEntityData(self, entity_jid_s, keys_list=None, profile=C.PROF_KEY_NONE): |
937 return self.getEntityData( | |
938 self.host.getClient(profile), jid.JID(entity_jid_s), keys_list) | |
939 | |
940 def getEntityData(self, client, entity_jid, keys_list=None): | |
941 """Get a list of cached values for entity | 941 """Get a list of cached values for entity |
942 | 942 |
943 @param entity_jid: JID of the entity | 943 @param entity_jid: JID of the entity |
944 @param keys_list (iterable,None): list of keys to get, None for everything | 944 @param keys_list (iterable,None): list of keys to get, None for everything |
945 @param profile_key: %(doc_profile_key)s | 945 @param profile_key: %(doc_profile_key)s |
947 if there is no value of a given key, resulting dict will | 947 if there is no value of a given key, resulting dict will |
948 have nothing with that key nether | 948 have nothing with that key nether |
949 | 949 |
950 @raise exceptions.UnknownEntityError: if entity is not in cache | 950 @raise exceptions.UnknownEntityError: if entity is not in cache |
951 """ | 951 """ |
952 client = self.host.getClient(profile_key) | |
953 profile_cache = self._getProfileCache(client) | 952 profile_cache = self._getProfileCache(client) |
954 try: | 953 try: |
955 entity_data = profile_cache[entity_jid.userhostJID()][entity_jid.resource] | 954 entity_data = profile_cache[entity_jid.userhostJID()][entity_jid.resource] |
956 except KeyError: | 955 except KeyError: |
957 raise exceptions.UnknownEntityError( | 956 raise exceptions.UnknownEntityError( |
962 if keys_list is None: | 961 if keys_list is None: |
963 return entity_data | 962 return entity_data |
964 | 963 |
965 return {key: entity_data[key] for key in keys_list if key in entity_data} | 964 return {key: entity_data[key] for key in keys_list if key in entity_data} |
966 | 965 |
967 def getEntityDatum(self, entity_jid, key, profile_key): | 966 def getEntityDatum(self, client, entity_jid, key): |
968 """Get a datum from entity | 967 """Get a datum from entity |
969 | 968 |
970 @param entity_jid: JID of the entity | 969 @param entity_jid: JID of the entity |
971 @param keys: key to get | 970 @param key: key to get |
972 @param profile_key: %(doc_profile_key)s | |
973 @return: requested value | 971 @return: requested value |
974 | 972 |
975 @raise exceptions.UnknownEntityError: if entity is not in cache | 973 @raise exceptions.UnknownEntityError: if entity is not in cache |
976 @raise KeyError: if there is no value for this key and this entity | 974 @raise KeyError: if there is no value for this key and this entity |
977 """ | 975 """ |
978 return self.getEntityData(entity_jid, (key,), profile_key)[key] | 976 return self.getEntityData(client, entity_jid, (key,))[key] |
979 | 977 |
980 def delEntityCache( | 978 def delEntityCache( |
981 self, entity_jid, delete_all_resources=True, profile_key=C.PROF_KEY_NONE | 979 self, entity_jid, delete_all_resources=True, profile_key=C.PROF_KEY_NONE |
982 ): | 980 ): |
983 """Remove all cached data for entity | 981 """Remove all cached data for entity |
1593 if not entity_jid.resource: | 1591 if not entity_jid.resource: |
1594 return bool( | 1592 return bool( |
1595 self.getAvailableResources(client, entity_jid) | 1593 self.getAvailableResources(client, entity_jid) |
1596 ) # is any resource is available, entity is available | 1594 ) # is any resource is available, entity is available |
1597 try: | 1595 try: |
1598 presence_data = self.getEntityDatum(entity_jid, "presence", client.profile) | 1596 presence_data = self.getEntityDatum(client, entity_jid, "presence") |
1599 except KeyError: | 1597 except KeyError: |
1600 log.debug("No presence information for {}".format(entity_jid)) | 1598 log.debug("No presence information for {}".format(entity_jid)) |
1601 return False | 1599 return False |
1602 return presence_data.show != C.PRESENCE_UNAVAILABLE | 1600 return presence_data.show != C.PRESENCE_UNAVAILABLE |