comparison sat/core/sat_main.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 6959c71ab8bf
children fee60f17ebac
comparison
equal deleted inserted replaced
3027:ff5bcb12ae60 3028:ab2696e34d29
1 #!/usr/bin/env python2 1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*- 2 # -*- coding: utf-8 -*-
3 3
4 # SAT: a jabber client 4 # SAT: a jabber client
5 # Copyright (C) 2009-2019 Jérôme Poisson (goffi@goffi.org) 5 # Copyright (C) 2009-2019 Jérôme Poisson (goffi@goffi.org)
6 6
64 self.initialised = defer.Deferred() 64 self.initialised = defer.Deferred()
65 self.profiles = {} 65 self.profiles = {}
66 self.plugins = {} 66 self.plugins = {}
67 # map for short name to whole namespace, 67 # map for short name to whole namespace,
68 self.ns_map = { 68 self.ns_map = {
69 u"x-data": xmpp.NS_X_DATA, 69 "x-data": xmpp.NS_X_DATA,
70 u"disco#info": xmpp.NS_DISCO_INFO, 70 "disco#info": xmpp.NS_DISCO_INFO,
71 } 71 }
72 # extended by plugins with registerNamespace 72 # extended by plugins with registerNamespace
73 self.memory = memory.Memory(self) 73 self.memory = memory.Memory(self)
74 self.trigger = ( 74 self.trigger = (
75 trigger.TriggerManager() 75 trigger.TriggerManager()
77 77
78 bridge_name = self.memory.getConfig("", "bridge", "dbus") 78 bridge_name = self.memory.getConfig("", "bridge", "dbus")
79 79
80 bridge_module = dynamic_import.bridge(bridge_name) 80 bridge_module = dynamic_import.bridge(bridge_name)
81 if bridge_module is None: 81 if bridge_module is None:
82 log.error(u"Can't find bridge module of name {}".format(bridge_name)) 82 log.error("Can't find bridge module of name {}".format(bridge_name))
83 sys.exit(1) 83 sys.exit(1)
84 log.info(u"using {} bridge".format(bridge_name)) 84 log.info("using {} bridge".format(bridge_name))
85 try: 85 try:
86 self.bridge = bridge_module.Bridge() 86 self.bridge = bridge_module.Bridge()
87 except exceptions.BridgeInitError: 87 except exceptions.BridgeInitError:
88 log.error(u"Bridge can't be initialised, can't start SàT core") 88 log.error("Bridge can't be initialised, can't start SàT core")
89 sys.exit(1) 89 sys.exit(1)
90 self.bridge.register_method("getReady", lambda: self.initialised) 90 self.bridge.register_method("getReady", lambda: self.initialised)
91 self.bridge.register_method("getVersion", lambda: self.full_version) 91 self.bridge.register_method("getVersion", lambda: self.full_version)
92 self.bridge.register_method("getFeatures", self.getFeatures) 92 self.bridge.register_method("getFeatures", self.getFeatures)
93 self.bridge.register_method("profileNameGet", self.memory.getProfileName) 93 self.bridge.register_method("profileNameGet", self.memory.getProfileName)
179 if version[-1] == "D": 179 if version[-1] == "D":
180 # we are in debug version, we add extra data 180 # we are in debug version, we add extra data
181 try: 181 try:
182 return self._version_cache 182 return self._version_cache
183 except AttributeError: 183 except AttributeError:
184 self._version_cache = u"{} « {} » ({})".format( 184 self._version_cache = "{} « {} » ({})".format(
185 version, C.APP_RELEASE_NAME, utils.getRepositoryData(sat) 185 version, C.APP_RELEASE_NAME, utils.getRepositoryData(sat)
186 ) 186 )
187 return self._version_cache 187 return self._version_cache
188 else: 188 else:
189 return version 189 return version
200 self._import_plugins() 200 self._import_plugins()
201 ui_contact_list.ContactList(self) 201 ui_contact_list.ContactList(self)
202 ui_profile_manager.ProfileManager(self) 202 ui_profile_manager.ProfileManager(self)
203 except Exception as e: 203 except Exception as e:
204 log.error( 204 log.error(
205 _(u"Could not initialize backend: {reason}").format( 205 _("Could not initialize backend: {reason}").format(
206 reason=str(e).decode("utf-8", "ignore") 206 reason=str(e).decode("utf-8", "ignore")
207 ) 207 )
208 ) 208 )
209 sys.exit(1) 209 sys.exit(1)
210 self._addBaseMenus() 210 self._addBaseMenus()
211 self.initialised.callback(None) 211 self.initialised.callback(None)
212 log.info(_(u"Backend is ready")) 212 log.info(_("Backend is ready"))
213 213
214 def _addBaseMenus(self): 214 def _addBaseMenus(self):
215 """Add base menus""" 215 """Add base menus"""
216 encryption.EncryptionHandler._importMenus(self) 216 encryption.EncryptionHandler._importMenus(self)
217 217
244 try: 244 try:
245 __import__(plugin_path) 245 __import__(plugin_path)
246 except exceptions.MissingModule as e: 246 except exceptions.MissingModule as e:
247 self._unimport_plugin(plugin_path) 247 self._unimport_plugin(plugin_path)
248 log.warning( 248 log.warning(
249 u"Can't import plugin [{path}] because of an unavailale third party " 249 "Can't import plugin [{path}] because of an unavailale third party "
250 u"module:\n{msg}".format( 250 "module:\n{msg}".format(
251 path=plugin_path, msg=e 251 path=plugin_path, msg=e
252 ) 252 )
253 ) 253 )
254 continue 254 continue
255 except exceptions.CancelError as e: 255 except exceptions.CancelError as e:
256 log.info( 256 log.info(
257 u"Plugin [{path}] cancelled its own import: {msg}".format( 257 "Plugin [{path}] cancelled its own import: {msg}".format(
258 path=plugin_path, msg=e 258 path=plugin_path, msg=e
259 ) 259 )
260 ) 260 )
261 self._unimport_plugin(plugin_path) 261 self._unimport_plugin(plugin_path)
262 continue 262 continue
263 except Exception as e: 263 except Exception as e:
264 import traceback 264 import traceback
265 265
266 log.error( 266 log.error(
267 _(u"Can't import plugin [{path}]:\n{error}").format( 267 _("Can't import plugin [{path}]:\n{error}").format(
268 path=plugin_path, error=traceback.format_exc() 268 path=plugin_path, error=traceback.format_exc()
269 ) 269 )
270 ) 270 )
271 self._unimport_plugin(plugin_path) 271 self._unimport_plugin(plugin_path)
272 continue 272 continue
273 mod = sys.modules[plugin_path] 273 mod = sys.modules[plugin_path]
274 plugin_info = mod.PLUGIN_INFO 274 plugin_info = mod.PLUGIN_INFO
275 import_name = plugin_info["import_name"] 275 import_name = plugin_info["import_name"]
276 276
277 plugin_modes = plugin_info[u"modes"] = set( 277 plugin_modes = plugin_info["modes"] = set(
278 plugin_info.setdefault(u"modes", C.PLUG_MODE_DEFAULT) 278 plugin_info.setdefault("modes", C.PLUG_MODE_DEFAULT)
279 ) 279 )
280 280
281 # if the plugin is an entry point, it must work in component mode 281 # if the plugin is an entry point, it must work in component mode
282 if plugin_info[u"type"] == C.PLUG_TYPE_ENTRY_POINT: 282 if plugin_info["type"] == C.PLUG_TYPE_ENTRY_POINT:
283 # if plugin is an entrypoint, we cache it 283 # if plugin is an entrypoint, we cache it
284 if C.PLUG_MODE_COMPONENT not in plugin_modes: 284 if C.PLUG_MODE_COMPONENT not in plugin_modes:
285 log.error( 285 log.error(
286 _( 286 _(
287 u"{type} type must be used with {mode} mode, ignoring plugin" 287 "{type} type must be used with {mode} mode, ignoring plugin"
288 ).format(type=C.PLUG_TYPE_ENTRY_POINT, mode=C.PLUG_MODE_COMPONENT) 288 ).format(type=C.PLUG_TYPE_ENTRY_POINT, mode=C.PLUG_MODE_COMPONENT)
289 ) 289 )
290 self._unimport_plugin(plugin_path) 290 self._unimport_plugin(plugin_path)
291 continue 291 continue
292 292
293 if import_name in plugins_to_import: 293 if import_name in plugins_to_import:
294 log.error( 294 log.error(
295 _( 295 _(
296 u"Name conflict for import name [{import_name}], can't import " 296 "Name conflict for import name [{import_name}], can't import "
297 u"plugin [{name}]" 297 "plugin [{name}]"
298 ).format(**plugin_info) 298 ).format(**plugin_info)
299 ) 299 )
300 continue 300 continue
301 plugins_to_import[import_name] = (plugin_path, mod, plugin_info) 301 plugins_to_import[import_name] = (plugin_path, mod, plugin_info)
302 while True: 302 while True:
318 PLUGIN_INFO['import_name'] 318 PLUGIN_INFO['import_name']
319 @param optional(bool): if False and plugin is not found, an ImportError exception 319 @param optional(bool): if False and plugin is not found, an ImportError exception
320 is raised 320 is raised
321 """ 321 """
322 if import_name in self.plugins: 322 if import_name in self.plugins:
323 log.debug(u"Plugin {} already imported, passing".format(import_name)) 323 log.debug("Plugin {} already imported, passing".format(import_name))
324 return 324 return
325 if not import_name: 325 if not import_name:
326 import_name, (plugin_path, mod, plugin_info) = plugins_to_import.popitem() 326 import_name, (plugin_path, mod, plugin_info) = plugins_to_import.popitem()
327 else: 327 else:
328 if not import_name in plugins_to_import: 328 if not import_name in plugins_to_import:
329 if optional: 329 if optional:
330 log.warning( 330 log.warning(
331 _(u"Recommended plugin not found: {}").format(import_name) 331 _("Recommended plugin not found: {}").format(import_name)
332 ) 332 )
333 return 333 return
334 msg = u"Dependency not found: {}".format(import_name) 334 msg = "Dependency not found: {}".format(import_name)
335 log.error(msg) 335 log.error(msg)
336 raise ImportError(msg) 336 raise ImportError(msg)
337 plugin_path, mod, plugin_info = plugins_to_import.pop(import_name) 337 plugin_path, mod, plugin_info = plugins_to_import.pop(import_name)
338 dependencies = plugin_info.setdefault("dependencies", []) 338 dependencies = plugin_info.setdefault("dependencies", [])
339 recommendations = plugin_info.setdefault("recommendations", []) 339 recommendations = plugin_info.setdefault("recommendations", [])
340 for to_import in dependencies + recommendations: 340 for to_import in dependencies + recommendations:
341 if to_import not in self.plugins: 341 if to_import not in self.plugins:
342 log.debug( 342 log.debug(
343 u"Recursively import dependency of [%s]: [%s]" 343 "Recursively import dependency of [%s]: [%s]"
344 % (import_name, to_import) 344 % (import_name, to_import)
345 ) 345 )
346 try: 346 try:
347 self._import_plugins_from_dict( 347 self._import_plugins_from_dict(
348 plugins_to_import, to_import, to_import not in dependencies 348 plugins_to_import, to_import, to_import not in dependencies
349 ) 349 )
350 except ImportError as e: 350 except ImportError as e:
351 log.warning( 351 log.warning(
352 _(u"Can't import plugin {name}: {error}").format( 352 _("Can't import plugin {name}: {error}").format(
353 name=plugin_info["name"], error=e 353 name=plugin_info["name"], error=e
354 ) 354 )
355 ) 355 )
356 if optional: 356 if optional:
357 return 357 return
360 # we instanciate the plugin here 360 # we instanciate the plugin here
361 try: 361 try:
362 self.plugins[import_name] = getattr(mod, plugin_info["main"])(self) 362 self.plugins[import_name] = getattr(mod, plugin_info["main"])(self)
363 except Exception as e: 363 except Exception as e:
364 log.warning( 364 log.warning(
365 u'Error while loading plugin "{name}", ignoring it: {error}'.format( 365 'Error while loading plugin "{name}", ignoring it: {error}'.format(
366 name=plugin_info["name"], error=e 366 name=plugin_info["name"], error=e
367 ) 367 )
368 ) 368 )
369 if optional: 369 if optional:
370 return 370 return
371 raise ImportError(u"Error during initiation") 371 raise ImportError("Error during initiation")
372 if C.bool(plugin_info.get(C.PI_HANDLER, C.BOOL_FALSE)): 372 if C.bool(plugin_info.get(C.PI_HANDLER, C.BOOL_FALSE)):
373 self.plugins[import_name].is_handler = True 373 self.plugins[import_name].is_handler = True
374 else: 374 else:
375 self.plugins[import_name].is_handler = False 375 self.plugins[import_name].is_handler = False
376 # we keep metadata as a Class attribute 376 # we keep metadata as a Class attribute
384 """ 384 """
385 # TODO: in the futur, it should be possible to hot unload a plugin 385 # TODO: in the futur, it should be possible to hot unload a plugin
386 # pluging depending on the unloaded one should be unloaded too 386 # pluging depending on the unloaded one should be unloaded too
387 # for now, just a basic call on plugin.unload is done 387 # for now, just a basic call on plugin.unload is done
388 defers_list = [] 388 defers_list = []
389 for plugin in self.plugins.itervalues(): 389 for plugin in self.plugins.values():
390 try: 390 try:
391 unload = plugin.unload 391 unload = plugin.unload
392 except AttributeError: 392 except AttributeError:
393 continue 393 continue
394 else: 394 else:
417 if options is None: 417 if options is None:
418 options = {} 418 options = {}
419 419
420 def connectProfile(__=None): 420 def connectProfile(__=None):
421 if self.isConnected(profile): 421 if self.isConnected(profile):
422 log.info(_(u"already connected !")) 422 log.info(_("already connected !"))
423 return True 423 return True
424 424
425 if self.memory.isComponent(profile): 425 if self.memory.isComponent(profile):
426 d = xmpp.SatXMPPComponent.startConnection(self, profile, max_retries) 426 d = xmpp.SatXMPPComponent.startConnection(self, profile, max_retries)
427 else: 427 else:
437 # FIXME: client should not be deleted if only disconnected 437 # FIXME: client should not be deleted if only disconnected
438 # it shoud be deleted only when session is finished 438 # it shoud be deleted only when session is finished
439 if not self.isConnected(profile_key): 439 if not self.isConnected(profile_key):
440 # isConnected is checked here and not on client 440 # isConnected is checked here and not on client
441 # because client is deleted when session is ended 441 # because client is deleted when session is ended
442 log.info(_(u"not connected !")) 442 log.info(_("not connected !"))
443 return defer.succeed(None) 443 return defer.succeed(None)
444 client = self.getClient(profile_key) 444 client = self.getClient(profile_key)
445 return client.entityDisconnect() 445 return client.entityDisconnect()
446 446
447 def getFeatures(self, profile_key=C.PROF_KEY_NONE): 447 def getFeatures(self, profile_key=C.PROF_KEY_NONE):
466 profile_key = C.PROF_KEY_NONE 466 profile_key = C.PROF_KEY_NONE
467 except exceptions.ProfileNotSetError: 467 except exceptions.ProfileNotSetError:
468 pass 468 pass
469 469
470 features = [] 470 features = []
471 for import_name, plugin in self.plugins.iteritems(): 471 for import_name, plugin in self.plugins.items():
472 try: 472 try:
473 features_d = defer.maybeDeferred(plugin.getFeatures, profile_key) 473 features_d = defer.maybeDeferred(plugin.getFeatures, profile_key)
474 except AttributeError: 474 except AttributeError:
475 features_d = defer.succeed({}) 475 features_d = defer.succeed({})
476 features.append(features_d) 476 features.append(features_d)
483 for name, (success, data) in zip(import_names, result): 483 for name, (success, data) in zip(import_names, result):
484 if success: 484 if success:
485 ret[name] = data 485 ret[name] = data
486 else: 486 else:
487 log.warning( 487 log.warning(
488 u"Error while getting features for {name}: {failure}".format( 488 "Error while getting features for {name}: {failure}".format(
489 name=name, failure=data 489 name=name, failure=data
490 ) 490 )
491 ) 491 )
492 ret[name] = {} 492 ret[name] = {}
493 return ret 493 return ret
494 494
495 d_list.addCallback(buildFeatures, self.plugins.keys()) 495 d_list.addCallback(buildFeatures, list(self.plugins.keys()))
496 return d_list 496 return d_list
497 497
498 def getContacts(self, profile_key): 498 def getContacts(self, profile_key):
499 client = self.getClient(profile_key) 499 client = self.getClient(profile_key)
500 500
525 log.error(_("Trying to remove reference to a client not referenced")) 525 log.error(_("Trying to remove reference to a client not referenced"))
526 else: 526 else:
527 self.memory.purgeProfileSession(profile) 527 self.memory.purgeProfileSession(profile)
528 528
529 def startService(self): 529 def startService(self):
530 log.info(u"Salut à toi ô mon frère !") 530 log.info("Salut à toi ô mon frère !")
531 531
532 def stopService(self): 532 def stopService(self):
533 log.info(u"Salut aussi à Rantanplan") 533 log.info("Salut aussi à Rantanplan")
534 return self.pluginsUnload() 534 return self.pluginsUnload()
535 535
536 def run(self): 536 def run(self):
537 log.debug(_("running app")) 537 log.debug(_("running app"))
538 reactor.run() 538 reactor.run()
574 Manage list through profile_key like C.PROF_KEY_ALL 574 Manage list through profile_key like C.PROF_KEY_ALL
575 @param profile_key: %(doc_profile_key)s 575 @param profile_key: %(doc_profile_key)s
576 @return: list of clients 576 @return: list of clients
577 """ 577 """
578 if not profile_key: 578 if not profile_key:
579 raise exceptions.DataError(_(u"profile_key must not be empty")) 579 raise exceptions.DataError(_("profile_key must not be empty"))
580 try: 580 try:
581 profile = self.memory.getProfileName(profile_key, True) 581 profile = self.memory.getProfileName(profile_key, True)
582 except exceptions.ProfileUnknownError: 582 except exceptions.ProfileUnknownError:
583 return [] 583 return []
584 if profile == C.PROF_KEY_ALL: 584 if profile == C.PROF_KEY_ALL:
585 return self.profiles.values() 585 return list(self.profiles.values())
586 elif profile[0] == "@": #  only profile keys can start with "@" 586 elif profile[0] == "@": #  only profile keys can start with "@"
587 raise exceptions.ProfileKeyUnknown 587 raise exceptions.ProfileKeyUnknown
588 return [self.profiles[profile]] 588 return [self.profiles[profile]]
589 589
590 def _getConfig(self, section, name): 590 def _getConfig(self, section, name):
592 592
593 @param section: section of the config file (None or '' for DEFAULT) 593 @param section: section of the config file (None or '' for DEFAULT)
594 @param name: name of the option 594 @param name: name of the option
595 @return: unicode representation of the option 595 @return: unicode representation of the option
596 """ 596 """
597 return unicode(self.memory.getConfig(section, name, "")) 597 return str(self.memory.getConfig(section, name, ""))
598 598
599 def logErrback(self, failure_, msg=_(u"Unexpected error: {failure_}")): 599 def logErrback(self, failure_, msg=_("Unexpected error: {failure_}")):
600 """Generic errback logging 600 """Generic errback logging
601 601
602 @param msg(unicode): error message ("failure_" key will be use for format) 602 @param msg(unicode): error message ("failure_" key will be use for format)
603 can be used as last errback to show unexpected error 603 can be used as last errback to show unexpected error
604 """ 604 """
608 #  namespaces 608 #  namespaces
609 609
610 def registerNamespace(self, short_name, namespace): 610 def registerNamespace(self, short_name, namespace):
611 """associate a namespace to a short name""" 611 """associate a namespace to a short name"""
612 if short_name in self.ns_map: 612 if short_name in self.ns_map:
613 raise exceptions.ConflictError(u"this short name is already used") 613 raise exceptions.ConflictError("this short name is already used")
614 self.ns_map[short_name] = namespace 614 self.ns_map[short_name] = namespace
615 615
616 def getNamespaces(self): 616 def getNamespaces(self):
617 return self.ns_map 617 return self.ns_map
618 618
619 def getNamespace(self, short_name): 619 def getNamespace(self, short_name):
620 try: 620 try:
621 return self.ns_map[short_name] 621 return self.ns_map[short_name]
622 except KeyError: 622 except KeyError:
623 raise exceptions.NotFound(u"namespace {short_name} is not registered" 623 raise exceptions.NotFound("namespace {short_name} is not registered"
624 .format(short_name=short_name)) 624 .format(short_name=short_name))
625 625
626 def getSessionInfos(self, profile_key): 626 def getSessionInfos(self, profile_key):
627 """compile interesting data on current profile session""" 627 """compile interesting data on current profile session"""
628 client = self.getClient(profile_key) 628 client = self.getClient(profile_key)
629 data = { 629 data = {
630 "jid": client.jid.full(), 630 "jid": client.jid.full(),
631 "started": unicode(int(client.started)) 631 "started": str(int(client.started))
632 } 632 }
633 return defer.succeed(data) 633 return defer.succeed(data)
634 634
635 # local dirs 635 # local dirs
636 636
712 def _encryptionPluginsGet(self): 712 def _encryptionPluginsGet(self):
713 plugins = encryption.EncryptionHandler.getPlugins() 713 plugins = encryption.EncryptionHandler.getPlugins()
714 ret = [] 714 ret = []
715 for p in plugins: 715 for p in plugins:
716 ret.append({ 716 ret.append({
717 u"name": p.name, 717 "name": p.name,
718 u"namespace": p.namespace, 718 "namespace": p.namespace,
719 u"priority": unicode(p.priority), 719 "priority": str(p.priority),
720 }) 720 })
721 return ret 721 return ret
722 722
723 def _encryptionTrustUIGet(self, to_jid_s, namespace, profile_key): 723 def _encryptionTrustUIGet(self, to_jid_s, namespace, profile_key):
724 client = self.getClient(profile_key) 724 client = self.getClient(profile_key)
738 return client.sendMessage( 738 return client.sendMessage(
739 to_jid, 739 to_jid,
740 message, 740 message,
741 subject, 741 subject,
742 mess_type, 742 mess_type,
743 {unicode(key): unicode(value) for key, value in extra.items()}, 743 {str(key): str(value) for key, value in list(extra.items())},
744 ) 744 )
745 745
746 def _setPresence(self, to="", show="", statuses=None, profile_key=C.PROF_KEY_NONE): 746 def _setPresence(self, to="", show="", statuses=None, profile_key=C.PROF_KEY_NONE):
747 return self.setPresence(jid.JID(to) if to else None, show, statuses, profile_key) 747 return self.setPresence(jid.JID(to) if to else None, show, statuses, profile_key)
748 748
772 @param profile_key: profile""" 772 @param profile_key: profile"""
773 profile = self.memory.getProfileName(profile_key) 773 profile = self.memory.getProfileName(profile_key)
774 assert profile 774 assert profile
775 to_jid = jid.JID(raw_jid) 775 to_jid = jid.JID(raw_jid)
776 log.debug( 776 log.debug(
777 _(u"subsciption request [%(subs_type)s] for %(jid)s") 777 _("subsciption request [%(subs_type)s] for %(jid)s")
778 % {"subs_type": subs_type, "jid": to_jid.full()} 778 % {"subs_type": subs_type, "jid": to_jid.full()}
779 ) 779 )
780 if subs_type == "subscribe": 780 if subs_type == "subscribe":
781 self.profiles[profile].presence.subscribe(to_jid) 781 self.profiles[profile].presence.subscribe(to_jid)
782 elif subs_type == "subscribed": 782 elif subs_type == "subscribed":
899 899
900 for idx, (success, infos) in enumerate(services_infos): 900 for idx, (success, infos) in enumerate(services_infos):
901 service_jid = services_jids[idx] 901 service_jid = services_jids[idx]
902 if not success: 902 if not success:
903 log.warning( 903 log.warning(
904 _(u"Can't find features for service {service_jid}, ignoring") 904 _("Can't find features for service {service_jid}, ignoring")
905 .format(service_jid=service_jid.full())) 905 .format(service_jid=service_jid.full()))
906 continue 906 continue
907 if (identities is not None 907 if (identities is not None
908 and not set(infos.identities.keys()).issuperset(identities)): 908 and not set(infos.identities.keys()).issuperset(identities)):
909 continue 909 continue
910 found_identities = [ 910 found_identities = [
911 (cat, type_, name or u"") 911 (cat, type_, name or "")
912 for (cat, type_), name in infos.identities.iteritems() 912 for (cat, type_), name in infos.identities.items()
913 ] 913 ]
914 found_service[service_jid.full()] = found_identities 914 found_service[service_jid.full()] = found_identities
915 915
916 to_find = [] 916 to_find = []
917 if own_jid: 917 if own_jid:
958 958
959 for idx, (success, infos) in enumerate(infos_data): 959 for idx, (success, infos) in enumerate(infos_data):
960 full_jid = full_jids[idx] 960 full_jid = full_jids[idx]
961 if not success: 961 if not success:
962 log.warning( 962 log.warning(
963 _(u"Can't retrieve {full_jid} infos, ignoring") 963 _("Can't retrieve {full_jid} infos, ignoring")
964 .format(full_jid=full_jid.full())) 964 .format(full_jid=full_jid.full()))
965 continue 965 continue
966 if infos.features.issuperset(namespaces): 966 if infos.features.issuperset(namespaces):
967 if identities is not None and not set( 967 if identities is not None and not set(
968 infos.identities.keys() 968 infos.identities.keys()
969 ).issuperset(identities): 969 ).issuperset(identities):
970 continue 970 continue
971 found_identities = [ 971 found_identities = [
972 (cat, type_, name or u"") 972 (cat, type_, name or "")
973 for (cat, type_), name in infos.identities.iteritems() 973 for (cat, type_), name in infos.identities.items()
974 ] 974 ]
975 found[full_jid.full()] = found_identities 975 found[full_jid.full()] = found_identities
976 976
977 defer.returnValue((found_service, found_own, found_roster)) 977 defer.returnValue((found_service, found_own, found_roster))
978 978
979 ## Generic HMI ## 979 ## Generic HMI ##
980 980
981 def _killAction(self, keep_id, client): 981 def _killAction(self, keep_id, client):
982 log.debug(u"Killing action {} for timeout".format(keep_id)) 982 log.debug("Killing action {} for timeout".format(keep_id))
983 client.actions[keep_id] 983 client.actions[keep_id]
984 984
985 def actionNew( 985 def actionNew(
986 self, 986 self,
987 action_data, 987 action_data,
996 @param keep_id(None, unicode): if not None, used to keep action for differed 996 @param keep_id(None, unicode): if not None, used to keep action for differed
997 retrieval. Must be set to the callback_id. 997 retrieval. Must be set to the callback_id.
998 Action will be deleted after 30 min. 998 Action will be deleted after 30 min.
999 @param profile: %(doc_profile)s 999 @param profile: %(doc_profile)s
1000 """ 1000 """
1001 id_ = unicode(uuid.uuid4()) 1001 id_ = str(uuid.uuid4())
1002 if keep_id is not None: 1002 if keep_id is not None:
1003 client = self.getClient(profile) 1003 client = self.getClient(profile)
1004 action_timer = reactor.callLater(60 * 30, self._killAction, keep_id, client) 1004 action_timer = reactor.callLater(60 * 30, self._killAction, keep_id, client)
1005 client.actions[keep_id] = (action_data, id_, security_limit, action_timer) 1005 client.actions[keep_id] = (action_data, id_, security_limit, action_timer)
1006 1006
1010 """Return current non answered actions 1010 """Return current non answered actions
1011 1011
1012 @param profile: %(doc_profile)s 1012 @param profile: %(doc_profile)s
1013 """ 1013 """
1014 client = self.getClient(profile) 1014 client = self.getClient(profile)
1015 return [action_tuple[:-1] for action_tuple in client.actions.itervalues()] 1015 return [action_tuple[:-1] for action_tuple in client.actions.values()]
1016 1016
1017 def registerProgressCb( 1017 def registerProgressCb(
1018 self, progress_id, callback, metadata=None, profile=C.PROF_KEY_NONE 1018 self, progress_id, callback, metadata=None, profile=C.PROF_KEY_NONE
1019 ): 1019 ):
1020 """Register a callback called when progress is requested for id""" 1020 """Register a callback called when progress is requested for id"""
1021 if metadata is None: 1021 if metadata is None:
1022 metadata = {} 1022 metadata = {}
1023 client = self.getClient(profile) 1023 client = self.getClient(profile)
1024 if progress_id in client._progress_cb: 1024 if progress_id in client._progress_cb:
1025 raise exceptions.ConflictError(u"Progress ID is not unique !") 1025 raise exceptions.ConflictError("Progress ID is not unique !")
1026 client._progress_cb[progress_id] = (callback, metadata) 1026 client._progress_cb[progress_id] = (callback, metadata)
1027 1027
1028 def removeProgressCb(self, progress_id, profile): 1028 def removeProgressCb(self, progress_id, profile):
1029 """Remove a progress callback""" 1029 """Remove a progress callback"""
1030 client = self.getClient(profile) 1030 client = self.getClient(profile)
1031 try: 1031 try:
1032 del client._progress_cb[progress_id] 1032 del client._progress_cb[progress_id]
1033 except KeyError: 1033 except KeyError:
1034 log.error(_(u"Trying to remove an unknow progress callback")) 1034 log.error(_("Trying to remove an unknow progress callback"))
1035 1035
1036 def _progressGet(self, progress_id, profile): 1036 def _progressGet(self, progress_id, profile):
1037 data = self.progressGet(progress_id, profile) 1037 data = self.progressGet(progress_id, profile)
1038 return {k: unicode(v) for k, v in data.iteritems()} 1038 return {k: str(v) for k, v in data.items()}
1039 1039
1040 def progressGet(self, progress_id, profile): 1040 def progressGet(self, progress_id, profile):
1041 """Return a dict with progress information 1041 """Return a dict with progress information
1042 1042
1043 @param progress_id(unicode): unique id of the progressing element 1043 @param progress_id(unicode): unique id of the progressing element
1055 data = {} 1055 data = {}
1056 return data 1056 return data
1057 1057
1058 def _progressGetAll(self, profile_key): 1058 def _progressGetAll(self, profile_key):
1059 progress_all = self.progressGetAll(profile_key) 1059 progress_all = self.progressGetAll(profile_key)
1060 for profile, progress_dict in progress_all.iteritems(): 1060 for profile, progress_dict in progress_all.items():
1061 for progress_id, data in progress_dict.iteritems(): 1061 for progress_id, data in progress_dict.items():
1062 for key, value in data.iteritems(): 1062 for key, value in data.items():
1063 data[key] = unicode(value) 1063 data[key] = str(value)
1064 return progress_all 1064 return progress_all
1065 1065
1066 def progressGetAllMetadata(self, profile_key): 1066 def progressGetAllMetadata(self, profile_key):
1067 """Return all progress metadata at once 1067 """Return all progress metadata at once
1068 1068
1080 progress_dict = {} 1080 progress_dict = {}
1081 progress_all[profile] = progress_dict 1081 progress_all[profile] = progress_dict
1082 for ( 1082 for (
1083 progress_id, 1083 progress_id,
1084 (__, progress_metadata), 1084 (__, progress_metadata),
1085 ) in client._progress_cb.iteritems(): 1085 ) in client._progress_cb.items():
1086 progress_dict[progress_id] = progress_metadata 1086 progress_dict[progress_id] = progress_metadata
1087 return progress_all 1087 return progress_all
1088 1088
1089 def progressGetAll(self, profile_key): 1089 def progressGetAll(self, profile_key):
1090 """Return all progress status at once 1090 """Return all progress status at once
1099 progress_all = {} 1099 progress_all = {}
1100 for client in clients: 1100 for client in clients:
1101 profile = client.profile 1101 profile = client.profile
1102 progress_dict = {} 1102 progress_dict = {}
1103 progress_all[profile] = progress_dict 1103 progress_all[profile] = progress_dict
1104 for progress_id, (progress_cb, __) in client._progress_cb.iteritems(): 1104 for progress_id, (progress_cb, __) in client._progress_cb.items():
1105 progress_dict[progress_id] = progress_cb(progress_id, profile) 1105 progress_dict[progress_id] = progress_cb(progress_id, profile)
1106 return progress_all 1106 return progress_all
1107 1107
1108 def registerCallback(self, callback, *args, **kwargs): 1108 def registerCallback(self, callback, *args, **kwargs):
1109 """Register a callback. 1109 """Register a callback.
1119 callback_id = kwargs.pop("force_id", None) 1119 callback_id = kwargs.pop("force_id", None)
1120 if callback_id is None: 1120 if callback_id is None:
1121 callback_id = str(uuid.uuid4()) 1121 callback_id = str(uuid.uuid4())
1122 else: 1122 else:
1123 if callback_id in self._cb_map: 1123 if callback_id in self._cb_map:
1124 raise exceptions.ConflictError(_(u"id already registered")) 1124 raise exceptions.ConflictError(_("id already registered"))
1125 self._cb_map[callback_id] = (callback, args, kwargs) 1125 self._cb_map[callback_id] = (callback, args, kwargs)
1126 1126
1127 if "one_shot" in kwargs: # One Shot callback are removed after 30 min 1127 if "one_shot" in kwargs: # One Shot callback are removed after 30 min
1128 1128
1129 def purgeCallback(): 1129 def purgeCallback():
1161 except exceptions.NotFound: 1161 except exceptions.NotFound:
1162 # client is not available yet 1162 # client is not available yet
1163 profile = self.memory.getProfileName(profile_key) 1163 profile = self.memory.getProfileName(profile_key)
1164 if not profile: 1164 if not profile:
1165 raise exceptions.ProfileUnknownError( 1165 raise exceptions.ProfileUnknownError(
1166 _(u"trying to launch action with a non-existant profile") 1166 _("trying to launch action with a non-existant profile")
1167 ) 1167 )
1168 else: 1168 else:
1169 profile = client.profile 1169 profile = client.profile
1170 # we check if the action is kept, and remove it 1170 # we check if the action is kept, and remove it
1171 try: 1171 try:
1177 del client.actions[callback_id] 1177 del client.actions[callback_id]
1178 1178
1179 try: 1179 try:
1180 callback, args, kwargs = self._cb_map[callback_id] 1180 callback, args, kwargs = self._cb_map[callback_id]
1181 except KeyError: 1181 except KeyError:
1182 raise exceptions.DataError(u"Unknown callback id {}".format(callback_id)) 1182 raise exceptions.DataError("Unknown callback id {}".format(callback_id))
1183 1183
1184 if kwargs.get("with_data", False): 1184 if kwargs.get("with_data", False):
1185 if data is None: 1185 if data is None:
1186 raise exceptions.DataError("Required data for this callback is missing") 1186 raise exceptions.DataError("Required data for this callback is missing")
1187 args, kwargs = ( 1187 args, kwargs = (
1208 """ 1208 """
1209 return tuple((p.lower().strip() for p in path)) 1209 return tuple((p.lower().strip() for p in path))
1210 1210
1211 def importMenu(self, path, callback, security_limit=C.NO_SECURITY_LIMIT, 1211 def importMenu(self, path, callback, security_limit=C.NO_SECURITY_LIMIT,
1212 help_string="", type_=C.MENU_GLOBAL): 1212 help_string="", type_=C.MENU_GLOBAL):
1213 """register a new menu for frontends 1213 r"""register a new menu for frontends
1214 1214
1215 @param path(iterable[unicode]): path to go to the menu 1215 @param path(iterable[unicode]): path to go to the menu
1216 (category/subcategory/.../item) (e.g.: ("File", "Open")) 1216 (category/subcategory/.../item) (e.g.: ("File", "Open"))
1217 /!\ use D_() instead of _() for translations (e.g. (D_("File"), D_("Open"))) 1217 /!\ use D_() instead of _() for translations (e.g. (D_("File"), D_("Open")))
1218 untranslated/lower case path can be used to identity a menu, for this reason 1218 untranslated/lower case path can be used to identity a menu, for this reason
1243 @return (unicode): menu_id (same as callback_id) 1243 @return (unicode): menu_id (same as callback_id)
1244 """ 1244 """
1245 1245
1246 if callable(callback): 1246 if callable(callback):
1247 callback_id = self.registerCallback(callback, with_data=True) 1247 callback_id = self.registerCallback(callback, with_data=True)
1248 elif isinstance(callback, basestring): 1248 elif isinstance(callback, str):
1249 # The callback is already registered 1249 # The callback is already registered
1250 callback_id = callback 1250 callback_id = callback
1251 try: 1251 try:
1252 callback, args, kwargs = self._cb_map[callback_id] 1252 callback, args, kwargs = self._cb_map[callback_id]
1253 except KeyError: 1253 except KeyError:
1254 raise exceptions.DataError("Unknown callback id") 1254 raise exceptions.DataError("Unknown callback id")
1255 kwargs["with_data"] = True # we have to be sure that we use extra data 1255 kwargs["with_data"] = True # we have to be sure that we use extra data
1256 else: 1256 else:
1257 raise exceptions.DataError("Unknown callback type") 1257 raise exceptions.DataError("Unknown callback type")
1258 1258
1259 for menu_data in self._menus.itervalues(): 1259 for menu_data in self._menus.values():
1260 if menu_data["path"] == path and menu_data["type"] == type_: 1260 if menu_data["path"] == path and menu_data["type"] == type_:
1261 raise exceptions.ConflictError( 1261 raise exceptions.ConflictError(
1262 _("A menu with the same path and type already exists") 1262 _("A menu with the same path and type already exists")
1263 ) 1263 )
1264 1264
1265 path_canonical = self._getMenuCanonicalPath(path) 1265 path_canonical = self._getMenuCanonicalPath(path)
1266 menu_key = (type_, path_canonical) 1266 menu_key = (type_, path_canonical)
1267 1267
1268 if menu_key in self._menus_paths: 1268 if menu_key in self._menus_paths:
1269 raise exceptions.ConflictError( 1269 raise exceptions.ConflictError(
1270 u"this menu path is already used: {path} ({menu_key})".format( 1270 "this menu path is already used: {path} ({menu_key})".format(
1271 path=path_canonical, menu_key=menu_key 1271 path=path_canonical, menu_key=menu_key
1272 ) 1272 )
1273 ) 1273 )
1274 1274
1275 menu_data = { 1275 menu_data = {
1298 - extra (dict(unicode, unicode)): extra data where key can be: 1298 - extra (dict(unicode, unicode)): extra data where key can be:
1299 - icon: name of the icon to use (TODO) 1299 - icon: name of the icon to use (TODO)
1300 - help_url: link to a page with more complete documentation (TODO) 1300 - help_url: link to a page with more complete documentation (TODO)
1301 """ 1301 """
1302 ret = [] 1302 ret = []
1303 for menu_id, menu_data in self._menus.iteritems(): 1303 for menu_id, menu_data in self._menus.items():
1304 type_ = menu_data["type"] 1304 type_ = menu_data["type"]
1305 path = menu_data["path"] 1305 path = menu_data["path"]
1306 menu_security_limit = menu_data["security_limit"] 1306 menu_security_limit = menu_data["security_limit"]
1307 if security_limit != C.NO_SECURITY_LIMIT and ( 1307 if security_limit != C.NO_SECURITY_LIMIT and (
1308 menu_security_limit == C.NO_SECURITY_LIMIT 1308 menu_security_limit == C.NO_SECURITY_LIMIT
1337 menu_key = (menu_type, canonical_path) 1337 menu_key = (menu_type, canonical_path)
1338 try: 1338 try:
1339 callback_id = self._menus_paths[menu_key] 1339 callback_id = self._menus_paths[menu_key]
1340 except KeyError: 1340 except KeyError:
1341 raise exceptions.NotFound( 1341 raise exceptions.NotFound(
1342 u"Can't find menu {path} ({menu_type})".format( 1342 "Can't find menu {path} ({menu_type})".format(
1343 path=canonical_path, menu_type=menu_type 1343 path=canonical_path, menu_type=menu_type
1344 ) 1344 )
1345 ) 1345 )
1346 return self.launchCallback(callback_id, data, client.profile) 1346 return self.launchCallback(callback_id, data, client.profile)
1347 1347