comparison sat_frontends/quick_frontend/quick_contact_list.py @ 3028:ab2696e34d29

Python 3 port: /!\ this is a huge commit /!\ starting from this commit, SàT is needs Python 3.6+ /!\ SàT maybe be instable or some feature may not work anymore, this will improve with time This patch port backend, bridge and frontends to Python 3. Roughly this has been done this way: - 2to3 tools has been applied (with python 3.7) - all references to python2 have been replaced with python3 (notably shebangs) - fixed files not handled by 2to3 (notably the shell script) - several manual fixes - fixed issues reported by Python 3 that where not handled in Python 2 - replaced "async" with "async_" when needed (it's a reserved word from Python 3.7) - replaced zope's "implements" with @implementer decorator - temporary hack to handle data pickled in database, as str or bytes may be returned, to be checked later - fixed hash comparison for password - removed some code which is not needed anymore with Python 3 - deactivated some code which needs to be checked (notably certificate validation) - tested with jp, fixed reported issues until some basic commands worked - ported Primitivus (after porting dependencies like urwid satext) - more manual fixes
author Goffi <goffi@goffi.org>
date Tue, 13 Aug 2019 19:08:41 +0200
parents 8990ed9aad31
children f8e3789912d0
comparison
equal deleted inserted replaced
3027:ff5bcb12ae60 3028:ab2696e34d29
30 30
31 log = getLogger(__name__) 31 log = getLogger(__name__)
32 32
33 try: 33 try:
34 # FIXME: to be removed when an acceptable solution is here 34 # FIXME: to be removed when an acceptable solution is here
35 unicode("") # XXX: unicode doesn't exist in pyjamas 35 str("") # XXX: unicode doesn't exist in pyjamas
36 except (TypeError, AttributeError): # Error raised is not the same depending on 36 except (TypeError, AttributeError): # Error raised is not the same depending on
37 # pyjsbuild options 37 # pyjsbuild options
38 # XXX: pyjamas' max doesn't support key argument, so we implement it ourself 38 # XXX: pyjamas' max doesn't support key argument, so we implement it ourself
39 pyjamas_max = max 39 pyjamas_max = max
40 40
44 return pyjamas_max(iter_cpy) 44 return pyjamas_max(iter_cpy)
45 45
46 # next doesn't exist in pyjamas 46 # next doesn't exist in pyjamas
47 def next(iterable, *args): 47 def next(iterable, *args):
48 try: 48 try:
49 return iterable.next() 49 return iterable.__next__()
50 except StopIteration as e: 50 except StopIteration as e:
51 if args: 51 if args:
52 return args[0] 52 return args[0]
53 raise e 53 raise e
54 54
182 """Return a dictionary binding the entities bare JIDs to their roster groups 182 """Return a dictionary binding the entities bare JIDs to their roster groups
183 183
184 @return (dict[jid.JID, set(unicode)]) 184 @return (dict[jid.JID, set(unicode)])
185 """ 185 """
186 result = {} 186 result = {}
187 for group, data in self._groups.iteritems(): 187 for group, data in self._groups.items():
188 for entity in data["jids"]: 188 for entity in data["jids"]:
189 result.setdefault(entity, set()).add(group) 189 result.setdefault(entity, set()).add(group)
190 return result 190 return result
191 191
192 @property 192 @property
201 def all_iter(self): 201 def all_iter(self):
202 """return all know entities in cache as an iterator of tuples 202 """return all know entities in cache as an iterator of tuples
203 203
204 entities are not sorted 204 entities are not sorted
205 """ 205 """
206 return self._cache.iteritems() 206 return iter(self._cache.items())
207 207
208 @property 208 @property
209 def items(self): 209 def items(self):
210 """Return item representation for all visible entities in cache 210 """Return item representation for all visible entities in cache
211 211
212 entities are not sorted 212 entities are not sorted
213 key: bare jid, value: data 213 key: bare jid, value: data
214 """ 214 """
215 return { 215 return {
216 jid_: cache 216 jid_: cache
217 for jid_, cache in self._cache.iteritems() 217 for jid_, cache in self._cache.items()
218 if self.entityVisible(jid_) 218 if self.entityVisible(jid_)
219 } 219 }
220 220
221 def getItem(self, entity): 221 def getItem(self, entity):
222 """Return item representation of requested entity 222 """Return item representation of requested entity
338 @param entity(jid.JID): must be a bare jid 338 @param entity(jid.JID): must be a bare jid
339 @return (jid.JID): bare jid + main resource 339 @return (jid.JID): bare jid + main resource
340 @raise ValueError: the entity is not bare 340 @raise ValueError: the entity is not bare
341 """ 341 """
342 if entity.resource: 342 if entity.resource:
343 raise ValueError(u"getFullJid must be used with a bare jid") 343 raise ValueError("getFullJid must be used with a bare jid")
344 main_resource = self.getCache(entity, C.CONTACT_MAIN_RESOURCE) 344 main_resource = self.getCache(entity, C.CONTACT_MAIN_RESOURCE)
345 return jid.JID(u"{}/{}".format(entity, main_resource)) 345 return jid.JID("{}/{}".format(entity, main_resource))
346 346
347 def setGroupData(self, group, name, value): 347 def setGroupData(self, group, name, value):
348 """Register a data for a group 348 """Register a data for a group
349 349
350 @param group: a valid (existing) group name 350 @param group: a valid (existing) group name
506 cache_attr = ( 506 cache_attr = (
507 cache[C.CONTACT_RESOURCES].setdefault(entity.resource, {}) 507 cache[C.CONTACT_RESOURCES].setdefault(entity.resource, {})
508 if entity.resource 508 if entity.resource
509 else cache 509 else cache
510 ) 510 )
511 for attribute, value in attributes.iteritems(): 511 for attribute, value in attributes.items():
512 if value is None: 512 if value is None:
513 # XXX: pyjamas hack: we need to use pop instead of del 513 # XXX: pyjamas hack: we need to use pop instead of del
514 try: 514 try:
515 cache_attr[attribute].pop(value) 515 cache_attr[attribute].pop(value)
516 except KeyError: 516 except KeyError:
594 entity_bare = entity.bare 594 entity_bare = entity.bare
595 was_visible = self.entityVisible(entity_bare) 595 was_visible = self.entityVisible(entity_bare)
596 try: 596 try:
597 groups = self._cache[entity_bare].get(C.CONTACT_GROUPS, set()) 597 groups = self._cache[entity_bare].get(C.CONTACT_GROUPS, set())
598 except KeyError: 598 except KeyError:
599 log.error(_(u"Trying to delete an unknow entity [{}]").format(entity)) 599 log.error(_("Trying to delete an unknow entity [{}]").format(entity))
600 try: 600 try:
601 self._roster.remove(entity_bare) 601 self._roster.remove(entity_bare)
602 except KeyError: 602 except KeyError:
603 pass 603 pass
604 del self._cache[entity_bare] 604 del self._cache[entity_bare]
637 else: 637 else:
638 try: 638 try:
639 del cache[C.CONTACT_RESOURCES][entity.resource] 639 del cache[C.CONTACT_RESOURCES][entity.resource]
640 except KeyError: 640 except KeyError:
641 log.error( 641 log.error(
642 u"Presence unavailable received " 642 "Presence unavailable received "
643 u"for an unknown resource [{}]".format(entity) 643 "for an unknown resource [{}]".format(entity)
644 ) 644 )
645 if not cache[C.CONTACT_RESOURCES]: 645 if not cache[C.CONTACT_RESOURCES]:
646 cache[C.CONTACT_MAIN_RESOURCE] = None 646 cache[C.CONTACT_MAIN_RESOURCE] = None
647 else: 647 else:
648 if not entity.resource: 648 if not entity.resource:
649 log.warning( 649 log.warning(
650 _( 650 _(
651 u"received presence from entity " 651 "received presence from entity "
652 u"without resource: {}".format(entity) 652 "without resource: {}".format(entity)
653 ) 653 )
654 ) 654 )
655 resources_data = cache[C.CONTACT_RESOURCES] 655 resources_data = cache[C.CONTACT_RESOURCES]
656 resource_data = resources_data.setdefault(entity.resource, {}) 656 resource_data = resources_data.setdefault(entity.resource, {})
657 resource_data[C.PRESENCE_SHOW] = show 657 resource_data[C.PRESENCE_SHOW] = show
701 @param entity(jid.JID): entity to unselect 701 @param entity(jid.JID): entity to unselect
702 """ 702 """
703 try: 703 try:
704 cache = self._cache[entity.bare] 704 cache = self._cache[entity.bare]
705 except: 705 except:
706 log.error(u"Try to unselect an entity not in cache") 706 log.error("Try to unselect an entity not in cache")
707 else: 707 else:
708 try: 708 try:
709 cache[C.CONTACT_SELECTED].remove(entity.resource) 709 cache[C.CONTACT_SELECTED].remove(entity.resource)
710 except KeyError: 710 except KeyError:
711 log.error(u"Try to unselect a not selected entity") 711 log.error("Try to unselect a not selected entity")
712 else: 712 else:
713 self._selected.remove(entity) 713 self._selected.remove(entity)
714 self.update([entity], C.UPDATE_SELECTION) 714 self.update([entity], C.UPDATE_SELECTION)
715 715
716 def select(self, entity): 716 def select(self, entity):
719 @param entity(jid.JID, None): entity to select (resource is significant) 719 @param entity(jid.JID, None): entity to select (resource is significant)
720 None to unselect all entities 720 None to unselect all entities
721 """ 721 """
722 if entity is None: 722 if entity is None:
723 self._selected.clear() 723 self._selected.clear()
724 for cache in self._cache.itervalues(): 724 for cache in self._cache.values():
725 cache[C.CONTACT_SELECTED].clear() 725 cache[C.CONTACT_SELECTED].clear()
726 self.update(type_=C.UPDATE_SELECTION, profile=self.profile) 726 self.update(type_=C.UPDATE_SELECTION, profile=self.profile)
727 else: 727 else:
728 log.debug(u"select %s" % entity) 728 log.debug("select %s" % entity)
729 try: 729 try:
730 cache = self._cache[entity.bare] 730 cache = self._cache[entity.bare]
731 except: 731 except:
732 log.error(u"Try to select an entity not in cache") 732 log.error("Try to select an entity not in cache")
733 else: 733 else:
734 cache[C.CONTACT_SELECTED].add(entity.resource) 734 cache[C.CONTACT_SELECTED].add(entity.resource)
735 self._selected.add(entity) 735 self._selected.add(entity)
736 self.update([entity], C.UPDATE_SELECTION, profile=self.profile) 736 self.update([entity], C.UPDATE_SELECTION, profile=self.profile)
737 737
775 super(QuickContactListHandler, self).__init__() 775 super(QuickContactListHandler, self).__init__()
776 self.host = host 776 self.host = host
777 global handler 777 global handler
778 if handler is not None: 778 if handler is not None:
779 raise exceptions.InternalError( 779 raise exceptions.InternalError(
780 u"QuickContactListHandler must be instanciated only once" 780 "QuickContactListHandler must be instanciated only once"
781 ) 781 )
782 handler = self 782 handler = self
783 self._clist = {} # key: profile, value: ProfileContactList 783 self._clist = {} # key: profile, value: ProfileContactList
784 self._widgets = set() 784 self._widgets = set()
785 self._update_locked = False # se to True to ignore updates 785 self._update_locked = False # se to True to ignore updates
792 """Check if entity is in contact list 792 """Check if entity is in contact list
793 793
794 @param entity (jid.JID): jid of the entity (resource is not ignored, 794 @param entity (jid.JID): jid of the entity (resource is not ignored,
795 use bare jid if needed) 795 use bare jid if needed)
796 """ 796 """
797 for contact_list in self._clist.itervalues(): 797 for contact_list in self._clist.values():
798 if entity in contact_list: 798 if entity in contact_list:
799 return True 799 return True
800 return False 800 return False
801 801
802 @property 802 @property
804 """Return all the bare JIDs of the roster entities. 804 """Return all the bare JIDs of the roster entities.
805 805
806 @return (set[jid.JID]) 806 @return (set[jid.JID])
807 """ 807 """
808 entities = set() 808 entities = set()
809 for contact_list in self._clist.itervalues(): 809 for contact_list in self._clist.values():
810 entities.update(contact_list.roster) 810 entities.update(contact_list.roster)
811 return entities 811 return entities
812 812
813 @property 813 @property
814 def roster_connected(self): 814 def roster_connected(self):
815 """Return all the bare JIDs of the roster entities that are connected. 815 """Return all the bare JIDs of the roster entities that are connected.
816 816
817 @return (set[jid.JID]) 817 @return (set[jid.JID])
818 """ 818 """
819 entities = set() 819 entities = set()
820 for contact_list in self._clist.itervalues(): 820 for contact_list in self._clist.values():
821 entities.update(contact_list.roster_connected) 821 entities.update(contact_list.roster_connected)
822 return entities 822 return entities
823 823
824 @property 824 @property
825 def roster_entities_by_group(self): 825 def roster_entities_by_group(self):
827 JIDs. This also includes the empty group (None key). 827 JIDs. This also includes the empty group (None key).
828 828
829 @return (dict[unicode,set(jid.JID)]) 829 @return (dict[unicode,set(jid.JID)])
830 """ 830 """
831 groups = {} 831 groups = {}
832 for contact_list in self._clist.itervalues(): 832 for contact_list in self._clist.values():
833 groups.update(contact_list.roster_entities_by_group) 833 groups.update(contact_list.roster_entities_by_group)
834 return groups 834 return groups
835 835
836 @property 836 @property
837 def roster_groups_by_entities(self): 837 def roster_groups_by_entities(self):
839 groups. 839 groups.
840 840
841 @return (dict[jid.JID, set(unicode)]) 841 @return (dict[jid.JID, set(unicode)])
842 """ 842 """
843 entities = {} 843 entities = {}
844 for contact_list in self._clist.itervalues(): 844 for contact_list in self._clist.values():
845 entities.update(contact_list.roster_groups_by_entities) 845 entities.update(contact_list.roster_groups_by_entities)
846 return entities 846 return entities
847 847
848 @property 848 @property
849 def selected(self): 849 def selected(self):
850 """Return contacts currently selected 850 """Return contacts currently selected
851 851
852 @return (set): set of selected entities 852 @return (set): set of selected entities
853 """ 853 """
854 entities = set() 854 entities = set()
855 for contact_list in self._clist.itervalues(): 855 for contact_list in self._clist.values():
856 entities.update(contact_list.selected) 856 entities.update(contact_list.selected)
857 return entities 857 return entities
858 858
859 @property 859 @property
860 def all_iter(self): 860 def all_iter(self):
861 """Return item representation for all entities in cache 861 """Return item representation for all entities in cache
862 862
863 items are unordered 863 items are unordered
864 """ 864 """
865 for profile, contact_list in self._clist.iteritems(): 865 for profile, contact_list in self._clist.items():
866 for bare_jid, cache in contact_list.all_iter: 866 for bare_jid, cache in contact_list.all_iter:
867 data = cache.copy() 867 data = cache.copy()
868 data[C.CONTACT_PROFILE] = profile 868 data[C.CONTACT_PROFILE] = profile
869 yield bare_jid, data 869 yield bare_jid, data
870 870
874 874
875 items are unordered 875 items are unordered
876 key: bare jid, value: data 876 key: bare jid, value: data
877 """ 877 """
878 items = {} 878 items = {}
879 for profile, contact_list in self._clist.iteritems(): 879 for profile, contact_list in self._clist.items():
880 for bare_jid, cache in contact_list.items.iteritems(): 880 for bare_jid, cache in contact_list.items.items():
881 data = cache.copy() 881 data = cache.copy()
882 items[bare_jid] = data 882 items[bare_jid] = data
883 data[C.CONTACT_PROFILE] = profile 883 data[C.CONTACT_PROFILE] = profile
884 return items 884 return items
885 885
951 (e.g. C.CONTACT_SPECIAL_GROUP) 951 (e.g. C.CONTACT_SPECIAL_GROUP)
952 None to return all special extras. 952 None to return all special extras.
953 @return (set[jid.JID]) 953 @return (set[jid.JID])
954 """ 954 """
955 entities = set() 955 entities = set()
956 for contact_list in self._clist.itervalues(): 956 for contact_list in self._clist.values():
957 entities.update(contact_list.getSpecialExtras(special_type)) 957 entities.update(contact_list.getSpecialExtras(special_type))
958 return entities 958 return entities
959 959
960 def _contactsFilled(self, profile): 960 def _contactsFilled(self, profile):
961 self._to_fill.remove(profile) 961 self._to_fill.remove(profile)
982 982
983 if profile is not None: 983 if profile is not None:
984 assert profile in self._clist 984 assert profile in self._clist
985 to_fill.add(profile) 985 to_fill.add(profile)
986 else: 986 else:
987 to_fill.update(self._clist.keys()) 987 to_fill.update(list(self._clist.keys()))
988 988
989 remaining = to_fill.difference(filled) 989 remaining = to_fill.difference(filled)
990 if remaining != to_fill: 990 if remaining != to_fill:
991 log.debug( 991 log.debug(
992 u"Not re-filling already filled contact list(s) for {}".format( 992 "Not re-filling already filled contact list(s) for {}".format(
993 u", ".join(to_fill.intersection(filled)) 993 ", ".join(to_fill.intersection(filled))
994 ) 994 )
995 ) 995 )
996 for profile in remaining: 996 for profile in remaining:
997 self._clist[profile]._fill() 997 self._clist[profile]._fill()
998 998
999 def clearContacts(self, keep_cache=False): 999 def clearContacts(self, keep_cache=False):
1000 """Clear all the contact list 1000 """Clear all the contact list
1001 1001
1002 @param keep_cache: if True, don't reset the cache 1002 @param keep_cache: if True, don't reset the cache
1003 """ 1003 """
1004 for contact_list in self._clist.itervalues(): 1004 for contact_list in self._clist.values():
1005 contact_list.clearContacts(keep_cache) 1005 contact_list.clearContacts(keep_cache)
1006 # we need a full update 1006 # we need a full update
1007 self.update() 1007 self.update()
1008 1008
1009 def select(self, entity): 1009 def select(self, entity):
1010 for contact_list in self._clist.itervalues(): 1010 for contact_list in self._clist.values():
1011 contact_list.select(entity) 1011 contact_list.select(entity)
1012 1012
1013 def unselect(self, entity): 1013 def unselect(self, entity):
1014 for contact_list in self._clist.itervalues(): 1014 for contact_list in self._clist.values():
1015 contact_list.select(entity) 1015 contact_list.select(entity)
1016 1016
1017 def lockUpdate(self, locked=True, do_update=True): 1017 def lockUpdate(self, locked=True, do_update=True):
1018 """Forbid contact list updates 1018 """Forbid contact list updates
1019 1019
1023 @param do_update(bool): if True, a full update is done after unlocking 1023 @param do_update(bool): if True, a full update is done after unlocking
1024 if set to False, widget state can be inconsistent, be sure to know 1024 if set to False, widget state can be inconsistent, be sure to know
1025 what youa re doing! 1025 what youa re doing!
1026 """ 1026 """
1027 log.debug( 1027 log.debug(
1028 u"Contact lists updates are now {}".format( 1028 "Contact lists updates are now {}".format(
1029 u"LOCKED" if locked else u"UNLOCKED" 1029 "LOCKED" if locked else "UNLOCKED"
1030 ) 1030 )
1031 ) 1031 )
1032 self._update_locked = locked 1032 self._update_locked = locked
1033 if not locked and do_update: 1033 if not locked and do_update:
1034 self.update() 1034 self.update()