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