Mercurial > libervia-backend
comparison sat_frontends/quick_frontend/quick_app.py @ 2664:e35a265ec174
quick frontend (app, chat): encryption handling:
- new QuickApp.ENCRYPTION_HANDLERS class attribute, if True (default), encryption handlers are set
- encryption plugins are retrieved on startup and cached in QuickApp.encryption_plugins list
- QuickChat's encrypted boolean attribute indicate if a session is currently encrypted. This is updated
automatically if ENCRYPTION_HANDLERS is set
- if ENCRYPTION_HANDLERS is set, messageEncryptionStarted and messageEncryptionStopped are called when
suitable.
author | Goffi <goffi@goffi.org> |
---|---|
date | Sat, 11 Aug 2018 18:24:55 +0200 |
parents | 6ef2b4fa90a5 |
children | bdb8276fd2da |
comparison
equal
deleted
inserted
replaced
2663:32b5f68a23b4 | 2664:e35a265ec174 |
---|---|
140 self._plug_profile_getFeaturesCb({}) | 140 self._plug_profile_getFeaturesCb({}) |
141 | 141 |
142 def _plug_profile_getFeaturesCb(self, features): | 142 def _plug_profile_getFeaturesCb(self, features): |
143 self.host.features = features | 143 self.host.features = features |
144 # FIXME: we don't use cached value at the moment, but keep the code for later use | 144 # FIXME: we don't use cached value at the moment, but keep the code for later use |
145 # it was previously used for avatars, but as we don't get full path here, it's better to request later | 145 # it was previously used for avatars, but as we don't get full path here, |
146 # self.host.bridge.getEntitiesData([], ProfileManager.cache_keys_to_get, profile=self.profile, callback=self._plug_profile_gotCachedValues, errback=self._plug_profile_failedCachedValues) | 146 # it's better to request later |
147 # self.host.bridge.getEntitiesData([], ProfileManager.cache_keys_to_get, | |
148 # profile=self.profile, | |
149 # callback=self._plug_profile_gotCachedValues, | |
150 # errback=self._plug_profile_failedCachedValues) | |
147 self._plug_profile_gotCachedValues({}) | 151 self._plug_profile_gotCachedValues({}) |
148 | 152 |
149 def _plug_profile_failedCachedValues(self, failure): | 153 def _plug_profile_failedCachedValues(self, failure): |
150 log.error(u"Couldn't get cached values: {}".format(failure)) | 154 log.error(u"Couldn't get cached values: {}".format(failure)) |
151 self._plug_profile_gotCachedValues({}) | 155 self._plug_profile_gotCachedValues({}) |
271 class QuickApp(object): | 275 class QuickApp(object): |
272 """This class contain the main methods needed for the frontend""" | 276 """This class contain the main methods needed for the frontend""" |
273 | 277 |
274 MB_HANDLER = True # Set to False if the frontend doesn't manage microblog | 278 MB_HANDLER = True # Set to False if the frontend doesn't manage microblog |
275 AVATARS_HANDLER = True # set to False if avatars are not used | 279 AVATARS_HANDLER = True # set to False if avatars are not used |
280 ENCRYPTION_HANDLERS = True # set to False if encryption is handled separatly | |
276 | 281 |
277 def __init__(self, bridge_factory, xmlui, check_options=None, connect_bridge=True): | 282 def __init__(self, bridge_factory, xmlui, check_options=None, connect_bridge=True): |
278 """Create a frontend application | 283 """Create a frontend application |
279 | 284 |
280 @param bridge_factory: method to use to create the Bridge | 285 @param bridge_factory: method to use to create the Bridge |
281 @param xmlui: xmlui module | 286 @param xmlui: xmlui module |
282 @param check_options: method to call to check options (usually command line arguments) | 287 @param check_options: method to call to check options (usually command line |
288 arguments) | |
283 """ | 289 """ |
284 self.xmlui = xmlui | 290 self.xmlui = xmlui |
285 self.menus = quick_menus.QuickMenusManager(self) | 291 self.menus = quick_menus.QuickMenusManager(self) |
286 ProfileManager.host = self | 292 ProfileManager.host = self |
287 self.profiles = ProfilesManager() | 293 self.profiles = ProfilesManager() |
288 self._plugs_in_progress = ( | 294 self._plugs_in_progress = ( |
289 set() | 295 set() |
290 ) # profiles currently being plugged, used to (un)lock contact list updates | 296 ) # profiles currently being plugged, used to (un)lock contact list updates |
291 self.ready_profiles = set() # profiles which are connected and ready | 297 self.ready_profiles = set() # profiles which are connected and ready |
292 self.signals_cache = {} # used to keep signal received between start of plug_profile and when the profile is actualy ready | 298 self.signals_cache = {} # used to keep signal received between start of |
299 # plug_profile and when the profile is actualy ready | |
293 self.contact_lists = quick_contact_list.QuickContactListHandler(self) | 300 self.contact_lists = quick_contact_list.QuickContactListHandler(self) |
294 self.widgets = quick_widgets.QuickWidgetsManager(self) | 301 self.widgets = quick_widgets.QuickWidgetsManager(self) |
295 if check_options is not None: | 302 if check_options is not None: |
296 self.options = check_options() | 303 self.options = check_options() |
297 else: | 304 else: |
301 self.selected_widget = ( | 308 self.selected_widget = ( |
302 None | 309 None |
303 ) # widget currently selected (must be filled by frontend) | 310 ) # widget currently selected (must be filled by frontend) |
304 | 311 |
305 # listeners | 312 # listeners |
306 self._listeners = {} # key: listener type ("avatar", "selected", etc), value: list of callbacks | 313 self._listeners = {} # key: listener type ("avatar", "selected", etc), |
314 # value: list of callbacks | |
307 | 315 |
308 # triggers | 316 # triggers |
309 self.trigger = ( | 317 self.trigger = ( |
310 trigger.TriggerManager() | 318 trigger.TriggerManager() |
311 ) # trigger are used to change the default behaviour | 319 ) # trigger are used to change the default behaviour |
318 | 326 |
319 self._notif_id = 0 | 327 self._notif_id = 0 |
320 self._notifications = OrderedDict() | 328 self._notifications = OrderedDict() |
321 self.features = None | 329 self.features = None |
322 self.ns_map = {} # map of short name to namespaces | 330 self.ns_map = {} # map of short name to namespaces |
331 self.encryption_plugins = [] | |
323 | 332 |
324 def connectBridge(self): | 333 def connectBridge(self): |
325 self.bridge.bridgeConnect(callback=self._bridgeCb, errback=self._bridgeEb) | 334 self.bridge.bridgeConnect(callback=self._bridgeCb, errback=self._bridgeEb) |
326 | 335 |
327 def _namespacesGetCb(self, ns_map): | 336 def _namespacesGetCb(self, ns_map): |
328 self.ns_map = ns_map | 337 self.ns_map = ns_map |
329 | 338 |
330 def _namespacesGetEb(self, failure_): | 339 def _namespacesGetEb(self, failure_): |
331 log.error(_(u"Can't get namespaces map: {msg}").format(msg=failure_)) | 340 log.error(_(u"Can't get namespaces map: {msg}").format(msg=failure_)) |
332 | 341 |
342 def _encryptionPluginsGetCb(self, plugins): | |
343 self.encryption_plugins = plugins | |
344 | |
345 def _encryptionPluginsGetEb(self, failure_): | |
346 log.warning(_(u"Can't retrieve encryption plugins: {msg}").format(msg=failure_)) | |
347 | |
333 def onBridgeConnected(self): | 348 def onBridgeConnected(self): |
334 self.bridge.namespacesGet( | 349 self.bridge.namespacesGet( |
335 callback=self._namespacesGetCb, errback=self._namespacesGetEb | 350 callback=self._namespacesGetCb, errback=self._namespacesGetEb) |
336 ) | 351 # we cache available encryption plugins, as we'll use them on earch |
352 # new chat widget | |
353 self.bridge.encryptionPluginsGet( | |
354 callback=self._encryptionPluginsGetCb, | |
355 errback=self._encryptionPluginsGetEb) | |
337 | 356 |
338 def _bridgeCb(self): | 357 def _bridgeCb(self): |
339 self.registerSignal("connected") | 358 self.registerSignal("connected") |
340 self.registerSignal("disconnected") | 359 self.registerSignal("disconnected") |
341 self.registerSignal("actionNew") | 360 self.registerSignal("actionNew") |
342 self.registerSignal("newContact") | 361 self.registerSignal("newContact") |
343 self.registerSignal("messageNew") | 362 self.registerSignal("messageNew") |
363 if self.ENCRYPTION_HANDLERS: | |
364 self.registerSignal("messageEncryptionStarted") | |
365 self.registerSignal("messageEncryptionStopped") | |
344 self.registerSignal("presenceUpdate") | 366 self.registerSignal("presenceUpdate") |
345 self.registerSignal("subscribe") | 367 self.registerSignal("subscribe") |
346 self.registerSignal("paramUpdate") | 368 self.registerSignal("paramUpdate") |
347 self.registerSignal("contactDeleted") | 369 self.registerSignal("contactDeleted") |
348 self.registerSignal("entityDataUpdated") | 370 self.registerSignal("entityDataUpdated") |
393 self, function_name, handler=None, iface="core", with_profile=True | 415 self, function_name, handler=None, iface="core", with_profile=True |
394 ): | 416 ): |
395 """Register a handler for a signal | 417 """Register a handler for a signal |
396 | 418 |
397 @param function_name (str): name of the signal to handle | 419 @param function_name (str): name of the signal to handle |
398 @param handler (instancemethod): method to call when the signal arrive, None for calling an automatically named handler (function_name + 'Handler') | 420 @param handler (instancemethod): method to call when the signal arrive, |
421 None for calling an automatically named handler (function_name + 'Handler') | |
399 @param iface (str): interface of the bridge to use ('core' or 'plugin') | 422 @param iface (str): interface of the bridge to use ('core' or 'plugin') |
400 @param with_profile (boolean): True if the signal concerns a specific profile, in that case the profile name has to be passed by the caller | 423 @param with_profile (boolean): True if the signal concerns a specific profile, |
424 in that case the profile name has to be passed by the caller | |
401 """ | 425 """ |
402 log.debug(u"registering signal {name}".format(name=function_name)) | 426 log.debug(u"registering signal {name}".format(name=function_name)) |
403 if handler is None: | 427 if handler is None: |
404 handler = getattr(self, "{}{}".format(function_name, "Handler")) | 428 handler = getattr(self, "{}{}".format(function_name, "Handler")) |
405 if not with_profile: | 429 if not with_profile: |
413 raise exceptions.ProfileNotSetError | 437 raise exceptions.ProfileNotSetError |
414 profile = args[-1] | 438 profile = args[-1] |
415 if profile is not None: | 439 if profile is not None: |
416 if not self.check_profile(profile): | 440 if not self.check_profile(profile): |
417 if profile in self.profiles: | 441 if profile in self.profiles: |
418 # profile is not ready but is in self.profiles, that's mean that it's being connecting and we need to cache the signal | 442 # profile is not ready but is in self.profiles, that's mean that |
443 # it's being connecting and we need to cache the signal | |
419 self.signals_cache.setdefault(profile, []).append( | 444 self.signals_cache.setdefault(profile, []).append( |
420 (function_name, handler, args, kwargs) | 445 (function_name, handler, args, kwargs) |
421 ) | 446 ) |
422 return # we ignore signal for profiles we don't manage | 447 return # we ignore signal for profiles we don't manage |
423 handler(*args, **kwargs) | 448 handler(*args, **kwargs) |
425 self.bridge.register_signal(function_name, signalReceived, iface) | 450 self.bridge.register_signal(function_name, signalReceived, iface) |
426 | 451 |
427 def addListener(self, type_, callback, profiles_filter=None): | 452 def addListener(self, type_, callback, profiles_filter=None): |
428 """Add a listener for an event | 453 """Add a listener for an event |
429 | 454 |
430 /!\ don't forget to remove listener when not used anymore (e.g. if you delete a widget) | 455 /!\ don't forget to remove listener when not used anymore (e.g. if you delete a |
456 widget) | |
431 @param type_: type of event, can be: | 457 @param type_: type of event, can be: |
432 - avatar: called when avatar data is updated | 458 - avatar: called when avatar data is updated |
433 args: (entity, avatar file, profile) | 459 args: (entity, avatar file, profile) |
434 - nick: called when nick data is updated | 460 - nick: called when nick data is updated |
435 args: (entity, new_nick, profile) | 461 args: (entity, new_nick, profile) |
442 - menu: called when a menu item is added or removed | 468 - menu: called when a menu item is added or removed |
443 args: (type_, path, path_i18n, item) were values are: | 469 args: (type_, path, path_i18n, item) were values are: |
444 type_: same as in [sat.core.sat_main.SAT.importMenu] | 470 type_: same as in [sat.core.sat_main.SAT.importMenu] |
445 path: same as in [sat.core.sat_main.SAT.importMenu] | 471 path: same as in [sat.core.sat_main.SAT.importMenu] |
446 path_i18n: translated path (or None if the item is removed) | 472 path_i18n: translated path (or None if the item is removed) |
447 item: instance of quick_menus.MenuItemBase or None if the item is removed | 473 item: instance of quick_menus.MenuItemBase or None if the item is |
474 removed | |
448 - gotMenus: called only once when menu are available (no arg) | 475 - gotMenus: called only once when menu are available (no arg) |
449 - progressFinished: called when a progressing action has just finished | 476 - progressFinished: called when a progressing action has just finished |
450 args: (progress_id, metadata, profile) | 477 args: (progress_id, metadata, profile) |
451 - progressError: called when a progressing action failed | 478 - progressError: called when a progressing action failed |
452 args: (progress_id, error_msg, profile): | 479 args: (progress_id, error_msg, profile): |
489 def check_profile(self, profile): | 516 def check_profile(self, profile): |
490 """Tell if the profile is currently followed by the application, and ready""" | 517 """Tell if the profile is currently followed by the application, and ready""" |
491 return profile in self.ready_profiles | 518 return profile in self.ready_profiles |
492 | 519 |
493 def postInit(self, profile_manager): | 520 def postInit(self, profile_manager): |
494 """Must be called after initialization is done, do all automatic task (auto plug profile) | 521 """Must be called after initialization is done, do all automatic task |
495 | 522 |
496 @param profile_manager: instance of a subclass of Quick_frontend.QuickProfileManager | 523 (auto plug profile) |
524 @param profile_manager: instance of a subclass of | |
525 Quick_frontend.QuickProfileManager | |
497 """ | 526 """ |
498 if self.options and self.options.profile: | 527 if self.options and self.options.profile: |
499 profile_manager.autoconnect([self.options.profile]) | 528 profile_manager.autoconnect([self.options.profile]) |
500 | 529 |
501 def profilePlugged(self, profile): | 530 def profilePlugged(self, profile): |
502 """Method called when the profile is fully plugged, to launch frontend specific workflow | 531 """Method called when the profile is fully plugged |
503 | 532 |
504 /!\ if you override the method and don't call the parent, be sure to add the profile to ready_profiles ! | 533 This will launch frontend specific workflow |
505 if you don't, all signals will stay in cache | 534 |
535 /!\ if you override the method and don't call the parent, be sure to add the | |
536 profile to ready_profiles ! if you don't, all signals will stay in cache | |
506 | 537 |
507 @param profile(unicode): %(doc_profile)s | 538 @param profile(unicode): %(doc_profile)s |
508 """ | 539 """ |
509 self._plugs_in_progress.remove(profile) | 540 self._plugs_in_progress.remove(profile) |
510 self.ready_profiles.add(profile) | 541 self.ready_profiles.add(profile) |
606 def newContactHandler(self, jid_s, attributes, groups, profile): | 637 def newContactHandler(self, jid_s, attributes, groups, profile): |
607 entity = jid.JID(jid_s) | 638 entity = jid.JID(jid_s) |
608 groups = list(groups) | 639 groups = list(groups) |
609 self.contact_lists[profile].setContact(entity, groups, attributes, in_roster=True) | 640 self.contact_lists[profile].setContact(entity, groups, attributes, in_roster=True) |
610 | 641 |
611 def messageNewHandler( | 642 def messageNewHandler(self, uid, timestamp, from_jid_s, to_jid_s, msg, subject, type_, |
612 self, uid, timestamp, from_jid_s, to_jid_s, msg, subject, type_, extra, profile | 643 extra, profile): |
613 ): | |
614 from_jid = jid.JID(from_jid_s) | 644 from_jid = jid.JID(from_jid_s) |
615 to_jid = jid.JID(to_jid_s) | 645 to_jid = jid.JID(to_jid_s) |
616 if not self.trigger.point( | 646 if not self.trigger.point( |
617 "messageNewTrigger", | 647 "messageNewTrigger", |
618 uid, | 648 uid, |
629 | 659 |
630 from_me = from_jid.bare == self.profiles[profile].whoami.bare | 660 from_me = from_jid.bare == self.profiles[profile].whoami.bare |
631 target = to_jid if from_me else from_jid | 661 target = to_jid if from_me else from_jid |
632 contact_list = self.contact_lists[profile] | 662 contact_list = self.contact_lists[profile] |
633 if target.resource and not contact_list.isRoom(target.bare): | 663 if target.resource and not contact_list.isRoom(target.bare): |
634 # we avoid resource locking, but we must keep resource for private MUC messages | 664 # we avoid resource locking, but we must keep resource for private MUC |
665 # messages | |
635 target = target.bare | 666 target = target.bare |
636 # we want to be sure to have at least one QuickChat instance | 667 # we want to be sure to have at least one QuickChat instance |
637 self.widgets.getOrCreateWidget( | 668 self.widgets.getOrCreateWidget( |
638 quick_chat.QuickChat, | 669 quick_chat.QuickChat, |
639 target, | 670 target, |
655 quick_chat.QuickChat, target=target, profiles=(profile,) | 686 quick_chat.QuickChat, target=target, profiles=(profile,) |
656 ): | 687 ): |
657 widget.messageNew( | 688 widget.messageNew( |
658 uid, timestamp, from_jid, target, msg, subject, type_, extra, profile | 689 uid, timestamp, from_jid, target, msg, subject, type_, extra, profile |
659 ) | 690 ) |
691 | |
692 def messageEncryptionStartedHandler(self, destinee_jid_s, plugin_data, profile): | |
693 destinee_jid = jid.JID(destinee_jid_s) | |
694 plugin_data = data_format.deserialise(plugin_data) | |
695 for widget in self.widgets.getWidgets(quick_chat.QuickChat, | |
696 target=destinee_jid.bare, | |
697 profiles=(profile,)): | |
698 widget.messageEncryptionStarted(plugin_data) | |
699 | |
700 def messageEncryptionStoppedHandler(self, destinee_jid_s, plugin_data, profile): | |
701 destinee_jid = jid.JID(destinee_jid_s) | |
702 for widget in self.widgets.getWidgets(quick_chat.QuickChat, | |
703 target=destinee_jid.bare, | |
704 profiles=(profile,)): | |
705 widget.messageEncryptionStopped(plugin_data) | |
660 | 706 |
661 def messageStateHandler(self, uid, status, profile): | 707 def messageStateHandler(self, uid, status, profile): |
662 for widget in self.widgets.getWidgets(quick_chat.QuickChat, profiles=(profile,)): | 708 for widget in self.widgets.getWidgets(quick_chat.QuickChat, profiles=(profile,)): |
663 widget.onMessageState(uid, status, profile) | 709 widget.onMessageState(uid, status, profile) |
664 | 710 |
678 if extra is None: | 724 if extra is None: |
679 extra = {} | 725 extra = {} |
680 if callback is None: | 726 if callback is None: |
681 callback = ( | 727 callback = ( |
682 lambda dummy=None: None | 728 lambda dummy=None: None |
683 ) # FIXME: optional argument is here because pyjamas doesn't support callback without arg with json proxy | 729 ) # FIXME: optional argument is here because pyjamas doesn't support callback |
730 # without arg with json proxy | |
684 if errback is None: | 731 if errback is None: |
685 errback = lambda failure: self.showDialog( | 732 errback = lambda failure: self.showDialog( |
686 failure.fullname, failure.message, "error" | 733 failure.fullname, failure.message, "error" |
687 ) | 734 ) |
688 | 735 |
714 raise NotImplementedError | 761 raise NotImplementedError |
715 | 762 |
716 def presenceUpdateHandler(self, entity_s, show, priority, statuses, profile): | 763 def presenceUpdateHandler(self, entity_s, show, priority, statuses, profile): |
717 log.debug( | 764 log.debug( |
718 _( | 765 _( |
719 u"presence update for %(entity)s (show=%(show)s, priority=%(priority)s, statuses=%(statuses)s) [profile:%(profile)s]" | 766 u"presence update for %(entity)s (show=%(show)s, priority=%(priority)s, " |
767 u"statuses=%(statuses)s) [profile:%(profile)s]" | |
720 ) | 768 ) |
721 % { | 769 % { |
722 "entity": entity_s, | 770 "entity": entity_s, |
723 C.PRESENCE_SHOW: show, | 771 C.PRESENCE_SHOW: show, |
724 C.PRESENCE_PRIORITY: priority, | 772 C.PRESENCE_PRIORITY: priority, |
1081 user_action=True, | 1129 user_action=True, |
1082 profile=C.PROF_KEY_NONE, | 1130 profile=C.PROF_KEY_NONE, |
1083 ): | 1131 ): |
1084 """Handle backend action | 1132 """Handle backend action |
1085 | 1133 |
1086 @param action_data(dict): action dict as sent by launchAction or returned by an UI action | 1134 @param action_data(dict): action dict as sent by launchAction or returned by an |
1135 UI action | |
1087 @param callback(None, callback): if not None, callback to use on XMLUI answer | 1136 @param callback(None, callback): if not None, callback to use on XMLUI answer |
1088 @param ui_show_cb(None, callback): if not None, method to call to show the XMLUI | 1137 @param ui_show_cb(None, callback): if not None, method to call to show the XMLUI |
1089 @param user_action(bool): if True, the action is a result of a user interaction | 1138 @param user_action(bool): if True, the action is a result of a user interaction |
1090 else the action come from backend direclty (i.e. actionNew) | 1139 else the action come from backend direclty (i.e. actionNew) |
1091 """ | 1140 """ |
1214 @param entity(jid.JID): entity to get avatar from | 1263 @param entity(jid.JID): entity to get avatar from |
1215 @param cache_only(bool): if False avatar will be requested if not in cache | 1264 @param cache_only(bool): if False avatar will be requested if not in cache |
1216 with current vCard based implementation, it's better to keep True | 1265 with current vCard based implementation, it's better to keep True |
1217 except if we request avatars for roster items | 1266 except if we request avatars for roster items |
1218 @param hash_only(bool): if True avatar hash is returned, else full path | 1267 @param hash_only(bool): if True avatar hash is returned, else full path |
1219 @param ignore_cache(bool): if False, won't check local cache and will request backend in every case | 1268 @param ignore_cache(bool): if False, won't check local cache and will request |
1269 backend in every case | |
1220 @return (unicode, None): avatar full path (None if no avatar found) | 1270 @return (unicode, None): avatar full path (None if no avatar found) |
1221 """ | 1271 """ |
1222 contact_list = self.contact_lists[profile] | 1272 contact_list = self.contact_lists[profile] |
1223 if ignore_cache: | 1273 if ignore_cache: |
1224 avatar = None | 1274 avatar = None |
1236 callback=lambda path: self._avatarGetCb( | 1286 callback=lambda path: self._avatarGetCb( |
1237 path, entity, contact_list, profile | 1287 path, entity, contact_list, profile |
1238 ), | 1288 ), |
1239 errback=lambda failure: self._avatarGetEb(failure, entity, contact_list), | 1289 errback=lambda failure: self._avatarGetEb(failure, entity, contact_list), |
1240 ) | 1290 ) |
1241 # we set avatar to empty string to avoid requesting several time the same avatar | 1291 # we set avatar to empty string to avoid requesting several time the same |
1242 # while we are waiting for avatarGet result | 1292 # avatar while we are waiting for avatarGet result |
1243 contact_list.setCache(entity, "avatar", "") | 1293 contact_list.setCache(entity, "avatar", "") |
1244 return avatar | 1294 return avatar |
1245 | 1295 |
1246 def getDefaultAvatar(self, entity=None): | 1296 def getDefaultAvatar(self, entity=None): |
1247 """return default avatar to use with given entity | 1297 """return default avatar to use with given entity |