comparison sat_frontends/quick_frontend/quick_app.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 e7cbe662838b
children f8e3789912d0
comparison
equal deleted inserted replaced
3027:ff5bcb12ae60 3028:ab2696e34d29
38 from collections import OrderedDict 38 from collections import OrderedDict
39 import time 39 import time
40 40
41 try: 41 try:
42 # FIXME: to be removed when an acceptable solution is here 42 # FIXME: to be removed when an acceptable solution is here
43 unicode("") # XXX: unicode doesn't exist in pyjamas 43 str("") # XXX: unicode doesn't exist in pyjamas
44 except ( 44 except (
45 TypeError, 45 TypeError,
46 AttributeError, 46 AttributeError,
47 ): # Error raised is not the same depending on pyjsbuild options 47 ): # Error raised is not the same depending on pyjsbuild options
48 unicode = str 48 str = str
49 49
50 50
51 class ProfileManager(object): 51 class ProfileManager(object):
52 """Class managing all data relative to one profile, and plugging in mechanism""" 52 """Class managing all data relative to one profile, and plugging in mechanism"""
53 53
137 callback=self._plug_profile_getFeaturesCb, 137 callback=self._plug_profile_getFeaturesCb,
138 errback=self._plug_profile_getFeaturesEb, 138 errback=self._plug_profile_getFeaturesEb,
139 ) 139 )
140 140
141 def _plug_profile_getFeaturesEb(self, failure): 141 def _plug_profile_getFeaturesEb(self, failure):
142 log.error(u"Couldn't get features: {}".format(failure)) 142 log.error("Couldn't get features: {}".format(failure))
143 self._plug_profile_getFeaturesCb({}) 143 self._plug_profile_getFeaturesCb({})
144 144
145 def _plug_profile_getFeaturesCb(self, features): 145 def _plug_profile_getFeaturesCb(self, features):
146 self.host.features = features 146 self.host.features = features
147 # FIXME: we don't use cached value at the moment, but keep the code for later use 147 # FIXME: we don't use cached value at the moment, but keep the code for later use
152 # callback=self._plug_profile_gotCachedValues, 152 # callback=self._plug_profile_gotCachedValues,
153 # errback=self._plug_profile_failedCachedValues) 153 # errback=self._plug_profile_failedCachedValues)
154 self._plug_profile_gotCachedValues({}) 154 self._plug_profile_gotCachedValues({})
155 155
156 def _plug_profile_failedCachedValues(self, failure): 156 def _plug_profile_failedCachedValues(self, failure):
157 log.error(u"Couldn't get cached values: {}".format(failure)) 157 log.error("Couldn't get cached values: {}".format(failure))
158 self._plug_profile_gotCachedValues({}) 158 self._plug_profile_gotCachedValues({})
159 159
160 def _plug_profile_gotCachedValues(self, cached_values): 160 def _plug_profile_gotCachedValues(self, cached_values):
161 contact_list = self.host.contact_lists[self.profile] 161 contact_list = self.host.contact_lists[self.profile]
162 # add the contact list and its listener 162 # add the contact list and its listener
163 for entity_s, data in cached_values.iteritems(): 163 for entity_s, data in cached_values.items():
164 for key, value in data.iteritems(): 164 for key, value in data.items():
165 self.host.entityDataUpdatedHandler(entity_s, key, value, self.profile) 165 self.host.entityDataUpdatedHandler(entity_s, key, value, self.profile)
166 166
167 if not self.connected: 167 if not self.connected:
168 self.host.setPresenceStatus(C.PRESENCE_UNAVAILABLE, "", profile=self.profile) 168 self.host.setPresenceStatus(C.PRESENCE_UNAVAILABLE, "", profile=self.profile)
169 else: 169 else:
201 contact, key, data[key], self.profile 201 contact, key, data[key], self.profile
202 ) 202 )
203 203
204 for contact in presences: 204 for contact in presences:
205 for res in presences[contact]: 205 for res in presences[contact]:
206 jabber_id = (u"%s/%s" % (jid.JID(contact).bare, res)) if res else contact 206 jabber_id = ("%s/%s" % (jid.JID(contact).bare, res)) if res else contact
207 show = presences[contact][res][0] 207 show = presences[contact][res][0]
208 priority = presences[contact][res][1] 208 priority = presences[contact][res][1]
209 statuses = presences[contact][res][2] 209 statuses = presences[contact][res][2]
210 self.host.presenceUpdateHandler( 210 self.host.presenceUpdateHandler(
211 jabber_id, show, priority, statuses, self.profile 211 jabber_id, show, priority, statuses, self.profile
214 contact, 214 contact,
215 ["avatar", "nick"], 215 ["avatar", "nick"],
216 self.profile, 216 self.profile,
217 callback=lambda data, contact=contact: gotEntityData(data, contact), 217 callback=lambda data, contact=contact: gotEntityData(data, contact),
218 errback=lambda failure, contact=contact: log.debug( 218 errback=lambda failure, contact=contact: log.debug(
219 u"No cache data for {}".format(contact) 219 "No cache data for {}".format(contact)
220 ), 220 ),
221 ) 221 )
222 222
223 # At this point, profile should be fully plugged 223 # At this point, profile should be fully plugged
224 # and we launch frontend specific method 224 # and we launch frontend specific method
236 236
237 def __contains__(self, profile): 237 def __contains__(self, profile):
238 return profile in self._profiles 238 return profile in self._profiles
239 239
240 def __iter__(self): 240 def __iter__(self):
241 return self._profiles.iterkeys() 241 return iter(self._profiles.keys())
242 242
243 def __getitem__(self, profile): 243 def __getitem__(self, profile):
244 return self._profiles[profile] 244 return self._profiles[profile]
245 245
246 def __len__(self): 246 def __len__(self):
247 return len(self._profiles) 247 return len(self._profiles)
248 248
249 def iteritems(self): 249 def items(self):
250 return self._profiles.iteritems() 250 return self._profiles.items()
251 251
252 def itervalues(self): 252 def values(self):
253 return self._profiles.itervalues() 253 return self._profiles.values()
254 254
255 def plug(self, profile): 255 def plug(self, profile):
256 if profile in self._profiles: 256 if profile in self._profiles:
257 raise exceptions.ConflictError( 257 raise exceptions.ConflictError(
258 "A profile of the name [{}] is already plugged".format(profile) 258 "A profile of the name [{}] is already plugged".format(profile)
269 host.contact_lists[profile].unplug() 269 host.contact_lists[profile].unplug()
270 270
271 del self._profiles[profile] 271 del self._profiles[profile]
272 272
273 def chooseOneProfile(self): 273 def chooseOneProfile(self):
274 return self._profiles.keys()[0] 274 return list(self._profiles.keys())[0]
275 275
276 276
277 class QuickApp(object): 277 class QuickApp(object):
278 """This class contain the main methods needed for the frontend""" 278 """This class contain the main methods needed for the frontend"""
279 279
348 348
349 def _namespacesGetCb(self, ns_map): 349 def _namespacesGetCb(self, ns_map):
350 self.ns_map = ns_map 350 self.ns_map = ns_map
351 351
352 def _namespacesGetEb(self, failure_): 352 def _namespacesGetEb(self, failure_):
353 log.error(_(u"Can't get namespaces map: {msg}").format(msg=failure_)) 353 log.error(_("Can't get namespaces map: {msg}").format(msg=failure_))
354 354
355 def _encryptionPluginsGetCb(self, plugins): 355 def _encryptionPluginsGetCb(self, plugins):
356 self.encryption_plugins = plugins 356 self.encryption_plugins = plugins
357 357
358 def _encryptionPluginsGetEb(self, failure_): 358 def _encryptionPluginsGetEb(self, failure_):
359 log.warning(_(u"Can't retrieve encryption plugins: {msg}").format(msg=failure_)) 359 log.warning(_("Can't retrieve encryption plugins: {msg}").format(msg=failure_))
360 360
361 def onBridgeConnected(self): 361 def onBridgeConnected(self):
362 self.bridge.namespacesGet( 362 self.bridge.namespacesGet(
363 callback=self._namespacesGetCb, errback=self._namespacesGetEb) 363 callback=self._namespacesGetCb, errback=self._namespacesGetEb)
364 # we cache available encryption plugins, as we'll use them on earch 364 # we cache available encryption plugins, as we'll use them on earch
400 quick_games.Radiocol.registerSignals(self) 400 quick_games.Radiocol.registerSignals(self)
401 self.onBridgeConnected() 401 self.onBridgeConnected()
402 402
403 def _bridgeEb(self, failure): 403 def _bridgeEb(self, failure):
404 if isinstance(failure, exceptions.BridgeExceptionNoService): 404 if isinstance(failure, exceptions.BridgeExceptionNoService):
405 print(_(u"Can't connect to SàT backend, are you sure it's launched ?")) 405 print((_("Can't connect to SàT backend, are you sure it's launched ?")))
406 sys.exit(1) 406 sys.exit(1)
407 elif isinstance(failure, exceptions.BridgeInitError): 407 elif isinstance(failure, exceptions.BridgeInitError):
408 print(_(u"Can't init bridge")) 408 print((_("Can't init bridge")))
409 sys.exit(1) 409 sys.exit(1)
410 else: 410 else:
411 print(_(u"Error while initialising bridge: {}".format(failure))) 411 print((_("Error while initialising bridge: {}".format(failure))))
412 412
413 @property 413 @property
414 def current_profile(self): 414 def current_profile(self):
415 """Profile that a user would expect to use""" 415 """Profile that a user would expect to use"""
416 try: 416 try:
457 @param state(bool): True: if the backend is resynchronising 457 @param state(bool): True: if the backend is resynchronising
458 False when we lose synchronisation, for instance if frontend is going to sleep 458 False when we lose synchronisation, for instance if frontend is going to sleep
459 or if connection has been lost and a reconnection is needed 459 or if connection has been lost and a reconnection is needed
460 """ 460 """
461 if state: 461 if state:
462 log.debug(u"we are synchronised with server") 462 log.debug("we are synchronised with server")
463 if self.AUTO_RESYNC: 463 if self.AUTO_RESYNC:
464 # we are resynchronising all widgets 464 # we are resynchronising all widgets
465 log.debug(u"doing a full widgets resynchronisation") 465 log.debug("doing a full widgets resynchronisation")
466 for w in self.widgets: 466 for w in self.widgets:
467 try: 467 try:
468 resync = w.resync 468 resync = w.resync
469 except AttributeError: 469 except AttributeError:
470 pass 470 pass
472 resync() 472 resync()
473 self.contact_lists.fill() 473 self.contact_lists.fill()
474 474
475 self._sync = state 475 self._sync = state
476 else: 476 else:
477 log.debug(u"we have lost synchronisation with server") 477 log.debug("we have lost synchronisation with server")
478 self._sync = state 478 self._sync = state
479 # we've lost synchronisation, all widgets must be notified 479 # we've lost synchronisation, all widgets must be notified
480 # note: this is always called independently of AUTO_RESYNC 480 # note: this is always called independently of AUTO_RESYNC
481 for w in self.widgets: 481 for w in self.widgets:
482 try: 482 try:
494 None for calling an automatically named handler (function_name + 'Handler') 494 None for calling an automatically named handler (function_name + 'Handler')
495 @param iface (str): interface of the bridge to use ('core' or 'plugin') 495 @param iface (str): interface of the bridge to use ('core' or 'plugin')
496 @param with_profile (boolean): True if the signal concerns a specific profile, 496 @param with_profile (boolean): True if the signal concerns a specific profile,
497 in that case the profile name has to be passed by the caller 497 in that case the profile name has to be passed by the caller
498 """ 498 """
499 log.debug(u"registering signal {name}".format(name=function_name)) 499 log.debug("registering signal {name}".format(name=function_name))
500 if handler is None: 500 if handler is None:
501 handler = getattr(self, "{}{}".format(function_name, "Handler")) 501 handler = getattr(self, "{}{}".format(function_name, "Handler"))
502 if not with_profile: 502 if not with_profile:
503 self.bridge.register_signal(function_name, handler, iface) 503 self.bridge.register_signal(function_name, handler, iface)
504 return 504 return
580 listeners = self._listeners[type_] 580 listeners = self._listeners[type_]
581 except KeyError: 581 except KeyError:
582 pass 582 pass
583 else: 583 else:
584 profile = kwargs.get("profile") 584 profile = kwargs.get("profile")
585 for listener, profiles_filter in listeners.iteritems(): 585 for listener, profiles_filter in listeners.items():
586 if profile is None or not profiles_filter or profile in profiles_filter: 586 if profile is None or not profiles_filter or profile in profiles_filter:
587 listener(*args, **kwargs) 587 listener(*args, **kwargs)
588 588
589 def check_profile(self, profile): 589 def check_profile(self, profile):
590 """Tell if the profile is currently followed by the application, and ready""" 590 """Tell if the profile is currently followed by the application, and ready"""
615 615
616 # profile is ready, we can call send signals that where is cache 616 # profile is ready, we can call send signals that where is cache
617 cached_signals = self.signals_cache.pop(profile, []) 617 cached_signals = self.signals_cache.pop(profile, [])
618 for function_name, handler, args, kwargs in cached_signals: 618 for function_name, handler, args, kwargs in cached_signals:
619 log.debug( 619 log.debug(
620 u"Calling cached signal [%s] with args %s and kwargs %s" 620 "Calling cached signal [%s] with args %s and kwargs %s"
621 % (function_name, args, kwargs) 621 % (function_name, args, kwargs)
622 ) 622 )
623 handler(*args, **kwargs) 623 handler(*args, **kwargs)
624 624
625 self.callListeners("profilePlugged", profile=profile) 625 self.callListeners("profilePlugged", profile=profile)
630 if not callback: 630 if not callback:
631 callback = lambda __: None 631 callback = lambda __: None
632 if not errback: 632 if not errback:
633 633
634 def errback(failure): 634 def errback(failure):
635 log.error(_(u"Can't connect profile [%s]") % failure) 635 log.error(_("Can't connect profile [%s]") % failure)
636 try: 636 try:
637 module = failure.module 637 module = failure.module
638 except AttributeError: 638 except AttributeError:
639 module = "" 639 module = ""
640 try: 640 try:
788 def messageStateHandler(self, uid, status, profile): 788 def messageStateHandler(self, uid, status, profile):
789 for widget in self.widgets.getWidgets(quick_chat.QuickChat, profiles=(profile,)): 789 for widget in self.widgets.getWidgets(quick_chat.QuickChat, profiles=(profile,)):
790 widget.onMessageState(uid, status, profile) 790 widget.onMessageState(uid, status, profile)
791 791
792 def messageSend(self, to_jid, message, subject=None, mess_type="auto", extra=None, callback=None, errback=None, profile_key=C.PROF_KEY_NONE): 792 def messageSend(self, to_jid, message, subject=None, mess_type="auto", extra=None, callback=None, errback=None, profile_key=C.PROF_KEY_NONE):
793 if not subject and not extra and (not message or message == {u'': u''}): 793 if not subject and not extra and (not message or message == {'': ''}):
794 log.debug(u"Not sending empty message") 794 log.debug("Not sending empty message")
795 return 795 return
796 796
797 if subject is None: 797 if subject is None:
798 subject = {} 798 subject = {}
799 if extra is None: 799 if extra is None:
810 810
811 if not self.trigger.point("messageSendTrigger", to_jid, message, subject, mess_type, extra, callback, errback, profile_key=profile_key): 811 if not self.trigger.point("messageSendTrigger", to_jid, message, subject, mess_type, extra, callback, errback, profile_key=profile_key):
812 return 812 return
813 813
814 self.bridge.messageSend( 814 self.bridge.messageSend(
815 unicode(to_jid), 815 str(to_jid),
816 message, 816 message,
817 subject, 817 subject,
818 mess_type, 818 mess_type,
819 extra, 819 extra,
820 profile_key, 820 profile_key,
855 self.callListeners("presence", entity, show, priority, statuses, profile=profile) 855 self.callListeners("presence", entity, show, priority, statuses, profile=profile)
856 856
857 def mucRoomJoinedHandler(self, room_jid_s, occupants, user_nick, subject, profile): 857 def mucRoomJoinedHandler(self, room_jid_s, occupants, user_nick, subject, profile):
858 """Called when a MUC room is joined""" 858 """Called when a MUC room is joined"""
859 log.debug( 859 log.debug(
860 u"Room [{room_jid}] joined by {profile}, users presents:{users}".format( 860 "Room [{room_jid}] joined by {profile}, users presents:{users}".format(
861 room_jid=room_jid_s, profile=profile, users=occupants.keys() 861 room_jid=room_jid_s, profile=profile, users=list(occupants.keys())
862 ) 862 )
863 ) 863 )
864 room_jid = jid.JID(room_jid_s) 864 room_jid = jid.JID(room_jid_s)
865 self.contact_lists[profile].setSpecial(room_jid, C.CONTACT_SPECIAL_GROUP) 865 self.contact_lists[profile].setSpecial(room_jid, C.CONTACT_SPECIAL_GROUP)
866 self.widgets.getOrCreateWidget( 866 self.widgets.getOrCreateWidget(
874 ) 874 )
875 875
876 def mucRoomLeftHandler(self, room_jid_s, profile): 876 def mucRoomLeftHandler(self, room_jid_s, profile):
877 """Called when a MUC room is left""" 877 """Called when a MUC room is left"""
878 log.debug( 878 log.debug(
879 u"Room [%(room_jid)s] left by %(profile)s" 879 "Room [%(room_jid)s] left by %(profile)s"
880 % {"room_jid": room_jid_s, "profile": profile} 880 % {"room_jid": room_jid_s, "profile": profile}
881 ) 881 )
882 room_jid = jid.JID(room_jid_s) 882 room_jid = jid.JID(room_jid_s)
883 chat_widget = self.widgets.getWidget(quick_chat.QuickChat, room_jid, profile) 883 chat_widget = self.widgets.getWidget(quick_chat.QuickChat, room_jid, profile)
884 if chat_widget: 884 if chat_widget:
891 chat_widget = self.widgets.getOrCreateWidget( 891 chat_widget = self.widgets.getOrCreateWidget(
892 quick_chat.QuickChat, room_jid, type_=C.CHAT_GROUP, profile=profile 892 quick_chat.QuickChat, room_jid, type_=C.CHAT_GROUP, profile=profile
893 ) 893 )
894 chat_widget.changeUserNick(old_nick, new_nick) 894 chat_widget.changeUserNick(old_nick, new_nick)
895 log.debug( 895 log.debug(
896 u"user [%(old_nick)s] is now known as [%(new_nick)s] in room [%(room_jid)s]" 896 "user [%(old_nick)s] is now known as [%(new_nick)s] in room [%(room_jid)s]"
897 % {"old_nick": old_nick, "new_nick": new_nick, "room_jid": room_jid} 897 % {"old_nick": old_nick, "new_nick": new_nick, "room_jid": room_jid}
898 ) 898 )
899 899
900 def mucRoomNewSubjectHandler(self, room_jid_s, subject, profile): 900 def mucRoomNewSubjectHandler(self, room_jid_s, subject, profile):
901 """Called when subject of MUC room change""" 901 """Called when subject of MUC room change"""
903 chat_widget = self.widgets.getOrCreateWidget( 903 chat_widget = self.widgets.getOrCreateWidget(
904 quick_chat.QuickChat, room_jid, type_=C.CHAT_GROUP, profile=profile 904 quick_chat.QuickChat, room_jid, type_=C.CHAT_GROUP, profile=profile
905 ) 905 )
906 chat_widget.setSubject(subject) 906 chat_widget.setSubject(subject)
907 log.debug( 907 log.debug(
908 u"new subject for room [%(room_jid)s]: %(subject)s" 908 "new subject for room [%(room_jid)s]: %(subject)s"
909 % {"room_jid": room_jid, "subject": subject} 909 % {"room_jid": room_jid, "subject": subject}
910 ) 910 )
911 911
912 def chatStateReceivedHandler(self, from_jid_s, state, profile): 912 def chatStateReceivedHandler(self, from_jid_s, state, profile):
913 """Called when a new chat state (XEP-0085) is received. 913 """Called when a new chat state (XEP-0085) is received.
972 @return (iter[dict]): notifications 972 @return (iter[dict]): notifications
973 """ 973 """
974 main_notif_dict = self.profiles[profile].notifications 974 main_notif_dict = self.profiles[profile].notifications
975 975
976 if entity is C.ENTITY_ALL: 976 if entity is C.ENTITY_ALL:
977 selected_notifs = main_notif_dict.itervalues() 977 selected_notifs = iter(main_notif_dict.values())
978 exact_jid = False 978 exact_jid = False
979 else: 979 else:
980 if entity is None: 980 if entity is None:
981 key = "" 981 key = ""
982 exact_jid = False 982 exact_jid = False
987 selected_notifs = (main_notif_dict.setdefault(key, {}),) 987 selected_notifs = (main_notif_dict.setdefault(key, {}),)
988 988
989 for notifs_from_select in selected_notifs: 989 for notifs_from_select in selected_notifs:
990 990
991 if type_ is None: 991 if type_ is None:
992 type_notifs = notifs_from_select.itervalues() 992 type_notifs = iter(notifs_from_select.values())
993 else: 993 else:
994 type_notifs = (notifs_from_select.get(type_, []),) 994 type_notifs = (notifs_from_select.get(type_, []),)
995 995
996 for notifs in type_notifs: 996 for notifs in type_notifs:
997 for notif in notifs: 997 for notif in notifs:
1078 """ 1078 """
1079 callbacks = self._progress_ids.setdefault(progress_id, []) 1079 callbacks = self._progress_ids.setdefault(progress_id, [])
1080 callbacks.append((callback, errback)) 1080 callbacks.append((callback, errback))
1081 1081
1082 def progressStartedHandler(self, pid, metadata, profile): 1082 def progressStartedHandler(self, pid, metadata, profile):
1083 log.info(u"Progress {} started".format(pid)) 1083 log.info("Progress {} started".format(pid))
1084 1084
1085 def progressFinishedHandler(self, pid, metadata, profile): 1085 def progressFinishedHandler(self, pid, metadata, profile):
1086 log.info(u"Progress {} finished".format(pid)) 1086 log.info("Progress {} finished".format(pid))
1087 try: 1087 try:
1088 callbacks = self._progress_ids.pop(pid) 1088 callbacks = self._progress_ids.pop(pid)
1089 except KeyError: 1089 except KeyError:
1090 pass 1090 pass
1091 else: 1091 else:
1093 if callback is not None: 1093 if callback is not None:
1094 callback(metadata, profile=profile) 1094 callback(metadata, profile=profile)
1095 self.callListeners("progressFinished", pid, metadata, profile=profile) 1095 self.callListeners("progressFinished", pid, metadata, profile=profile)
1096 1096
1097 def progressErrorHandler(self, pid, err_msg, profile): 1097 def progressErrorHandler(self, pid, err_msg, profile):
1098 log.warning(u"Progress {pid} error: {err_msg}".format(pid=pid, err_msg=err_msg)) 1098 log.warning("Progress {pid} error: {err_msg}".format(pid=pid, err_msg=err_msg))
1099 try: 1099 try:
1100 callbacks = self._progress_ids.pop(pid) 1100 callbacks = self._progress_ids.pop(pid)
1101 except KeyError: 1101 except KeyError:
1102 pass 1102 pass
1103 else: 1103 else:
1107 self.callListeners("progressError", pid, err_msg, profile=profile) 1107 self.callListeners("progressError", pid, err_msg, profile=profile)
1108 1108
1109 def _subscribe_cb(self, answer, data): 1109 def _subscribe_cb(self, answer, data):
1110 entity, profile = data 1110 entity, profile = data
1111 type_ = "subscribed" if answer else "unsubscribed" 1111 type_ = "subscribed" if answer else "unsubscribed"
1112 self.bridge.subscription(type_, unicode(entity.bare), profile_key=profile) 1112 self.bridge.subscription(type_, str(entity.bare), profile_key=profile)
1113 1113
1114 def subscribeHandler(self, type, raw_jid, profile): 1114 def subscribeHandler(self, type, raw_jid, profile):
1115 """Called when a subsciption management signal is received""" 1115 """Called when a subsciption management signal is received"""
1116 entity = jid.JID(raw_jid) 1116 entity = jid.JID(raw_jid)
1117 if type == "subscribed": 1117 if type == "subscribed":
1118 # this is a subscription confirmation, we just have to inform user 1118 # this is a subscription confirmation, we just have to inform user
1119 # TODO: call self.getEntityMBlog to add the new contact blogs 1119 # TODO: call self.getEntityMBlog to add the new contact blogs
1120 self.showDialog( 1120 self.showDialog(
1121 _(u"The contact {contact} has accepted your subscription").format( 1121 _("The contact {contact} has accepted your subscription").format(
1122 contact=entity.bare 1122 contact=entity.bare
1123 ), 1123 ),
1124 _(u"Subscription confirmation"), 1124 _("Subscription confirmation"),
1125 ) 1125 )
1126 elif type == "unsubscribed": 1126 elif type == "unsubscribed":
1127 # this is a subscription refusal, we just have to inform user 1127 # this is a subscription refusal, we just have to inform user
1128 self.showDialog( 1128 self.showDialog(
1129 _(u"The contact {contact} has refused your subscription").format( 1129 _("The contact {contact} has refused your subscription").format(
1130 contact=entity.bare 1130 contact=entity.bare
1131 ), 1131 ),
1132 _(u"Subscription refusal"), 1132 _("Subscription refusal"),
1133 "error", 1133 "error",
1134 ) 1134 )
1135 elif type == "subscribe": 1135 elif type == "subscribe":
1136 # this is a subscriptionn request, we have to ask for user confirmation 1136 # this is a subscriptionn request, we have to ask for user confirmation
1137 # TODO: use sat.stdui.ui_contact_list to display the groups selector 1137 # TODO: use sat.stdui.ui_contact_list to display the groups selector
1138 self.showDialog( 1138 self.showDialog(
1139 _( 1139 _(
1140 u"The contact {contact} wants to subscribe to your presence" 1140 "The contact {contact} wants to subscribe to your presence"
1141 u".\nDo you accept ?" 1141 ".\nDo you accept ?"
1142 ).format(contact=entity.bare), 1142 ).format(contact=entity.bare),
1143 _("Subscription confirmation"), 1143 _("Subscription confirmation"),
1144 "yes/no", 1144 "yes/no",
1145 answer_cb=self._subscribe_cb, 1145 answer_cb=self._subscribe_cb,
1146 answer_data=(entity, profile), 1146 answer_data=(entity, profile),
1147 ) 1147 )
1148 1148
1149 def _debugHandler(self, action, parameters, profile): 1149 def _debugHandler(self, action, parameters, profile):
1150 if action == u"widgets_dump": 1150 if action == "widgets_dump":
1151 from pprint import pformat 1151 from pprint import pformat
1152 log.info(u"Widgets dump:\n{data}".format(data=pformat(self.widgets._widgets))) 1152 log.info("Widgets dump:\n{data}".format(data=pformat(self.widgets._widgets)))
1153 else: 1153 else:
1154 log.warning(u"Unknown debug action: {action}".format(action=action)) 1154 log.warning("Unknown debug action: {action}".format(action=action))
1155 1155
1156 1156
1157 def showDialog(self, message, title, type="info", answer_cb=None, answer_data=None): 1157 def showDialog(self, message, title, type="info", answer_cb=None, answer_data=None):
1158 """Show a dialog to user 1158 """Show a dialog to user
1159 1159
1176 def showAlert(self, message): 1176 def showAlert(self, message):
1177 # FIXME: doesn't seems used anymore, to remove? 1177 # FIXME: doesn't seems used anymore, to remove?
1178 pass # FIXME 1178 pass # FIXME
1179 1179
1180 def dialogFailure(self, failure): 1180 def dialogFailure(self, failure):
1181 log.warning(u"Failure: {}".format(failure)) 1181 log.warning("Failure: {}".format(failure))
1182 1182
1183 def progressIdHandler(self, progress_id, profile): 1183 def progressIdHandler(self, progress_id, profile):
1184 """Callback used when an action result in a progress id""" 1184 """Callback used when an action result in a progress id"""
1185 log.info(u"Progress ID received: {}".format(progress_id)) 1185 log.info("Progress ID received: {}".format(progress_id))
1186 1186
1187 def isHidden(self): 1187 def isHidden(self):
1188 """Tells if the frontend window is hidden. 1188 """Tells if the frontend window is hidden.
1189 1189
1190 @return bool 1190 @return bool
1191 """ 1191 """
1192 raise NotImplementedError 1192 raise NotImplementedError
1193 1193
1194 def paramUpdateHandler(self, name, value, namespace, profile): 1194 def paramUpdateHandler(self, name, value, namespace, profile):
1195 log.debug( 1195 log.debug(
1196 _(u"param update: [%(namespace)s] %(name)s = %(value)s") 1196 _("param update: [%(namespace)s] %(name)s = %(value)s")
1197 % {"namespace": namespace, "name": name, "value": value} 1197 % {"namespace": namespace, "name": name, "value": value}
1198 ) 1198 )
1199 if (namespace, name) == ("Connection", "JabberID"): 1199 if (namespace, name) == ("Connection", "JabberID"):
1200 log.debug(_(u"Changing JID to %s") % value) 1200 log.debug(_("Changing JID to %s") % value)
1201 self.profiles[profile].whoami = jid.JID(value) 1201 self.profiles[profile].whoami = jid.JID(value)
1202 elif (namespace, name) == ("General", C.SHOW_OFFLINE_CONTACTS): 1202 elif (namespace, name) == ("General", C.SHOW_OFFLINE_CONTACTS):
1203 self.contact_lists[profile].showOfflineContacts(C.bool(value)) 1203 self.contact_lists[profile].showOfflineContacts(C.bool(value))
1204 elif (namespace, name) == ("General", C.SHOW_EMPTY_GROUPS): 1204 elif (namespace, name) == ("General", C.SHOW_EMPTY_GROUPS):
1205 self.contact_lists[profile].showEmptyGroups(C.bool(value)) 1205 self.contact_lists[profile].showEmptyGroups(C.bool(value))
1262 self.registerProgressCbs(progress_id, progress_cb, progress_eb) 1262 self.registerProgressCbs(progress_id, progress_cb, progress_eb)
1263 self.progressIdHandler(progress_id, profile) 1263 self.progressIdHandler(progress_id, profile)
1264 1264
1265 # we ignore metadata 1265 # we ignore metadata
1266 action_data = { 1266 action_data = {
1267 k: v for k, v in action_data.iteritems() if not k.startswith("meta_") 1267 k: v for k, v in action_data.items() if not k.startswith("meta_")
1268 } 1268 }
1269 1269
1270 if action_data: 1270 if action_data:
1271 raise exceptions.DataError( 1271 raise exceptions.DataError(
1272 u"Not all keys in action_data are managed ({keys})".format( 1272 "Not all keys in action_data are managed ({keys})".format(
1273 keys=", ".join(action_data.keys()) 1273 keys=", ".join(list(action_data.keys()))
1274 ) 1274 )
1275 ) 1275 )
1276 1276
1277 def _actionCb(self, data, callback, callback_id, profile): 1277 def _actionCb(self, data, callback, callback_id, profile):
1278 if callback is None: 1278 if callback is None:
1346 contact_list.setCache(entity, "avatar", path) 1346 contact_list.setCache(entity, "avatar", path)
1347 self.callListeners("avatar", entity, path, profile=profile) 1347 self.callListeners("avatar", entity, path, profile=profile)
1348 1348
1349 def _avatarGetEb(self, failure_, entity, contact_list): 1349 def _avatarGetEb(self, failure_, entity, contact_list):
1350 # FIXME: bridge needs a proper error handling 1350 # FIXME: bridge needs a proper error handling
1351 if "NotFound" in unicode(failure_): 1351 if "NotFound" in str(failure_):
1352 log.info(u"No avatar found for {entity}".format(entity=entity)) 1352 log.info("No avatar found for {entity}".format(entity=entity))
1353 else: 1353 else:
1354 log.warning(u"Can't get avatar: {}".format(failure_)) 1354 log.warning("Can't get avatar: {}".format(failure_))
1355 contact_list.setCache(entity, "avatar", self.getDefaultAvatar(entity)) 1355 contact_list.setCache(entity, "avatar", self.getDefaultAvatar(entity))
1356 1356
1357 def getAvatar( 1357 def getAvatar(
1358 self, 1358 self,
1359 entity, 1359 entity,
1381 avatar = contact_list.getCache(entity, "avatar", bare_default=None) 1381 avatar = contact_list.getCache(entity, "avatar", bare_default=None)
1382 except exceptions.NotFound: 1382 except exceptions.NotFound:
1383 avatar = None 1383 avatar = None
1384 if avatar is None: 1384 if avatar is None:
1385 self.bridge.avatarGet( 1385 self.bridge.avatarGet(
1386 unicode(entity), 1386 str(entity),
1387 cache_only, 1387 cache_only,
1388 hash_only, 1388 hash_only,
1389 profile=profile, 1389 profile=profile,
1390 callback=lambda path: self._avatarGetCb( 1390 callback=lambda path: self._avatarGetCb(
1391 path, entity, contact_list, profile 1391 path, entity, contact_list, profile
1411 self.bridge.disconnect(profile) 1411 self.bridge.disconnect(profile)
1412 1412
1413 def onExit(self): 1413 def onExit(self):
1414 """Must be called when the frontend is terminating""" 1414 """Must be called when the frontend is terminating"""
1415 to_unplug = [] 1415 to_unplug = []
1416 for profile, profile_manager in self.profiles.iteritems(): 1416 for profile, profile_manager in self.profiles.items():
1417 if profile_manager.connected and profile_manager.autodisconnect: 1417 if profile_manager.connected and profile_manager.autodisconnect:
1418 # The user wants autodisconnection 1418 # The user wants autodisconnection
1419 self.disconnect(profile) 1419 self.disconnect(profile)
1420 to_unplug.append(profile) 1420 to_unplug.append(profile)
1421 for profile in to_unplug: 1421 for profile in to_unplug: