comparison sat/core/sat_main.py @ 2624:56f94936df1e

code style reformatting using black
author Goffi <goffi@goffi.org>
date Wed, 27 Jun 2018 20:14:46 +0200
parents 9446f1ea9eac
children 189e38fb11ff
comparison
equal deleted inserted replaced
2623:49533de4540b 2624:56f94936df1e
25 from twisted.internet import reactor 25 from twisted.internet import reactor
26 from wokkel.xmppim import RosterItem 26 from wokkel.xmppim import RosterItem
27 from sat.core import xmpp 27 from sat.core import xmpp
28 from sat.core import exceptions 28 from sat.core import exceptions
29 from sat.core.log import getLogger 29 from sat.core.log import getLogger
30
30 log = getLogger(__name__) 31 log = getLogger(__name__)
31 from sat.core.constants import Const as C 32 from sat.core.constants import Const as C
32 from sat.memory import memory 33 from sat.memory import memory
33 from sat.memory import cache 34 from sat.memory import cache
34 from sat.tools import trigger 35 from sat.tools import trigger
41 import sys 42 import sys
42 import os.path 43 import os.path
43 import uuid 44 import uuid
44 45
45 try: 46 try:
46 from collections import OrderedDict # only available from python 2.7 47 from collections import OrderedDict # only available from python 2.7
47 except ImportError: 48 except ImportError:
48 from ordereddict import OrderedDict 49 from ordereddict import OrderedDict
49 50
50 51
51 class SAT(service.Service): 52 class SAT(service.Service):
52
53 def __init__(self): 53 def __init__(self):
54 self._cb_map = {} # map from callback_id to callbacks 54 self._cb_map = {} # map from callback_id to callbacks
55 self._menus = OrderedDict() # dynamic menus. key: callback_id, value: menu data (dictionnary) 55 self._menus = (
56 OrderedDict()
57 ) # dynamic menus. key: callback_id, value: menu data (dictionnary)
56 self._menus_paths = {} # path to id. key: (menu_type, lower case tuple of path), value: menu id 58 self._menus_paths = {} # path to id. key: (menu_type, lower case tuple of path), value: menu id
57 self.initialised = defer.Deferred() 59 self.initialised = defer.Deferred()
58 self.profiles = {} 60 self.profiles = {}
59 self.plugins = {} 61 self.plugins = {}
60 self.ns_map = {u'x-data': u'jabber:x:data'} # map for short name to whole namespace, 62 self.ns_map = {
61 # extended by plugins with registerNamespace 63 u"x-data": u"jabber:x:data"
64 } #  map for short name to whole namespace,
65 #  extended by plugins with registerNamespace
62 self.memory = memory.Memory(self) 66 self.memory = memory.Memory(self)
63 self.trigger = trigger.TriggerManager() # trigger are used to change SàT behaviour 67 self.trigger = (
64 68 trigger.TriggerManager()
65 bridge_name = self.memory.getConfig('', 'bridge', 'dbus') 69 ) # trigger are used to change SàT behaviour
70
71 bridge_name = self.memory.getConfig("", "bridge", "dbus")
66 72
67 bridge_module = dynamic_import.bridge(bridge_name) 73 bridge_module = dynamic_import.bridge(bridge_name)
68 if bridge_module is None: 74 if bridge_module is None:
69 log.error(u"Can't find bridge module of name {}".format(bridge_name)) 75 log.error(u"Can't find bridge module of name {}".format(bridge_name))
70 sys.exit(1) 76 sys.exit(1)
77 self.bridge.register_method("getReady", lambda: self.initialised) 83 self.bridge.register_method("getReady", lambda: self.initialised)
78 self.bridge.register_method("getVersion", lambda: self.full_version) 84 self.bridge.register_method("getVersion", lambda: self.full_version)
79 self.bridge.register_method("getFeatures", self.getFeatures) 85 self.bridge.register_method("getFeatures", self.getFeatures)
80 self.bridge.register_method("profileNameGet", self.memory.getProfileName) 86 self.bridge.register_method("profileNameGet", self.memory.getProfileName)
81 self.bridge.register_method("profilesListGet", self.memory.getProfilesList) 87 self.bridge.register_method("profilesListGet", self.memory.getProfilesList)
82 self.bridge.register_method("getEntityData", lambda jid_, keys, profile: self.memory.getEntityData(jid.JID(jid_), keys, profile)) 88 self.bridge.register_method(
89 "getEntityData",
90 lambda jid_, keys, profile: self.memory.getEntityData(
91 jid.JID(jid_), keys, profile
92 ),
93 )
83 self.bridge.register_method("getEntitiesData", self.memory._getEntitiesData) 94 self.bridge.register_method("getEntitiesData", self.memory._getEntitiesData)
84 self.bridge.register_method("profileCreate", self.memory.createProfile) 95 self.bridge.register_method("profileCreate", self.memory.createProfile)
85 self.bridge.register_method("asyncDeleteProfile", self.memory.asyncDeleteProfile) 96 self.bridge.register_method("asyncDeleteProfile", self.memory.asyncDeleteProfile)
86 self.bridge.register_method("profileStartSession", self.memory.startSession) 97 self.bridge.register_method("profileStartSession", self.memory.startSession)
87 self.bridge.register_method("profileIsSessionStarted", self.memory._isSessionStarted) 98 self.bridge.register_method(
99 "profileIsSessionStarted", self.memory._isSessionStarted
100 )
88 self.bridge.register_method("profileSetDefault", self.memory.profileSetDefault) 101 self.bridge.register_method("profileSetDefault", self.memory.profileSetDefault)
89 self.bridge.register_method("connect", self._connect) 102 self.bridge.register_method("connect", self._connect)
90 self.bridge.register_method("disconnect", self.disconnect) 103 self.bridge.register_method("disconnect", self.disconnect)
91 self.bridge.register_method("getContacts", self.getContacts) 104 self.bridge.register_method("getContacts", self.getContacts)
92 self.bridge.register_method("getContactsFromGroup", self.getContactsFromGroup) 105 self.bridge.register_method("getContactsFromGroup", self.getContactsFromGroup)
93 self.bridge.register_method("getMainResource", self.memory._getMainResource) 106 self.bridge.register_method("getMainResource", self.memory._getMainResource)
94 self.bridge.register_method("getPresenceStatuses", self.memory._getPresenceStatuses) 107 self.bridge.register_method(
108 "getPresenceStatuses", self.memory._getPresenceStatuses
109 )
95 self.bridge.register_method("getWaitingSub", self.memory.getWaitingSub) 110 self.bridge.register_method("getWaitingSub", self.memory.getWaitingSub)
96 self.bridge.register_method("messageSend", self._messageSend) 111 self.bridge.register_method("messageSend", self._messageSend)
97 self.bridge.register_method("getConfig", self._getConfig) 112 self.bridge.register_method("getConfig", self._getConfig)
98 self.bridge.register_method("setParam", self.setParam) 113 self.bridge.register_method("setParam", self.setParam)
99 self.bridge.register_method("getParamA", self.memory.getStringParamA) 114 self.bridge.register_method("getParamA", self.memory.getStringParamA)
100 self.bridge.register_method("asyncGetParamA", self.memory.asyncGetStringParamA) 115 self.bridge.register_method("asyncGetParamA", self.memory.asyncGetStringParamA)
101 self.bridge.register_method("asyncGetParamsValuesFromCategory", self.memory.asyncGetParamsValuesFromCategory) 116 self.bridge.register_method(
117 "asyncGetParamsValuesFromCategory",
118 self.memory.asyncGetParamsValuesFromCategory,
119 )
102 self.bridge.register_method("getParamsUI", self.memory.getParamsUI) 120 self.bridge.register_method("getParamsUI", self.memory.getParamsUI)
103 self.bridge.register_method("getParamsCategories", self.memory.getParamsCategories) 121 self.bridge.register_method(
122 "getParamsCategories", self.memory.getParamsCategories
123 )
104 self.bridge.register_method("paramsRegisterApp", self.memory.paramsRegisterApp) 124 self.bridge.register_method("paramsRegisterApp", self.memory.paramsRegisterApp)
105 self.bridge.register_method("historyGet", self.memory._historyGet) 125 self.bridge.register_method("historyGet", self.memory._historyGet)
106 self.bridge.register_method("setPresence", self._setPresence) 126 self.bridge.register_method("setPresence", self._setPresence)
107 self.bridge.register_method("subscription", self.subscription) 127 self.bridge.register_method("subscription", self.subscription)
108 self.bridge.register_method("addContact", self._addContact) 128 self.bridge.register_method("addContact", self._addContact)
133 153
134 @property 154 @property
135 def full_version(self): 155 def full_version(self):
136 """Return the full version of SàT (with release name and extra data when in development mode)""" 156 """Return the full version of SàT (with release name and extra data when in development mode)"""
137 version = self.version 157 version = self.version
138 if version[-1] == 'D': 158 if version[-1] == "D":
139 # we are in debug version, we add extra data 159 # we are in debug version, we add extra data
140 try: 160 try:
141 return self._version_cache 161 return self._version_cache
142 except AttributeError: 162 except AttributeError:
143 self._version_cache = u"{} « {} » ({})".format(version, C.APP_RELEASE_NAME, utils.getRepositoryData(sat)) 163 self._version_cache = u"{} « {} » ({})".format(
164 version, C.APP_RELEASE_NAME, utils.getRepositoryData(sat)
165 )
144 return self._version_cache 166 return self._version_cache
145 else: 167 else:
146 return version 168 return version
147 169
148 @property 170 @property
156 try: 178 try:
157 self._import_plugins() 179 self._import_plugins()
158 ui_contact_list.ContactList(self) 180 ui_contact_list.ContactList(self)
159 ui_profile_manager.ProfileManager(self) 181 ui_profile_manager.ProfileManager(self)
160 except Exception as e: 182 except Exception as e:
161 log.error(_(u"Could not initialize backend: {reason}").format( 183 log.error(
162 reason = str(e).decode('utf-8', 'ignore'))) 184 _(u"Could not initialize backend: {reason}").format(
185 reason=str(e).decode("utf-8", "ignore")
186 )
187 )
163 sys.exit(1) 188 sys.exit(1)
164 self.initialised.callback(None) 189 self.initialised.callback(None)
165 log.info(_(u"Backend is ready")) 190 log.info(_(u"Backend is ready"))
166 191
167 def _unimport_plugin(self, plugin_path): 192 def _unimport_plugin(self, plugin_path):
178 # FIXME: should use imp 203 # FIXME: should use imp
179 # TODO: do not import all plugins if no needed: component plugins are not needed if we 204 # TODO: do not import all plugins if no needed: component plugins are not needed if we
180 # just use a client, and plugin blacklisting should be possible in sat.conf 205 # just use a client, and plugin blacklisting should be possible in sat.conf
181 plugins_path = os.path.dirname(sat.plugins.__file__) 206 plugins_path = os.path.dirname(sat.plugins.__file__)
182 plugin_glob = "plugin*." + C.PLUGIN_EXT 207 plugin_glob = "plugin*." + C.PLUGIN_EXT
183 plug_lst = [os.path.splitext(plugin)[0] for plugin in map(os.path.basename, glob(os.path.join(plugins_path, plugin_glob)))] 208 plug_lst = [
209 os.path.splitext(plugin)[0]
210 for plugin in map(
211 os.path.basename, glob(os.path.join(plugins_path, plugin_glob))
212 )
213 ]
184 plugins_to_import = {} # plugins we still have to import 214 plugins_to_import = {} # plugins we still have to import
185 for plug in plug_lst: 215 for plug in plug_lst:
186 plugin_path = 'sat.plugins.' + plug 216 plugin_path = "sat.plugins." + plug
187 try: 217 try:
188 __import__(plugin_path) 218 __import__(plugin_path)
189 except exceptions.MissingModule as e: 219 except exceptions.MissingModule as e:
190 self._unimport_plugin(plugin_path) 220 self._unimport_plugin(plugin_path)
191 log.warning(u"Can't import plugin [{path}] because of an unavailale third party module:\n{msg}".format( 221 log.warning(
192 path=plugin_path, msg=e)) 222 u"Can't import plugin [{path}] because of an unavailale third party module:\n{msg}".format(
223 path=plugin_path, msg=e
224 )
225 )
193 continue 226 continue
194 except exceptions.CancelError as e: 227 except exceptions.CancelError as e:
195 log.info(u"Plugin [{path}] cancelled its own import: {msg}".format(path=plugin_path, msg=e)) 228 log.info(
229 u"Plugin [{path}] cancelled its own import: {msg}".format(
230 path=plugin_path, msg=e
231 )
232 )
196 self._unimport_plugin(plugin_path) 233 self._unimport_plugin(plugin_path)
197 continue 234 continue
198 except Exception as e: 235 except Exception as e:
199 import traceback 236 import traceback
200 log.error(_(u"Can't import plugin [{path}]:\n{error}").format(path=plugin_path, error=traceback.format_exc())) 237
238 log.error(
239 _(u"Can't import plugin [{path}]:\n{error}").format(
240 path=plugin_path, error=traceback.format_exc()
241 )
242 )
201 self._unimport_plugin(plugin_path) 243 self._unimport_plugin(plugin_path)
202 continue 244 continue
203 mod = sys.modules[plugin_path] 245 mod = sys.modules[plugin_path]
204 plugin_info = mod.PLUGIN_INFO 246 plugin_info = mod.PLUGIN_INFO
205 import_name = plugin_info['import_name'] 247 import_name = plugin_info["import_name"]
206 248
207 plugin_modes = plugin_info[u'modes'] = set(plugin_info.setdefault(u"modes", C.PLUG_MODE_DEFAULT)) 249 plugin_modes = plugin_info[u"modes"] = set(
250 plugin_info.setdefault(u"modes", C.PLUG_MODE_DEFAULT)
251 )
208 252
209 # if the plugin is an entry point, it must work in component mode 253 # if the plugin is an entry point, it must work in component mode
210 if plugin_info[u'type'] == C.PLUG_TYPE_ENTRY_POINT: 254 if plugin_info[u"type"] == C.PLUG_TYPE_ENTRY_POINT:
211 # if plugin is an entrypoint, we cache it 255 # if plugin is an entrypoint, we cache it
212 if C.PLUG_MODE_COMPONENT not in plugin_modes: 256 if C.PLUG_MODE_COMPONENT not in plugin_modes:
213 log.error(_(u"{type} type must be used with {mode} mode, ignoring plugin").format( 257 log.error(
214 type = C.PLUG_TYPE_ENTRY_POINT, mode = C.PLUG_MODE_COMPONENT)) 258 _(
259 u"{type} type must be used with {mode} mode, ignoring plugin"
260 ).format(type=C.PLUG_TYPE_ENTRY_POINT, mode=C.PLUG_MODE_COMPONENT)
261 )
215 self._unimport_plugin(plugin_path) 262 self._unimport_plugin(plugin_path)
216 continue 263 continue
217 264
218 if import_name in plugins_to_import: 265 if import_name in plugins_to_import:
219 log.error(_(u"Name conflict for import name [{import_name}], can't import plugin [{name}]").format(**plugin_info)) 266 log.error(
267 _(
268 u"Name conflict for import name [{import_name}], can't import plugin [{name}]"
269 ).format(**plugin_info)
270 )
220 continue 271 continue
221 plugins_to_import[import_name] = (plugin_path, mod, plugin_info) 272 plugins_to_import[import_name] = (plugin_path, mod, plugin_info)
222 while True: 273 while True:
223 try: 274 try:
224 self._import_plugins_from_dict(plugins_to_import) 275 self._import_plugins_from_dict(plugins_to_import)
225 except ImportError: 276 except ImportError:
226 pass 277 pass
227 if not plugins_to_import: 278 if not plugins_to_import:
228 break 279 break
229 280
230 def _import_plugins_from_dict(self, plugins_to_import, import_name=None, optional=False): 281 def _import_plugins_from_dict(
282 self, plugins_to_import, import_name=None, optional=False
283 ):
231 """Recursively import and their dependencies in the right order 284 """Recursively import and their dependencies in the right order
232 285
233 @param plugins_to_import(dict): key=import_name and values=(plugin_path, module, plugin_info) 286 @param plugins_to_import(dict): key=import_name and values=(plugin_path, module, plugin_info)
234 @param import_name(unicode, None): name of the plugin to import as found in PLUGIN_INFO['import_name'] 287 @param import_name(unicode, None): name of the plugin to import as found in PLUGIN_INFO['import_name']
235 @param optional(bool): if False and plugin is not found, an ImportError exception is raised 288 @param optional(bool): if False and plugin is not found, an ImportError exception is raised
236 """ 289 """
237 if import_name in self.plugins: 290 if import_name in self.plugins:
238 log.debug(u'Plugin {} already imported, passing'.format(import_name)) 291 log.debug(u"Plugin {} already imported, passing".format(import_name))
239 return 292 return
240 if not import_name: 293 if not import_name:
241 import_name, (plugin_path, mod, plugin_info) = plugins_to_import.popitem() 294 import_name, (plugin_path, mod, plugin_info) = plugins_to_import.popitem()
242 else: 295 else:
243 if not import_name in plugins_to_import: 296 if not import_name in plugins_to_import:
244 if optional: 297 if optional:
245 log.warning(_(u"Recommended plugin not found: {}").format(import_name)) 298 log.warning(
299 _(u"Recommended plugin not found: {}").format(import_name)
300 )
246 return 301 return
247 msg = u"Dependency not found: {}".format(import_name) 302 msg = u"Dependency not found: {}".format(import_name)
248 log.error(msg) 303 log.error(msg)
249 raise ImportError(msg) 304 raise ImportError(msg)
250 plugin_path, mod, plugin_info = plugins_to_import.pop(import_name) 305 plugin_path, mod, plugin_info = plugins_to_import.pop(import_name)
251 dependencies = plugin_info.setdefault("dependencies", []) 306 dependencies = plugin_info.setdefault("dependencies", [])
252 recommendations = plugin_info.setdefault("recommendations", []) 307 recommendations = plugin_info.setdefault("recommendations", [])
253 for to_import in dependencies + recommendations: 308 for to_import in dependencies + recommendations:
254 if to_import not in self.plugins: 309 if to_import not in self.plugins:
255 log.debug(u'Recursively import dependency of [%s]: [%s]' % (import_name, to_import)) 310 log.debug(
311 u"Recursively import dependency of [%s]: [%s]"
312 % (import_name, to_import)
313 )
256 try: 314 try:
257 self._import_plugins_from_dict(plugins_to_import, to_import, to_import not in dependencies) 315 self._import_plugins_from_dict(
316 plugins_to_import, to_import, to_import not in dependencies
317 )
258 except ImportError as e: 318 except ImportError as e:
259 log.warning(_(u"Can't import plugin {name}: {error}").format(name=plugin_info['name'], error=e)) 319 log.warning(
320 _(u"Can't import plugin {name}: {error}").format(
321 name=plugin_info["name"], error=e
322 )
323 )
260 if optional: 324 if optional:
261 return 325 return
262 raise e 326 raise e
263 log.info("importing plugin: {}".format(plugin_info['name'])) 327 log.info("importing plugin: {}".format(plugin_info["name"]))
264 # we instanciate the plugin here 328 # we instanciate the plugin here
265 try: 329 try:
266 self.plugins[import_name] = getattr(mod, plugin_info['main'])(self) 330 self.plugins[import_name] = getattr(mod, plugin_info["main"])(self)
267 except Exception as e: 331 except Exception as e:
268 log.warning(u'Error while loading plugin "{name}", ignoring it: {error}' 332 log.warning(
269 .format(name=plugin_info['name'], error=e)) 333 u'Error while loading plugin "{name}", ignoring it: {error}'.format(
334 name=plugin_info["name"], error=e
335 )
336 )
270 if optional: 337 if optional:
271 return 338 return
272 raise ImportError(u"Error during initiation") 339 raise ImportError(u"Error during initiation")
273 if C.bool(plugin_info.get(C.PI_HANDLER, C.BOOL_FALSE)): 340 if C.bool(plugin_info.get(C.PI_HANDLER, C.BOOL_FALSE)):
274 self.plugins[import_name].is_handler = True 341 self.plugins[import_name].is_handler = True
275 else: 342 else:
276 self.plugins[import_name].is_handler = False 343 self.plugins[import_name].is_handler = False
277 # we keep metadata as a Class attribute 344 # we keep metadata as a Class attribute
278 self.plugins[import_name]._info = plugin_info 345 self.plugins[import_name]._info = plugin_info
279 #TODO: test xmppclient presence and register handler parent 346 # TODO: test xmppclient presence and register handler parent
280 347
281 def pluginsUnload(self): 348 def pluginsUnload(self):
282 """Call unload method on every loaded plugin, if exists 349 """Call unload method on every loaded plugin, if exists
283 350
284 @return (D): A deferred which return None when all method have been called 351 @return (D): A deferred which return None when all method have been called
294 continue 361 continue
295 else: 362 else:
296 defers_list.append(defer.maybeDeferred(unload)) 363 defers_list.append(defer.maybeDeferred(unload))
297 return defers_list 364 return defers_list
298 365
299 def _connect(self, profile_key, password='', options=None): 366 def _connect(self, profile_key, password="", options=None):
300 profile = self.memory.getProfileName(profile_key) 367 profile = self.memory.getProfileName(profile_key)
301 return self.connect(profile, password, options) 368 return self.connect(profile, password, options)
302 369
303 def connect(self, profile, password='', options=None, max_retries=C.XMPP_MAX_RETRIES): 370 def connect(self, profile, password="", options=None, max_retries=C.XMPP_MAX_RETRIES):
304 """Connect a profile (i.e. connect client.component to XMPP server) 371 """Connect a profile (i.e. connect client.component to XMPP server)
305 372
306 Retrieve the individual parameters, authenticate the profile 373 Retrieve the individual parameters, authenticate the profile
307 and initiate the connection to the associated XMPP server. 374 and initiate the connection to the associated XMPP server.
308 @param profile: %(doc_profile)s 375 @param profile: %(doc_profile)s
314 - True if the XMPP connection was already established 381 - True if the XMPP connection was already established
315 - False if the XMPP connection has been initiated (it may still fail) 382 - False if the XMPP connection has been initiated (it may still fail)
316 @raise exceptions.PasswordError: Profile password is wrong 383 @raise exceptions.PasswordError: Profile password is wrong
317 """ 384 """
318 if options is None: 385 if options is None:
319 options={} 386 options = {}
387
320 def connectProfile(dummy=None): 388 def connectProfile(dummy=None):
321 if self.isConnected(profile): 389 if self.isConnected(profile):
322 log.info(_("already connected !")) 390 log.info(_("already connected !"))
323 return True 391 return True
324 392
373 except AttributeError: 441 except AttributeError:
374 features_d = defer.succeed({}) 442 features_d = defer.succeed({})
375 features.append(features_d) 443 features.append(features_d)
376 444
377 d_list = defer.DeferredList(features) 445 d_list = defer.DeferredList(features)
446
378 def buildFeatures(result, import_names): 447 def buildFeatures(result, import_names):
379 assert len(result) == len(import_names) 448 assert len(result) == len(import_names)
380 ret = {} 449 ret = {}
381 for name, (success, data) in zip (import_names, result): 450 for name, (success, data) in zip(import_names, result):
382 if success: 451 if success:
383 ret[name] = data 452 ret[name] = data
384 else: 453 else:
385 log.warning(u"Error while getting features for {name}: {failure}".format( 454 log.warning(
386 name=name, failure=data)) 455 u"Error while getting features for {name}: {failure}".format(
456 name=name, failure=data
457 )
458 )
387 ret[name] = {} 459 ret[name] = {}
388 return ret 460 return ret
389 461
390 d_list.addCallback(buildFeatures, self.plugins.keys()) 462 d_list.addCallback(buildFeatures, self.plugins.keys())
391 return d_list 463 return d_list
392 464
393 def getContacts(self, profile_key): 465 def getContacts(self, profile_key):
394 client = self.getClient(profile_key) 466 client = self.getClient(profile_key)
467
395 def got_roster(dummy): 468 def got_roster(dummy):
396 ret = [] 469 ret = []
397 for item in client.roster.getItems(): # we get all items for client's roster 470 for item in client.roster.getItems(): # we get all items for client's roster
398 # and convert them to expected format 471 # and convert them to expected format
399 attr = client.roster.getAttributes(item) 472 attr = client.roster.getAttributes(item)
465 538
466 @param profile_key: %(doc_profile_key)s 539 @param profile_key: %(doc_profile_key)s
467 @return: list of clients 540 @return: list of clients
468 """ 541 """
469 if not profile_key: 542 if not profile_key:
470 raise exceptions.DataError(_(u'profile_key must not be empty')) 543 raise exceptions.DataError(_(u"profile_key must not be empty"))
471 try: 544 try:
472 profile = self.memory.getProfileName(profile_key, True) 545 profile = self.memory.getProfileName(profile_key, True)
473 except exceptions.ProfileUnknownError: 546 except exceptions.ProfileUnknownError:
474 return [] 547 return []
475 if profile == C.PROF_KEY_ALL: 548 if profile == C.PROF_KEY_ALL:
476 return self.profiles.values() 549 return self.profiles.values()
477 elif profile[0] == '@': # only profile keys can start with "@" 550 elif profile[0] == "@": #  only profile keys can start with "@"
478 raise exceptions.ProfileKeyUnknown 551 raise exceptions.ProfileKeyUnknown
479 return [self.profiles[profile]] 552 return [self.profiles[profile]]
480 553
481 def _getConfig(self, section, name): 554 def _getConfig(self, section, name):
482 """Get the main configuration option 555 """Get the main configuration option
483 556
484 @param section: section of the config file (None or '' for DEFAULT) 557 @param section: section of the config file (None or '' for DEFAULT)
485 @param name: name of the option 558 @param name: name of the option
486 @return: unicode representation of the option 559 @return: unicode representation of the option
487 """ 560 """
488 return unicode(self.memory.getConfig(section, name, '')) 561 return unicode(self.memory.getConfig(section, name, ""))
489 562
490 def logErrback(self, failure_): 563 def logErrback(self, failure_):
491 """generic errback logging 564 """generic errback logging
492 565
493 can be used as last errback to show unexpected error 566 can be used as last errback to show unexpected error
494 """ 567 """
495 log.error(_(u"Unexpected error: {}".format(failure_))) 568 log.error(_(u"Unexpected error: {}".format(failure_)))
496 return failure_ 569 return failure_
497 570
498 # namespaces 571 #  namespaces
499 572
500 def registerNamespace(self, short_name, namespace): 573 def registerNamespace(self, short_name, namespace):
501 """associate a namespace to a short name""" 574 """associate a namespace to a short name"""
502 if short_name in self.ns_map: 575 if short_name in self.ns_map:
503 raise exceptions.ConflictError(u'this short name is already used') 576 raise exceptions.ConflictError(u"this short name is already used")
504 self.ns_map[short_name] = namespace 577 self.ns_map[short_name] = namespace
505 578
506 def getNamespaces(self): 579 def getNamespaces(self):
507 return self.ns_map 580 return self.ns_map
508 581
509 def getSessionInfos(self, profile_key): 582 def getSessionInfos(self, profile_key):
510 """compile interesting data on current profile session""" 583 """compile interesting data on current profile session"""
511 client = self.getClient(profile_key) 584 client = self.getClient(profile_key)
512 data = { 585 data = {"jid": client.jid.full(), "started": unicode(int(client.started))}
513 "jid": client.jid.full(),
514 "started": unicode(int(client.started)),
515 }
516 return defer.succeed(data) 586 return defer.succeed(data)
517 587
518 # local dirs 588 # local dirs
519 589
520 def getLocalPath(self, client, dir_name, *extra_path, **kwargs): 590 def getLocalPath(self, client, dir_name, *extra_path, **kwargs):
529 @param *extra_path: extra path element(s) to use 599 @param *extra_path: extra path element(s) to use
530 @return (unicode): path 600 @return (unicode): path
531 """ 601 """
532 # FIXME: component and profile are parsed with **kwargs because of python 2 limitations 602 # FIXME: component and profile are parsed with **kwargs because of python 2 limitations
533 # once moved to python 3, this can be fixed 603 # once moved to python 3, this can be fixed
534 component = kwargs.pop('component', False) 604 component = kwargs.pop("component", False)
535 profile = kwargs.pop('profile', True) 605 profile = kwargs.pop("profile", True)
536 assert not kwargs 606 assert not kwargs
537 607
538 path_elts = [self.memory.getConfig('', 'local_dir')] 608 path_elts = [self.memory.getConfig("", "local_dir")]
539 if component: 609 if component:
540 path_elts.append(C.COMPONENTS_DIR) 610 path_elts.append(C.COMPONENTS_DIR)
541 path_elts.append(regex.pathEscape(dir_name)) 611 path_elts.append(regex.pathEscape(dir_name))
542 if extra_path: 612 if extra_path:
543 path_elts.extend([regex.pathEscape(p) for p in extra_path]) 613 path_elts.extend([regex.pathEscape(p) for p in extra_path])
559 @param profile_key: key_word or profile name to determine profile name 629 @param profile_key: key_word or profile name to determine profile name
560 @return: True if connected 630 @return: True if connected
561 """ 631 """
562 profile = self.memory.getProfileName(profile_key) 632 profile = self.memory.getProfileName(profile_key)
563 if not profile: 633 if not profile:
564 log.error(_('asking connection status for a non-existant profile')) 634 log.error(_("asking connection status for a non-existant profile"))
565 raise exceptions.ProfileUnknownError(profile_key) 635 raise exceptions.ProfileUnknownError(profile_key)
566 if profile not in self.profiles: 636 if profile not in self.profiles:
567 return False 637 return False
568 return self.profiles[profile].isConnected() 638 return self.profiles[profile].isConnected()
569 639
570 ## XMPP methods ## 640 ## XMPP methods ##
571 641
572 def _messageSend(self, to_jid_s, message, subject=None, mess_type='auto', extra=None, profile_key=C.PROF_KEY_NONE): 642 def _messageSend(
643 self,
644 to_jid_s,
645 message,
646 subject=None,
647 mess_type="auto",
648 extra=None,
649 profile_key=C.PROF_KEY_NONE,
650 ):
573 client = self.getClient(profile_key) 651 client = self.getClient(profile_key)
574 to_jid = jid.JID(to_jid_s) 652 to_jid = jid.JID(to_jid_s)
575 #XXX: we need to use the dictionary comprehension because D-Bus return its own types, and pickle can't manage them. TODO: Need to find a better way 653 # XXX: we need to use the dictionary comprehension because D-Bus return its own types, and pickle can't manage them. TODO: Need to find a better way
576 return client.sendMessage(to_jid, message, subject, mess_type, {unicode(key): unicode(value) for key, value in extra.items()}) 654 return client.sendMessage(
655 to_jid,
656 message,
657 subject,
658 mess_type,
659 {unicode(key): unicode(value) for key, value in extra.items()},
660 )
577 661
578 def _setPresence(self, to="", show="", statuses=None, profile_key=C.PROF_KEY_NONE): 662 def _setPresence(self, to="", show="", statuses=None, profile_key=C.PROF_KEY_NONE):
579 return self.setPresence(jid.JID(to) if to else None, show, statuses, profile_key) 663 return self.setPresence(jid.JID(to) if to else None, show, statuses, profile_key)
580 664
581 def setPresence(self, to_jid=None, show="", statuses=None, profile_key=C.PROF_KEY_NONE): 665 def setPresence(
666 self, to_jid=None, show="", statuses=None, profile_key=C.PROF_KEY_NONE
667 ):
582 """Send our presence information""" 668 """Send our presence information"""
583 if statuses is None: 669 if statuses is None:
584 statuses = {} 670 statuses = {}
585 profile = self.memory.getProfileName(profile_key) 671 profile = self.memory.getProfileName(profile_key)
586 assert profile 672 assert profile
587 priority = int(self.memory.getParamA("Priority", "Connection", profile_key=profile)) 673 priority = int(
674 self.memory.getParamA("Priority", "Connection", profile_key=profile)
675 )
588 self.profiles[profile].presence.available(to_jid, show, statuses, priority) 676 self.profiles[profile].presence.available(to_jid, show, statuses, priority)
589 #XXX: FIXME: temporary fix to work around openfire 3.7.0 bug (presence is not broadcasted to generating resource) 677 # XXX: FIXME: temporary fix to work around openfire 3.7.0 bug (presence is not broadcasted to generating resource)
590 if '' in statuses: 678 if "" in statuses:
591 statuses[C.PRESENCE_STATUSES_DEFAULT] = statuses.pop('') 679 statuses[C.PRESENCE_STATUSES_DEFAULT] = statuses.pop("")
592 self.bridge.presenceUpdate(self.profiles[profile].jid.full(), show, 680 self.bridge.presenceUpdate(
593 int(priority), statuses, profile) 681 self.profiles[profile].jid.full(), show, int(priority), statuses, profile
682 )
594 683
595 def subscription(self, subs_type, raw_jid, profile_key): 684 def subscription(self, subs_type, raw_jid, profile_key):
596 """Called to manage subscription 685 """Called to manage subscription
597 @param subs_type: subsciption type (cf RFC 3921) 686 @param subs_type: subsciption type (cf RFC 3921)
598 @param raw_jid: unicode entity's jid 687 @param raw_jid: unicode entity's jid
599 @param profile_key: profile""" 688 @param profile_key: profile"""
600 profile = self.memory.getProfileName(profile_key) 689 profile = self.memory.getProfileName(profile_key)
601 assert profile 690 assert profile
602 to_jid = jid.JID(raw_jid) 691 to_jid = jid.JID(raw_jid)
603 log.debug(_(u'subsciption request [%(subs_type)s] for %(jid)s') % {'subs_type': subs_type, 'jid': to_jid.full()}) 692 log.debug(
693 _(u"subsciption request [%(subs_type)s] for %(jid)s")
694 % {"subs_type": subs_type, "jid": to_jid.full()}
695 )
604 if subs_type == "subscribe": 696 if subs_type == "subscribe":
605 self.profiles[profile].presence.subscribe(to_jid) 697 self.profiles[profile].presence.subscribe(to_jid)
606 elif subs_type == "subscribed": 698 elif subs_type == "subscribed":
607 self.profiles[profile].presence.subscribed(to_jid) 699 self.profiles[profile].presence.subscribed(to_jid)
608 elif subs_type == "unsubscribe": 700 elif subs_type == "unsubscribe":
669 return self.memory.disco.findServiceEntities(*args, **kwargs) 761 return self.memory.disco.findServiceEntities(*args, **kwargs)
670 762
671 def findFeaturesSet(self, *args, **kwargs): 763 def findFeaturesSet(self, *args, **kwargs):
672 return self.memory.disco.findFeaturesSet(*args, **kwargs) 764 return self.memory.disco.findFeaturesSet(*args, **kwargs)
673 765
674 def _findByFeatures(self, namespaces, identities, bare_jids, service, roster, own_jid, local_device, profile_key): 766 def _findByFeatures(
767 self,
768 namespaces,
769 identities,
770 bare_jids,
771 service,
772 roster,
773 own_jid,
774 local_device,
775 profile_key,
776 ):
675 client = self.getClient(profile_key) 777 client = self.getClient(profile_key)
676 return self.findByFeatures(client, namespaces, identities, bare_jids, service, roster, own_jid, local_device) 778 return self.findByFeatures(
779 client,
780 namespaces,
781 identities,
782 bare_jids,
783 service,
784 roster,
785 own_jid,
786 local_device,
787 )
677 788
678 @defer.inlineCallbacks 789 @defer.inlineCallbacks
679 def findByFeatures(self, client, namespaces, identities=None, bare_jids=False, service=True, roster=True, own_jid=True, local_device=False): 790 def findByFeatures(
791 self,
792 client,
793 namespaces,
794 identities=None,
795 bare_jids=False,
796 service=True,
797 roster=True,
798 own_jid=True,
799 local_device=False,
800 ):
680 """retrieve all services or contacts managing a set a features 801 """retrieve all services or contacts managing a set a features
681 802
682 @param namespaces(list[unicode]): features which must be handled 803 @param namespaces(list[unicode]): features which must be handled
683 @param identities(list[tuple[unicode,unicode]], None): if not None or empty, only keep those identities 804 @param identities(list[tuple[unicode,unicode]], None): if not None or empty, only keep those identities
684 tuple must by (category, type) 805 tuple must by (category, type)
695 - roster entities 816 - roster entities
696 """ 817 """
697 if not identities: 818 if not identities:
698 identities = None 819 identities = None
699 if not namespaces and not identities: 820 if not namespaces and not identities:
700 raise exceptions.DataError("at least one namespace or one identity must be set") 821 raise exceptions.DataError(
822 "at least one namespace or one identity must be set"
823 )
701 found_service = {} 824 found_service = {}
702 found_own = {} 825 found_own = {}
703 found_roster = {} 826 found_roster = {}
704 if service: 827 if service:
705 services_jids = yield self.findFeaturesSet(client, namespaces) 828 services_jids = yield self.findFeaturesSet(client, namespaces)
706 for service_jid in services_jids: 829 for service_jid in services_jids:
707 infos = yield self.getDiscoInfos(client, service_jid) 830 infos = yield self.getDiscoInfos(client, service_jid)
708 if identities is not None and not set(infos.identities.keys()).issuperset(identities): 831 if identities is not None and not set(infos.identities.keys()).issuperset(
832 identities
833 ):
709 continue 834 continue
710 found_identities = [(cat, type_, name or u'') for (cat, type_), name in infos.identities.iteritems()] 835 found_identities = [
836 (cat, type_, name or u"")
837 for (cat, type_), name in infos.identities.iteritems()
838 ]
711 found_service[service_jid.full()] = found_identities 839 found_service[service_jid.full()] = found_identities
712 840
713 jids = [] 841 jids = []
714 if roster: 842 if roster:
715 jids.extend(client.roster.getJids()) 843 jids.extend(client.roster.getJids())
716 if own_jid: 844 if own_jid:
717 jids.append(client.jid.userhostJID()) 845 jids.append(client.jid.userhostJID())
718 846
719 for found, jids in ((found_own, [client.jid.userhostJID()]), 847 for found, jids in (
720 (found_roster, client.roster.getJids())): 848 (found_own, [client.jid.userhostJID()]),
849 (found_roster, client.roster.getJids()),
850 ):
721 for jid_ in jids: 851 for jid_ in jids:
722 if jid_.resource: 852 if jid_.resource:
723 if bare_jids: 853 if bare_jids:
724 continue 854 continue
725 resources = [jid_.resource] 855 resources = [jid_.resource]
735 full_jid = jid.JID(tuple=(jid_.user, jid_.host, resource)) 865 full_jid = jid.JID(tuple=(jid_.user, jid_.host, resource))
736 if full_jid == client.jid and not local_device: 866 if full_jid == client.jid and not local_device:
737 continue 867 continue
738 infos = yield self.getDiscoInfos(client, full_jid) 868 infos = yield self.getDiscoInfos(client, full_jid)
739 if infos.features.issuperset(namespaces): 869 if infos.features.issuperset(namespaces):
740 if identities is not None and not set(infos.identities.keys()).issuperset(identities): 870 if identities is not None and not set(
871 infos.identities.keys()
872 ).issuperset(identities):
741 continue 873 continue
742 found_identities = [(cat, type_, name or u'') for (cat, type_), name in infos.identities.iteritems()] 874 found_identities = [
875 (cat, type_, name or u"")
876 for (cat, type_), name in infos.identities.iteritems()
877 ]
743 found[full_jid.full()] = found_identities 878 found[full_jid.full()] = found_identities
744 879
745 defer.returnValue((found_service, found_own, found_roster)) 880 defer.returnValue((found_service, found_own, found_roster))
746 881
747 ## Generic HMI ## 882 ## Generic HMI ##
748 883
749 def _killAction(self, keep_id, client): 884 def _killAction(self, keep_id, client):
750 log.debug(u"Killing action {} for timeout".format(keep_id)) 885 log.debug(u"Killing action {} for timeout".format(keep_id))
751 client.actions[keep_id] 886 client.actions[keep_id]
752 887
753 def actionNew(self, action_data, security_limit=C.NO_SECURITY_LIMIT, keep_id=None, profile=C.PROF_KEY_NONE): 888 def actionNew(
889 self,
890 action_data,
891 security_limit=C.NO_SECURITY_LIMIT,
892 keep_id=None,
893 profile=C.PROF_KEY_NONE,
894 ):
754 """Shortcut to bridge.actionNew which generate and id and keep for retrieval 895 """Shortcut to bridge.actionNew which generate and id and keep for retrieval
755 896
756 @param action_data(dict): action data (see bridge documentation) 897 @param action_data(dict): action data (see bridge documentation)
757 @param security_limit: %(doc_security_limit)s 898 @param security_limit: %(doc_security_limit)s
758 @param keep_id(None, unicode): if not None, used to keep action for differed retrieval 899 @param keep_id(None, unicode): if not None, used to keep action for differed retrieval
761 @param profile: %(doc_profile)s 902 @param profile: %(doc_profile)s
762 """ 903 """
763 id_ = unicode(uuid.uuid4()) 904 id_ = unicode(uuid.uuid4())
764 if keep_id is not None: 905 if keep_id is not None:
765 client = self.getClient(profile) 906 client = self.getClient(profile)
766 action_timer = reactor.callLater(60*30, self._killAction, keep_id, client) 907 action_timer = reactor.callLater(60 * 30, self._killAction, keep_id, client)
767 client.actions[keep_id] = (action_data, id_, security_limit, action_timer) 908 client.actions[keep_id] = (action_data, id_, security_limit, action_timer)
768 909
769 self.bridge.actionNew(action_data, id_, security_limit, profile) 910 self.bridge.actionNew(action_data, id_, security_limit, profile)
770 911
771 def actionsGet(self, profile): 912 def actionsGet(self, profile):
774 @param profile: %(doc_profile)s 915 @param profile: %(doc_profile)s
775 """ 916 """
776 client = self.getClient(profile) 917 client = self.getClient(profile)
777 return [action_tuple[:-1] for action_tuple in client.actions.itervalues()] 918 return [action_tuple[:-1] for action_tuple in client.actions.itervalues()]
778 919
779 def registerProgressCb(self, progress_id, callback, metadata=None, profile=C.PROF_KEY_NONE): 920 def registerProgressCb(
921 self, progress_id, callback, metadata=None, profile=C.PROF_KEY_NONE
922 ):
780 """Register a callback called when progress is requested for id""" 923 """Register a callback called when progress is requested for id"""
781 if metadata is None: 924 if metadata is None:
782 metadata = {} 925 metadata = {}
783 client = self.getClient(profile) 926 client = self.getClient(profile)
784 if progress_id in client._progress_cb: 927 if progress_id in client._progress_cb:
793 except KeyError: 936 except KeyError:
794 log.error(_(u"Trying to remove an unknow progress callback")) 937 log.error(_(u"Trying to remove an unknow progress callback"))
795 938
796 def _progressGet(self, progress_id, profile): 939 def _progressGet(self, progress_id, profile):
797 data = self.progressGet(progress_id, profile) 940 data = self.progressGet(progress_id, profile)
798 return {k: unicode(v) for k,v in data.iteritems()} 941 return {k: unicode(v) for k, v in data.iteritems()}
799 942
800 def progressGet(self, progress_id, profile): 943 def progressGet(self, progress_id, profile):
801 """Return a dict with progress information 944 """Return a dict with progress information
802 945
803 @param progress_id(unicode): unique id of the progressing element 946 @param progress_id(unicode): unique id of the progressing element
835 progress_all = {} 978 progress_all = {}
836 for client in clients: 979 for client in clients:
837 profile = client.profile 980 profile = client.profile
838 progress_dict = {} 981 progress_dict = {}
839 progress_all[profile] = progress_dict 982 progress_all[profile] = progress_dict
840 for progress_id, (dummy, progress_metadata) in client._progress_cb.iteritems(): 983 for (
984 progress_id,
985 (dummy, progress_metadata),
986 ) in client._progress_cb.iteritems():
841 progress_dict[progress_id] = progress_metadata 987 progress_dict[progress_id] = progress_metadata
842 return progress_all 988 return progress_all
843 989
844 def progressGetAll(self, profile_key): 990 def progressGetAll(self, profile_key):
845 """Return all progress status at once 991 """Return all progress status at once
868 with_data(bool): True if the callback use the optional data dict 1014 with_data(bool): True if the callback use the optional data dict
869 force_id(unicode): id to avoid generated id. Can lead to name conflict, avoid if possible 1015 force_id(unicode): id to avoid generated id. Can lead to name conflict, avoid if possible
870 one_shot(bool): True to delete callback once it have been called 1016 one_shot(bool): True to delete callback once it have been called
871 @return: id of the registered callback 1017 @return: id of the registered callback
872 """ 1018 """
873 callback_id = kwargs.pop('force_id', None) 1019 callback_id = kwargs.pop("force_id", None)
874 if callback_id is None: 1020 if callback_id is None:
875 callback_id = str(uuid.uuid4()) 1021 callback_id = str(uuid.uuid4())
876 else: 1022 else:
877 if callback_id in self._cb_map: 1023 if callback_id in self._cb_map:
878 raise exceptions.ConflictError(_(u"id already registered")) 1024 raise exceptions.ConflictError(_(u"id already registered"))
879 self._cb_map[callback_id] = (callback, args, kwargs) 1025 self._cb_map[callback_id] = (callback, args, kwargs)
880 1026
881 if "one_shot" in kwargs: # One Shot callback are removed after 30 min 1027 if "one_shot" in kwargs: # One Shot callback are removed after 30 min
1028
882 def purgeCallback(): 1029 def purgeCallback():
883 try: 1030 try:
884 self.removeCallback(callback_id) 1031 self.removeCallback(callback_id)
885 except KeyError: 1032 except KeyError:
886 pass 1033 pass
1034
887 reactor.callLater(1800, purgeCallback) 1035 reactor.callLater(1800, purgeCallback)
888 1036
889 return callback_id 1037 return callback_id
890 1038
891 def removeCallback(self, callback_id): 1039 def removeCallback(self, callback_id):
904 - xmlui: a XMLUI need to be displayed 1052 - xmlui: a XMLUI need to be displayed
905 - validated: if present, can be used to launch a callback, it can have the values 1053 - validated: if present, can be used to launch a callback, it can have the values
906 - C.BOOL_TRUE 1054 - C.BOOL_TRUE
907 - C.BOOL_FALSE 1055 - C.BOOL_FALSE
908 """ 1056 """
909 # FIXME: security limit need to be checked here 1057 #  FIXME: security limit need to be checked here
910 try: 1058 try:
911 client = self.getClient(profile_key) 1059 client = self.getClient(profile_key)
912 except exceptions.NotFound: 1060 except exceptions.NotFound:
913 # client is not available yet 1061 # client is not available yet
914 profile = self.memory.getProfileName(profile_key) 1062 profile = self.memory.getProfileName(profile_key)
915 if not profile: 1063 if not profile:
916 raise exceptions.ProfileUnknownError(_(u'trying to launch action with a non-existant profile')) 1064 raise exceptions.ProfileUnknownError(
1065 _(u"trying to launch action with a non-existant profile")
1066 )
917 else: 1067 else:
918 profile = client.profile 1068 profile = client.profile
919 # we check if the action is kept, and remove it 1069 # we check if the action is kept, and remove it
920 try: 1070 try:
921 action_tuple = client.actions[callback_id] 1071 action_tuple = client.actions[callback_id]
922 except KeyError: 1072 except KeyError:
923 pass 1073 pass
924 else: 1074 else:
925 action_tuple[-1].cancel() # the last item is the action timer 1075 action_tuple[-1].cancel() # the last item is the action timer
926 del client.actions[callback_id] 1076 del client.actions[callback_id]
927 1077
928 try: 1078 try:
929 callback, args, kwargs = self._cb_map[callback_id] 1079 callback, args, kwargs = self._cb_map[callback_id]
930 except KeyError: 1080 except KeyError:
931 raise exceptions.DataError(u"Unknown callback id {}".format(callback_id)) 1081 raise exceptions.DataError(u"Unknown callback id {}".format(callback_id))
932 1082
933 if kwargs.get("with_data", False): 1083 if kwargs.get("with_data", False):
934 if data is None: 1084 if data is None:
935 raise exceptions.DataError("Required data for this callback is missing") 1085 raise exceptions.DataError("Required data for this callback is missing")
936 args,kwargs=list(args)[:],kwargs.copy() # we don't want to modify the original (kw)args 1086 args, kwargs = (
1087 list(args)[:],
1088 kwargs.copy(),
1089 ) # we don't want to modify the original (kw)args
937 args.insert(0, data) 1090 args.insert(0, data)
938 kwargs["profile"] = profile 1091 kwargs["profile"] = profile
939 del kwargs["with_data"] 1092 del kwargs["with_data"]
940 1093
941 if kwargs.pop('one_shot', False): 1094 if kwargs.pop("one_shot", False):
942 self.removeCallback(callback_id) 1095 self.removeCallback(callback_id)
943 1096
944 return defer.maybeDeferred(callback, *args, **kwargs) 1097 return defer.maybeDeferred(callback, *args, **kwargs)
945 1098
946 #Menus management 1099 # Menus management
947 1100
948 def _getMenuCanonicalPath(self, path): 1101 def _getMenuCanonicalPath(self, path):
949 """give canonical form of path 1102 """give canonical form of path
950 1103
951 canonical form is a tuple of the path were every element is stripped and lowercase 1104 canonical form is a tuple of the path were every element is stripped and lowercase
952 @param path(iterable[unicode]): untranslated path to menu 1105 @param path(iterable[unicode]): untranslated path to menu
953 @return (tuple[unicode]): canonical form of path 1106 @return (tuple[unicode]): canonical form of path
954 """ 1107 """
955 return tuple((p.lower().strip() for p in path)) 1108 return tuple((p.lower().strip() for p in path))
956 1109
957 def importMenu(self, path, callback, security_limit=C.NO_SECURITY_LIMIT, help_string="", type_=C.MENU_GLOBAL): 1110 def importMenu(
1111 self,
1112 path,
1113 callback,
1114 security_limit=C.NO_SECURITY_LIMIT,
1115 help_string="",
1116 type_=C.MENU_GLOBAL,
1117 ):
958 """register a new menu for frontends 1118 """register a new menu for frontends
959 1119
960 @param path(iterable[unicode]): path to go to the menu (category/subcategory/.../item) (e.g.: ("File", "Open")) 1120 @param path(iterable[unicode]): path to go to the menu (category/subcategory/.../item) (e.g.: ("File", "Open"))
961 /!\ use D_() instead of _() for translations (e.g. (D_("File"), D_("Open"))) 1121 /!\ use D_() instead of _() for translations (e.g. (D_("File"), D_("Open")))
962 untranslated/lower case path can be used to identity a menu, for this reason it must be unique independently of case. 1122 untranslated/lower case path can be used to identity a menu, for this reason it must be unique independently of case.
987 callback_id = callback 1147 callback_id = callback
988 try: 1148 try:
989 callback, args, kwargs = self._cb_map[callback_id] 1149 callback, args, kwargs = self._cb_map[callback_id]
990 except KeyError: 1150 except KeyError:
991 raise exceptions.DataError("Unknown callback id") 1151 raise exceptions.DataError("Unknown callback id")
992 kwargs["with_data"] = True # we have to be sure that we use extra data 1152 kwargs["with_data"] = True # we have to be sure that we use extra data
993 else: 1153 else:
994 raise exceptions.DataError("Unknown callback type") 1154 raise exceptions.DataError("Unknown callback type")
995 1155
996 for menu_data in self._menus.itervalues(): 1156 for menu_data in self._menus.itervalues():
997 if menu_data['path'] == path and menu_data['type'] == type_: 1157 if menu_data["path"] == path and menu_data["type"] == type_:
998 raise exceptions.ConflictError(_("A menu with the same path and type already exists")) 1158 raise exceptions.ConflictError(
1159 _("A menu with the same path and type already exists")
1160 )
999 1161
1000 path_canonical = self._getMenuCanonicalPath(path) 1162 path_canonical = self._getMenuCanonicalPath(path)
1001 menu_key = (type_, path_canonical) 1163 menu_key = (type_, path_canonical)
1002 1164
1003 if menu_key in self._menus_paths: 1165 if menu_key in self._menus_paths:
1004 raise exceptions.ConflictError(u"this menu path is already used: {path} ({menu_key})".format( 1166 raise exceptions.ConflictError(
1005 path=path_canonical, menu_key=menu_key)) 1167 u"this menu path is already used: {path} ({menu_key})".format(
1006 1168 path=path_canonical, menu_key=menu_key
1007 menu_data = {'path': tuple(path), 1169 )
1008 'path_canonical': path_canonical, 1170 )
1009 'security_limit': security_limit, 1171
1010 'help_string': help_string, 1172 menu_data = {
1011 'type': type_ 1173 "path": tuple(path),
1012 } 1174 "path_canonical": path_canonical,
1175 "security_limit": security_limit,
1176 "help_string": help_string,
1177 "type": type_,
1178 }
1013 1179
1014 self._menus[callback_id] = menu_data 1180 self._menus[callback_id] = menu_data
1015 self._menus_paths[menu_key] = callback_id 1181 self._menus_paths[menu_key] = callback_id
1016 1182
1017 return callback_id 1183 return callback_id
1018 1184
1019 def getMenus(self, language='', security_limit=C.NO_SECURITY_LIMIT): 1185 def getMenus(self, language="", security_limit=C.NO_SECURITY_LIMIT):
1020 """Return all menus registered 1186 """Return all menus registered
1021 1187
1022 @param language: language used for translation, or empty string for default 1188 @param language: language used for translation, or empty string for default
1023 @param security_limit: %(doc_security_limit)s 1189 @param security_limit: %(doc_security_limit)s
1024 @return: array of tuple with: 1190 @return: array of tuple with:
1030 - icon: name of the icon to use (TODO) 1196 - icon: name of the icon to use (TODO)
1031 - help_url: link to a page with more complete documentation (TODO) 1197 - help_url: link to a page with more complete documentation (TODO)
1032 """ 1198 """
1033 ret = [] 1199 ret = []
1034 for menu_id, menu_data in self._menus.iteritems(): 1200 for menu_id, menu_data in self._menus.iteritems():
1035 type_ = menu_data['type'] 1201 type_ = menu_data["type"]
1036 path = menu_data['path'] 1202 path = menu_data["path"]
1037 menu_security_limit = menu_data['security_limit'] 1203 menu_security_limit = menu_data["security_limit"]
1038 if security_limit!=C.NO_SECURITY_LIMIT and (menu_security_limit==C.NO_SECURITY_LIMIT or menu_security_limit>security_limit): 1204 if security_limit != C.NO_SECURITY_LIMIT and (
1205 menu_security_limit == C.NO_SECURITY_LIMIT
1206 or menu_security_limit > security_limit
1207 ):
1039 continue 1208 continue
1040 languageSwitch(language) 1209 languageSwitch(language)
1041 path_i18n = [_(elt) for elt in path] 1210 path_i18n = [_(elt) for elt in path]
1042 languageSwitch() 1211 languageSwitch()
1043 extra = {} # TODO: manage extra data like icon 1212 extra = {} # TODO: manage extra data like icon
1044 ret.append((menu_id, type_, path, path_i18n, extra)) 1213 ret.append((menu_id, type_, path, path_i18n, extra))
1045 1214
1046 return ret 1215 return ret
1047 1216
1048 def _launchMenu(self, menu_type, path, data=None, security_limit=C.NO_SECURITY_LIMIT, profile_key=C.PROF_KEY_NONE): 1217 def _launchMenu(
1218 self,
1219 menu_type,
1220 path,
1221 data=None,
1222 security_limit=C.NO_SECURITY_LIMIT,
1223 profile_key=C.PROF_KEY_NONE,
1224 ):
1049 client = self.getClient(profile_key) 1225 client = self.getClient(profile_key)
1050 return self.launchMenu(client, menu_type, path, data, security_limit) 1226 return self.launchMenu(client, menu_type, path, data, security_limit)
1051 1227
1052 def launchMenu(self, client, menu_type, path, data=None, security_limit=C.NO_SECURITY_LIMIT): 1228 def launchMenu(
1229 self, client, menu_type, path, data=None, security_limit=C.NO_SECURITY_LIMIT
1230 ):
1053 """launch action a menu action 1231 """launch action a menu action
1054 1232
1055 @param menu_type(unicode): type of menu to launch 1233 @param menu_type(unicode): type of menu to launch
1056 @param path(iterable[unicode]): canonical path of the menu 1234 @param path(iterable[unicode]): canonical path of the menu
1057 @params data(dict): menu data 1235 @params data(dict): menu data
1062 canonical_path = self._getMenuCanonicalPath(path) 1240 canonical_path = self._getMenuCanonicalPath(path)
1063 menu_key = (menu_type, canonical_path) 1241 menu_key = (menu_type, canonical_path)
1064 try: 1242 try:
1065 callback_id = self._menus_paths[menu_key] 1243 callback_id = self._menus_paths[menu_key]
1066 except KeyError: 1244 except KeyError:
1067 raise exceptions.NotFound(u"Can't find menu {path} ({menu_type})".format( 1245 raise exceptions.NotFound(
1068 path=canonical_path, menu_type=menu_type)) 1246 u"Can't find menu {path} ({menu_type})".format(
1247 path=canonical_path, menu_type=menu_type
1248 )
1249 )
1069 return self.launchCallback(callback_id, data, client.profile) 1250 return self.launchCallback(callback_id, data, client.profile)
1070 1251
1071 def getMenuHelp(self, menu_id, language=''): 1252 def getMenuHelp(self, menu_id, language=""):
1072 """return the help string of the menu 1253 """return the help string of the menu
1073 1254
1074 @param menu_id: id of the menu (same as callback_id) 1255 @param menu_id: id of the menu (same as callback_id)
1075 @param language: language used for translation, or empty string for default 1256 @param language: language used for translation, or empty string for default
1076 @param return: translated help 1257 @param return: translated help
1079 try: 1260 try:
1080 menu_data = self._menus[menu_id] 1261 menu_data = self._menus[menu_id]
1081 except KeyError: 1262 except KeyError:
1082 raise exceptions.DataError("Trying to access an unknown menu") 1263 raise exceptions.DataError("Trying to access an unknown menu")
1083 languageSwitch(language) 1264 languageSwitch(language)
1084 help_string = _(menu_data['help_string']) 1265 help_string = _(menu_data["help_string"])
1085 languageSwitch() 1266 languageSwitch()
1086 return help_string 1267 return help_string