comparison sat/core/sat_main.py @ 4037:524856bd7b19

massive refactoring to switch from camelCase to snake_case: historically, Libervia (SàT before) was using camelCase as allowed by PEP8 when using a pre-PEP8 code, to use the same coding style as in Twisted. However, snake_case is more readable and it's better to follow PEP8 best practices, so it has been decided to move on full snake_case. Because Libervia has a huge codebase, this ended with a ugly mix of camelCase and snake_case. To fix that, this patch does a big refactoring by renaming every function and method (including bridge) that are not coming from Twisted or Wokkel, to use fully snake_case. This is a massive change, and may result in some bugs.
author Goffi <goffi@goffi.org>
date Sat, 08 Apr 2023 13:54:42 +0200
parents 7bf7677b893d
children 2594e1951cf7
comparison
equal deleted inserted replaced
4036:c4464d7ae97b 4037:524856bd7b19
24 from pathlib import Path 24 from pathlib import Path
25 from typing import Optional, List, Tuple, Dict 25 from typing import Optional, List, Tuple, Dict
26 26
27 from wokkel.data_form import Option 27 from wokkel.data_form import Option
28 import sat 28 import sat
29 from sat.core.i18n import _, D_, languageSwitch 29 from sat.core.i18n import _, D_, language_switch
30 from sat.core import patches 30 from sat.core import patches
31 patches.apply() 31 patches.apply()
32 from twisted.application import service 32 from twisted.application import service
33 from twisted.internet import defer 33 from twisted.internet import defer
34 from twisted.words.protocols.jabber import jid 34 from twisted.words.protocols.jabber import jid
67 # value: menu id 67 # value: menu id
68 self.initialised = defer.Deferred() 68 self.initialised = defer.Deferred()
69 self.profiles = {} 69 self.profiles = {}
70 self.plugins = {} 70 self.plugins = {}
71 # map for short name to whole namespace, 71 # map for short name to whole namespace,
72 # extended by plugins with registerNamespace 72 # extended by plugins with register_namespace
73 self.ns_map = { 73 self.ns_map = {
74 "x-data": xmpp.NS_X_DATA, 74 "x-data": xmpp.NS_X_DATA,
75 "disco#info": xmpp.NS_DISCO_INFO, 75 "disco#info": xmpp.NS_DISCO_INFO,
76 } 76 }
77 77
82 trigger.TriggerManager() 82 trigger.TriggerManager()
83 ) 83 )
84 84
85 bridge_name = ( 85 bridge_name = (
86 os.getenv("LIBERVIA_BRIDGE_NAME") 86 os.getenv("LIBERVIA_BRIDGE_NAME")
87 or self.memory.getConfig("", "bridge", "dbus") 87 or self.memory.config_get("", "bridge", "dbus")
88 ) 88 )
89 89
90 bridge_module = dynamic_import.bridge(bridge_name) 90 bridge_module = dynamic_import.bridge(bridge_name)
91 if bridge_module is None: 91 if bridge_module is None:
92 log.error(f"Can't find bridge module of name {bridge_name}") 92 log.error(f"Can't find bridge module of name {bridge_name}")
93 sys.exit(1) 93 sys.exit(1)
94 log.info(f"using {bridge_name} bridge") 94 log.info(f"using {bridge_name} bridge")
95 try: 95 try:
96 self.bridge = bridge_module.Bridge() 96 self.bridge = bridge_module.bridge()
97 except exceptions.BridgeInitError: 97 except exceptions.BridgeInitError:
98 log.exception("Bridge can't be initialised, can't start Libervia Backend") 98 log.exception("bridge can't be initialised, can't start Libervia Backend")
99 sys.exit(1) 99 sys.exit(1)
100 100
101 defer.ensureDeferred(self._post_init()) 101 defer.ensureDeferred(self._post_init())
102 102
103 @property 103 @property
116 # we are in debug version, we add extra data 116 # we are in debug version, we add extra data
117 try: 117 try:
118 return self._version_cache 118 return self._version_cache
119 except AttributeError: 119 except AttributeError:
120 self._version_cache = "{} « {} » ({})".format( 120 self._version_cache = "{} « {} » ({})".format(
121 version, C.APP_RELEASE_NAME, utils.getRepositoryData(sat) 121 version, C.APP_RELEASE_NAME, utils.get_repository_data(sat)
122 ) 122 )
123 return self._version_cache 123 return self._version_cache
124 else: 124 else:
125 return version 125 return version
126 126
128 def bridge_name(self): 128 def bridge_name(self):
129 return os.path.splitext(os.path.basename(self.bridge.__file__))[0] 129 return os.path.splitext(os.path.basename(self.bridge.__file__))[0]
130 130
131 async def _post_init(self): 131 async def _post_init(self):
132 try: 132 try:
133 bridge_pi = self.bridge.postInit 133 bridge_pi = self.bridge.post_init
134 except AttributeError: 134 except AttributeError:
135 pass 135 pass
136 else: 136 else:
137 try: 137 try:
138 await bridge_pi() 138 await bridge_pi()
140 log.exception("Could not initialize bridge") 140 log.exception("Could not initialize bridge")
141 # because init is not complete at this stage, we use callLater 141 # because init is not complete at this stage, we use callLater
142 reactor.callLater(0, self.stop) 142 reactor.callLater(0, self.stop)
143 return 143 return
144 144
145 self.bridge.register_method("getReady", lambda: self.initialised) 145 self.bridge.register_method("ready_get", lambda: self.initialised)
146 self.bridge.register_method("getVersion", lambda: self.full_version) 146 self.bridge.register_method("version_get", lambda: self.full_version)
147 self.bridge.register_method("getFeatures", self.getFeatures) 147 self.bridge.register_method("features_get", self.features_get)
148 self.bridge.register_method("profileNameGet", self.memory.getProfileName) 148 self.bridge.register_method("profile_name_get", self.memory.get_profile_name)
149 self.bridge.register_method("profilesListGet", self.memory.getProfilesList) 149 self.bridge.register_method("profiles_list_get", self.memory.get_profiles_list)
150 self.bridge.register_method("getEntityData", self.memory._getEntityData) 150 self.bridge.register_method("entity_data_get", self.memory._get_entity_data)
151 self.bridge.register_method("getEntitiesData", self.memory._getEntitiesData) 151 self.bridge.register_method("entities_data_get", self.memory._get_entities_data)
152 self.bridge.register_method("profileCreate", self.memory.createProfile) 152 self.bridge.register_method("profile_create", self.memory.create_profile)
153 self.bridge.register_method("asyncDeleteProfile", self.memory.asyncDeleteProfile) 153 self.bridge.register_method("profile_delete_async", self.memory.profile_delete_async)
154 self.bridge.register_method("profileStartSession", self.memory.startSession) 154 self.bridge.register_method("profile_start_session", self.memory.start_session)
155 self.bridge.register_method( 155 self.bridge.register_method(
156 "profileIsSessionStarted", self.memory._isSessionStarted 156 "profile_is_session_started", self.memory._is_session_started
157 ) 157 )
158 self.bridge.register_method("profileSetDefault", self.memory.profileSetDefault) 158 self.bridge.register_method("profile_set_default", self.memory.profile_set_default)
159 self.bridge.register_method("connect", self._connect) 159 self.bridge.register_method("connect", self._connect)
160 self.bridge.register_method("disconnect", self.disconnect) 160 self.bridge.register_method("disconnect", self.disconnect)
161 self.bridge.register_method("contactGet", self._contactGet) 161 self.bridge.register_method("contact_get", self._contact_get)
162 self.bridge.register_method("getContacts", self.getContacts) 162 self.bridge.register_method("contacts_get", self.contacts_get)
163 self.bridge.register_method("getContactsFromGroup", self.getContactsFromGroup) 163 self.bridge.register_method("contacts_get_from_group", self.contacts_get_from_group)
164 self.bridge.register_method("getMainResource", self.memory._getMainResource) 164 self.bridge.register_method("main_resource_get", self.memory._get_main_resource)
165 self.bridge.register_method( 165 self.bridge.register_method(
166 "getPresenceStatuses", self.memory._getPresenceStatuses 166 "presence_statuses_get", self.memory._get_presence_statuses
167 ) 167 )
168 self.bridge.register_method("getWaitingSub", self.memory.getWaitingSub) 168 self.bridge.register_method("sub_waiting_get", self.memory.sub_waiting_get)
169 self.bridge.register_method("messageSend", self._messageSend) 169 self.bridge.register_method("message_send", self._message_send)
170 self.bridge.register_method("messageEncryptionStart", 170 self.bridge.register_method("message_encryption_start",
171 self._messageEncryptionStart) 171 self._message_encryption_start)
172 self.bridge.register_method("messageEncryptionStop", 172 self.bridge.register_method("message_encryption_stop",
173 self._messageEncryptionStop) 173 self._message_encryption_stop)
174 self.bridge.register_method("messageEncryptionGet", 174 self.bridge.register_method("message_encryption_get",
175 self._messageEncryptionGet) 175 self._message_encryption_get)
176 self.bridge.register_method("encryptionNamespaceGet", 176 self.bridge.register_method("encryption_namespace_get",
177 self._encryptionNamespaceGet) 177 self._encryption_namespace_get)
178 self.bridge.register_method("encryptionPluginsGet", self._encryptionPluginsGet) 178 self.bridge.register_method("encryption_plugins_get", self._encryption_plugins_get)
179 self.bridge.register_method("encryptionTrustUIGet", self._encryptionTrustUIGet) 179 self.bridge.register_method("encryption_trust_ui_get", self._encryption_trust_ui_get)
180 self.bridge.register_method("getConfig", self._getConfig) 180 self.bridge.register_method("config_get", self._get_config)
181 self.bridge.register_method("setParam", self.setParam) 181 self.bridge.register_method("param_set", self.param_set)
182 self.bridge.register_method("getParamA", self.memory.getStringParamA) 182 self.bridge.register_method("param_get_a", self.memory.get_string_param_a)
183 self.bridge.register_method("privateDataGet", self.memory._privateDataGet) 183 self.bridge.register_method("private_data_get", self.memory._private_data_get)
184 self.bridge.register_method("privateDataSet", self.memory._privateDataSet) 184 self.bridge.register_method("private_data_set", self.memory._private_data_set)
185 self.bridge.register_method("privateDataDelete", self.memory._privateDataDelete) 185 self.bridge.register_method("private_data_delete", self.memory._private_data_delete)
186 self.bridge.register_method("asyncGetParamA", self.memory.asyncGetStringParamA) 186 self.bridge.register_method("param_get_a_async", self.memory.async_get_string_param_a)
187 self.bridge.register_method( 187 self.bridge.register_method(
188 "asyncGetParamsValuesFromCategory", 188 "params_values_from_category_get_async",
189 self.memory._getParamsValuesFromCategory, 189 self.memory._get_params_values_from_category,
190 ) 190 )
191 self.bridge.register_method("getParamsUI", self.memory._getParamsUI) 191 self.bridge.register_method("param_ui_get", self.memory._get_params_ui)
192 self.bridge.register_method( 192 self.bridge.register_method(
193 "getParamsCategories", self.memory.getParamsCategories 193 "params_categories_get", self.memory.params_categories_get
194 ) 194 )
195 self.bridge.register_method("paramsRegisterApp", self.memory.paramsRegisterApp) 195 self.bridge.register_method("params_register_app", self.memory.params_register_app)
196 self.bridge.register_method("historyGet", self.memory._historyGet) 196 self.bridge.register_method("history_get", self.memory._history_get)
197 self.bridge.register_method("setPresence", self._setPresence) 197 self.bridge.register_method("presence_set", self._set_presence)
198 self.bridge.register_method("subscription", self.subscription) 198 self.bridge.register_method("subscription", self.subscription)
199 self.bridge.register_method("addContact", self._addContact) 199 self.bridge.register_method("contact_add", self._add_contact)
200 self.bridge.register_method("updateContact", self._updateContact) 200 self.bridge.register_method("contact_update", self._update_contact)
201 self.bridge.register_method("delContact", self._delContact) 201 self.bridge.register_method("contact_del", self._del_contact)
202 self.bridge.register_method("rosterResync", self._rosterResync) 202 self.bridge.register_method("roster_resync", self._roster_resync)
203 self.bridge.register_method("isConnected", self.isConnected) 203 self.bridge.register_method("is_connected", self.is_connected)
204 self.bridge.register_method("launchAction", self.launchCallback) 204 self.bridge.register_method("action_launch", self.launch_callback)
205 self.bridge.register_method("actionsGet", self.actionsGet) 205 self.bridge.register_method("actions_get", self.actions_get)
206 self.bridge.register_method("progressGet", self._progressGet) 206 self.bridge.register_method("progress_get", self._progress_get)
207 self.bridge.register_method("progressGetAll", self._progressGetAll) 207 self.bridge.register_method("progress_get_all", self._progress_get_all)
208 self.bridge.register_method("menusGet", self.getMenus) 208 self.bridge.register_method("menus_get", self.get_menus)
209 self.bridge.register_method("menuHelpGet", self.getMenuHelp) 209 self.bridge.register_method("menu_help_get", self.get_menu_help)
210 self.bridge.register_method("menuLaunch", self._launchMenu) 210 self.bridge.register_method("menu_launch", self._launch_menu)
211 self.bridge.register_method("discoInfos", self.memory.disco._discoInfos) 211 self.bridge.register_method("disco_infos", self.memory.disco._disco_infos)
212 self.bridge.register_method("discoItems", self.memory.disco._discoItems) 212 self.bridge.register_method("disco_items", self.memory.disco._disco_items)
213 self.bridge.register_method("discoFindByFeatures", self._findByFeatures) 213 self.bridge.register_method("disco_find_by_features", self._find_by_features)
214 self.bridge.register_method("saveParamsTemplate", self.memory.save_xml) 214 self.bridge.register_method("params_template_save", self.memory.save_xml)
215 self.bridge.register_method("loadParamsTemplate", self.memory.load_xml) 215 self.bridge.register_method("params_template_load", self.memory.load_xml)
216 self.bridge.register_method("sessionInfosGet", self.getSessionInfos) 216 self.bridge.register_method("session_infos_get", self.get_session_infos)
217 self.bridge.register_method("devicesInfosGet", self._getDevicesInfos) 217 self.bridge.register_method("devices_infos_get", self._get_devices_infos)
218 self.bridge.register_method("namespacesGet", self.getNamespaces) 218 self.bridge.register_method("namespaces_get", self.get_namespaces)
219 self.bridge.register_method("imageCheck", self._imageCheck) 219 self.bridge.register_method("image_check", self._image_check)
220 self.bridge.register_method("imageResize", self._imageResize) 220 self.bridge.register_method("image_resize", self._image_resize)
221 self.bridge.register_method("imageGeneratePreview", self._imageGeneratePreview) 221 self.bridge.register_method("image_generate_preview", self._image_generate_preview)
222 self.bridge.register_method("imageConvert", self._imageConvert) 222 self.bridge.register_method("image_convert", self._image_convert)
223 223
224 224
225 await self.memory.initialise() 225 await self.memory.initialise()
226 self.common_cache = cache.Cache(self, None) 226 self.common_cache = cache.Cache(self, None)
227 log.info(_("Memory initialised")) 227 log.info(_("Memory initialised"))
230 ui_contact_list.ContactList(self) 230 ui_contact_list.ContactList(self)
231 ui_profile_manager.ProfileManager(self) 231 ui_profile_manager.ProfileManager(self)
232 except Exception as e: 232 except Exception as e:
233 log.error(f"Could not initialize backend: {e}") 233 log.error(f"Could not initialize backend: {e}")
234 sys.exit(1) 234 sys.exit(1)
235 self._addBaseMenus() 235 self._add_base_menus()
236 236
237 self.initialised.callback(None) 237 self.initialised.callback(None)
238 log.info(_("Backend is ready")) 238 log.info(_("Backend is ready"))
239 239
240 # profile autoconnection must be done after self.initialised is called because 240 # profile autoconnection must be done after self.initialised is called because
241 # startSession waits for it. 241 # start_session waits for it.
242 autoconnect_dict = await self.memory.storage.getIndParamValues( 242 autoconnect_dict = await self.memory.storage.get_ind_param_values(
243 category='Connection', name='autoconnect_backend', 243 category='Connection', name='autoconnect_backend',
244 ) 244 )
245 profiles_autoconnect = [p for p, v in autoconnect_dict.items() if C.bool(v)] 245 profiles_autoconnect = [p for p, v in autoconnect_dict.items() if C.bool(v)]
246 if not self.trigger.point("profilesAutoconnect", profiles_autoconnect): 246 if not self.trigger.point("profilesAutoconnect", profiles_autoconnect):
247 return 247 return
262 _("Can't autoconnect profile {profile}: {reason}").format( 262 _("Can't autoconnect profile {profile}: {reason}").format(
263 profile = profile, 263 profile = profile,
264 reason = result) 264 reason = result)
265 ) 265 )
266 266
267 def _addBaseMenus(self): 267 def _add_base_menus(self):
268 """Add base menus""" 268 """Add base menus"""
269 encryption.EncryptionHandler._importMenus(self) 269 encryption.EncryptionHandler._import_menus(self)
270 270
271 def _unimport_plugin(self, plugin_path): 271 def _unimport_plugin(self, plugin_path):
272 """remove a plugin from sys.modules if it is there""" 272 """remove a plugin from sys.modules if it is there"""
273 try: 273 try:
274 del sys.modules[plugin_path] 274 del sys.modules[plugin_path]
275 except KeyError: 275 except KeyError:
276 pass 276 pass
277 277
278 def _import_plugins(self): 278 def _import_plugins(self):
279 """Import all plugins found in plugins directory""" 279 """import all plugins found in plugins directory"""
280 # FIXME: module imported but cancelled should be deleted 280 # FIXME: module imported but cancelled should be deleted
281 # TODO: make this more generic and reusable in tools.common 281 # TODO: make this more generic and reusable in tools.common
282 # FIXME: should use imp 282 # FIXME: should use imp
283 # TODO: do not import all plugins if no needed: component plugins are not needed 283 # TODO: do not import all plugins if no needed: component plugins are not needed
284 # if we just use a client, and plugin blacklisting should be possible in 284 # if we just use a client, and plugin blacklisting should be possible in
444 self.plugins[import_name].is_handler = False 444 self.plugins[import_name].is_handler = False
445 # we keep metadata as a Class attribute 445 # we keep metadata as a Class attribute
446 self.plugins[import_name]._info = plugin_info 446 self.plugins[import_name]._info = plugin_info
447 # TODO: test xmppclient presence and register handler parent 447 # TODO: test xmppclient presence and register handler parent
448 448
449 def pluginsUnload(self): 449 def plugins_unload(self):
450 """Call unload method on every loaded plugin, if exists 450 """Call unload method on every loaded plugin, if exists
451 451
452 @return (D): A deferred which return None when all method have been called 452 @return (D): A deferred which return None when all method have been called
453 """ 453 """
454 # TODO: in the futur, it should be possible to hot unload a plugin 454 # TODO: in the futur, it should be possible to hot unload a plugin
459 try: 459 try:
460 unload = plugin.unload 460 unload = plugin.unload
461 except AttributeError: 461 except AttributeError:
462 continue 462 continue
463 else: 463 else:
464 defers_list.append(utils.asDeferred(unload)) 464 defers_list.append(utils.as_deferred(unload))
465 return defers_list 465 return defers_list
466 466
467 def _connect(self, profile_key, password="", options=None): 467 def _connect(self, profile_key, password="", options=None):
468 profile = self.memory.getProfileName(profile_key) 468 profile = self.memory.get_profile_name(profile_key)
469 return defer.ensureDeferred(self.connect(profile, password, options)) 469 return defer.ensureDeferred(self.connect(profile, password, options))
470 470
471 async def connect( 471 async def connect(
472 self, profile, password="", options=None, max_retries=C.XMPP_MAX_RETRIES): 472 self, profile, password="", options=None, max_retries=C.XMPP_MAX_RETRIES):
473 """Connect a profile (i.e. connect client.component to XMPP server) 473 """Connect a profile (i.e. connect client.component to XMPP server)
485 @raise exceptions.PasswordError: Profile password is wrong 485 @raise exceptions.PasswordError: Profile password is wrong
486 """ 486 """
487 if options is None: 487 if options is None:
488 options = {} 488 options = {}
489 489
490 await self.memory.startSession(password, profile) 490 await self.memory.start_session(password, profile)
491 491
492 if self.isConnected(profile): 492 if self.is_connected(profile):
493 log.info(_("already connected !")) 493 log.info(_("already connected !"))
494 return True 494 return True
495 495
496 if self.memory.isComponent(profile): 496 if self.memory.is_component(profile):
497 await xmpp.SatXMPPComponent.startConnection(self, profile, max_retries) 497 await xmpp.SatXMPPComponent.start_connection(self, profile, max_retries)
498 else: 498 else:
499 await xmpp.SatXMPPClient.startConnection(self, profile, max_retries) 499 await xmpp.SatXMPPClient.start_connection(self, profile, max_retries)
500 500
501 return False 501 return False
502 502
503 def disconnect(self, profile_key): 503 def disconnect(self, profile_key):
504 """disconnect from jabber server""" 504 """disconnect from jabber server"""
505 # FIXME: client should not be deleted if only disconnected 505 # FIXME: client should not be deleted if only disconnected
506 # it shoud be deleted only when session is finished 506 # it shoud be deleted only when session is finished
507 if not self.isConnected(profile_key): 507 if not self.is_connected(profile_key):
508 # isConnected is checked here and not on client 508 # is_connected is checked here and not on client
509 # because client is deleted when session is ended 509 # because client is deleted when session is ended
510 log.info(_("not connected !")) 510 log.info(_("not connected !"))
511 return defer.succeed(None) 511 return defer.succeed(None)
512 client = self.getClient(profile_key) 512 client = self.get_client(profile_key)
513 return client.entityDisconnect() 513 return client.entity_disconnect()
514 514
515 def getFeatures(self, profile_key=C.PROF_KEY_NONE): 515 def features_get(self, profile_key=C.PROF_KEY_NONE):
516 """Get available features 516 """Get available features
517 517
518 Return list of activated plugins and plugin specific data 518 Return list of activated plugins and plugin specific data
519 @param profile_key: %(doc_profile_key)s 519 @param profile_key: %(doc_profile_key)s
520 C.PROF_KEY_NONE can be used to have general plugins data (i.e. not profile 520 C.PROF_KEY_NONE can be used to have general plugins data (i.e. not profile
526 If this method doesn't exists, an empty dict is returned. 526 If this method doesn't exists, an empty dict is returned.
527 """ 527 """
528 try: 528 try:
529 # FIXME: there is no method yet to check profile session 529 # FIXME: there is no method yet to check profile session
530 # as soon as one is implemented, it should be used here 530 # as soon as one is implemented, it should be used here
531 self.getClient(profile_key) 531 self.get_client(profile_key)
532 except KeyError: 532 except KeyError:
533 log.warning("Requesting features for a profile outside a session") 533 log.warning("Requesting features for a profile outside a session")
534 profile_key = C.PROF_KEY_NONE 534 profile_key = C.PROF_KEY_NONE
535 except exceptions.ProfileNotSetError: 535 except exceptions.ProfileNotSetError:
536 pass 536 pass
537 537
538 features = [] 538 features = []
539 for import_name, plugin in self.plugins.items(): 539 for import_name, plugin in self.plugins.items():
540 try: 540 try:
541 features_d = utils.asDeferred(plugin.getFeatures, profile_key) 541 features_d = utils.as_deferred(plugin.features_get, profile_key)
542 except AttributeError: 542 except AttributeError:
543 features_d = defer.succeed({}) 543 features_d = defer.succeed({})
544 features.append(features_d) 544 features.append(features_d)
545 545
546 d_list = defer.DeferredList(features) 546 d_list = defer.DeferredList(features)
547 547
548 def buildFeatures(result, import_names): 548 def build_features(result, import_names):
549 assert len(result) == len(import_names) 549 assert len(result) == len(import_names)
550 ret = {} 550 ret = {}
551 for name, (success, data) in zip(import_names, result): 551 for name, (success, data) in zip(import_names, result):
552 if success: 552 if success:
553 ret[name] = data 553 ret[name] = data
558 ) 558 )
559 ) 559 )
560 ret[name] = {} 560 ret[name] = {}
561 return ret 561 return ret
562 562
563 d_list.addCallback(buildFeatures, list(self.plugins.keys())) 563 d_list.addCallback(build_features, list(self.plugins.keys()))
564 return d_list 564 return d_list
565 565
566 def _contactGet(self, entity_jid_s, profile_key): 566 def _contact_get(self, entity_jid_s, profile_key):
567 client = self.getClient(profile_key) 567 client = self.get_client(profile_key)
568 entity_jid = jid.JID(entity_jid_s) 568 entity_jid = jid.JID(entity_jid_s)
569 return defer.ensureDeferred(self.getContact(client, entity_jid)) 569 return defer.ensureDeferred(self.get_contact(client, entity_jid))
570 570
571 async def getContact(self, client, entity_jid): 571 async def get_contact(self, client, entity_jid):
572 # we want to be sure that roster has been received 572 # we want to be sure that roster has been received
573 await client.roster.got_roster 573 await client.roster.got_roster
574 item = client.roster.getItem(entity_jid) 574 item = client.roster.get_item(entity_jid)
575 if item is None: 575 if item is None:
576 raise exceptions.NotFound(f"{entity_jid} is not in roster!") 576 raise exceptions.NotFound(f"{entity_jid} is not in roster!")
577 return (client.roster.getAttributes(item), list(item.groups)) 577 return (client.roster.get_attributes(item), list(item.groups))
578 578
579 def getContacts(self, profile_key): 579 def contacts_get(self, profile_key):
580 client = self.getClient(profile_key) 580 client = self.get_client(profile_key)
581 581
582 def got_roster(__): 582 def got_roster(__):
583 ret = [] 583 ret = []
584 for item in client.roster.getItems(): # we get all items for client's roster 584 for item in client.roster.get_items(): # we get all items for client's roster
585 # and convert them to expected format 585 # and convert them to expected format
586 attr = client.roster.getAttributes(item) 586 attr = client.roster.get_attributes(item)
587 # we use full() and not userhost() because jid with resources are allowed 587 # we use full() and not userhost() because jid with resources are allowed
588 # in roster, even if it's not common. 588 # in roster, even if it's not common.
589 ret.append([item.entity.full(), attr, list(item.groups)]) 589 ret.append([item.entity.full(), attr, list(item.groups)])
590 return ret 590 return ret
591 591
592 return client.roster.got_roster.addCallback(got_roster) 592 return client.roster.got_roster.addCallback(got_roster)
593 593
594 def getContactsFromGroup(self, group, profile_key): 594 def contacts_get_from_group(self, group, profile_key):
595 client = self.getClient(profile_key) 595 client = self.get_client(profile_key)
596 return [jid_.full() for jid_ in client.roster.getJidsFromGroup(group)] 596 return [jid_.full() for jid_ in client.roster.get_jids_from_group(group)]
597 597
598 def purgeEntity(self, profile): 598 def purge_entity(self, profile):
599 """Remove reference to a profile client/component and purge cache 599 """Remove reference to a profile client/component and purge cache
600 600
601 the garbage collector can then free the memory 601 the garbage collector can then free the memory
602 """ 602 """
603 try: 603 try:
604 del self.profiles[profile] 604 del self.profiles[profile]
605 except KeyError: 605 except KeyError:
606 log.error(_("Trying to remove reference to a client not referenced")) 606 log.error(_("Trying to remove reference to a client not referenced"))
607 else: 607 else:
608 self.memory.purgeProfileSession(profile) 608 self.memory.purge_profile_session(profile)
609 609
610 def startService(self): 610 def startService(self):
611 self._init() 611 self._init()
612 log.info("Salut à toi ô mon frère !") 612 log.info("Salut à toi ô mon frère !")
613 613
614 def stopService(self): 614 def stopService(self):
615 log.info("Salut aussi à Rantanplan") 615 log.info("Salut aussi à Rantanplan")
616 return self.pluginsUnload() 616 return self.plugins_unload()
617 617
618 def run(self): 618 def run(self):
619 log.debug(_("running app")) 619 log.debug(_("running app"))
620 reactor.run() 620 reactor.run()
621 621
623 log.debug(_("stopping app")) 623 log.debug(_("stopping app"))
624 reactor.stop() 624 reactor.stop()
625 625
626 ## Misc methods ## 626 ## Misc methods ##
627 627
628 def getJidNStream(self, profile_key): 628 def get_jid_n_stream(self, profile_key):
629 """Convenient method to get jid and stream from profile key 629 """Convenient method to get jid and stream from profile key
630 @return: tuple (jid, xmlstream) from profile, can be None""" 630 @return: tuple (jid, xmlstream) from profile, can be None"""
631 # TODO: deprecate this method (getClient is enough) 631 # TODO: deprecate this method (get_client is enough)
632 profile = self.memory.getProfileName(profile_key) 632 profile = self.memory.get_profile_name(profile_key)
633 if not profile or not self.profiles[profile].isConnected(): 633 if not profile or not self.profiles[profile].is_connected():
634 return (None, None) 634 return (None, None)
635 return (self.profiles[profile].jid, self.profiles[profile].xmlstream) 635 return (self.profiles[profile].jid, self.profiles[profile].xmlstream)
636 636
637 def getClient(self, profile_key: str) -> xmpp.SatXMPPClient: 637 def get_client(self, profile_key: str) -> xmpp.SatXMPPClient:
638 """Convenient method to get client from profile key 638 """Convenient method to get client from profile key
639 639
640 @return: the client 640 @return: the client
641 @raise exceptions.ProfileKeyUnknown: the profile or profile key doesn't exist 641 @raise exceptions.ProfileKeyUnknown: the profile or profile key doesn't exist
642 @raise exceptions.NotFound: client is not available 642 @raise exceptions.NotFound: client is not available
643 This happen if profile has not been used yet 643 This happen if profile has not been used yet
644 """ 644 """
645 profile = self.memory.getProfileName(profile_key) 645 profile = self.memory.get_profile_name(profile_key)
646 if not profile: 646 if not profile:
647 raise exceptions.ProfileKeyUnknown 647 raise exceptions.ProfileKeyUnknown
648 try: 648 try:
649 return self.profiles[profile] 649 return self.profiles[profile]
650 except KeyError: 650 except KeyError:
651 raise exceptions.NotFound(profile_key) 651 raise exceptions.NotFound(profile_key)
652 652
653 def getClients(self, profile_key): 653 def get_clients(self, profile_key):
654 """Convenient method to get list of clients from profile key 654 """Convenient method to get list of clients from profile key
655 655
656 Manage list through profile_key like C.PROF_KEY_ALL 656 Manage list through profile_key like C.PROF_KEY_ALL
657 @param profile_key: %(doc_profile_key)s 657 @param profile_key: %(doc_profile_key)s
658 @return: list of clients 658 @return: list of clients
659 """ 659 """
660 if not profile_key: 660 if not profile_key:
661 raise exceptions.DataError(_("profile_key must not be empty")) 661 raise exceptions.DataError(_("profile_key must not be empty"))
662 try: 662 try:
663 profile = self.memory.getProfileName(profile_key, True) 663 profile = self.memory.get_profile_name(profile_key, True)
664 except exceptions.ProfileUnknownError: 664 except exceptions.ProfileUnknownError:
665 return [] 665 return []
666 if profile == C.PROF_KEY_ALL: 666 if profile == C.PROF_KEY_ALL:
667 return list(self.profiles.values()) 667 return list(self.profiles.values())
668 elif profile[0] == "@": #  only profile keys can start with "@" 668 elif profile[0] == "@": #  only profile keys can start with "@"
669 raise exceptions.ProfileKeyUnknown 669 raise exceptions.ProfileKeyUnknown
670 return [self.profiles[profile]] 670 return [self.profiles[profile]]
671 671
672 def _getConfig(self, section, name): 672 def _get_config(self, section, name):
673 """Get the main configuration option 673 """Get the main configuration option
674 674
675 @param section: section of the config file (None or '' for DEFAULT) 675 @param section: section of the config file (None or '' for DEFAULT)
676 @param name: name of the option 676 @param name: name of the option
677 @return: unicode representation of the option 677 @return: unicode representation of the option
678 """ 678 """
679 return str(self.memory.getConfig(section, name, "")) 679 return str(self.memory.config_get(section, name, ""))
680 680
681 def logErrback(self, failure_, msg=_("Unexpected error: {failure_}")): 681 def log_errback(self, failure_, msg=_("Unexpected error: {failure_}")):
682 """Generic errback logging 682 """Generic errback logging
683 683
684 @param msg(unicode): error message ("failure_" key will be use for format) 684 @param msg(unicode): error message ("failure_" key will be use for format)
685 can be used as last errback to show unexpected error 685 can be used as last errback to show unexpected error
686 """ 686 """
687 log.error(msg.format(failure_=failure_)) 687 log.error(msg.format(failure_=failure_))
688 return failure_ 688 return failure_
689 689
690 #  namespaces 690 #  namespaces
691 691
692 def registerNamespace(self, short_name, namespace): 692 def register_namespace(self, short_name, namespace):
693 """associate a namespace to a short name""" 693 """associate a namespace to a short name"""
694 if short_name in self.ns_map: 694 if short_name in self.ns_map:
695 raise exceptions.ConflictError("this short name is already used") 695 raise exceptions.ConflictError("this short name is already used")
696 log.debug(f"registering namespace {short_name} => {namespace}") 696 log.debug(f"registering namespace {short_name} => {namespace}")
697 self.ns_map[short_name] = namespace 697 self.ns_map[short_name] = namespace
698 698
699 def getNamespaces(self): 699 def get_namespaces(self):
700 return self.ns_map 700 return self.ns_map
701 701
702 def getNamespace(self, short_name): 702 def get_namespace(self, short_name):
703 try: 703 try:
704 return self.ns_map[short_name] 704 return self.ns_map[short_name]
705 except KeyError: 705 except KeyError:
706 raise exceptions.NotFound("namespace {short_name} is not registered" 706 raise exceptions.NotFound("namespace {short_name} is not registered"
707 .format(short_name=short_name)) 707 .format(short_name=short_name))
708 708
709 def getSessionInfos(self, profile_key): 709 def get_session_infos(self, profile_key):
710 """compile interesting data on current profile session""" 710 """compile interesting data on current profile session"""
711 client = self.getClient(profile_key) 711 client = self.get_client(profile_key)
712 data = { 712 data = {
713 "jid": client.jid.full(), 713 "jid": client.jid.full(),
714 "started": str(int(client.started)) 714 "started": str(int(client.started))
715 } 715 }
716 return defer.succeed(data) 716 return defer.succeed(data)
717 717
718 def _getDevicesInfos(self, bare_jid, profile_key): 718 def _get_devices_infos(self, bare_jid, profile_key):
719 client = self.getClient(profile_key) 719 client = self.get_client(profile_key)
720 if not bare_jid: 720 if not bare_jid:
721 bare_jid = None 721 bare_jid = None
722 d = defer.ensureDeferred(self.getDevicesInfos(client, bare_jid)) 722 d = defer.ensureDeferred(self.get_devices_infos(client, bare_jid))
723 d.addCallback(lambda data: data_format.serialise(data)) 723 d.addCallback(lambda data: data_format.serialise(data))
724 return d 724 return d
725 725
726 async def getDevicesInfos(self, client, bare_jid=None): 726 async def get_devices_infos(self, client, bare_jid=None):
727 """compile data on an entity devices 727 """compile data on an entity devices
728 728
729 @param bare_jid(jid.JID, None): bare jid of entity to check 729 @param bare_jid(jid.JID, None): bare jid of entity to check
730 None to use client own jid 730 None to use client own jid
731 @return (list[dict]): list of data, one item per resource. 731 @return (list[dict]): list of data, one item per resource.
735 own_jid = client.jid.userhostJID() 735 own_jid = client.jid.userhostJID()
736 if bare_jid is None: 736 if bare_jid is None:
737 bare_jid = own_jid 737 bare_jid = own_jid
738 else: 738 else:
739 bare_jid = jid.JID(bare_jid) 739 bare_jid = jid.JID(bare_jid)
740 resources = self.memory.getAllResources(client, bare_jid) 740 resources = self.memory.get_all_resources(client, bare_jid)
741 if bare_jid == own_jid: 741 if bare_jid == own_jid:
742 # our own jid is not stored in memory's cache 742 # our own jid is not stored in memory's cache
743 resources.add(client.jid.resource) 743 resources.add(client.jid.resource)
744 ret_data = [] 744 ret_data = []
745 for resource in resources: 745 for resource in resources:
746 res_jid = copy.copy(bare_jid) 746 res_jid = copy.copy(bare_jid)
747 res_jid.resource = resource 747 res_jid.resource = resource
748 cache_data = self.memory.getEntityData(client, res_jid) 748 cache_data = self.memory.entity_data_get(client, res_jid)
749 res_data = { 749 res_data = {
750 "resource": resource, 750 "resource": resource,
751 } 751 }
752 try: 752 try:
753 presence = cache_data['presence'] 753 presence = cache_data['presence']
758 "show": presence.show, 758 "show": presence.show,
759 "priority": presence.priority, 759 "priority": presence.priority,
760 "statuses": presence.statuses, 760 "statuses": presence.statuses,
761 } 761 }
762 762
763 disco = await self.getDiscoInfos(client, res_jid) 763 disco = await self.get_disco_infos(client, res_jid)
764 764
765 for (category, type_), name in disco.identities.items(): 765 for (category, type_), name in disco.identities.items():
766 identities = res_data.setdefault('identities', []) 766 identities = res_data.setdefault('identities', [])
767 identities.append({ 767 identities.append({
768 "name": name, 768 "name": name,
774 774
775 return ret_data 775 return ret_data
776 776
777 # images 777 # images
778 778
779 def _imageCheck(self, path): 779 def _image_check(self, path):
780 report = image.check(self, path) 780 report = image.check(self, path)
781 return data_format.serialise(report) 781 return data_format.serialise(report)
782 782
783 def _imageResize(self, path, width, height): 783 def _image_resize(self, path, width, height):
784 d = image.resize(path, (width, height)) 784 d = image.resize(path, (width, height))
785 d.addCallback(lambda new_image_path: str(new_image_path)) 785 d.addCallback(lambda new_image_path: str(new_image_path))
786 return d 786 return d
787 787
788 def _imageGeneratePreview(self, path, profile_key): 788 def _image_generate_preview(self, path, profile_key):
789 client = self.getClient(profile_key) 789 client = self.get_client(profile_key)
790 d = defer.ensureDeferred(self.imageGeneratePreview(client, Path(path))) 790 d = defer.ensureDeferred(self.image_generate_preview(client, Path(path)))
791 d.addCallback(lambda preview_path: str(preview_path)) 791 d.addCallback(lambda preview_path: str(preview_path))
792 return d 792 return d
793 793
794 async def imageGeneratePreview(self, client, path): 794 async def image_generate_preview(self, client, path):
795 """Helper method to generate in cache a preview of an image 795 """Helper method to generate in cache a preview of an image
796 796
797 @param path(Path): path to the image 797 @param path(Path): path to the image
798 @return (Path): path to the generated preview 798 @return (Path): path to the generated preview
799 """ 799 """
805 else: 805 else:
806 # we use hash as id, to re-use potentially existing preview 806 # we use hash as id, to re-use potentially existing preview
807 path_hash = hashlib.sha256(str(path).encode()).hexdigest() 807 path_hash = hashlib.sha256(str(path).encode()).hexdigest()
808 uid = f"{path.stem}_{path_hash}_preview" 808 uid = f"{path.stem}_{path_hash}_preview"
809 filename = f"{uid}{path.suffix.lower()}" 809 filename = f"{uid}{path.suffix.lower()}"
810 metadata = client.cache.getMetadata(uid=uid) 810 metadata = client.cache.get_metadata(uid=uid)
811 if metadata is not None: 811 if metadata is not None:
812 preview_path = metadata['path'] 812 preview_path = metadata['path']
813 else: 813 else:
814 with client.cache.cacheData( 814 with client.cache.cache_data(
815 source='HOST_PREVIEW', 815 source='HOST_PREVIEW',
816 uid=uid, 816 uid=uid,
817 filename=filename) as cache_f: 817 filename=filename) as cache_f:
818 818
819 preview_path = await image.resize( 819 preview_path = await image.resize(
822 dest=cache_f 822 dest=cache_f
823 ) 823 )
824 824
825 return preview_path 825 return preview_path
826 826
827 def _imageConvert(self, source, dest, extra, profile_key): 827 def _image_convert(self, source, dest, extra, profile_key):
828 client = self.getClient(profile_key) if profile_key else None 828 client = self.get_client(profile_key) if profile_key else None
829 source = Path(source) 829 source = Path(source)
830 dest = None if not dest else Path(dest) 830 dest = None if not dest else Path(dest)
831 extra = data_format.deserialise(extra) 831 extra = data_format.deserialise(extra)
832 d = defer.ensureDeferred(self.imageConvert(client, source, dest, extra)) 832 d = defer.ensureDeferred(self.image_convert(client, source, dest, extra))
833 d.addCallback(lambda dest_path: str(dest_path)) 833 d.addCallback(lambda dest_path: str(dest_path))
834 return d 834 return d
835 835
836 async def imageConvert(self, client, source, dest=None, extra=None): 836 async def image_convert(self, client, source, dest=None, extra=None):
837 """Helper method to convert an image from one format to an other 837 """Helper method to convert an image from one format to an other
838 838
839 @param client(SatClient, None): client to use for caching 839 @param client(SatClient, None): client to use for caching
840 this parameter is only used if dest is None 840 this parameter is only used if dest is None
841 if client is None, common cache will be used insted of profile cache 841 if client is None, common cache will be used insted of profile cache
859 filename = f"{uid}.png" 859 filename = f"{uid}.png"
860 if client is None: 860 if client is None:
861 cache = self.common_cache 861 cache = self.common_cache
862 else: 862 else:
863 cache = client.cache 863 cache = client.cache
864 metadata = cache.getMetadata(uid=uid) 864 metadata = cache.get_metadata(uid=uid)
865 if metadata is not None: 865 if metadata is not None:
866 # there is already a conversion for this image in cache 866 # there is already a conversion for this image in cache
867 return metadata['path'] 867 return metadata['path']
868 else: 868 else:
869 with cache.cacheData( 869 with cache.cache_data(
870 source='HOST_IMAGE_CONVERT', 870 source='HOST_IMAGE_CONVERT',
871 uid=uid, 871 uid=uid,
872 filename=filename) as cache_f: 872 filename=filename) as cache_f:
873 873
874 converted_path = await image.convert( 874 converted_path = await image.convert(
898 @param dir_name: name of the main path directory 898 @param dir_name: name of the main path directory
899 @param *extra_path: extra path element(s) to use 899 @param *extra_path: extra path element(s) to use
900 @param component: if True, path will be prefixed with C.COMPONENTS_DIR 900 @param component: if True, path will be prefixed with C.COMPONENTS_DIR
901 @return: path 901 @return: path
902 """ 902 """
903 local_dir = self.memory.getConfig("", "local_dir") 903 local_dir = self.memory.config_get("", "local_dir")
904 if not local_dir: 904 if not local_dir:
905 raise exceptions.InternalError("local_dir must be set") 905 raise exceptions.InternalError("local_dir must be set")
906 path_elts = [] 906 path_elts = []
907 if component: 907 if component:
908 path_elts.append(C.COMPONENTS_DIR) 908 path_elts.append(C.COMPONENTS_DIR)
909 path_elts.append(regex.pathEscape(dir_name)) 909 path_elts.append(regex.path_escape(dir_name))
910 if extra_path: 910 if extra_path:
911 path_elts.extend([regex.pathEscape(p) for p in extra_path]) 911 path_elts.extend([regex.path_escape(p) for p in extra_path])
912 if client is not None: 912 if client is not None:
913 path_elts.append(regex.pathEscape(client.profile)) 913 path_elts.append(regex.path_escape(client.profile))
914 local_path = Path(*path_elts) 914 local_path = Path(*path_elts)
915 local_path.mkdir(0o700, parents=True, exist_ok=True) 915 local_path.mkdir(0o700, parents=True, exist_ok=True)
916 return local_path 916 return local_path
917 917
918 ## Client management ## 918 ## Client management ##
919 919
920 def setParam(self, name, value, category, security_limit, profile_key): 920 def param_set(self, name, value, category, security_limit, profile_key):
921 """set wanted paramater and notice observers""" 921 """set wanted paramater and notice observers"""
922 self.memory.setParam(name, value, category, security_limit, profile_key) 922 self.memory.param_set(name, value, category, security_limit, profile_key)
923 923
924 def isConnected(self, profile_key): 924 def is_connected(self, profile_key):
925 """Return connection status of profile 925 """Return connection status of profile
926 926
927 @param profile_key: key_word or profile name to determine profile name 927 @param profile_key: key_word or profile name to determine profile name
928 @return: True if connected 928 @return: True if connected
929 """ 929 """
930 profile = self.memory.getProfileName(profile_key) 930 profile = self.memory.get_profile_name(profile_key)
931 if not profile: 931 if not profile:
932 log.error(_("asking connection status for a non-existant profile")) 932 log.error(_("asking connection status for a non-existant profile"))
933 raise exceptions.ProfileUnknownError(profile_key) 933 raise exceptions.ProfileUnknownError(profile_key)
934 if profile not in self.profiles: 934 if profile not in self.profiles:
935 return False 935 return False
936 return self.profiles[profile].isConnected() 936 return self.profiles[profile].is_connected()
937 937
938 ## Encryption ## 938 ## Encryption ##
939 939
940 def registerEncryptionPlugin(self, *args, **kwargs): 940 def register_encryption_plugin(self, *args, **kwargs):
941 return encryption.EncryptionHandler.registerPlugin(*args, **kwargs) 941 return encryption.EncryptionHandler.register_plugin(*args, **kwargs)
942 942
943 def _messageEncryptionStart(self, to_jid_s, namespace, replace=False, 943 def _message_encryption_start(self, to_jid_s, namespace, replace=False,
944 profile_key=C.PROF_KEY_NONE): 944 profile_key=C.PROF_KEY_NONE):
945 client = self.getClient(profile_key) 945 client = self.get_client(profile_key)
946 to_jid = jid.JID(to_jid_s) 946 to_jid = jid.JID(to_jid_s)
947 return defer.ensureDeferred( 947 return defer.ensureDeferred(
948 client.encryption.start(to_jid, namespace or None, replace)) 948 client.encryption.start(to_jid, namespace or None, replace))
949 949
950 def _messageEncryptionStop(self, to_jid_s, profile_key=C.PROF_KEY_NONE): 950 def _message_encryption_stop(self, to_jid_s, profile_key=C.PROF_KEY_NONE):
951 client = self.getClient(profile_key) 951 client = self.get_client(profile_key)
952 to_jid = jid.JID(to_jid_s) 952 to_jid = jid.JID(to_jid_s)
953 return defer.ensureDeferred( 953 return defer.ensureDeferred(
954 client.encryption.stop(to_jid)) 954 client.encryption.stop(to_jid))
955 955
956 def _messageEncryptionGet(self, to_jid_s, profile_key=C.PROF_KEY_NONE): 956 def _message_encryption_get(self, to_jid_s, profile_key=C.PROF_KEY_NONE):
957 client = self.getClient(profile_key) 957 client = self.get_client(profile_key)
958 to_jid = jid.JID(to_jid_s) 958 to_jid = jid.JID(to_jid_s)
959 session_data = client.encryption.getSession(to_jid) 959 session_data = client.encryption.getSession(to_jid)
960 return client.encryption.getBridgeData(session_data) 960 return client.encryption.get_bridge_data(session_data)
961 961
962 def _encryptionNamespaceGet(self, name): 962 def _encryption_namespace_get(self, name):
963 return encryption.EncryptionHandler.getNSFromName(name) 963 return encryption.EncryptionHandler.get_ns_from_name(name)
964 964
965 def _encryptionPluginsGet(self): 965 def _encryption_plugins_get(self):
966 plugins = encryption.EncryptionHandler.getPlugins() 966 plugins = encryption.EncryptionHandler.getPlugins()
967 ret = [] 967 ret = []
968 for p in plugins: 968 for p in plugins:
969 ret.append({ 969 ret.append({
970 "name": p.name, 970 "name": p.name,
972 "priority": p.priority, 972 "priority": p.priority,
973 "directed": p.directed, 973 "directed": p.directed,
974 }) 974 })
975 return data_format.serialise(ret) 975 return data_format.serialise(ret)
976 976
977 def _encryptionTrustUIGet(self, to_jid_s, namespace, profile_key): 977 def _encryption_trust_ui_get(self, to_jid_s, namespace, profile_key):
978 client = self.getClient(profile_key) 978 client = self.get_client(profile_key)
979 to_jid = jid.JID(to_jid_s) 979 to_jid = jid.JID(to_jid_s)
980 d = defer.ensureDeferred( 980 d = defer.ensureDeferred(
981 client.encryption.getTrustUI(to_jid, namespace=namespace or None)) 981 client.encryption.get_trust_ui(to_jid, namespace=namespace or None))
982 d.addCallback(lambda xmlui: xmlui.toXml()) 982 d.addCallback(lambda xmlui: xmlui.toXml())
983 return d 983 return d
984 984
985 ## XMPP methods ## 985 ## XMPP methods ##
986 986
987 def _messageSend( 987 def _message_send(
988 self, to_jid_s, message, subject=None, mess_type="auto", extra_s="", 988 self, to_jid_s, message, subject=None, mess_type="auto", extra_s="",
989 profile_key=C.PROF_KEY_NONE): 989 profile_key=C.PROF_KEY_NONE):
990 client = self.getClient(profile_key) 990 client = self.get_client(profile_key)
991 to_jid = jid.JID(to_jid_s) 991 to_jid = jid.JID(to_jid_s)
992 return client.sendMessage( 992 return client.sendMessage(
993 to_jid, 993 to_jid,
994 message, 994 message,
995 subject, 995 subject,
996 mess_type, 996 mess_type,
997 data_format.deserialise(extra_s) 997 data_format.deserialise(extra_s)
998 ) 998 )
999 999
1000 def _setPresence(self, to="", show="", statuses=None, profile_key=C.PROF_KEY_NONE): 1000 def _set_presence(self, to="", show="", statuses=None, profile_key=C.PROF_KEY_NONE):
1001 return self.setPresence(jid.JID(to) if to else None, show, statuses, profile_key) 1001 return self.presence_set(jid.JID(to) if to else None, show, statuses, profile_key)
1002 1002
1003 def setPresence(self, to_jid=None, show="", statuses=None, 1003 def presence_set(self, to_jid=None, show="", statuses=None,
1004 profile_key=C.PROF_KEY_NONE): 1004 profile_key=C.PROF_KEY_NONE):
1005 """Send our presence information""" 1005 """Send our presence information"""
1006 if statuses is None: 1006 if statuses is None:
1007 statuses = {} 1007 statuses = {}
1008 profile = self.memory.getProfileName(profile_key) 1008 profile = self.memory.get_profile_name(profile_key)
1009 assert profile 1009 assert profile
1010 priority = int( 1010 priority = int(
1011 self.memory.getParamA("Priority", "Connection", profile_key=profile) 1011 self.memory.param_get_a("Priority", "Connection", profile_key=profile)
1012 ) 1012 )
1013 self.profiles[profile].presence.available(to_jid, show, statuses, priority) 1013 self.profiles[profile].presence.available(to_jid, show, statuses, priority)
1014 # XXX: FIXME: temporary fix to work around openfire 3.7.0 bug (presence is not 1014 # XXX: FIXME: temporary fix to work around openfire 3.7.0 bug (presence is not
1015 # broadcasted to generating resource) 1015 # broadcasted to generating resource)
1016 if "" in statuses: 1016 if "" in statuses:
1017 statuses[C.PRESENCE_STATUSES_DEFAULT] = statuses.pop("") 1017 statuses[C.PRESENCE_STATUSES_DEFAULT] = statuses.pop("")
1018 self.bridge.presenceUpdate( 1018 self.bridge.presence_update(
1019 self.profiles[profile].jid.full(), show, int(priority), statuses, profile 1019 self.profiles[profile].jid.full(), show, int(priority), statuses, profile
1020 ) 1020 )
1021 1021
1022 def subscription(self, subs_type, raw_jid, profile_key): 1022 def subscription(self, subs_type, raw_jid, profile_key):
1023 """Called to manage subscription 1023 """Called to manage subscription
1024 @param subs_type: subsciption type (cf RFC 3921) 1024 @param subs_type: subsciption type (cf RFC 3921)
1025 @param raw_jid: unicode entity's jid 1025 @param raw_jid: unicode entity's jid
1026 @param profile_key: profile""" 1026 @param profile_key: profile"""
1027 profile = self.memory.getProfileName(profile_key) 1027 profile = self.memory.get_profile_name(profile_key)
1028 assert profile 1028 assert profile
1029 to_jid = jid.JID(raw_jid) 1029 to_jid = jid.JID(raw_jid)
1030 log.debug( 1030 log.debug(
1031 _("subsciption request [%(subs_type)s] for %(jid)s") 1031 _("subsciption request [%(subs_type)s] for %(jid)s")
1032 % {"subs_type": subs_type, "jid": to_jid.full()} 1032 % {"subs_type": subs_type, "jid": to_jid.full()}
1038 elif subs_type == "unsubscribe": 1038 elif subs_type == "unsubscribe":
1039 self.profiles[profile].presence.unsubscribe(to_jid) 1039 self.profiles[profile].presence.unsubscribe(to_jid)
1040 elif subs_type == "unsubscribed": 1040 elif subs_type == "unsubscribed":
1041 self.profiles[profile].presence.unsubscribed(to_jid) 1041 self.profiles[profile].presence.unsubscribed(to_jid)
1042 1042
1043 def _addContact(self, to_jid_s, profile_key): 1043 def _add_contact(self, to_jid_s, profile_key):
1044 return self.addContact(jid.JID(to_jid_s), profile_key) 1044 return self.contact_add(jid.JID(to_jid_s), profile_key)
1045 1045
1046 def addContact(self, to_jid, profile_key): 1046 def contact_add(self, to_jid, profile_key):
1047 """Add a contact in roster list""" 1047 """Add a contact in roster list"""
1048 profile = self.memory.getProfileName(profile_key) 1048 profile = self.memory.get_profile_name(profile_key)
1049 assert profile 1049 assert profile
1050 # presence is sufficient, as a roster push will be sent according to 1050 # presence is sufficient, as a roster push will be sent according to
1051 # RFC 6121 §3.1.2 1051 # RFC 6121 §3.1.2
1052 self.profiles[profile].presence.subscribe(to_jid) 1052 self.profiles[profile].presence.subscribe(to_jid)
1053 1053
1054 def _updateContact(self, to_jid_s, name, groups, profile_key): 1054 def _update_contact(self, to_jid_s, name, groups, profile_key):
1055 client = self.getClient(profile_key) 1055 client = self.get_client(profile_key)
1056 return self.updateContact(client, jid.JID(to_jid_s), name, groups) 1056 return self.contact_update(client, jid.JID(to_jid_s), name, groups)
1057 1057
1058 def updateContact(self, client, to_jid, name, groups): 1058 def contact_update(self, client, to_jid, name, groups):
1059 """update a contact in roster list""" 1059 """update a contact in roster list"""
1060 roster_item = RosterItem(to_jid) 1060 roster_item = RosterItem(to_jid)
1061 roster_item.name = name or u'' 1061 roster_item.name = name or u''
1062 roster_item.groups = set(groups) 1062 roster_item.groups = set(groups)
1063 if not self.trigger.point("roster_update", client, roster_item): 1063 if not self.trigger.point("roster_update", client, roster_item):
1064 return 1064 return
1065 return client.roster.setItem(roster_item) 1065 return client.roster.setItem(roster_item)
1066 1066
1067 def _delContact(self, to_jid_s, profile_key): 1067 def _del_contact(self, to_jid_s, profile_key):
1068 return self.delContact(jid.JID(to_jid_s), profile_key) 1068 return self.contact_del(jid.JID(to_jid_s), profile_key)
1069 1069
1070 def delContact(self, to_jid, profile_key): 1070 def contact_del(self, to_jid, profile_key):
1071 """Remove contact from roster list""" 1071 """Remove contact from roster list"""
1072 profile = self.memory.getProfileName(profile_key) 1072 profile = self.memory.get_profile_name(profile_key)
1073 assert profile 1073 assert profile
1074 self.profiles[profile].presence.unsubscribe(to_jid) # is not asynchronous 1074 self.profiles[profile].presence.unsubscribe(to_jid) # is not asynchronous
1075 return self.profiles[profile].roster.removeItem(to_jid) 1075 return self.profiles[profile].roster.removeItem(to_jid)
1076 1076
1077 def _rosterResync(self, profile_key): 1077 def _roster_resync(self, profile_key):
1078 client = self.getClient(profile_key) 1078 client = self.get_client(profile_key)
1079 return client.roster.resync() 1079 return client.roster.resync()
1080 1080
1081 ## Discovery ## 1081 ## Discovery ##
1082 # discovery methods are shortcuts to self.memory.disco 1082 # discovery methods are shortcuts to self.memory.disco
1083 # the main difference with client.disco is that self.memory.disco manage cache 1083 # the main difference with client.disco is that self.memory.disco manage cache
1084 1084
1085 def hasFeature(self, *args, **kwargs): 1085 def hasFeature(self, *args, **kwargs):
1086 return self.memory.disco.hasFeature(*args, **kwargs) 1086 return self.memory.disco.hasFeature(*args, **kwargs)
1087 1087
1088 def checkFeature(self, *args, **kwargs): 1088 def check_feature(self, *args, **kwargs):
1089 return self.memory.disco.checkFeature(*args, **kwargs) 1089 return self.memory.disco.check_feature(*args, **kwargs)
1090 1090
1091 def checkFeatures(self, *args, **kwargs): 1091 def check_features(self, *args, **kwargs):
1092 return self.memory.disco.checkFeatures(*args, **kwargs) 1092 return self.memory.disco.check_features(*args, **kwargs)
1093 1093
1094 def hasIdentity(self, *args, **kwargs): 1094 def has_identity(self, *args, **kwargs):
1095 return self.memory.disco.hasIdentity(*args, **kwargs) 1095 return self.memory.disco.has_identity(*args, **kwargs)
1096 1096
1097 def getDiscoInfos(self, *args, **kwargs): 1097 def get_disco_infos(self, *args, **kwargs):
1098 return self.memory.disco.getInfos(*args, **kwargs) 1098 return self.memory.disco.get_infos(*args, **kwargs)
1099 1099
1100 def getDiscoItems(self, *args, **kwargs): 1100 def getDiscoItems(self, *args, **kwargs):
1101 return self.memory.disco.getItems(*args, **kwargs) 1101 return self.memory.disco.get_items(*args, **kwargs)
1102 1102
1103 def findServiceEntity(self, *args, **kwargs): 1103 def find_service_entity(self, *args, **kwargs):
1104 return self.memory.disco.findServiceEntity(*args, **kwargs) 1104 return self.memory.disco.find_service_entity(*args, **kwargs)
1105 1105
1106 def findServiceEntities(self, *args, **kwargs): 1106 def find_service_entities(self, *args, **kwargs):
1107 return self.memory.disco.findServiceEntities(*args, **kwargs) 1107 return self.memory.disco.find_service_entities(*args, **kwargs)
1108 1108
1109 def findFeaturesSet(self, *args, **kwargs): 1109 def find_features_set(self, *args, **kwargs):
1110 return self.memory.disco.findFeaturesSet(*args, **kwargs) 1110 return self.memory.disco.find_features_set(*args, **kwargs)
1111 1111
1112 def _findByFeatures(self, namespaces, identities, bare_jids, service, roster, own_jid, 1112 def _find_by_features(self, namespaces, identities, bare_jids, service, roster, own_jid,
1113 local_device, profile_key): 1113 local_device, profile_key):
1114 client = self.getClient(profile_key) 1114 client = self.get_client(profile_key)
1115 identities = [tuple(i) for i in identities] if identities else None 1115 identities = [tuple(i) for i in identities] if identities else None
1116 return defer.ensureDeferred(self.findByFeatures( 1116 return defer.ensureDeferred(self.find_by_features(
1117 client, namespaces, identities, bare_jids, service, roster, own_jid, 1117 client, namespaces, identities, bare_jids, service, roster, own_jid,
1118 local_device)) 1118 local_device))
1119 1119
1120 async def findByFeatures( 1120 async def find_by_features(
1121 self, 1121 self,
1122 client: xmpp.SatXMPPEntity, 1122 client: xmpp.SatXMPPEntity,
1123 namespaces: List[str], 1123 namespaces: List[str],
1124 identities: Optional[List[Tuple[str, str]]]=None, 1124 identities: Optional[List[Tuple[str, str]]]=None,
1125 bare_jids: bool=False, 1125 bare_jids: bool=False,
1162 ) 1162 )
1163 found_service = {} 1163 found_service = {}
1164 found_own = {} 1164 found_own = {}
1165 found_roster = {} 1165 found_roster = {}
1166 if service: 1166 if service:
1167 services_jids = await self.findFeaturesSet(client, namespaces) 1167 services_jids = await self.find_features_set(client, namespaces)
1168 services_jids = list(services_jids) # we need a list to map results below 1168 services_jids = list(services_jids) # we need a list to map results below
1169 services_infos = await defer.DeferredList( 1169 services_infos = await defer.DeferredList(
1170 [self.getDiscoInfos(client, service_jid) for service_jid in services_jids] 1170 [self.get_disco_infos(client, service_jid) for service_jid in services_jids]
1171 ) 1171 )
1172 1172
1173 for idx, (success, infos) in enumerate(services_infos): 1173 for idx, (success, infos) in enumerate(services_infos):
1174 service_jid = services_jids[idx] 1174 service_jid = services_jids[idx]
1175 if not success: 1175 if not success:
1188 1188
1189 to_find = [] 1189 to_find = []
1190 if own_jid: 1190 if own_jid:
1191 to_find.append((found_own, [client.jid.userhostJID()])) 1191 to_find.append((found_own, [client.jid.userhostJID()]))
1192 if roster: 1192 if roster:
1193 to_find.append((found_roster, client.roster.getJids())) 1193 to_find.append((found_roster, client.roster.get_jids()))
1194 1194
1195 for found, jids in to_find: 1195 for found, jids in to_find:
1196 full_jids = [] 1196 full_jids = []
1197 disco_defers = [] 1197 disco_defers = []
1198 1198
1204 else: 1204 else:
1205 if bare_jids: 1205 if bare_jids:
1206 resources = [None] 1206 resources = [None]
1207 else: 1207 else:
1208 try: 1208 try:
1209 resources = self.memory.getAvailableResources(client, jid_) 1209 resources = self.memory.get_available_resources(client, jid_)
1210 except exceptions.UnknownEntityError: 1210 except exceptions.UnknownEntityError:
1211 continue 1211 continue
1212 if not resources and jid_ == client.jid.userhostJID() and own_jid: 1212 if not resources and jid_ == client.jid.userhostJID() and own_jid:
1213 # small hack to avoid missing our own resource when this 1213 # small hack to avoid missing our own resource when this
1214 # method is called at the very beginning of the session 1214 # method is called at the very beginning of the session
1218 full_jid = jid.JID(tuple=(jid_.user, jid_.host, resource)) 1218 full_jid = jid.JID(tuple=(jid_.user, jid_.host, resource))
1219 if full_jid == client.jid and not local_device: 1219 if full_jid == client.jid and not local_device:
1220 continue 1220 continue
1221 full_jids.append(full_jid) 1221 full_jids.append(full_jid)
1222 1222
1223 disco_defers.append(self.getDiscoInfos(client, full_jid)) 1223 disco_defers.append(self.get_disco_infos(client, full_jid))
1224 1224
1225 d_list = defer.DeferredList(disco_defers) 1225 d_list = defer.DeferredList(disco_defers)
1226 # XXX: 10 seconds may be too low for slow connections (e.g. mobiles) 1226 # XXX: 10 seconds may be too low for slow connections (e.g. mobiles)
1227 # but for discovery, that's also the time the user will wait the first time 1227 # but for discovery, that's also the time the user will wait the first time
1228 # before seing the page, if something goes wrong. 1228 # before seing the page, if something goes wrong.
1249 1249
1250 return (found_service, found_own, found_roster) 1250 return (found_service, found_own, found_roster)
1251 1251
1252 ## Generic HMI ## 1252 ## Generic HMI ##
1253 1253
1254 def _killAction(self, keep_id, client): 1254 def _kill_action(self, keep_id, client):
1255 log.debug("Killing action {} for timeout".format(keep_id)) 1255 log.debug("Killing action {} for timeout".format(keep_id))
1256 client.actions[keep_id] 1256 client.actions[keep_id]
1257 1257
1258 def actionNew( 1258 def action_new(
1259 self, 1259 self,
1260 action_data, 1260 action_data,
1261 security_limit=C.NO_SECURITY_LIMIT, 1261 security_limit=C.NO_SECURITY_LIMIT,
1262 keep_id=None, 1262 keep_id=None,
1263 profile=C.PROF_KEY_NONE, 1263 profile=C.PROF_KEY_NONE,
1264 ): 1264 ):
1265 """Shortcut to bridge.actionNew which generate and id and keep for retrieval 1265 """Shortcut to bridge.action_new which generate and id and keep for retrieval
1266 1266
1267 @param action_data(dict): action data (see bridge documentation) 1267 @param action_data(dict): action data (see bridge documentation)
1268 @param security_limit: %(doc_security_limit)s 1268 @param security_limit: %(doc_security_limit)s
1269 @param keep_id(None, unicode): if not None, used to keep action for differed 1269 @param keep_id(None, unicode): if not None, used to keep action for differed
1270 retrieval. Must be set to the callback_id. 1270 retrieval. Must be set to the callback_id.
1271 Action will be deleted after 30 min. 1271 Action will be deleted after 30 min.
1272 @param profile: %(doc_profile)s 1272 @param profile: %(doc_profile)s
1273 """ 1273 """
1274 id_ = str(uuid.uuid4()) 1274 id_ = str(uuid.uuid4())
1275 if keep_id is not None: 1275 if keep_id is not None:
1276 client = self.getClient(profile) 1276 client = self.get_client(profile)
1277 action_timer = reactor.callLater(60 * 30, self._killAction, keep_id, client) 1277 action_timer = reactor.callLater(60 * 30, self._kill_action, keep_id, client)
1278 client.actions[keep_id] = (action_data, id_, security_limit, action_timer) 1278 client.actions[keep_id] = (action_data, id_, security_limit, action_timer)
1279 1279
1280 self.bridge.actionNew(action_data, id_, security_limit, profile) 1280 self.bridge.action_new(action_data, id_, security_limit, profile)
1281 1281
1282 def actionsGet(self, profile): 1282 def actions_get(self, profile):
1283 """Return current non answered actions 1283 """Return current non answered actions
1284 1284
1285 @param profile: %(doc_profile)s 1285 @param profile: %(doc_profile)s
1286 """ 1286 """
1287 client = self.getClient(profile) 1287 client = self.get_client(profile)
1288 return [action_tuple[:-1] for action_tuple in client.actions.values()] 1288 return [action_tuple[:-1] for action_tuple in client.actions.values()]
1289 1289
1290 def registerProgressCb( 1290 def register_progress_cb(
1291 self, progress_id, callback, metadata=None, profile=C.PROF_KEY_NONE 1291 self, progress_id, callback, metadata=None, profile=C.PROF_KEY_NONE
1292 ): 1292 ):
1293 """Register a callback called when progress is requested for id""" 1293 """Register a callback called when progress is requested for id"""
1294 if metadata is None: 1294 if metadata is None:
1295 metadata = {} 1295 metadata = {}
1296 client = self.getClient(profile) 1296 client = self.get_client(profile)
1297 if progress_id in client._progress_cb: 1297 if progress_id in client._progress_cb:
1298 raise exceptions.ConflictError("Progress ID is not unique !") 1298 raise exceptions.ConflictError("Progress ID is not unique !")
1299 client._progress_cb[progress_id] = (callback, metadata) 1299 client._progress_cb[progress_id] = (callback, metadata)
1300 1300
1301 def removeProgressCb(self, progress_id, profile): 1301 def remove_progress_cb(self, progress_id, profile):
1302 """Remove a progress callback""" 1302 """Remove a progress callback"""
1303 client = self.getClient(profile) 1303 client = self.get_client(profile)
1304 try: 1304 try:
1305 del client._progress_cb[progress_id] 1305 del client._progress_cb[progress_id]
1306 except KeyError: 1306 except KeyError:
1307 log.error(_("Trying to remove an unknow progress callback")) 1307 log.error(_("Trying to remove an unknow progress callback"))
1308 1308
1309 def _progressGet(self, progress_id, profile): 1309 def _progress_get(self, progress_id, profile):
1310 data = self.progressGet(progress_id, profile) 1310 data = self.progress_get(progress_id, profile)
1311 return {k: str(v) for k, v in data.items()} 1311 return {k: str(v) for k, v in data.items()}
1312 1312
1313 def progressGet(self, progress_id, profile): 1313 def progress_get(self, progress_id, profile):
1314 """Return a dict with progress information 1314 """Return a dict with progress information
1315 1315
1316 @param progress_id(unicode): unique id of the progressing element 1316 @param progress_id(unicode): unique id of the progressing element
1317 @param profile: %(doc_profile)s 1317 @param profile: %(doc_profile)s
1318 @return (dict): data with the following keys: 1318 @return (dict): data with the following keys:
1319 'position' (int): current possition 1319 'position' (int): current possition
1320 'size' (int): end_position 1320 'size' (int): end_position
1321 if id doesn't exists (may be a finished progression), and empty dict is 1321 if id doesn't exists (may be a finished progression), and empty dict is
1322 returned 1322 returned
1323 """ 1323 """
1324 client = self.getClient(profile) 1324 client = self.get_client(profile)
1325 try: 1325 try:
1326 data = client._progress_cb[progress_id][0](progress_id, profile) 1326 data = client._progress_cb[progress_id][0](progress_id, profile)
1327 except KeyError: 1327 except KeyError:
1328 data = {} 1328 data = {}
1329 return data 1329 return data
1330 1330
1331 def _progressGetAll(self, profile_key): 1331 def _progress_get_all(self, profile_key):
1332 progress_all = self.progressGetAll(profile_key) 1332 progress_all = self.progress_get_all(profile_key)
1333 for profile, progress_dict in progress_all.items(): 1333 for profile, progress_dict in progress_all.items():
1334 for progress_id, data in progress_dict.items(): 1334 for progress_id, data in progress_dict.items():
1335 for key, value in data.items(): 1335 for key, value in data.items():
1336 data[key] = str(value) 1336 data[key] = str(value)
1337 return progress_all 1337 return progress_all
1338 1338
1339 def progressGetAllMetadata(self, profile_key): 1339 def progress_get_all_metadata(self, profile_key):
1340 """Return all progress metadata at once 1340 """Return all progress metadata at once
1341 1341
1342 @param profile_key: %(doc_profile)s 1342 @param profile_key: %(doc_profile)s
1343 if C.PROF_KEY_ALL is used, all progress metadata from all profiles are 1343 if C.PROF_KEY_ALL is used, all progress metadata from all profiles are
1344 returned 1344 returned
1345 @return (dict[dict[dict]]): a dict which map profile to progress_dict 1345 @return (dict[dict[dict]]): a dict which map profile to progress_dict
1346 progress_dict map progress_id to progress_data 1346 progress_dict map progress_id to progress_data
1347 progress_metadata is the same dict as sent by [progressStarted] 1347 progress_metadata is the same dict as sent by [progress_started]
1348 """ 1348 """
1349 clients = self.getClients(profile_key) 1349 clients = self.get_clients(profile_key)
1350 progress_all = {} 1350 progress_all = {}
1351 for client in clients: 1351 for client in clients:
1352 profile = client.profile 1352 profile = client.profile
1353 progress_dict = {} 1353 progress_dict = {}
1354 progress_all[profile] = progress_dict 1354 progress_all[profile] = progress_dict
1357 (__, progress_metadata), 1357 (__, progress_metadata),
1358 ) in client._progress_cb.items(): 1358 ) in client._progress_cb.items():
1359 progress_dict[progress_id] = progress_metadata 1359 progress_dict[progress_id] = progress_metadata
1360 return progress_all 1360 return progress_all
1361 1361
1362 def progressGetAll(self, profile_key): 1362 def progress_get_all(self, profile_key):
1363 """Return all progress status at once 1363 """Return all progress status at once
1364 1364
1365 @param profile_key: %(doc_profile)s 1365 @param profile_key: %(doc_profile)s
1366 if C.PROF_KEY_ALL is used, all progress status from all profiles are returned 1366 if C.PROF_KEY_ALL is used, all progress status from all profiles are returned
1367 @return (dict[dict[dict]]): a dict which map profile to progress_dict 1367 @return (dict[dict[dict]]): a dict which map profile to progress_dict
1368 progress_dict map progress_id to progress_data 1368 progress_dict map progress_id to progress_data
1369 progress_data is the same dict as returned by [progressGet] 1369 progress_data is the same dict as returned by [progress_get]
1370 """ 1370 """
1371 clients = self.getClients(profile_key) 1371 clients = self.get_clients(profile_key)
1372 progress_all = {} 1372 progress_all = {}
1373 for client in clients: 1373 for client in clients:
1374 profile = client.profile 1374 profile = client.profile
1375 progress_dict = {} 1375 progress_dict = {}
1376 progress_all[profile] = progress_dict 1376 progress_all[profile] = progress_dict
1377 for progress_id, (progress_cb, __) in client._progress_cb.items(): 1377 for progress_id, (progress_cb, __) in client._progress_cb.items():
1378 progress_dict[progress_id] = progress_cb(progress_id, profile) 1378 progress_dict[progress_id] = progress_cb(progress_id, profile)
1379 return progress_all 1379 return progress_all
1380 1380
1381 def registerCallback(self, callback, *args, **kwargs): 1381 def register_callback(self, callback, *args, **kwargs):
1382 """Register a callback. 1382 """Register a callback.
1383 1383
1384 @param callback(callable): method to call 1384 @param callback(callable): method to call
1385 @param kwargs: can contain: 1385 @param kwargs: can contain:
1386 with_data(bool): True if the callback use the optional data dict 1386 with_data(bool): True if the callback use the optional data dict
1397 raise exceptions.ConflictError(_("id already registered")) 1397 raise exceptions.ConflictError(_("id already registered"))
1398 self._cb_map[callback_id] = (callback, args, kwargs) 1398 self._cb_map[callback_id] = (callback, args, kwargs)
1399 1399
1400 if "one_shot" in kwargs: # One Shot callback are removed after 30 min 1400 if "one_shot" in kwargs: # One Shot callback are removed after 30 min
1401 1401
1402 def purgeCallback(): 1402 def purge_callback():
1403 try: 1403 try:
1404 self.removeCallback(callback_id) 1404 self.removeCallback(callback_id)
1405 except KeyError: 1405 except KeyError:
1406 pass 1406 pass
1407 1407
1408 reactor.callLater(1800, purgeCallback) 1408 reactor.callLater(1800, purge_callback)
1409 1409
1410 return callback_id 1410 return callback_id
1411 1411
1412 def removeCallback(self, callback_id): 1412 def removeCallback(self, callback_id):
1413 """ Remove a previously registered callback 1413 """ Remove a previously registered callback
1414 @param callback_id: id returned by [registerCallback] """ 1414 @param callback_id: id returned by [register_callback] """
1415 log.debug("Removing callback [%s]" % callback_id) 1415 log.debug("Removing callback [%s]" % callback_id)
1416 del self._cb_map[callback_id] 1416 del self._cb_map[callback_id]
1417 1417
1418 def launchCallback(self, callback_id, data=None, profile_key=C.PROF_KEY_NONE): 1418 def launch_callback(self, callback_id, data=None, profile_key=C.PROF_KEY_NONE):
1419 """Launch a specific callback 1419 """Launch a specific callback
1420 1420
1421 @param callback_id: id of the action (callback) to launch 1421 @param callback_id: id of the action (callback) to launch
1422 @param data: optional data 1422 @param data: optional data
1423 @profile_key: %(doc_profile_key)s 1423 @profile_key: %(doc_profile_key)s
1428 - C.BOOL_TRUE 1428 - C.BOOL_TRUE
1429 - C.BOOL_FALSE 1429 - C.BOOL_FALSE
1430 """ 1430 """
1431 #  FIXME: security limit need to be checked here 1431 #  FIXME: security limit need to be checked here
1432 try: 1432 try:
1433 client = self.getClient(profile_key) 1433 client = self.get_client(profile_key)
1434 except exceptions.NotFound: 1434 except exceptions.NotFound:
1435 # client is not available yet 1435 # client is not available yet
1436 profile = self.memory.getProfileName(profile_key) 1436 profile = self.memory.get_profile_name(profile_key)
1437 if not profile: 1437 if not profile:
1438 raise exceptions.ProfileUnknownError( 1438 raise exceptions.ProfileUnknownError(
1439 _("trying to launch action with a non-existant profile") 1439 _("trying to launch action with a non-existant profile")
1440 ) 1440 )
1441 else: 1441 else:
1466 del kwargs["with_data"] 1466 del kwargs["with_data"]
1467 1467
1468 if kwargs.pop("one_shot", False): 1468 if kwargs.pop("one_shot", False):
1469 self.removeCallback(callback_id) 1469 self.removeCallback(callback_id)
1470 1470
1471 return utils.asDeferred(callback, *args, **kwargs) 1471 return utils.as_deferred(callback, *args, **kwargs)
1472 1472
1473 # Menus management 1473 # Menus management
1474 1474
1475 def _getMenuCanonicalPath(self, path): 1475 def _get_menu_canonical_path(self, path):
1476 """give canonical form of path 1476 """give canonical form of path
1477 1477
1478 canonical form is a tuple of the path were every element is stripped and lowercase 1478 canonical form is a tuple of the path were every element is stripped and lowercase
1479 @param path(iterable[unicode]): untranslated path to menu 1479 @param path(iterable[unicode]): untranslated path to menu
1480 @return (tuple[unicode]): canonical form of path 1480 @return (tuple[unicode]): canonical form of path
1481 """ 1481 """
1482 return tuple((p.lower().strip() for p in path)) 1482 return tuple((p.lower().strip() for p in path))
1483 1483
1484 def importMenu(self, path, callback, security_limit=C.NO_SECURITY_LIMIT, 1484 def import_menu(self, path, callback, security_limit=C.NO_SECURITY_LIMIT,
1485 help_string="", type_=C.MENU_GLOBAL): 1485 help_string="", type_=C.MENU_GLOBAL):
1486 r"""register a new menu for frontends 1486 r"""register a new menu for frontends
1487 1487
1488 @param path(iterable[unicode]): path to go to the menu 1488 @param path(iterable[unicode]): path to go to the menu
1489 (category/subcategory/.../item) (e.g.: ("File", "Open")) 1489 (category/subcategory/.../item) (e.g.: ("File", "Open"))
1490 /!\ use D_() instead of _() for translations (e.g. (D_("File"), D_("Open"))) 1490 /!\ use D_() instead of _() for translations (e.g. (D_("File"), D_("Open")))
1491 untranslated/lower case path can be used to identity a menu, for this reason 1491 untranslated/lower case path can be used to identity a menu, for this reason
1492 it must be unique independently of case. 1492 it must be unique independently of case.
1493 @param callback(callable): method to be called when menuitem is selected, callable 1493 @param callback(callable): method to be called when menuitem is selected, callable
1494 or a callback id (string) as returned by [registerCallback] 1494 or a callback id (string) as returned by [register_callback]
1495 @param security_limit(int): %(doc_security_limit)s 1495 @param security_limit(int): %(doc_security_limit)s
1496 /!\ security_limit MUST be added to data in launchCallback if used #TODO 1496 /!\ security_limit MUST be added to data in launch_callback if used #TODO
1497 @param help_string(unicode): string used to indicate what the menu do (can be 1497 @param help_string(unicode): string used to indicate what the menu do (can be
1498 show as a tooltip). 1498 show as a tooltip).
1499 /!\ use D_() instead of _() for translations 1499 /!\ use D_() instead of _() for translations
1500 @param type(unicode): one of: 1500 @param type(unicode): one of:
1501 - C.MENU_GLOBAL: classical menu, can be shown in a menubar on top (e.g. 1501 - C.MENU_GLOBAL: classical menu, can be shown in a menubar on top (e.g.
1515 menu_data must contain a "group" data 1515 menu_data must contain a "group" data
1516 @return (unicode): menu_id (same as callback_id) 1516 @return (unicode): menu_id (same as callback_id)
1517 """ 1517 """
1518 1518
1519 if callable(callback): 1519 if callable(callback):
1520 callback_id = self.registerCallback(callback, with_data=True) 1520 callback_id = self.register_callback(callback, with_data=True)
1521 elif isinstance(callback, str): 1521 elif isinstance(callback, str):
1522 # The callback is already registered 1522 # The callback is already registered
1523 callback_id = callback 1523 callback_id = callback
1524 try: 1524 try:
1525 callback, args, kwargs = self._cb_map[callback_id] 1525 callback, args, kwargs = self._cb_map[callback_id]
1533 if menu_data["path"] == path and menu_data["type"] == type_: 1533 if menu_data["path"] == path and menu_data["type"] == type_:
1534 raise exceptions.ConflictError( 1534 raise exceptions.ConflictError(
1535 _("A menu with the same path and type already exists") 1535 _("A menu with the same path and type already exists")
1536 ) 1536 )
1537 1537
1538 path_canonical = self._getMenuCanonicalPath(path) 1538 path_canonical = self._get_menu_canonical_path(path)
1539 menu_key = (type_, path_canonical) 1539 menu_key = (type_, path_canonical)
1540 1540
1541 if menu_key in self._menus_paths: 1541 if menu_key in self._menus_paths:
1542 raise exceptions.ConflictError( 1542 raise exceptions.ConflictError(
1543 "this menu path is already used: {path} ({menu_key})".format( 1543 "this menu path is already used: {path} ({menu_key})".format(
1556 self._menus[callback_id] = menu_data 1556 self._menus[callback_id] = menu_data
1557 self._menus_paths[menu_key] = callback_id 1557 self._menus_paths[menu_key] = callback_id
1558 1558
1559 return callback_id 1559 return callback_id
1560 1560
1561 def getMenus(self, language="", security_limit=C.NO_SECURITY_LIMIT): 1561 def get_menus(self, language="", security_limit=C.NO_SECURITY_LIMIT):
1562 """Return all menus registered 1562 """Return all menus registered
1563 1563
1564 @param language: language used for translation, or empty string for default 1564 @param language: language used for translation, or empty string for default
1565 @param security_limit: %(doc_security_limit)s 1565 @param security_limit: %(doc_security_limit)s
1566 @return: array of tuple with: 1566 @return: array of tuple with:
1580 if security_limit != C.NO_SECURITY_LIMIT and ( 1580 if security_limit != C.NO_SECURITY_LIMIT and (
1581 menu_security_limit == C.NO_SECURITY_LIMIT 1581 menu_security_limit == C.NO_SECURITY_LIMIT
1582 or menu_security_limit > security_limit 1582 or menu_security_limit > security_limit
1583 ): 1583 ):
1584 continue 1584 continue
1585 languageSwitch(language) 1585 language_switch(language)
1586 path_i18n = [_(elt) for elt in path] 1586 path_i18n = [_(elt) for elt in path]
1587 languageSwitch() 1587 language_switch()
1588 extra = {} # TODO: manage extra data like icon 1588 extra = {} # TODO: manage extra data like icon
1589 ret.append((menu_id, type_, path, path_i18n, extra)) 1589 ret.append((menu_id, type_, path, path_i18n, extra))
1590 1590
1591 return ret 1591 return ret
1592 1592
1593 def _launchMenu(self, menu_type, path, data=None, security_limit=C.NO_SECURITY_LIMIT, 1593 def _launch_menu(self, menu_type, path, data=None, security_limit=C.NO_SECURITY_LIMIT,
1594 profile_key=C.PROF_KEY_NONE): 1594 profile_key=C.PROF_KEY_NONE):
1595 client = self.getClient(profile_key) 1595 client = self.get_client(profile_key)
1596 return self.launchMenu(client, menu_type, path, data, security_limit) 1596 return self.launch_menu(client, menu_type, path, data, security_limit)
1597 1597
1598 def launchMenu(self, client, menu_type, path, data=None, 1598 def launch_menu(self, client, menu_type, path, data=None,
1599 security_limit=C.NO_SECURITY_LIMIT): 1599 security_limit=C.NO_SECURITY_LIMIT):
1600 """launch action a menu action 1600 """launch action a menu action
1601 1601
1602 @param menu_type(unicode): type of menu to launch 1602 @param menu_type(unicode): type of menu to launch
1603 @param path(iterable[unicode]): canonical path of the menu 1603 @param path(iterable[unicode]): canonical path of the menu
1604 @params data(dict): menu data 1604 @params data(dict): menu data
1605 @raise NotFound: this path is not known 1605 @raise NotFound: this path is not known
1606 """ 1606 """
1607 # FIXME: manage security_limit here 1607 # FIXME: manage security_limit here
1608 # defaut security limit should be high instead of C.NO_SECURITY_LIMIT 1608 # defaut security limit should be high instead of C.NO_SECURITY_LIMIT
1609 canonical_path = self._getMenuCanonicalPath(path) 1609 canonical_path = self._get_menu_canonical_path(path)
1610 menu_key = (menu_type, canonical_path) 1610 menu_key = (menu_type, canonical_path)
1611 try: 1611 try:
1612 callback_id = self._menus_paths[menu_key] 1612 callback_id = self._menus_paths[menu_key]
1613 except KeyError: 1613 except KeyError:
1614 raise exceptions.NotFound( 1614 raise exceptions.NotFound(
1615 "Can't find menu {path} ({menu_type})".format( 1615 "Can't find menu {path} ({menu_type})".format(
1616 path=canonical_path, menu_type=menu_type 1616 path=canonical_path, menu_type=menu_type
1617 ) 1617 )
1618 ) 1618 )
1619 return self.launchCallback(callback_id, data, client.profile) 1619 return self.launch_callback(callback_id, data, client.profile)
1620 1620
1621 def getMenuHelp(self, menu_id, language=""): 1621 def get_menu_help(self, menu_id, language=""):
1622 """return the help string of the menu 1622 """return the help string of the menu
1623 1623
1624 @param menu_id: id of the menu (same as callback_id) 1624 @param menu_id: id of the menu (same as callback_id)
1625 @param language: language used for translation, or empty string for default 1625 @param language: language used for translation, or empty string for default
1626 @param return: translated help 1626 @param return: translated help
1628 """ 1628 """
1629 try: 1629 try:
1630 menu_data = self._menus[menu_id] 1630 menu_data = self._menus[menu_id]
1631 except KeyError: 1631 except KeyError:
1632 raise exceptions.DataError("Trying to access an unknown menu") 1632 raise exceptions.DataError("Trying to access an unknown menu")
1633 languageSwitch(language) 1633 language_switch(language)
1634 help_string = _(menu_data["help_string"]) 1634 help_string = _(menu_data["help_string"])
1635 languageSwitch() 1635 language_switch()
1636 return help_string 1636 return help_string