comparison libervia/frontends/quick_frontend/quick_app.py @ 4270:0d7bb4df2343

Reformatted code base using black.
author Goffi <goffi@goffi.org>
date Wed, 19 Jun 2024 18:44:57 +0200
parents d01b8d002619
children
comparison
equal deleted inserted replaced
4269:64a85ce8be70 4270:0d7bb4df2343
42 42
43 # TODO: handle waiting XMLUI requests: getWaitingConf doesn't exist anymore 43 # TODO: handle waiting XMLUI requests: getWaitingConf doesn't exist anymore
44 # and a way to keep some XMLUI request between sessions is expected in backend 44 # and a way to keep some XMLUI request between sessions is expected in backend
45 host = None 45 host = None
46 bridge = None 46 bridge = None
47 cache_keys_to_get = ['avatar', 'nicknames'] 47 cache_keys_to_get = ["avatar", "nicknames"]
48 48
49 def __init__(self, profile): 49 def __init__(self, profile):
50 self.profile = profile 50 self.profile = profile
51 self.connected = False 51 self.connected = False
52 self.whoami = None 52 self.whoami = None
134 log.error("Couldn't get features: {}".format(failure)) 134 log.error("Couldn't get features: {}".format(failure))
135 self._plug_profile_get_features_cb({}) 135 self._plug_profile_get_features_cb({})
136 136
137 def _plug_profile_get_features_cb(self, features): 137 def _plug_profile_get_features_cb(self, features):
138 self.host.features = features 138 self.host.features = features
139 self.host.bridge.entities_data_get([], ProfileManager.cache_keys_to_get, 139 self.host.bridge.entities_data_get(
140 profile=self.profile, 140 [],
141 callback=self._plug_profile_got_cached_values, 141 ProfileManager.cache_keys_to_get,
142 errback=self._plug_profile_failed_cached_values) 142 profile=self.profile,
143 callback=self._plug_profile_got_cached_values,
144 errback=self._plug_profile_failed_cached_values,
145 )
143 146
144 def _plug_profile_failed_cached_values(self, failure): 147 def _plug_profile_failed_cached_values(self, failure):
145 log.error("Couldn't get cached values: {}".format(failure)) 148 log.error("Couldn't get cached values: {}".format(failure))
146 self._plug_profile_got_cached_values({}) 149 self._plug_profile_got_cached_values({})
147 150
151 for entity_s, data in cached_values.items(): 154 for entity_s, data in cached_values.items():
152 for key, value in data.items(): 155 for key, value in data.items():
153 self.host.entity_data_updated_handler(entity_s, key, value, self.profile) 156 self.host.entity_data_updated_handler(entity_s, key, value, self.profile)
154 157
155 if not self.connected: 158 if not self.connected:
156 self.host.set_presence_status(C.PRESENCE_UNAVAILABLE, "", profile=self.profile) 159 self.host.set_presence_status(
160 C.PRESENCE_UNAVAILABLE, "", profile=self.profile
161 )
157 else: 162 else:
158 163
159 contact_list.fill() 164 contact_list.fill()
160 self.host.set_presence_status(profile=self.profile) 165 self.host.set_presence_status(profile=self.profile)
161 166
256 #: if False, frontend must call resync itself when suitable (e.g. widget is being 261 #: if False, frontend must call resync itself when suitable (e.g. widget is being
257 #: visible) 262 #: visible)
258 AUTO_RESYNC = True 263 AUTO_RESYNC = True
259 264
260 def __init__( 265 def __init__(
261 self, bridge_factory, xmlui, check_options=None, connect_bridge=True, 266 self,
262 async_bridge_factory=None 267 bridge_factory,
268 xmlui,
269 check_options=None,
270 connect_bridge=True,
271 async_bridge_factory=None,
263 ): 272 ):
264 """Create a frontend application 273 """Create a frontend application
265 274
266 @param bridge_factory: method to use to create the bridge 275 @param bridge_factory: method to use to create the bridge
267 @param xmlui: xmlui module 276 @param xmlui: xmlui module
274 self.profiles = ProfilesManager() 283 self.profiles = ProfilesManager()
275 # profiles currently being plugged, used to (un)lock contact list updates 284 # profiles currently being plugged, used to (un)lock contact list updates
276 self._plugs_in_progress = set() 285 self._plugs_in_progress = set()
277 self.ready_profiles = set() # profiles which are connected and ready 286 self.ready_profiles = set() # profiles which are connected and ready
278 self.signals_cache = {} # used to keep signal received between start of 287 self.signals_cache = {} # used to keep signal received between start of
279 # plug_profile and when the profile is actualy ready 288 # plug_profile and when the profile is actualy ready
280 self.contact_lists = quick_contact_list.QuickContactListHandler(self) 289 self.contact_lists = quick_contact_list.QuickContactListHandler(self)
281 self.widgets = quick_widgets.QuickWidgetsManager(self) 290 self.widgets = quick_widgets.QuickWidgetsManager(self)
282 if check_options is not None: 291 if check_options is not None:
283 self.options = check_options() 292 self.options = check_options()
284 else: 293 else:
287 # see selected_widget setter and getter 296 # see selected_widget setter and getter
288 self._selected_widget = None 297 self._selected_widget = None
289 298
290 # listeners are callable watching events 299 # listeners are callable watching events
291 self._listeners = {} # key: listener type ("avatar", "selected", etc), 300 self._listeners = {} # key: listener type ("avatar", "selected", etc),
292 # value: list of callbacks 301 # value: list of callbacks
293 302
294 # cf. [register_action_handler] 303 # cf. [register_action_handler]
295 self._action_handlers: dict[str, Callable[[dict, str, int, str], None]] = {} 304 self._action_handlers: dict[str, Callable[[dict, str, int, str], None]] = {}
296 305
297 # triggers 306 # triggers
389 print((_("Error while initialising bridge: {}".format(failure)))) 398 print((_("Error while initialising bridge: {}".format(failure))))
390 399
391 def on_backend_ready(self): 400 def on_backend_ready(self):
392 log.info("backend is ready") 401 log.info("backend is ready")
393 self.bridge.namespaces_get( 402 self.bridge.namespaces_get(
394 callback=self._namespaces_get_cb, errback=self._namespaces_get_eb) 403 callback=self._namespaces_get_cb, errback=self._namespaces_get_eb
404 )
395 # we cache available encryption plugins, as we'll use them on each 405 # we cache available encryption plugins, as we'll use them on each
396 # new chat widget 406 # new chat widget
397 self.bridge.encryption_plugins_get( 407 self.bridge.encryption_plugins_get(
398 callback=self._encryption_plugins_get_cb, 408 callback=self._encryption_plugins_get_cb,
399 errback=self._encryption_plugins_get_eb) 409 errback=self._encryption_plugins_get_eb,
400 410 )
401 411
402 @property 412 @property
403 def current_profile(self): 413 def current_profile(self):
404 """Profile that a user would expect to use""" 414 """Profile that a user would expect to use"""
405 try: 415 try:
594 self._listeners[type_].pop(callback) 604 self._listeners[type_].pop(callback)
595 except KeyError: 605 except KeyError:
596 if not ignore_missing: 606 if not ignore_missing:
597 log.error( 607 log.error(
598 f"Trying to remove an inexisting listener (type = {type_}): " 608 f"Trying to remove an inexisting listener (type = {type_}): "
599 f"{callback}") 609 f"{callback}"
610 )
600 611
601 def call_listeners(self, type_, *args, **kwargs): 612 def call_listeners(self, type_, *args, **kwargs):
602 """Call the methods which listen type_ event. If a profiles filter has 613 """Call the methods which listen type_ event. If a profiles filter has
603 been register with a listener and profile argument is not None, the 614 been register with a listener and profile argument is not None, the
604 listener will be called only if profile is in the profiles filter list. 615 listener will be called only if profile is in the profiles filter list.
767 ) 778 )
768 779
769 def contact_new_handler(self, jid_s, attributes, groups, profile): 780 def contact_new_handler(self, jid_s, attributes, groups, profile):
770 entity = jid.JID(jid_s) 781 entity = jid.JID(jid_s)
771 groups = list(groups) 782 groups = list(groups)
772 self.contact_lists[profile].set_contact(entity, groups, attributes, in_roster=True) 783 self.contact_lists[profile].set_contact(
784 entity, groups, attributes, in_roster=True
785 )
773 786
774 def message_new_handler( 787 def message_new_handler(
775 self, uid, timestamp, from_jid_s, to_jid_s, msg, subject, type_, extra_s, 788 self, uid, timestamp, from_jid_s, to_jid_s, msg, subject, type_, extra_s, profile
776 profile): 789 ):
777 from_jid = jid.JID(from_jid_s) 790 from_jid = jid.JID(from_jid_s)
778 to_jid = jid.JID(to_jid_s) 791 to_jid = jid.JID(to_jid_s)
779 extra = data_format.deserialise(extra_s) 792 extra = data_format.deserialise(extra_s)
780 if not self.trigger.point( 793 if not self.trigger.point(
781 "messageNewTrigger", uid, timestamp, from_jid, to_jid, msg, subject, type_, 794 "messageNewTrigger",
782 extra, profile=profile,): 795 uid,
796 timestamp,
797 from_jid,
798 to_jid,
799 msg,
800 subject,
801 type_,
802 extra,
803 profile=profile,
804 ):
783 return 805 return
784 806
785 from_me = from_jid.bare == self.profiles[profile].whoami.bare 807 from_me = from_jid.bare == self.profiles[profile].whoami.bare
786 mess_to_jid = to_jid if from_me else from_jid 808 mess_to_jid = to_jid if from_me else from_jid
787 target = mess_to_jid.bare 809 target = mess_to_jid.bare
798 target = target 820 target = target
799 # we want to be sure to have at least one QuickChat instance 821 # we want to be sure to have at least one QuickChat instance
800 self.widgets.get_or_create_widget( 822 self.widgets.get_or_create_widget(
801 quick_chat.QuickChat, 823 quick_chat.QuickChat,
802 target, 824 target,
803 type_ = C.CHAT_GROUP if is_room else C.CHAT_ONE2ONE, 825 type_=C.CHAT_GROUP if is_room else C.CHAT_ONE2ONE,
804 on_new_widget = None, 826 on_new_widget=None,
805 profile = profile, 827 profile=profile,
806 ) 828 )
807 829
808 if ( 830 if (
809 not from_jid in contact_list 831 not from_jid in contact_list
810 and from_jid.bare != self.profiles[profile].whoami.bare 832 and from_jid.bare != self.profiles[profile].whoami.bare
822 ) 844 )
823 845
824 def message_encryption_started_handler(self, destinee_jid_s, plugin_data, profile): 846 def message_encryption_started_handler(self, destinee_jid_s, plugin_data, profile):
825 destinee_jid = jid.JID(destinee_jid_s) 847 destinee_jid = jid.JID(destinee_jid_s)
826 plugin_data = data_format.deserialise(plugin_data) 848 plugin_data = data_format.deserialise(plugin_data)
827 for widget in self.widgets.get_widgets(quick_chat.QuickChat, 849 for widget in self.widgets.get_widgets(
828 target=destinee_jid.bare, 850 quick_chat.QuickChat, target=destinee_jid.bare, profiles=(profile,)
829 profiles=(profile,)): 851 ):
830 widget.message_encryption_started(plugin_data) 852 widget.message_encryption_started(plugin_data)
831 853
832 def message_encryption_stopped_handler(self, destinee_jid_s, plugin_data, profile): 854 def message_encryption_stopped_handler(self, destinee_jid_s, plugin_data, profile):
833 destinee_jid = jid.JID(destinee_jid_s) 855 destinee_jid = jid.JID(destinee_jid_s)
834 for widget in self.widgets.get_widgets(quick_chat.QuickChat, 856 for widget in self.widgets.get_widgets(
835 target=destinee_jid.bare, 857 quick_chat.QuickChat, target=destinee_jid.bare, profiles=(profile,)
836 profiles=(profile,)): 858 ):
837 widget.message_encryption_stopped(plugin_data) 859 widget.message_encryption_stopped(plugin_data)
838 860
839 def message_state_handler(self, uid, status, profile): 861 def message_state_handler(self, uid, status, profile):
840 for widget in self.widgets.get_widgets(quick_chat.QuickChat, profiles=(profile,)): 862 for widget in self.widgets.get_widgets(quick_chat.QuickChat, profiles=(profile,)):
841 widget.on_message_state(uid, status, profile) 863 widget.on_message_state(uid, status, profile)
842 864
843 def message_send(self, to_jid, message, subject=None, mess_type="auto", extra=None, callback=None, errback=None, profile_key=C.PROF_KEY_NONE): 865 def message_send(
844 if not subject and not extra and (not message or message == {'': ''}): 866 self,
867 to_jid,
868 message,
869 subject=None,
870 mess_type="auto",
871 extra=None,
872 callback=None,
873 errback=None,
874 profile_key=C.PROF_KEY_NONE,
875 ):
876 if not subject and not extra and (not message or message == {"": ""}):
845 log.debug("Not sending empty message") 877 log.debug("Not sending empty message")
846 return 878 return
847 879
848 if subject is None: 880 if subject is None:
849 subject = {} 881 subject = {}
851 extra = {} 883 extra = {}
852 if callback is None: 884 if callback is None:
853 callback = ( 885 callback = (
854 lambda __=None: None 886 lambda __=None: None
855 ) # FIXME: optional argument is here because pyjamas doesn't support callback 887 ) # FIXME: optional argument is here because pyjamas doesn't support callback
856 # without arg with json proxy 888 # without arg with json proxy
857 if errback is None: 889 if errback is None:
858 errback = lambda failure: self.show_dialog( 890 errback = lambda failure: self.show_dialog(
859 message=failure.message, title=failure.fullname, type="error" 891 message=failure.message, title=failure.fullname, type="error"
860 ) 892 )
861 893
862 if not self.trigger.point("messageSendTrigger", to_jid, message, subject, mess_type, extra, callback, errback, profile_key=profile_key): 894 if not self.trigger.point(
895 "messageSendTrigger",
896 to_jid,
897 message,
898 subject,
899 mess_type,
900 extra,
901 callback,
902 errback,
903 profile_key=profile_key,
904 ):
863 return 905 return
864 906
865 self.bridge.message_send( 907 self.bridge.message_send(
866 str(to_jid), 908 str(to_jid),
867 message, 909 message,
904 return 946 return
905 947
906 self.call_listeners("presence", entity, show, priority, statuses, profile=profile) 948 self.call_listeners("presence", entity, show, priority, statuses, profile=profile)
907 949
908 def muc_room_joined_handler( 950 def muc_room_joined_handler(
909 self, room_jid_s, occupants, user_nick, subject, statuses, profile): 951 self, room_jid_s, occupants, user_nick, subject, statuses, profile
952 ):
910 """Called when a MUC room is joined""" 953 """Called when a MUC room is joined"""
911 log.debug( 954 log.debug(
912 "Room [{room_jid}] joined by {profile}, users presents:{users}".format( 955 "Room [{room_jid}] joined by {profile}, users presents:{users}".format(
913 room_jid=room_jid_s, profile=profile, users=list(occupants.keys()) 956 room_jid=room_jid_s, profile=profile, users=list(occupants.keys())
914 ) 957 )
934 ) 977 )
935 room_jid = jid.JID(room_jid_s) 978 room_jid = jid.JID(room_jid_s)
936 chat_widget = self.widgets.get_widget(quick_chat.QuickChat, room_jid, profile) 979 chat_widget = self.widgets.get_widget(quick_chat.QuickChat, room_jid, profile)
937 if chat_widget: 980 if chat_widget:
938 self.widgets.delete_widget( 981 self.widgets.delete_widget(
939 chat_widget, all_instances=True, explicit_close=True) 982 chat_widget, all_instances=True, explicit_close=True
983 )
940 self.contact_lists[profile].remove_contact(room_jid) 984 self.contact_lists[profile].remove_contact(room_jid)
941 985
942 def muc_room_user_changed_nick_handler(self, room_jid_s, old_nick, new_nick, profile): 986 def muc_room_user_changed_nick_handler(self, room_jid_s, old_nick, new_nick, profile):
943 """Called when an user joined a MUC room""" 987 """Called when an user joined a MUC room"""
944 room_jid = jid.JID(room_jid_s) 988 room_jid = jid.JID(room_jid_s)
969 @param from_jid_s (unicode): JID of a contact or C.ENTITY_ALL 1013 @param from_jid_s (unicode): JID of a contact or C.ENTITY_ALL
970 @param state (unicode): new state 1014 @param state (unicode): new state
971 @param profile (unicode): current profile 1015 @param profile (unicode): current profile
972 """ 1016 """
973 from_jid = jid.JID(from_jid_s) 1017 from_jid = jid.JID(from_jid_s)
974 for widget in self.widgets.get_widgets(quick_chat.QuickChat, target=from_jid.bare, 1018 for widget in self.widgets.get_widgets(
975 profiles=(profile,)): 1019 quick_chat.QuickChat, target=from_jid.bare, profiles=(profile,)
1020 ):
976 widget.on_chat_state(from_jid, state, profile) 1021 widget.on_chat_state(from_jid, state, profile)
977 1022
978 def notify(self, type_, entity=None, message=None, subject=None, callback=None, 1023 def notify(
979 cb_args=None, widget=None, profile=C.PROF_KEY_NONE): 1024 self,
1025 type_,
1026 entity=None,
1027 message=None,
1028 subject=None,
1029 callback=None,
1030 cb_args=None,
1031 widget=None,
1032 profile=C.PROF_KEY_NONE,
1033 ):
980 """Trigger an event notification 1034 """Trigger an event notification
981 1035
982 @param type_(unicode): notifation kind, 1036 @param type_(unicode): notifation kind,
983 one of C.NOTIFY_* constant or any custom type specific to frontend 1037 one of C.NOTIFY_* constant or any custom type specific to frontend
984 @param entity(jid.JID, None): entity involved in the notification 1038 @param entity(jid.JID, None): entity involved in the notification
1007 type_notifs.append(notif_data) 1061 type_notifs.append(notif_data)
1008 self._notifications[self._notif_id] = notif_data 1062 self._notifications[self._notif_id] = notif_data
1009 self._notif_id += 1 1063 self._notif_id += 1
1010 self.call_listeners("notification", entity, notif_data, profile=profile) 1064 self.call_listeners("notification", entity, notif_data, profile=profile)
1011 1065
1012 def get_notifs(self, entity=None, type_=None, exact_jid=None, profile=C.PROF_KEY_NONE): 1066 def get_notifs(
1067 self, entity=None, type_=None, exact_jid=None, profile=C.PROF_KEY_NONE
1068 ):
1013 """return notifications for given entity 1069 """return notifications for given entity
1014 1070
1015 @param entity(jid.JID, None, C.ENTITY_ALL): jid of the entity to check 1071 @param entity(jid.JID, None, C.ENTITY_ALL): jid of the entity to check
1016 bare jid to get all notifications, full jid to filter on resource 1072 bare jid to get all notifications, full jid to filter on resource
1017 None to get general notifications 1073 None to get general notifications
1201 ) 1257 )
1202 1258
1203 def _debug_handler(self, action, parameters, profile): 1259 def _debug_handler(self, action, parameters, profile):
1204 if action == "widgets_dump": 1260 if action == "widgets_dump":
1205 from pprint import pformat 1261 from pprint import pformat
1262
1206 log.info("Widgets dump:\n{data}".format(data=pformat(self.widgets._widgets))) 1263 log.info("Widgets dump:\n{data}".format(data=pformat(self.widgets._widgets)))
1207 else: 1264 else:
1208 log.warning("Unknown debug action: {action}".format(action=action)) 1265 log.warning("Unknown debug action: {action}".format(action=action))
1209
1210 1266
1211 def show_dialog(self, message, title, type="info", answer_cb=None, answer_data=None): 1267 def show_dialog(self, message, title, type="info", answer_cb=None, answer_data=None):
1212 """Show a dialog to user 1268 """Show a dialog to user
1213 1269
1214 Frontends must override this method 1270 Frontends must override this method
1274 assert isinstance(value, dict) or value is None 1330 assert isinstance(value, dict) or value is None
1275 self.contact_lists[profile].set_cache(entity, "avatar", value) 1331 self.contact_lists[profile].set_cache(entity, "avatar", value)
1276 self.call_listeners("avatar", entity, value, profile=profile) 1332 self.call_listeners("avatar", entity, value, profile=profile)
1277 1333
1278 def register_action_handler( 1334 def register_action_handler(
1279 self, 1335 self, action_type: str, handler: Callable[[dict, str, int, str], None]
1280 action_type: str,
1281 handler: Callable[[dict, str, int, str], None]
1282 ) -> None: 1336 ) -> None:
1283 """Register a handler for action type. 1337 """Register a handler for action type.
1284 1338
1285 If an action of this type is received, the handler will be used, otherwise, 1339 If an action of this type is received, the handler will be used, otherwise,
1286 generic ``action_manager`` is used 1340 generic ``action_manager`` is used
1305 self._action_handlers[action_type] = handler 1359 self._action_handlers[action_type] = handler
1306 1360
1307 def action_manager( 1361 def action_manager(
1308 self, 1362 self,
1309 action_data: dict, 1363 action_data: dict,
1310 callback: Callable|None = None, 1364 callback: Callable | None = None,
1311 ui_show_cb: Callable|None = None, 1365 ui_show_cb: Callable | None = None,
1312 user_action: bool = True, 1366 user_action: bool = True,
1313 action_id: str|None = None, 1367 action_id: str | None = None,
1314 progress_cb: Callable|None = None, 1368 progress_cb: Callable | None = None,
1315 progress_eb: Callable|None = None, 1369 progress_eb: Callable | None = None,
1316 profile: str = C.PROF_KEY_NONE 1370 profile: str = C.PROF_KEY_NONE,
1317 ) -> None: 1371 ) -> None:
1318 """Handle backend action 1372 """Handle backend action
1319 1373
1320 @param action_data: action dict as sent by action_launch or returned by an 1374 @param action_data: action dict as sent by action_launch or returned by an
1321 UI action 1375 UI action
1384 data = dict() 1438 data = dict()
1385 action_cb = lambda data: self._action_cb( 1439 action_cb = lambda data: self._action_cb(
1386 data_format.deserialise(data), callback, callback_id, profile 1440 data_format.deserialise(data), callback, callback_id, profile
1387 ) 1441 )
1388 self.bridge.action_launch( 1442 self.bridge.action_launch(
1389 callback_id, data_format.serialise(data), profile, callback=action_cb, 1443 callback_id,
1390 errback=self.dialog_failure 1444 data_format.serialise(data),
1445 profile,
1446 callback=action_cb,
1447 errback=self.dialog_failure,
1391 ) 1448 )
1392 1449
1393 def launch_menu( 1450 def launch_menu(
1394 self, 1451 self,
1395 menu_type, 1452 menu_type,