comparison sat/core/xmpp.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 78b5f356900c
children c23cad65ae99
comparison
equal deleted inserted replaced
4036:c4464d7ae97b 4037:524856bd7b19
87 return partial(getattr(self.plugin, attr), self.client) 87 return partial(getattr(self.plugin, attr), self.client)
88 88
89 89
90 class SatXMPPEntity(core_types.SatXMPPEntity): 90 class SatXMPPEntity(core_types.SatXMPPEntity):
91 """Common code for Client and Component""" 91 """Common code for Client and Component"""
92 # profile is added there when startConnection begins and removed when it is finished 92 # profile is added there when start_connection begins and removed when it is finished
93 profiles_connecting = set() 93 profiles_connecting = set()
94 94
95 def __init__(self, host_app, profile, max_retries): 95 def __init__(self, host_app, profile, max_retries):
96 factory = self.factory 96 factory = self.factory
97 97
98 # we monkey patch clientConnectionLost to handle networkEnabled/networkDisabled 98 # we monkey patch clientConnectionLost to handle network_enabled/network_disabled
99 # and to allow plugins to tune reconnection mechanism 99 # and to allow plugins to tune reconnection mechanism
100 clientConnectionFailed_ori = factory.clientConnectionFailed 100 clientConnectionFailed_ori = factory.clientConnectionFailed
101 clientConnectionLost_ori = factory.clientConnectionLost 101 clientConnectionLost_ori = factory.clientConnectionLost
102 factory.clientConnectionFailed = partial( 102 factory.clientConnectionFailed = partial(
103 self.connectionTerminated, term_type="failed", cb=clientConnectionFailed_ori) 103 self.connection_terminated, term_type="failed", cb=clientConnectionFailed_ori)
104 factory.clientConnectionLost = partial( 104 factory.clientConnectionLost = partial(
105 self.connectionTerminated, term_type="lost", cb=clientConnectionLost_ori) 105 self.connection_terminated, term_type="lost", cb=clientConnectionLost_ori)
106 106
107 factory.maxRetries = max_retries 107 factory.maxRetries = max_retries
108 factory.maxDelay = 30 108 factory.maxDelay = 30
109 # when self._connected_d is None, we are not connected 109 # when self._connected_d is None, we are not connected
110 # else, it's a deferred which fire on disconnection 110 # else, it's a deferred which fire on disconnection
127 def __repr__(self): 127 def __repr__(self):
128 return f"{super().__repr__()} - profile: {self.profile!r}" 128 return f"{super().__repr__()} - profile: {self.profile!r}"
129 129
130 ## initialisation ## 130 ## initialisation ##
131 131
132 async def _callConnectionTriggers(self, connection_timer): 132 async def _call_connection_triggers(self, connection_timer):
133 """Call conneting trigger prepare connected trigger 133 """Call conneting trigger prepare connected trigger
134 134
135 @param plugins(iterable): plugins to use 135 @param plugins(iterable): plugins to use
136 @return (list[object, callable]): plugin to trigger tuples with: 136 @return (list[object, callable]): plugin to trigger tuples with:
137 - plugin instance 137 - plugin instance
138 - profileConnected* triggers (to call after connection) 138 - profile_connected* triggers (to call after connection)
139 """ 139 """
140 plugin_conn_cb = [] 140 plugin_conn_cb = []
141 for plugin in self._getPluginsList(): 141 for plugin in self._get_plugins_list():
142 # we check if plugin handle client mode 142 # we check if plugin handle client mode
143 if plugin.is_handler: 143 if plugin.is_handler:
144 plugin.getHandler(self).setHandlerParent(self) 144 plugin.get_handler(self).setHandlerParent(self)
145 145
146 # profileConnecting/profileConnected methods handling 146 # profile_connecting/profile_connected methods handling
147 147
148 timer = connection_timer[plugin] = { 148 timer = connection_timer[plugin] = {
149 "total": 0 149 "total": 0
150 } 150 }
151 # profile connecting is called right now (before actually starting client) 151 # profile connecting is called right now (before actually starting client)
152 connecting_cb = getattr(plugin, "profileConnecting", None) 152 connecting_cb = getattr(plugin, "profile_connecting", None)
153 if connecting_cb is not None: 153 if connecting_cb is not None:
154 connecting_start = time.time() 154 connecting_start = time.time()
155 await utils.asDeferred(connecting_cb, self) 155 await utils.as_deferred(connecting_cb, self)
156 timer["connecting"] = time.time() - connecting_start 156 timer["connecting"] = time.time() - connecting_start
157 timer["total"] += timer["connecting"] 157 timer["total"] += timer["connecting"]
158 158
159 # profile connected is called after client is ready and roster is got 159 # profile connected is called after client is ready and roster is got
160 connected_cb = getattr(plugin, "profileConnected", None) 160 connected_cb = getattr(plugin, "profile_connected", None)
161 if connected_cb is not None: 161 if connected_cb is not None:
162 plugin_conn_cb.append((plugin, connected_cb)) 162 plugin_conn_cb.append((plugin, connected_cb))
163 163
164 return plugin_conn_cb 164 return plugin_conn_cb
165 165
166 def _getPluginsList(self): 166 def _get_plugins_list(self):
167 """Return list of plugin to use 167 """Return list of plugin to use
168 168
169 need to be implemented by subclasses 169 need to be implemented by subclasses
170 this list is used to call profileConnect* triggers 170 this list is used to call profileConnect* triggers
171 @return(iterable[object]): plugins to use 171 @return(iterable[object]): plugins to use
172 """ 172 """
173 raise NotImplementedError 173 raise NotImplementedError
174 174
175 def _createSubProtocols(self): 175 def _create_sub_protocols(self):
176 return 176 return
177 177
178 def entityConnected(self): 178 def entity_connected(self):
179 """Called once connection is done 179 """Called once connection is done
180 180
181 may return a Deferred, to perform initialisation tasks 181 may return a Deferred, to perform initialisation tasks
182 """ 182 """
183 return 183 return
187 callback: Callable, 187 callback: Callable,
188 entity: "SatXMPPEntity", 188 entity: "SatXMPPEntity",
189 timer: Dict[str, float] 189 timer: Dict[str, float]
190 ) -> None: 190 ) -> None:
191 connected_start = time.time() 191 connected_start = time.time()
192 await utils.asDeferred(callback, entity) 192 await utils.as_deferred(callback, entity)
193 timer["connected"] = time.time() - connected_start 193 timer["connected"] = time.time() - connected_start
194 timer["total"] += timer["connected"] 194 timer["total"] += timer["connected"]
195 195
196 @classmethod 196 @classmethod
197 async def startConnection(cls, host, profile, max_retries): 197 async def start_connection(cls, host, profile, max_retries):
198 """instantiate the entity and start the connection""" 198 """instantiate the entity and start the connection"""
199 # FIXME: reconnection doesn't seems to be handled correclty 199 # FIXME: reconnection doesn't seems to be handled correclty
200 # (client is deleted then recreated from scratch) 200 # (client is deleted then recreated from scratch)
201 # most of methods called here should be called once on first connection 201 # most of methods called here should be called once on first connection
202 # (e.g. adding subprotocols) 202 # (e.g. adding subprotocols)
206 raise exceptions.CancelError(f"{profile} is already being connected") 206 raise exceptions.CancelError(f"{profile} is already being connected")
207 cls.profiles_connecting.add(profile) 207 cls.profiles_connecting.add(profile)
208 try: 208 try:
209 try: 209 try:
210 port = int( 210 port = int(
211 host.memory.getParamA( 211 host.memory.param_get_a(
212 C.FORCE_PORT_PARAM, "Connection", profile_key=profile 212 C.FORCE_PORT_PARAM, "Connection", profile_key=profile
213 ) 213 )
214 ) 214 )
215 except ValueError: 215 except ValueError:
216 log.debug(_("Can't parse port value, using default value")) 216 log.debug(_("Can't parse port value, using default value"))
217 port = ( 217 port = (
218 None 218 None
219 ) # will use default value 5222 or be retrieved from a DNS SRV record 219 ) # will use default value 5222 or be retrieved from a DNS SRV record
220 220
221 password = await host.memory.asyncGetParamA( 221 password = await host.memory.param_get_a_async(
222 "Password", "Connection", profile_key=profile 222 "Password", "Connection", profile_key=profile
223 ) 223 )
224 224
225 entity_jid_s = await host.memory.asyncGetParamA( 225 entity_jid_s = await host.memory.param_get_a_async(
226 "JabberID", "Connection", profile_key=profile) 226 "JabberID", "Connection", profile_key=profile)
227 entity_jid = jid.JID(entity_jid_s) 227 entity_jid = jid.JID(entity_jid_s)
228 228
229 if not entity_jid.resource and not cls.is_component and entity_jid.user: 229 if not entity_jid.resource and not cls.is_component and entity_jid.user:
230 # if no resource is specified, we create our own instead of using 230 # if no resource is specified, we create our own instead of using
231 # server returned one, as it will then stay stable in case of 231 # server returned one, as it will then stay stable in case of
232 # reconnection. we only do that for client and if there is a user part, to 232 # reconnection. we only do that for client and if there is a user part, to
233 # let server decide for anonymous login 233 # let server decide for anonymous login
234 resource_dict = await host.memory.storage.getPrivates( 234 resource_dict = await host.memory.storage.get_privates(
235 "core:xmpp", ["resource"] , profile=profile) 235 "core:xmpp", ["resource"] , profile=profile)
236 try: 236 try:
237 resource = resource_dict["resource"] 237 resource = resource_dict["resource"]
238 except KeyError: 238 except KeyError:
239 resource = f"{C.APP_NAME_FILE}.{shortuuid.uuid()}" 239 resource = f"{C.APP_NAME_FILE}.{shortuuid.uuid()}"
240 await host.memory.storage.setPrivateValue( 240 await host.memory.storage.set_private_value(
241 "core:xmpp", "resource", resource, profile=profile) 241 "core:xmpp", "resource", resource, profile=profile)
242 242
243 log.info(_("We'll use the stable resource {resource}").format( 243 log.info(_("We'll use the stable resource {resource}").format(
244 resource=resource)) 244 resource=resource))
245 entity_jid.resource = resource 245 entity_jid.resource = resource
246 246
247 if profile in host.profiles: 247 if profile in host.profiles:
248 if host.profiles[profile].isConnected(): 248 if host.profiles[profile].is_connected():
249 raise exceptions.InternalError( 249 raise exceptions.InternalError(
250 f"There is already a connected profile of name {profile!r} in " 250 f"There is already a connected profile of name {profile!r} in "
251 f"host") 251 f"host")
252 log.debug( 252 log.debug(
253 "removing unconnected profile {profile!r}") 253 "removing unconnected profile {profile!r}")
254 del host.profiles[profile] 254 del host.profiles[profile]
255 entity = host.profiles[profile] = cls( 255 entity = host.profiles[profile] = cls(
256 host, profile, entity_jid, password, 256 host, profile, entity_jid, password,
257 host.memory.getParamA(C.FORCE_SERVER_PARAM, "Connection", 257 host.memory.param_get_a(C.FORCE_SERVER_PARAM, "Connection",
258 profile_key=profile) or None, 258 profile_key=profile) or None,
259 port, max_retries, 259 port, max_retries,
260 ) 260 )
261 261
262 await entity.encryption.loadSessions() 262 await entity.encryption.load_sessions()
263 263
264 entity._createSubProtocols() 264 entity._create_sub_protocols()
265 265
266 entity.fallBack = SatFallbackHandler(host) 266 entity.fallBack = SatFallbackHandler(host)
267 entity.fallBack.setHandlerParent(entity) 267 entity.fallBack.setHandlerParent(entity)
268 268
269 entity.versionHandler = SatVersionHandler(C.APP_NAME, host.full_version) 269 entity.versionHandler = SatVersionHandler(C.APP_NAME, host.full_version)
273 entity.identityHandler.setHandlerParent(entity) 273 entity.identityHandler.setHandlerParent(entity)
274 274
275 log.debug(_("setting plugins parents")) 275 log.debug(_("setting plugins parents"))
276 276
277 connection_timer: Dict[str, Dict[str, float]] = {} 277 connection_timer: Dict[str, Dict[str, float]] = {}
278 plugin_conn_cb = await entity._callConnectionTriggers(connection_timer) 278 plugin_conn_cb = await entity._call_connection_triggers(connection_timer)
279 279
280 entity.startService() 280 entity.startService()
281 281
282 await entity.conn_deferred 282 await entity.conn_deferred
283 283
284 await defer.maybeDeferred(entity.entityConnected) 284 await defer.maybeDeferred(entity.entity_connected)
285 285
286 # Call profileConnected callback for all plugins, 286 # Call profile_connected callback for all plugins,
287 # and print error message if any of them fails 287 # and print error message if any of them fails
288 conn_cb_list = [] 288 conn_cb_list = []
289 for plugin, callback in plugin_conn_cb: 289 for plugin, callback in plugin_conn_cb:
290 conn_cb_list.append( 290 conn_cb_list.append(
291 defer.ensureDeferred( 291 defer.ensureDeferred(
294 ) 294 )
295 ) 295 )
296 ) 296 )
297 list_d = defer.DeferredList(conn_cb_list) 297 list_d = defer.DeferredList(conn_cb_list)
298 298
299 def logPluginResults(results): 299 def log_plugin_results(results):
300 if not results: 300 if not results:
301 log.info("no plugin loaded") 301 log.info("no plugin loaded")
302 return 302 return
303 all_succeed = all([success for success, result in results]) 303 all_succeed = all([success for success, result in results])
304 if not all_succeed: 304 if not all_succeed:
345 log.debug( 345 log.debug(
346 f" Plugins total={total_plugins:.2f}s real={total_real:.2f}s\n" 346 f" Plugins total={total_plugins:.2f}s real={total_real:.2f}s\n"
347 ) 347 )
348 348
349 await list_d.addCallback( 349 await list_d.addCallback(
350 logPluginResults 350 log_plugin_results
351 ) # FIXME: we should have a timeout here, and a way to know if a plugin freeze 351 ) # FIXME: we should have a timeout here, and a way to know if a plugin freeze
352 # TODO: mesure launch time of each plugin 352 # TODO: mesure launch time of each plugin
353 finally: 353 finally:
354 cls.profiles_connecting.remove(profile) 354 cls.profiles_connecting.remove(profile)
355 355
356 def _disconnectionCb(self, __): 356 def _disconnection_cb(self, __):
357 self._connected_d = None 357 self._connected_d = None
358 358
359 def _disconnectionEb(self, failure_): 359 def _disconnection_eb(self, failure_):
360 log.error(_("Error while disconnecting: {}".format(failure_))) 360 log.error(_("Error while disconnecting: {}".format(failure_)))
361 361
362 def _authd(self, xmlstream): 362 def _authd(self, xmlstream):
363 super(SatXMPPEntity, self)._authd(xmlstream) 363 super(SatXMPPEntity, self)._authd(xmlstream)
364 log.debug(_("{profile} identified").format(profile=self.profile)) 364 log.debug(_("{profile} identified").format(profile=self.profile))
365 self.streamInitialized() 365 self.stream_initialized()
366 366
367 def _finish_connection(self, __): 367 def _finish_connection(self, __):
368 if self.conn_deferred.called: 368 if self.conn_deferred.called:
369 # can happen in case of forced disconnection by server 369 # can happen in case of forced disconnection by server
370 log.debug(f"{self} has already been connected") 370 log.debug(f"{self} has already been connected")
371 else: 371 else:
372 self.conn_deferred.callback(None) 372 self.conn_deferred.callback(None)
373 373
374 def streamInitialized(self): 374 def stream_initialized(self):
375 """Called after _authd""" 375 """Called after _authd"""
376 log.debug(_("XML stream is initialized")) 376 log.debug(_("XML stream is initialized"))
377 if not self.host_app.trigger.point("xml_init", self): 377 if not self.host_app.trigger.point("xml_init", self):
378 return 378 return
379 self.postStreamInit() 379 self.post_stream_init()
380 380
381 def postStreamInit(self): 381 def post_stream_init(self):
382 """Workflow after stream initalisation.""" 382 """Workflow after stream initalisation."""
383 log.info( 383 log.info(
384 _("********** [{profile}] CONNECTED **********").format(profile=self.profile) 384 _("********** [{profile}] CONNECTED **********").format(profile=self.profile)
385 ) 385 )
386 386
387 # the following Deferred is used to know when we are connected 387 # the following Deferred is used to know when we are connected
388 # so we need to be set it to None when connection is lost 388 # so we need to be set it to None when connection is lost
389 self._connected_d = defer.Deferred() 389 self._connected_d = defer.Deferred()
390 self._connected_d.addCallback(self._cleanConnection) 390 self._connected_d.addCallback(self._clean_connection)
391 self._connected_d.addCallback(self._disconnectionCb) 391 self._connected_d.addCallback(self._disconnection_cb)
392 self._connected_d.addErrback(self._disconnectionEb) 392 self._connected_d.addErrback(self._disconnection_eb)
393 393
394 # we send the signal to the clients 394 # we send the signal to the clients
395 self.host_app.bridge.connected(self.jid.full(), self.profile) 395 self.host_app.bridge.connected(self.jid.full(), self.profile)
396 396
397 self.disco = SatDiscoProtocol(self) 397 self.disco = SatDiscoProtocol(self)
419 # we already chained an errback, no need to raise an exception 419 # we already chained an errback, no need to raise an exception
420 pass 420 pass
421 421
422 ## connection ## 422 ## connection ##
423 423
424 def connectionTerminated(self, connector, reason, term_type, cb): 424 def connection_terminated(self, connector, reason, term_type, cb):
425 """Display disconnection reason, and call factory method 425 """Display disconnection reason, and call factory method
426 426
427 This method is monkey patched to factory, allowing plugins to handle finely 427 This method is monkey patched to factory, allowing plugins to handle finely
428 reconnection with the triggers. 428 reconnection with the triggers.
429 @param connector(twisted.internet.base.BaseConnector): current connector 429 @param connector(twisted.internet.base.BaseConnector): current connector
451 log.warning(f"[{self.profile}] Connection {term_type}: {reason_str}") 451 log.warning(f"[{self.profile}] Connection {term_type}: {reason_str}")
452 if not self.host_app.trigger.point("connection_" + term_type, connector, reason): 452 if not self.host_app.trigger.point("connection_" + term_type, connector, reason):
453 return 453 return
454 return cb(connector, reason) 454 return cb(connector, reason)
455 455
456 def networkDisabled(self): 456 def network_disabled(self):
457 """Indicate that network has been completely disabled 457 """Indicate that network has been completely disabled
458 458
459 In other words, internet is not available anymore and transport must be stopped. 459 In other words, internet is not available anymore and transport must be stopped.
460 Retrying is disabled too, as it makes no sense to try without network, and it may 460 Retrying is disabled too, as it makes no sense to try without network, and it may
461 use resources (notably battery on mobiles). 461 use resources (notably battery on mobiles).
464 self.factory.continueTrying = 0 464 self.factory.continueTrying = 0
465 self._network_disabled = True 465 self._network_disabled = True
466 if self.xmlstream is not None: 466 if self.xmlstream is not None:
467 self.xmlstream.transport.abortConnection() 467 self.xmlstream.transport.abortConnection()
468 468
469 def networkEnabled(self): 469 def network_enabled(self):
470 """Indicate that network has been (re)enabled 470 """Indicate that network has been (re)enabled
471 471
472 This happens when e.g. user activate WIFI connection. 472 This happens when e.g. user activate WIFI connection.
473 """ 473 """
474 try: 474 try:
475 connector = self._saved_connector 475 connector = self._saved_connector
476 network_disabled = self._network_disabled 476 network_disabled = self._network_disabled
477 except AttributeError: 477 except AttributeError:
478 # connection has not been stopped by networkDisabled 478 # connection has not been stopped by network_disabled
479 # we don't have to restart it 479 # we don't have to restart it
480 log.debug(f"no connection to restart [{self.profile}]") 480 log.debug(f"no connection to restart [{self.profile}]")
481 return 481 return
482 else: 482 else:
483 del self._network_disabled 483 del self._network_disabled
494 send_hooks = [] 494 send_hooks = []
495 receive_hooks = [] 495 receive_hooks = []
496 self.host_app.trigger.point( 496 self.host_app.trigger.point(
497 "stream_hooks", self, receive_hooks, send_hooks) 497 "stream_hooks", self, receive_hooks, send_hooks)
498 for hook in receive_hooks: 498 for hook in receive_hooks:
499 xs.addHook(C.STREAM_HOOK_RECEIVE, hook) 499 xs.add_hook(C.STREAM_HOOK_RECEIVE, hook)
500 for hook in send_hooks: 500 for hook in send_hooks:
501 xs.addHook(C.STREAM_HOOK_SEND, hook) 501 xs.add_hook(C.STREAM_HOOK_SEND, hook)
502 super(SatXMPPEntity, self)._connected(xs) 502 super(SatXMPPEntity, self)._connected(xs)
503 503
504 def disconnectProfile(self, reason): 504 def disconnect_profile(self, reason):
505 if self._connected_d is not None: 505 if self._connected_d is not None:
506 self.host_app.bridge.disconnected( 506 self.host_app.bridge.disconnected(
507 self.profile 507 self.profile
508 ) # we send the signal to the clients 508 ) # we send the signal to the clients
509 log.info( 509 log.info(
514 # we purge only if no new connection attempt is expected 514 # we purge only if no new connection attempt is expected
515 if not self.factory.continueTrying: 515 if not self.factory.continueTrying:
516 log.debug("continueTrying not set, purging entity") 516 log.debug("continueTrying not set, purging entity")
517 self._connected_d.callback(None) 517 self._connected_d.callback(None)
518 # and we remove references to this client 518 # and we remove references to this client
519 self.host_app.purgeEntity(self.profile) 519 self.host_app.purge_entity(self.profile)
520 520
521 if not self.conn_deferred.called: 521 if not self.conn_deferred.called:
522 if reason is None: 522 if reason is None:
523 err = error.StreamError("Server unexpectedly closed the connection") 523 err = error.StreamError("Server unexpectedly closed the connection")
524 else: 524 else:
533 "Please contact your server administrator.")) 533 "Please contact your server administrator."))
534 self.factory.stopTrying() 534 self.factory.stopTrying()
535 try: 535 try:
536 # with invalid certificate, we should not retry to connect 536 # with invalid certificate, we should not retry to connect
537 # so we delete saved connector to avoid reconnection if 537 # so we delete saved connector to avoid reconnection if
538 # networkEnabled is called. 538 # network_enabled is called.
539 del self._saved_connector 539 del self._saved_connector
540 except AttributeError: 540 except AttributeError:
541 pass 541 pass
542 except (IndexError, TypeError): 542 except (IndexError, TypeError):
543 pass 543 pass
545 545
546 def _disconnected(self, reason): 546 def _disconnected(self, reason):
547 super(SatXMPPEntity, self)._disconnected(reason) 547 super(SatXMPPEntity, self)._disconnected(reason)
548 if not self.host_app.trigger.point("disconnected", self, reason): 548 if not self.host_app.trigger.point("disconnected", self, reason):
549 return 549 return
550 self.disconnectProfile(reason) 550 self.disconnect_profile(reason)
551 551
552 @defer.inlineCallbacks 552 @defer.inlineCallbacks
553 def _cleanConnection(self, __): 553 def _clean_connection(self, __):
554 """method called on disconnection 554 """method called on disconnection
555 555
556 used to call profileDisconnected* triggers 556 used to call profile_disconnected* triggers
557 """ 557 """
558 trigger_name = "profileDisconnected" 558 trigger_name = "profile_disconnected"
559 for plugin in self._getPluginsList(): 559 for plugin in self._get_plugins_list():
560 disconnected_cb = getattr(plugin, trigger_name, None) 560 disconnected_cb = getattr(plugin, trigger_name, None)
561 if disconnected_cb is not None: 561 if disconnected_cb is not None:
562 yield disconnected_cb(self) 562 yield disconnected_cb(self)
563 563
564 def isConnected(self): 564 def is_connected(self):
565 """Return True is client is fully connected 565 """Return True is client is fully connected
566 566
567 client is considered fully connected if transport is started and all plugins 567 client is considered fully connected if transport is started and all plugins
568 are initialised 568 are initialised
569 """ 569 """
572 except AttributeError: 572 except AttributeError:
573 return False 573 return False
574 574
575 return self._connected_d is not None and transport_connected 575 return self._connected_d is not None and transport_connected
576 576
577 def entityDisconnect(self): 577 def entity_disconnect(self):
578 if not self.host_app.trigger.point("disconnecting", self): 578 if not self.host_app.trigger.point("disconnecting", self):
579 return 579 return
580 log.info(_("Disconnecting...")) 580 log.info(_("Disconnecting..."))
581 self.stopService() 581 self.stopService()
582 if self._connected_d is not None: 582 if self._connected_d is not None:
607 iq_error_elt = error.StanzaError( 607 iq_error_elt = error.StanzaError(
608 condition, text=text, appCondition=appCondition 608 condition, text=text, appCondition=appCondition
609 ).toResponse(iq_elt) 609 ).toResponse(iq_elt)
610 self.xmlstream.send(iq_error_elt) 610 self.xmlstream.send(iq_error_elt)
611 611
612 def generateMessageXML( 612 def generate_message_xml(
613 self, 613 self,
614 data: core_types.MessageData, 614 data: core_types.MessageData,
615 post_xml_treatments: Optional[defer.Deferred] = None 615 post_xml_treatments: Optional[defer.Deferred] = None
616 ) -> core_types.MessageData: 616 ) -> core_types.MessageData:
617 """Generate <message/> stanza from message data 617 """Generate <message/> stanza from message data
664 return data 664 return data
665 665
666 @property 666 @property
667 def is_admin(self) -> bool: 667 def is_admin(self) -> bool:
668 """True if a client is an administrator with extra privileges""" 668 """True if a client is an administrator with extra privileges"""
669 return self.host_app.memory.isAdmin(self.profile) 669 return self.host_app.memory.is_admin(self.profile)
670 670
671 def addPostXmlCallbacks(self, post_xml_treatments): 671 def add_post_xml_callbacks(self, post_xml_treatments):
672 """Used to add class level callbacks at the end of the workflow 672 """Used to add class level callbacks at the end of the workflow
673 673
674 @param post_xml_treatments(D): the same Deferred as in sendMessage trigger 674 @param post_xml_treatments(D): the same Deferred as in sendMessage trigger
675 """ 675 """
676 raise NotImplementedError 676 raise NotImplementedError
683 # it is intended for things like end 2 end encryption. 683 # it is intended for things like end 2 end encryption.
684 # *DO NOT* cancel (i.e. return False) without very good reason 684 # *DO NOT* cancel (i.e. return False) without very good reason
685 # (out of band transmission for instance). 685 # (out of band transmission for instance).
686 # e2e should have a priority of 0 here, and out of band transmission 686 # e2e should have a priority of 0 here, and out of band transmission
687 # a lower priority 687 # a lower priority
688 if not (await self.host_app.trigger.asyncPoint("send", self, obj)): 688 if not (await self.host_app.trigger.async_point("send", self, obj)):
689 return 689 return
690 super().send(obj) 690 super().send(obj)
691 691
692 def send(self, obj): 692 def send(self, obj):
693 defer.ensureDeferred(self.a_send(obj)) 693 defer.ensureDeferred(self.a_send(obj))
694 694
695 async def sendMessageData(self, mess_data): 695 async def send_message_data(self, mess_data):
696 """Convenient method to send message data to stream 696 """Convenient method to send message data to stream
697 697
698 This method will send mess_data[u'xml'] to stream, but a trigger is there 698 This method will send mess_data[u'xml'] to stream, but a trigger is there
699 The trigger can't be cancelled, it's a good place for e2e encryption which 699 The trigger can't be cancelled, it's a good place for e2e encryption which
700 don't handle full stanza encryption 700 don't handle full stanza encryption
701 This trigger can return a Deferred (it's an asyncPoint) 701 This trigger can return a Deferred (it's an async_point)
702 @param mess_data(dict): message data as constructed by onMessage workflow 702 @param mess_data(dict): message data as constructed by onMessage workflow
703 @return (dict): mess_data (so it can be used in a deferred chain) 703 @return (dict): mess_data (so it can be used in a deferred chain)
704 """ 704 """
705 # XXX: This is the last trigger before u"send" (last but one globally) 705 # XXX: This is the last trigger before u"send" (last but one globally)
706 # for sending message. 706 # for sending message.
707 # This is intented for e2e encryption which doesn't do full stanza 707 # This is intented for e2e encryption which doesn't do full stanza
708 # encryption (e.g. OTR) 708 # encryption (e.g. OTR)
709 # This trigger point can't cancel the method 709 # This trigger point can't cancel the method
710 await self.host_app.trigger.asyncPoint("sendMessageData", self, mess_data, 710 await self.host_app.trigger.async_point("send_message_data", self, mess_data,
711 triggers_no_cancel=True) 711 triggers_no_cancel=True)
712 await self.a_send(mess_data["xml"]) 712 await self.a_send(mess_data["xml"])
713 return mess_data 713 return mess_data
714 714
715 def sendMessage( 715 def sendMessage(
760 if data["subject"]: 760 if data["subject"]:
761 data["type"] = C.MESS_TYPE_NORMAL 761 data["type"] = C.MESS_TYPE_NORMAL
762 elif not data["to"].resource: 762 elif not data["to"].resource:
763 # we may have a groupchat message, we check if the we know this jid 763 # we may have a groupchat message, we check if the we know this jid
764 try: 764 try:
765 entity_type = self.host_app.memory.getEntityDatum( 765 entity_type = self.host_app.memory.get_entity_datum(
766 self, data["to"], C.ENTITY_TYPE 766 self, data["to"], C.ENTITY_TYPE
767 ) 767 )
768 # FIXME: should entity_type manage resources ? 768 # FIXME: should entity_type manage resources ?
769 except (exceptions.UnknownEntityError, KeyError): 769 except (exceptions.UnknownEntityError, KeyError):
770 entity_type = "contact" 770 entity_type = "contact"
781 # thing internally, this could be unified 781 # thing internally, this could be unified
782 send_only = data["extra"].get("send_only", False) 782 send_only = data["extra"].get("send_only", False)
783 783
784 if not no_trigger and not send_only: 784 if not no_trigger and not send_only:
785 # is the session encrypted? If so we indicate it in data 785 # is the session encrypted? If so we indicate it in data
786 self.encryption.setEncryptionFlag(data) 786 self.encryption.set_encryption_flag(data)
787 787
788 if not self.host_app.trigger.point( 788 if not self.host_app.trigger.point(
789 "sendMessage" + self.trigger_suffix, 789 "sendMessage" + self.trigger_suffix,
790 self, 790 self,
791 data, 791 data,
795 return defer.succeed(None) 795 return defer.succeed(None)
796 796
797 log.debug(_("Sending message (type {type}, to {to})") 797 log.debug(_("Sending message (type {type}, to {to})")
798 .format(type=data["type"], to=to_jid.full())) 798 .format(type=data["type"], to=to_jid.full()))
799 799
800 pre_xml_treatments.addCallback(lambda __: self.generateMessageXML(data, post_xml_treatments)) 800 pre_xml_treatments.addCallback(lambda __: self.generate_message_xml(data, post_xml_treatments))
801 pre_xml_treatments.addCallback(lambda __: post_xml_treatments) 801 pre_xml_treatments.addCallback(lambda __: post_xml_treatments)
802 pre_xml_treatments.addErrback(self._cancelErrorTrap) 802 pre_xml_treatments.addErrback(self._cancel_error_trap)
803 post_xml_treatments.addCallback( 803 post_xml_treatments.addCallback(
804 lambda __: defer.ensureDeferred(self.sendMessageData(data)) 804 lambda __: defer.ensureDeferred(self.send_message_data(data))
805 ) 805 )
806 if send_only: 806 if send_only:
807 log.debug(_("Triggers, storage and echo have been inhibited by the " 807 log.debug(_("Triggers, storage and echo have been inhibited by the "
808 "'send_only' parameter")) 808 "'send_only' parameter"))
809 else: 809 else:
810 self.addPostXmlCallbacks(post_xml_treatments) 810 self.add_post_xml_callbacks(post_xml_treatments)
811 post_xml_treatments.addErrback(self._cancelErrorTrap) 811 post_xml_treatments.addErrback(self._cancel_error_trap)
812 post_xml_treatments.addErrback(self.host_app.logErrback) 812 post_xml_treatments.addErrback(self.host_app.log_errback)
813 pre_xml_treatments.callback(data) 813 pre_xml_treatments.callback(data)
814 return pre_xml_treatments 814 return pre_xml_treatments
815 815
816 def _cancelErrorTrap(self, failure): 816 def _cancel_error_trap(self, failure):
817 """A message sending can be cancelled by a plugin treatment""" 817 """A message sending can be cancelled by a plugin treatment"""
818 failure.trap(exceptions.CancelError) 818 failure.trap(exceptions.CancelError)
819 819
820 def isMessagePrintable(self, mess_data): 820 def is_message_printable(self, mess_data):
821 """Return True if a message contain payload to show in frontends""" 821 """Return True if a message contain payload to show in frontends"""
822 return ( 822 return (
823 mess_data["message"] or mess_data["subject"] 823 mess_data["message"] or mess_data["subject"]
824 or mess_data["extra"].get(C.KEY_ATTACHMENTS) 824 or mess_data["extra"].get(C.KEY_ATTACHMENTS)
825 or mess_data["type"] == C.MESS_TYPE_INFO 825 or mess_data["type"] == C.MESS_TYPE_INFO
826 ) 826 )
827 827
828 async def messageAddToHistory(self, data): 828 async def message_add_to_history(self, data):
829 """Store message into database (for local history) 829 """Store message into database (for local history)
830 830
831 @param data: message data dictionnary 831 @param data: message data dictionnary
832 @param client: profile's client 832 @param client: profile's client
833 """ 833 """
834 if data["type"] != C.MESS_TYPE_GROUPCHAT: 834 if data["type"] != C.MESS_TYPE_GROUPCHAT:
835 # we don't add groupchat message to history, as we get them back 835 # we don't add groupchat message to history, as we get them back
836 # and they will be added then 836 # and they will be added then
837 837
838 # we need a message to store 838 # we need a message to store
839 if self.isMessagePrintable(data): 839 if self.is_message_printable(data):
840 await self.host_app.memory.addToHistory(self, data) 840 await self.host_app.memory.add_to_history(self, data)
841 else: 841 else:
842 log.warning( 842 log.warning(
843 "No message found" 843 "No message found"
844 ) # empty body should be managed by plugins before this point 844 ) # empty body should be managed by plugins before this point
845 return data 845 return data
846 846
847 def messageGetBridgeArgs(self, data): 847 def message_get_bridge_args(self, data):
848 """Generate args to use with bridge from data dict""" 848 """Generate args to use with bridge from data dict"""
849 return (data["uid"], data["timestamp"], data["from"].full(), 849 return (data["uid"], data["timestamp"], data["from"].full(),
850 data["to"].full(), data["message"], data["subject"], 850 data["to"].full(), data["message"], data["subject"],
851 data["type"], data_format.serialise(data["extra"])) 851 data["type"], data_format.serialise(data["extra"]))
852 852
853 853
854 def messageSendToBridge(self, data): 854 def message_send_to_bridge(self, data):
855 """Send message to bridge, so frontends can display it 855 """Send message to bridge, so frontends can display it
856 856
857 @param data: message data dictionnary 857 @param data: message data dictionnary
858 @param client: profile's client 858 @param client: profile's client
859 """ 859 """
860 if data["type"] != C.MESS_TYPE_GROUPCHAT: 860 if data["type"] != C.MESS_TYPE_GROUPCHAT:
861 # we don't send groupchat message to bridge, as we get them back 861 # we don't send groupchat message to bridge, as we get them back
862 # and they will be added the 862 # and they will be added the
863 863
864 # we need a message to send something 864 # we need a message to send something
865 if self.isMessagePrintable(data): 865 if self.is_message_printable(data):
866 866
867 # We send back the message, so all frontends are aware of it 867 # We send back the message, so all frontends are aware of it
868 self.host_app.bridge.messageNew( 868 self.host_app.bridge.message_new(
869 *self.messageGetBridgeArgs(data), 869 *self.message_get_bridge_args(data),
870 profile=self.profile 870 profile=self.profile
871 ) 871 )
872 else: 872 else:
873 log.warning(_("No message found")) 873 log.warning(_("No message found"))
874 return data 874 return data
911 self.identities = [disco.DiscoIdentity("client", "pc", C.APP_NAME)] 911 self.identities = [disco.DiscoIdentity("client", "pc", C.APP_NAME)]
912 if sys.platform == "android": 912 if sys.platform == "android":
913 # for now we consider Android devices to be always phones 913 # for now we consider Android devices to be always phones
914 self.identities = [disco.DiscoIdentity("client", "phone", C.APP_NAME)] 914 self.identities = [disco.DiscoIdentity("client", "phone", C.APP_NAME)]
915 915
916 hosts_map = host_app.memory.getConfig(None, "hosts_dict", {}) 916 hosts_map = host_app.memory.config_get(None, "hosts_dict", {})
917 if host is None and user_jid.host in hosts_map: 917 if host is None and user_jid.host in hosts_map:
918 host_data = hosts_map[user_jid.host] 918 host_data = hosts_map[user_jid.host]
919 if isinstance(host_data, str): 919 if isinstance(host_data, str):
920 host = host_data 920 host = host_data
921 elif isinstance(host_data, dict): 921 elif isinstance(host_data, dict):
932 log.info( 932 log.info(
933 "using {host}:{port} for host {host_ori} as requested in config" 933 "using {host}:{port} for host {host_ori} as requested in config"
934 .format(host_ori=user_jid.host, host=host, port=port) 934 .format(host_ori=user_jid.host, host=host, port=port)
935 ) 935 )
936 936
937 self.check_certificate = host_app.memory.getParamA( 937 self.check_certificate = host_app.memory.param_get_a(
938 "check_certificate", "Connection", profile_key=profile) 938 "check_certificate", "Connection", profile_key=profile)
939 939
940 if self.check_certificate: 940 if self.check_certificate:
941 tls_required, configurationForTLS = True, None 941 tls_required, configurationForTLS = True, None
942 else: 942 else:
952 if not self.check_certificate: 952 if not self.check_certificate:
953 msg = (_("Certificate validation is deactivated, this is unsecure and " 953 msg = (_("Certificate validation is deactivated, this is unsecure and "
954 "somebody may be spying on you. If you have no good reason to disable " 954 "somebody may be spying on you. If you have no good reason to disable "
955 "certificate validation, please activate \"Check certificate\" in your " 955 "certificate validation, please activate \"Check certificate\" in your "
956 "settings in \"Connection\" tab.")) 956 "settings in \"Connection\" tab."))
957 xml_tools.quickNote(host_app, self, msg, _("Security notice"), 957 xml_tools.quick_note(host_app, self, msg, _("Security notice"),
958 level = C.XMLUI_DATA_LVL_WARNING) 958 level = C.XMLUI_DATA_LVL_WARNING)
959 959
960 @property 960 @property
961 def server_jid(self): 961 def server_jid(self):
962 return jid.JID(self.jid.host) 962 return jid.JID(self.jid.host)
963 963
964 def _getPluginsList(self): 964 def _get_plugins_list(self):
965 for p in self.host_app.plugins.values(): 965 for p in self.host_app.plugins.values():
966 if C.PLUG_MODE_CLIENT in p._info["modes"]: 966 if C.PLUG_MODE_CLIENT in p._info["modes"]:
967 yield p 967 yield p
968 968
969 def _createSubProtocols(self): 969 def _create_sub_protocols(self):
970 self.messageProt = SatMessageProtocol(self.host_app) 970 self.messageProt = SatMessageProtocol(self.host_app)
971 self.messageProt.setHandlerParent(self) 971 self.messageProt.setHandlerParent(self)
972 972
973 self.roster = SatRosterProtocol(self.host_app) 973 self.roster = SatRosterProtocol(self.host_app)
974 self.roster.setHandlerParent(self) 974 self.roster.setHandlerParent(self)
975 975
976 self.presence = SatPresenceProtocol(self.host_app) 976 self.presence = SatPresenceProtocol(self.host_app)
977 self.presence.setHandlerParent(self) 977 self.presence.setHandlerParent(self)
978 978
979 @classmethod 979 @classmethod
980 async def startConnection(cls, host, profile, max_retries): 980 async def start_connection(cls, host, profile, max_retries):
981 try: 981 try:
982 await super(SatXMPPClient, cls).startConnection(host, profile, max_retries) 982 await super(SatXMPPClient, cls).start_connection(host, profile, max_retries)
983 except exceptions.CancelError as e: 983 except exceptions.CancelError as e:
984 log.warning(f"startConnection cancelled: {e}") 984 log.warning(f"start_connection cancelled: {e}")
985 return 985 return
986 entity = host.profiles[profile] 986 entity = host.profiles[profile]
987 # we finally send our presence 987 # we finally send our presence
988 entity.presence.available() 988 entity.presence.available()
989 989
990 def entityConnected(self): 990 def entity_connected(self):
991 # we want to be sure that we got the roster 991 # we want to be sure that we got the roster
992 return self.roster.got_roster 992 return self.roster.got_roster
993 993
994 def addPostXmlCallbacks(self, post_xml_treatments): 994 def add_post_xml_callbacks(self, post_xml_treatments):
995 post_xml_treatments.addCallback(self.messageProt.completeAttachments) 995 post_xml_treatments.addCallback(self.messageProt.complete_attachments)
996 post_xml_treatments.addCallback( 996 post_xml_treatments.addCallback(
997 lambda ret: defer.ensureDeferred(self.messageAddToHistory(ret)) 997 lambda ret: defer.ensureDeferred(self.message_add_to_history(ret))
998 ) 998 )
999 post_xml_treatments.addCallback(self.messageSendToBridge) 999 post_xml_treatments.addCallback(self.message_send_to_bridge)
1000 1000
1001 def feedback( 1001 def feedback(
1002 self, 1002 self,
1003 to_jid: jid.JID, 1003 to_jid: jid.JID,
1004 message: str, 1004 message: str,
1013 @param extra: extra data to use in particular, info subtype can be specified with 1013 @param extra: extra data to use in particular, info subtype can be specified with
1014 MESS_EXTRA_INFO 1014 MESS_EXTRA_INFO
1015 """ 1015 """
1016 if extra is None: 1016 if extra is None:
1017 extra = {} 1017 extra = {}
1018 self.host_app.bridge.messageNew( 1018 self.host_app.bridge.message_new(
1019 uid=str(uuid.uuid4()), 1019 uid=str(uuid.uuid4()),
1020 timestamp=time.time(), 1020 timestamp=time.time(),
1021 from_jid=self.jid.full(), 1021 from_jid=self.jid.full(),
1022 to_jid=to_jid.full(), 1022 to_jid=to_jid.full(),
1023 message={"": message}, 1023 message={"": message},
1026 extra=data_format.serialise(extra), 1026 extra=data_format.serialise(extra),
1027 profile=self.profile, 1027 profile=self.profile,
1028 ) 1028 )
1029 1029
1030 def _finish_connection(self, __): 1030 def _finish_connection(self, __):
1031 d = self.roster.requestRoster() 1031 d = self.roster.request_roster()
1032 d.addCallback(lambda __: super(SatXMPPClient, self)._finish_connection(__)) 1032 d.addCallback(lambda __: super(SatXMPPClient, self)._finish_connection(__))
1033 1033
1034 1034
1035 @implementer(iwokkel.IDisco) 1035 @implementer(iwokkel.IDisco)
1036 class SatXMPPComponent(SatXMPPEntity, component.Component): 1036 class SatXMPPComponent(SatXMPPEntity, component.Component):
1055 self.started = time.time() 1055 self.started = time.time()
1056 if port is None: 1056 if port is None:
1057 port = C.XMPP_COMPONENT_PORT 1057 port = C.XMPP_COMPONENT_PORT
1058 1058
1059 ## entry point ## 1059 ## entry point ##
1060 entry_point = host_app.memory.getEntryPoint(profile) 1060 entry_point = host_app.memory.get_entry_point(profile)
1061 try: 1061 try:
1062 self.entry_plugin = host_app.plugins[entry_point] 1062 self.entry_plugin = host_app.plugins[entry_point]
1063 except KeyError: 1063 except KeyError:
1064 raise exceptions.NotFound( 1064 raise exceptions.NotFound(
1065 _("The requested entry point ({entry_point}) is not available").format( 1065 _("The requested entry point ({entry_point}) is not available").format(
1088 1088
1089 @property 1089 @property
1090 def is_admin(self) -> bool: 1090 def is_admin(self) -> bool:
1091 return False 1091 return False
1092 1092
1093 def _createSubProtocols(self): 1093 def _create_sub_protocols(self):
1094 self.messageProt = SatMessageProtocol(self.host_app) 1094 self.messageProt = SatMessageProtocol(self.host_app)
1095 self.messageProt.setHandlerParent(self) 1095 self.messageProt.setHandlerParent(self)
1096 1096
1097 def _buildDependencies(self, current, plugins, required=True): 1097 def _build_dependencies(self, current, plugins, required=True):
1098 """build recursively dependencies needed for a plugin 1098 """build recursively dependencies needed for a plugin
1099 1099
1100 this method build list of plugin needed for a component and raises 1100 this method build list of plugin needed for a component and raises
1101 errors if they are not available or not allowed for components 1101 errors if they are not available or not allowed for components
1102 @param current(object): parent plugin to check 1102 @param current(object): parent plugin to check
1126 1126
1127 for import_name in current._info.get(C.PI_DEPENDENCIES, []): 1127 for import_name in current._info.get(C.PI_DEPENDENCIES, []):
1128 # plugins are already loaded as dependencies 1128 # plugins are already loaded as dependencies
1129 # so we know they are in self.host_app.plugins 1129 # so we know they are in self.host_app.plugins
1130 dep = self.host_app.plugins[import_name] 1130 dep = self.host_app.plugins[import_name]
1131 self._buildDependencies(dep, plugins) 1131 self._build_dependencies(dep, plugins)
1132 1132
1133 for import_name in current._info.get(C.PI_RECOMMENDATIONS, []): 1133 for import_name in current._info.get(C.PI_RECOMMENDATIONS, []):
1134 # here plugins are only recommendations, 1134 # here plugins are only recommendations,
1135 # so they may not exist in self.host_app.plugins 1135 # so they may not exist in self.host_app.plugins
1136 try: 1136 try:
1137 dep = self.host_app.plugins[import_name] 1137 dep = self.host_app.plugins[import_name]
1138 except KeyError: 1138 except KeyError:
1139 continue 1139 continue
1140 self._buildDependencies(dep, plugins, required=False) 1140 self._build_dependencies(dep, plugins, required=False)
1141 1141
1142 if current not in plugins: 1142 if current not in plugins:
1143 # current can be required for several plugins and so 1143 # current can be required for several plugins and so
1144 # it can already be present in the list 1144 # it can already be present in the list
1145 plugins.append(current) 1145 plugins.append(current)
1146 1146
1147 def _getPluginsList(self): 1147 def _get_plugins_list(self):
1148 # XXX: for component we don't launch all plugins triggers 1148 # XXX: for component we don't launch all plugins triggers
1149 # but only the ones from which there is a dependency 1149 # but only the ones from which there is a dependency
1150 plugins = [] 1150 plugins = []
1151 self._buildDependencies(self.entry_plugin, plugins) 1151 self._build_dependencies(self.entry_plugin, plugins)
1152 return plugins 1152 return plugins
1153 1153
1154 def entityConnected(self): 1154 def entity_connected(self):
1155 # we can now launch entry point 1155 # we can now launch entry point
1156 try: 1156 try:
1157 start_cb = self.entry_plugin.componentStart 1157 start_cb = self.entry_plugin.componentStart
1158 except AttributeError: 1158 except AttributeError:
1159 return 1159 return
1160 else: 1160 else:
1161 return start_cb(self) 1161 return start_cb(self)
1162 1162
1163 def addPostXmlCallbacks(self, post_xml_treatments): 1163 def add_post_xml_callbacks(self, post_xml_treatments):
1164 if self.sendHistory: 1164 if self.sendHistory:
1165 post_xml_treatments.addCallback( 1165 post_xml_treatments.addCallback(
1166 lambda ret: defer.ensureDeferred(self.messageAddToHistory(ret)) 1166 lambda ret: defer.ensureDeferred(self.message_add_to_history(ret))
1167 ) 1167 )
1168 1168
1169 def getOwnerFromJid(self, to_jid: jid.JID) -> jid.JID: 1169 def get_owner_from_jid(self, to_jid: jid.JID) -> jid.JID:
1170 """Retrieve "owner" of a component resource from the destination jid of the request 1170 """Retrieve "owner" of a component resource from the destination jid of the request
1171 1171
1172 This method needs plugin XEP-0106 for unescaping, if you use it you must add the 1172 This method needs plugin XEP-0106 for unescaping, if you use it you must add the
1173 plugin to your dependencies. 1173 plugin to your dependencies.
1174 A "user" part must be present in "to_jid" (otherwise, the component itself is addressed) 1174 A "user" part must be present in "to_jid" (otherwise, the component itself is addressed)
1185 return jid.JID(user) 1185 return jid.JID(user)
1186 else: 1186 else:
1187 # only user part is specified, we use our own host to build the full jid 1187 # only user part is specified, we use our own host to build the full jid
1188 return jid.JID(None, (user, self.host, None)) 1188 return jid.JID(None, (user, self.host, None))
1189 1189
1190 def getOwnerAndPeer(self, iq_elt: domish.Element) -> Tuple[jid.JID, jid.JID]: 1190 def get_owner_and_peer(self, iq_elt: domish.Element) -> Tuple[jid.JID, jid.JID]:
1191 """Retrieve owner of a component jid, and the jid of the requesting peer 1191 """Retrieve owner of a component jid, and the jid of the requesting peer
1192 1192
1193 "owner" is found by either unescaping full jid from node, or by combining node 1193 "owner" is found by either unescaping full jid from node, or by combining node
1194 with our host. 1194 with our host.
1195 Peer jid is the requesting jid from the IQ element 1195 Peer jid is the requesting jid from the IQ element
1196 @param iq_elt: IQ stanza sent from the requested 1196 @param iq_elt: IQ stanza sent from the requested
1197 @return: owner and peer JIDs 1197 @return: owner and peer JIDs
1198 """ 1198 """
1199 to_jid = jid.JID(iq_elt['to']) 1199 to_jid = jid.JID(iq_elt['to'])
1200 if to_jid.user: 1200 if to_jid.user:
1201 owner = self.getOwnerFromJid(to_jid) 1201 owner = self.get_owner_from_jid(to_jid)
1202 else: 1202 else:
1203 owner = jid.JID(iq_elt["from"]).userhostJID() 1203 owner = jid.JID(iq_elt["from"]).userhostJID()
1204 1204
1205 peer_jid = jid.JID(iq_elt["from"]) 1205 peer_jid = jid.JID(iq_elt["from"])
1206 return peer_jid, owner 1206 return peer_jid, owner
1207 1207
1208 def getVirtualClient(self, jid_: jid.JID) -> SatXMPPEntity: 1208 def get_virtual_client(self, jid_: jid.JID) -> SatXMPPEntity:
1209 """Get client for this component with a specified jid 1209 """Get client for this component with a specified jid
1210 1210
1211 This is needed to perform operations with a virtual JID corresponding to a virtual 1211 This is needed to perform operations with a virtual JID corresponding to a virtual
1212 entity (e.g. identified of a legacy network account) instead of the JID of the 1212 entity (e.g. identified of a legacy network account) instead of the JID of the
1213 gateway itself. 1213 gateway itself.
1227 1227
1228 @property 1228 @property
1229 def client(self): 1229 def client(self):
1230 return self.parent 1230 return self.parent
1231 1231
1232 def normalizeNS(self, elt: domish.Element, namespace: Optional[str]) -> None: 1232 def normalize_ns(self, elt: domish.Element, namespace: Optional[str]) -> None:
1233 if elt.uri == namespace: 1233 if elt.uri == namespace:
1234 elt.defaultUri = elt.uri = C.NS_CLIENT 1234 elt.defaultUri = elt.uri = C.NS_CLIENT
1235 for child in elt.elements(): 1235 for child in elt.elements():
1236 self.normalizeNS(child, namespace) 1236 self.normalize_ns(child, namespace)
1237 1237
1238 def parseMessage(self, message_elt): 1238 def parse_message(self, message_elt):
1239 """Parse a message XML and return message_data 1239 """Parse a message XML and return message_data
1240 1240
1241 @param message_elt(domish.Element): raw <message> xml 1241 @param message_elt(domish.Element): raw <message> xml
1242 @param client(SatXMPPClient, None): client to map message id to uid 1242 @param client(SatXMPPClient, None): client to map message id to uid
1243 if None, mapping will not be done 1243 if None, mapping will not be done
1244 @return(dict): message data 1244 @return(dict): message data
1245 """ 1245 """
1246 if message_elt.name != "message": 1246 if message_elt.name != "message":
1247 log.warning(_( 1247 log.warning(_(
1248 "parseMessage used with a non <message/> stanza, ignoring: {xml}" 1248 "parse_message used with a non <message/> stanza, ignoring: {xml}"
1249 .format(xml=message_elt.toXml()))) 1249 .format(xml=message_elt.toXml())))
1250 return {} 1250 return {}
1251 1251
1252 if message_elt.uri == None: 1252 if message_elt.uri == None:
1253 # xmlns may be None when wokkel element parsing strip out root namespace 1253 # xmlns may be None when wokkel element parsing strip out root namespace
1254 self.normalizeNS(message_elt, None) 1254 self.normalize_ns(message_elt, None)
1255 elif message_elt.uri != C.NS_CLIENT: 1255 elif message_elt.uri != C.NS_CLIENT:
1256 log.warning(_( 1256 log.warning(_(
1257 "received <message> with a wrong namespace: {xml}" 1257 "received <message> with a wrong namespace: {xml}"
1258 .format(xml=message_elt.toXml()))) 1258 .format(xml=message_elt.toXml())))
1259 1259
1295 # delay and timestamp 1295 # delay and timestamp
1296 try: 1296 try:
1297 received_timestamp = message_elt._received_timestamp 1297 received_timestamp = message_elt._received_timestamp
1298 except AttributeError: 1298 except AttributeError:
1299 # message_elt._received_timestamp should have been set in onMessage 1299 # message_elt._received_timestamp should have been set in onMessage
1300 # but if parseMessage is called directly, it can be missing 1300 # but if parse_message is called directly, it can be missing
1301 log.debug("missing received timestamp for {message_elt}".format( 1301 log.debug("missing received timestamp for {message_elt}".format(
1302 message_elt=message_elt)) 1302 message_elt=message_elt))
1303 received_timestamp = time.time() 1303 received_timestamp = time.time()
1304 1304
1305 try: 1305 try:
1314 data["delay_sender"] = parsed_delay.sender.full() 1314 data["delay_sender"] = parsed_delay.sender.full()
1315 1315
1316 self.host.trigger.point("message_parse", client, message_elt, data) 1316 self.host.trigger.point("message_parse", client, message_elt, data)
1317 return data 1317 return data
1318 1318
1319 def _onMessageStartWorkflow(self, cont, client, message_elt, post_treat): 1319 def _on_message_start_workflow(self, cont, client, message_elt, post_treat):
1320 """Parse message and do post treatments 1320 """Parse message and do post treatments
1321 1321
1322 It is the first callback called after messageReceived trigger 1322 It is the first callback called after messageReceived trigger
1323 @param cont(bool): workflow will continue only if this is True 1323 @param cont(bool): workflow will continue only if this is True
1324 @param message_elt(domish.Element): message stanza 1324 @param message_elt(domish.Element): message stanza
1325 may have be modified by triggers 1325 may have be modified by triggers
1326 @param post_treat(defer.Deferred): post parsing treatments 1326 @param post_treat(defer.Deferred): post parsing treatments
1327 """ 1327 """
1328 if not cont: 1328 if not cont:
1329 return 1329 return
1330 data = self.parseMessage(message_elt) 1330 data = self.parse_message(message_elt)
1331 post_treat.addCallback(self.completeAttachments) 1331 post_treat.addCallback(self.complete_attachments)
1332 post_treat.addCallback(self.skipEmptyMessage) 1332 post_treat.addCallback(self.skip_empty_message)
1333 if not client.is_component or client.receiveHistory: 1333 if not client.is_component or client.receiveHistory:
1334 post_treat.addCallback( 1334 post_treat.addCallback(
1335 lambda ret: defer.ensureDeferred(self.addToHistory(ret)) 1335 lambda ret: defer.ensureDeferred(self.add_to_history(ret))
1336 ) 1336 )
1337 if not client.is_component: 1337 if not client.is_component:
1338 post_treat.addCallback(self.bridgeSignal, data) 1338 post_treat.addCallback(self.bridge_signal, data)
1339 post_treat.addErrback(self.cancelErrorTrap) 1339 post_treat.addErrback(self.cancel_error_trap)
1340 post_treat.callback(data) 1340 post_treat.callback(data)
1341 1341
1342 def onMessage(self, message_elt): 1342 def onMessage(self, message_elt):
1343 # TODO: handle threads 1343 # TODO: handle threads
1344 message_elt._received_timestamp = time.time() 1344 message_elt._received_timestamp = time.time()
1346 if not "from" in message_elt.attributes: 1346 if not "from" in message_elt.attributes:
1347 message_elt["from"] = client.jid.host 1347 message_elt["from"] = client.jid.host
1348 log.debug(_("got message from: {from_}").format(from_=message_elt["from"])) 1348 log.debug(_("got message from: {from_}").format(from_=message_elt["from"]))
1349 if self.client.is_component and message_elt.uri == component.NS_COMPONENT_ACCEPT: 1349 if self.client.is_component and message_elt.uri == component.NS_COMPONENT_ACCEPT:
1350 # we use client namespace all the time to simplify parsing 1350 # we use client namespace all the time to simplify parsing
1351 self.normalizeNS(message_elt, component.NS_COMPONENT_ACCEPT) 1351 self.normalize_ns(message_elt, component.NS_COMPONENT_ACCEPT)
1352 1352
1353 # plugin can add their treatments to this deferred 1353 # plugin can add their treatments to this deferred
1354 post_treat = defer.Deferred() 1354 post_treat = defer.Deferred()
1355 1355
1356 d = self.host.trigger.asyncPoint( 1356 d = self.host.trigger.async_point(
1357 "messageReceived", client, message_elt, post_treat 1357 "messageReceived", client, message_elt, post_treat
1358 ) 1358 )
1359 1359
1360 d.addCallback(self._onMessageStartWorkflow, client, message_elt, post_treat) 1360 d.addCallback(self._on_message_start_workflow, client, message_elt, post_treat)
1361 1361
1362 def completeAttachments(self, data): 1362 def complete_attachments(self, data):
1363 """Complete missing metadata of attachments""" 1363 """Complete missing metadata of attachments"""
1364 for attachment in data['extra'].get(C.KEY_ATTACHMENTS, []): 1364 for attachment in data['extra'].get(C.KEY_ATTACHMENTS, []):
1365 if "name" not in attachment and "url" in attachment: 1365 if "name" not in attachment and "url" in attachment:
1366 name = (Path(unquote(urlparse(attachment['url']).path)).name 1366 name = (Path(unquote(urlparse(attachment['url']).path)).name
1367 or C.FILE_DEFAULT_NAME) 1367 or C.FILE_DEFAULT_NAME)
1372 if media_type: 1372 if media_type:
1373 attachment[C.KEY_ATTACHMENTS_MEDIA_TYPE] = media_type 1373 attachment[C.KEY_ATTACHMENTS_MEDIA_TYPE] = media_type
1374 1374
1375 return data 1375 return data
1376 1376
1377 def skipEmptyMessage(self, data): 1377 def skip_empty_message(self, data):
1378 if not data["message"] and not data["extra"] and not data["subject"]: 1378 if not data["message"] and not data["extra"] and not data["subject"]:
1379 raise failure.Failure(exceptions.CancelError("Cancelled empty message")) 1379 raise failure.Failure(exceptions.CancelError("Cancelled empty message"))
1380 return data 1380 return data
1381 1381
1382 async def addToHistory(self, data): 1382 async def add_to_history(self, data):
1383 if data.pop("history", None) == C.HISTORY_SKIP: 1383 if data.pop("history", None) == C.HISTORY_SKIP:
1384 log.debug("history is skipped as requested") 1384 log.debug("history is skipped as requested")
1385 data["extra"]["history"] = C.HISTORY_SKIP 1385 data["extra"]["history"] = C.HISTORY_SKIP
1386 else: 1386 else:
1387 # we need a message to store 1387 # we need a message to store
1388 if self.parent.isMessagePrintable(data): 1388 if self.parent.is_message_printable(data):
1389 return await self.host.memory.addToHistory(self.parent, data) 1389 return await self.host.memory.add_to_history(self.parent, data)
1390 else: 1390 else:
1391 log.debug("not storing empty message to history: {data}" 1391 log.debug("not storing empty message to history: {data}"
1392 .format(data=data)) 1392 .format(data=data))
1393 1393
1394 def bridgeSignal(self, __, data): 1394 def bridge_signal(self, __, data):
1395 try: 1395 try:
1396 data["extra"]["received_timestamp"] = str(data["received_timestamp"]) 1396 data["extra"]["received_timestamp"] = str(data["received_timestamp"])
1397 data["extra"]["delay_sender"] = data["delay_sender"] 1397 data["extra"]["delay_sender"] = data["delay_sender"]
1398 except KeyError: 1398 except KeyError:
1399 pass 1399 pass
1400 if self.client.encryption.isEncrypted(data): 1400 if self.client.encryption.isEncrypted(data):
1401 data["extra"]["encrypted"] = True 1401 data["extra"]["encrypted"] = True
1402 if data is not None: 1402 if data is not None:
1403 if self.parent.isMessagePrintable(data): 1403 if self.parent.is_message_printable(data):
1404 self.host.bridge.messageNew( 1404 self.host.bridge.message_new(
1405 data["uid"], 1405 data["uid"],
1406 data["timestamp"], 1406 data["timestamp"],
1407 data["from"].full(), 1407 data["from"].full(),
1408 data["to"].full(), 1408 data["to"].full(),
1409 data["message"], 1409 data["message"],
1415 else: 1415 else:
1416 log.debug("Discarding bridge signal for empty message: {data}".format( 1416 log.debug("Discarding bridge signal for empty message: {data}".format(
1417 data=data)) 1417 data=data))
1418 return data 1418 return data
1419 1419
1420 def cancelErrorTrap(self, failure_): 1420 def cancel_error_trap(self, failure_):
1421 """A message sending can be cancelled by a plugin treatment""" 1421 """A message sending can be cancelled by a plugin treatment"""
1422 failure_.trap(exceptions.CancelError) 1422 failure_.trap(exceptions.CancelError)
1423 1423
1424 1424
1425 class SatRosterProtocol(xmppim.RosterClientProtocol): 1425 class SatRosterProtocol(xmppim.RosterClientProtocol):
1431 # XXX: the two following dicts keep a local copy of the roster 1431 # XXX: the two following dicts keep a local copy of the roster
1432 self._jids = {} # map from jids to RosterItem: key=jid value=RosterItem 1432 self._jids = {} # map from jids to RosterItem: key=jid value=RosterItem
1433 self._groups = {} # map from groups to jids: key=group value=set of jids 1433 self._groups = {} # map from groups to jids: key=group value=set of jids
1434 1434
1435 def __contains__(self, entity_jid): 1435 def __contains__(self, entity_jid):
1436 return self.isJidInRoster(entity_jid) 1436 return self.is_jid_in_roster(entity_jid)
1437 1437
1438 @property 1438 @property
1439 def versioning(self): 1439 def versioning(self):
1440 """True if server support roster versioning""" 1440 """True if server support roster versioning"""
1441 return (NS_ROSTER_VER, 'ver') in self.parent.xmlstream.features 1441 return (NS_ROSTER_VER, 'ver') in self.parent.xmlstream.features
1447 This property return a new PersistentDict on each call, it must be loaded 1447 This property return a new PersistentDict on each call, it must be loaded
1448 manually if necessary 1448 manually if necessary
1449 """ 1449 """
1450 return persistent.PersistentDict(NS_ROSTER_VER, self.parent.profile) 1450 return persistent.PersistentDict(NS_ROSTER_VER, self.parent.profile)
1451 1451
1452 def _registerItem(self, item): 1452 def _register_item(self, item):
1453 """Register item in local cache 1453 """Register item in local cache
1454 1454
1455 item must be already registered in self._jids before this method is called 1455 item must be already registered in self._jids before this method is called
1456 @param item (RosterIem): item added 1456 @param item (RosterIem): item added
1457 """ 1457 """
1475 1475
1476 for group in item.groups: 1476 for group in item.groups:
1477 self._groups.setdefault(group, set()).add(item.entity) 1477 self._groups.setdefault(group, set()).add(item.entity)
1478 1478
1479 @defer.inlineCallbacks 1479 @defer.inlineCallbacks
1480 def _cacheRoster(self, version): 1480 def _cache_roster(self, version):
1481 """Serialise local roster and save it to storage 1481 """Serialise local roster and save it to storage
1482 1482
1483 @param version(unicode): version of roster in local cache 1483 @param version(unicode): version of roster in local cache
1484 """ 1484 """
1485 roster_cache = self.roster_cache 1485 roster_cache = self.roster_cache
1499 """ 1499 """
1500 roster_cache = self.roster_cache 1500 roster_cache = self.roster_cache
1501 yield roster_cache.clear() 1501 yield roster_cache.clear()
1502 self._jids.clear() 1502 self._jids.clear()
1503 self._groups.clear() 1503 self._groups.clear()
1504 yield self.requestRoster() 1504 yield self.request_roster()
1505 1505
1506 @defer.inlineCallbacks 1506 @defer.inlineCallbacks
1507 def requestRoster(self): 1507 def request_roster(self):
1508 """Ask the server for Roster list """ 1508 """Ask the server for Roster list """
1509 if self.versioning: 1509 if self.versioning:
1510 log.info(_("our server support roster versioning, we use it")) 1510 log.info(_("our server support roster versioning, we use it"))
1511 roster_cache = self.roster_cache 1511 roster_cache = self.roster_cache
1512 yield roster_cache.load() 1512 yield roster_cache.load()
1524 continue 1524 continue
1525 roster_jid = jid.JID(roster_jid_s) 1525 roster_jid = jid.JID(roster_jid_s)
1526 roster_item_elt = generic.parseXml(roster_item_elt_s.encode('utf-8')) 1526 roster_item_elt = generic.parseXml(roster_item_elt_s.encode('utf-8'))
1527 roster_item = xmppim.RosterItem.fromElement(roster_item_elt) 1527 roster_item = xmppim.RosterItem.fromElement(roster_item_elt)
1528 self._jids[roster_jid] = roster_item 1528 self._jids[roster_jid] = roster_item
1529 self._registerItem(roster_item) 1529 self._register_item(roster_item)
1530 else: 1530 else:
1531 log.warning(_("our server doesn't support roster versioning")) 1531 log.warning(_("our server doesn't support roster versioning"))
1532 version = None 1532 version = None
1533 1533
1534 log.debug("requesting roster") 1534 log.debug("requesting roster")
1551 item.jid 1551 item.jid
1552 ) 1552 )
1553 ) 1553 )
1554 self.removeItem(item.entity) # FIXME: to be checked 1554 self.removeItem(item.entity) # FIXME: to be checked
1555 else: 1555 else:
1556 self._registerItem(item) 1556 self._register_item(item)
1557 yield self._cacheRoster(roster.version) 1557 yield self._cache_roster(roster.version)
1558 1558
1559 if not self.got_roster.called: 1559 if not self.got_roster.called:
1560 # got_roster may already be called if we use resync() 1560 # got_roster may already be called if we use resync()
1561 self.got_roster.callback(None) 1561 self.got_roster.callback(None)
1562 1562
1565 @param to_jid: a JID instance 1565 @param to_jid: a JID instance
1566 @return: Deferred 1566 @return: Deferred
1567 """ 1567 """
1568 return xmppim.RosterClientProtocol.removeItem(self, to_jid) 1568 return xmppim.RosterClientProtocol.removeItem(self, to_jid)
1569 1569
1570 def getAttributes(self, item): 1570 def get_attributes(self, item):
1571 """Return dictionary of attributes as used in bridge from a RosterItem 1571 """Return dictionary of attributes as used in bridge from a RosterItem
1572 1572
1573 @param item: RosterItem 1573 @param item: RosterItem
1574 @return: dictionary of attributes 1574 @return: dictionary of attributes
1575 """ 1575 """
1600 if not jids_set: 1600 if not jids_set:
1601 del self._groups[group] 1601 del self._groups[group]
1602 except KeyError: 1602 except KeyError:
1603 pass # no previous item registration (or it's been cleared) 1603 pass # no previous item registration (or it's been cleared)
1604 self._jids[entity] = item 1604 self._jids[entity] = item
1605 self._registerItem(item) 1605 self._register_item(item)
1606 self.host.bridge.newContact( 1606 self.host.bridge.contact_new(
1607 entity.full(), self.getAttributes(item), list(item.groups), 1607 entity.full(), self.get_attributes(item), list(item.groups),
1608 self.parent.profile 1608 self.parent.profile
1609 ) 1609 )
1610 1610
1611 def removeReceived(self, request): 1611 def removeReceived(self, request):
1612 entity = request.item.entity 1612 entity = request.item.entity
1643 f"there is no cache for the group [{group}] of the removed roster " 1643 f"there is no cache for the group [{group}] of the removed roster "
1644 f"item [{entity}]" 1644 f"item [{entity}]"
1645 ) 1645 )
1646 1646
1647 # then we send the bridge signal 1647 # then we send the bridge signal
1648 self.host.bridge.contactDeleted(entity.full(), self.parent.profile) 1648 self.host.bridge.contact_deleted(entity.full(), self.parent.profile)
1649 1649
1650 def getGroups(self): 1650 def get_groups(self):
1651 """Return a list of groups""" 1651 """Return a list of groups"""
1652 return list(self._groups.keys()) 1652 return list(self._groups.keys())
1653 1653
1654 def getItem(self, entity_jid): 1654 def get_item(self, entity_jid):
1655 """Return RosterItem for a given jid 1655 """Return RosterItem for a given jid
1656 1656
1657 @param entity_jid(jid.JID): jid of the contact 1657 @param entity_jid(jid.JID): jid of the contact
1658 @return(RosterItem, None): RosterItem instance 1658 @return(RosterItem, None): RosterItem instance
1659 None if contact is not in cache 1659 None if contact is not in cache
1660 """ 1660 """
1661 return self._jids.get(entity_jid, None) 1661 return self._jids.get(entity_jid, None)
1662 1662
1663 def getJids(self): 1663 def get_jids(self):
1664 """Return all jids of the roster""" 1664 """Return all jids of the roster"""
1665 return list(self._jids.keys()) 1665 return list(self._jids.keys())
1666 1666
1667 def isJidInRoster(self, entity_jid): 1667 def is_jid_in_roster(self, entity_jid):
1668 """Return True if jid is in roster""" 1668 """Return True if jid is in roster"""
1669 if not isinstance(entity_jid, jid.JID): 1669 if not isinstance(entity_jid, jid.JID):
1670 raise exceptions.InternalError( 1670 raise exceptions.InternalError(
1671 f"a JID is expected, not {type(entity_jid)}: {entity_jid!r}") 1671 f"a JID is expected, not {type(entity_jid)}: {entity_jid!r}")
1672 return entity_jid in self._jids 1672 return entity_jid in self._jids
1673 1673
1674 def isSubscribedFrom(self, entity_jid: jid.JID) -> bool: 1674 def is_subscribed_from(self, entity_jid: jid.JID) -> bool:
1675 """Return True if entity is authorised to see our presence""" 1675 """Return True if entity is authorised to see our presence"""
1676 try: 1676 try:
1677 item = self._jids[entity_jid.userhostJID()] 1677 item = self._jids[entity_jid.userhostJID()]
1678 except KeyError: 1678 except KeyError:
1679 return False 1679 return False
1680 return item.subscriptionFrom 1680 return item.subscriptionFrom
1681 1681
1682 def isSubscribedTo(self, entity_jid: jid.JID) -> bool: 1682 def is_subscribed_to(self, entity_jid: jid.JID) -> bool:
1683 """Return True if we are subscribed to entity""" 1683 """Return True if we are subscribed to entity"""
1684 try: 1684 try:
1685 item = self._jids[entity_jid.userhostJID()] 1685 item = self._jids[entity_jid.userhostJID()]
1686 except KeyError: 1686 except KeyError:
1687 return False 1687 return False
1688 return item.subscriptionTo 1688 return item.subscriptionTo
1689 1689
1690 def getItems(self): 1690 def get_items(self):
1691 """Return all items of the roster""" 1691 """Return all items of the roster"""
1692 return list(self._jids.values()) 1692 return list(self._jids.values())
1693 1693
1694 def getJidsFromGroup(self, group): 1694 def get_jids_from_group(self, group):
1695 try: 1695 try:
1696 return self._groups[group] 1696 return self._groups[group]
1697 except KeyError: 1697 except KeyError:
1698 raise exceptions.UnknownGroupError(group) 1698 raise exceptions.UnknownGroupError(group)
1699 1699
1700 def getJidsSet(self, type_, groups=None): 1700 def get_jids_set(self, type_, groups=None):
1701 """Helper method to get a set of jids 1701 """Helper method to get a set of jids
1702 1702
1703 @param type_(unicode): one of: 1703 @param type_(unicode): one of:
1704 C.ALL: get all jids from roster 1704 C.ALL: get all jids from roster
1705 C.GROUP: get jids from groups (listed in "groups") 1705 C.GROUP: get jids from groups (listed in "groups")
1708 """ 1708 """
1709 if type_ == C.ALL and groups is not None: 1709 if type_ == C.ALL and groups is not None:
1710 raise ValueError("groups must not be set for {} type".format(C.ALL)) 1710 raise ValueError("groups must not be set for {} type".format(C.ALL))
1711 1711
1712 if type_ == C.ALL: 1712 if type_ == C.ALL:
1713 return set(self.getJids()) 1713 return set(self.get_jids())
1714 elif type_ == C.GROUP: 1714 elif type_ == C.GROUP:
1715 jids = set() 1715 jids = set()
1716 for group in groups: 1716 for group in groups:
1717 jids.update(self.getJidsFromGroup(group)) 1717 jids.update(self.get_jids_from_group(group))
1718 return jids 1718 return jids
1719 else: 1719 else:
1720 raise ValueError("Unexpected type_ {}".format(type_)) 1720 raise ValueError("Unexpected type_ {}".format(type_))
1721 1721
1722 def getNick(self, entity_jid): 1722 def get_nick(self, entity_jid):
1723 """Return a nick name for an entity 1723 """Return a nick name for an entity
1724 1724
1725 return nick choosed by user if available 1725 return nick choosed by user if available
1726 else return user part of entity_jid 1726 else return user part of entity_jid
1727 """ 1727 """
1728 item = self.getItem(entity_jid) 1728 item = self.get_item(entity_jid)
1729 if item is None: 1729 if item is None:
1730 return entity_jid.user 1730 return entity_jid.user
1731 else: 1731 else:
1732 return item.name or entity_jid.user 1732 return item.name or entity_jid.user
1733 1733
1759 if not self.host.trigger.point( 1759 if not self.host.trigger.point(
1760 "presence_received", self.parent, entity, show, priority, statuses 1760 "presence_received", self.parent, entity, show, priority, statuses
1761 ): 1761 ):
1762 return 1762 return
1763 1763
1764 self.host.memory.setPresenceStatus( 1764 self.host.memory.set_presence_status(
1765 entity, show or "", int(priority), statuses, self.parent.profile 1765 entity, show or "", int(priority), statuses, self.parent.profile
1766 ) 1766 )
1767 1767
1768 # now it's time to notify frontends 1768 # now it's time to notify frontends
1769 self.host.bridge.presenceUpdate( 1769 self.host.bridge.presence_update(
1770 entity.full(), show or "", int(priority), statuses, self.parent.profile 1770 entity.full(), show or "", int(priority), statuses, self.parent.profile
1771 ) 1771 )
1772 1772
1773 def unavailableReceived(self, entity, statuses=None): 1773 def unavailableReceived(self, entity, statuses=None):
1774 log.debug( 1774 log.debug(
1789 1789
1790 # now it's time to notify frontends 1790 # now it's time to notify frontends
1791 # if the entity is not known yet in this session or is already unavailable, 1791 # if the entity is not known yet in this session or is already unavailable,
1792 # there is no need to send an unavailable signal 1792 # there is no need to send an unavailable signal
1793 try: 1793 try:
1794 presence = self.host.memory.getEntityDatum( 1794 presence = self.host.memory.get_entity_datum(
1795 self.client, entity, "presence" 1795 self.client, entity, "presence"
1796 ) 1796 )
1797 except (KeyError, exceptions.UnknownEntityError): 1797 except (KeyError, exceptions.UnknownEntityError):
1798 # the entity has not been seen yet in this session 1798 # the entity has not been seen yet in this session
1799 pass 1799 pass
1800 else: 1800 else:
1801 if presence.show != C.PRESENCE_UNAVAILABLE: 1801 if presence.show != C.PRESENCE_UNAVAILABLE:
1802 self.host.bridge.presenceUpdate( 1802 self.host.bridge.presence_update(
1803 entity.full(), 1803 entity.full(),
1804 C.PRESENCE_UNAVAILABLE, 1804 C.PRESENCE_UNAVAILABLE,
1805 0, 1805 0,
1806 statuses, 1806 statuses,
1807 self.parent.profile, 1807 self.parent.profile,
1808 ) 1808 )
1809 1809
1810 self.host.memory.setPresenceStatus( 1810 self.host.memory.set_presence_status(
1811 entity, C.PRESENCE_UNAVAILABLE, 0, statuses, self.parent.profile 1811 entity, C.PRESENCE_UNAVAILABLE, 0, statuses, self.parent.profile
1812 ) 1812 )
1813 1813
1814 def available(self, entity=None, show=None, statuses=None, priority=None): 1814 def available(self, entity=None, show=None, statuses=None, priority=None):
1815 """Set a presence and statuses. 1815 """Set a presence and statuses.
1820 the entry key beeing a language code on 2 characters or "default". 1820 the entry key beeing a language code on 2 characters or "default".
1821 """ 1821 """
1822 if priority is None: 1822 if priority is None:
1823 try: 1823 try:
1824 priority = int( 1824 priority = int(
1825 self.host.memory.getParamA( 1825 self.host.memory.param_get_a(
1826 "Priority", "Connection", profile_key=self.parent.profile 1826 "Priority", "Connection", profile_key=self.parent.profile
1827 ) 1827 )
1828 ) 1828 )
1829 except ValueError: 1829 except ValueError:
1830 priority = 0 1830 priority = 0
1849 1849
1850 @defer.inlineCallbacks 1850 @defer.inlineCallbacks
1851 def subscribed(self, entity): 1851 def subscribed(self, entity):
1852 yield self.parent.roster.got_roster 1852 yield self.parent.roster.got_roster
1853 xmppim.PresenceClientProtocol.subscribed(self, entity) 1853 xmppim.PresenceClientProtocol.subscribed(self, entity)
1854 self.host.memory.delWaitingSub(entity.userhost(), self.parent.profile) 1854 self.host.memory.del_waiting_sub(entity.userhost(), self.parent.profile)
1855 item = self.parent.roster.getItem(entity) 1855 item = self.parent.roster.get_item(entity)
1856 if ( 1856 if (
1857 not item or not item.subscriptionTo 1857 not item or not item.subscriptionTo
1858 ): # we automatically subscribe to 'to' presence 1858 ): # we automatically subscribe to 'to' presence
1859 log.debug(_('sending automatic "from" subscription request')) 1859 log.debug(_('sending automatic "from" subscription request'))
1860 self.subscribe(entity) 1860 self.subscribe(entity)
1861 1861
1862 def unsubscribed(self, entity): 1862 def unsubscribed(self, entity):
1863 xmppim.PresenceClientProtocol.unsubscribed(self, entity) 1863 xmppim.PresenceClientProtocol.unsubscribed(self, entity)
1864 self.host.memory.delWaitingSub(entity.userhost(), self.parent.profile) 1864 self.host.memory.del_waiting_sub(entity.userhost(), self.parent.profile)
1865 1865
1866 def subscribedReceived(self, entity): 1866 def subscribedReceived(self, entity):
1867 log.debug(_("subscription approved for [%s]") % entity.userhost()) 1867 log.debug(_("subscription approved for [%s]") % entity.userhost())
1868 self.host.bridge.subscribe("subscribed", entity.userhost(), self.parent.profile) 1868 self.host.bridge.subscribe("subscribed", entity.userhost(), self.parent.profile)
1869 1869
1873 1873
1874 @defer.inlineCallbacks 1874 @defer.inlineCallbacks
1875 def subscribeReceived(self, entity): 1875 def subscribeReceived(self, entity):
1876 log.debug(_("subscription request from [%s]") % entity.userhost()) 1876 log.debug(_("subscription request from [%s]") % entity.userhost())
1877 yield self.parent.roster.got_roster 1877 yield self.parent.roster.got_roster
1878 item = self.parent.roster.getItem(entity) 1878 item = self.parent.roster.get_item(entity)
1879 if item and item.subscriptionTo: 1879 if item and item.subscriptionTo:
1880 # We automatically accept subscription if we are already subscribed to 1880 # We automatically accept subscription if we are already subscribed to
1881 # contact presence 1881 # contact presence
1882 log.debug(_("sending automatic subscription acceptance")) 1882 log.debug(_("sending automatic subscription acceptance"))
1883 self.subscribed(entity) 1883 self.subscribed(entity)
1884 else: 1884 else:
1885 self.host.memory.addWaitingSub( 1885 self.host.memory.add_waiting_sub(
1886 "subscribe", entity.userhost(), self.parent.profile 1886 "subscribe", entity.userhost(), self.parent.profile
1887 ) 1887 )
1888 self.host.bridge.subscribe( 1888 self.host.bridge.subscribe(
1889 "subscribe", entity.userhost(), self.parent.profile 1889 "subscribe", entity.userhost(), self.parent.profile
1890 ) 1890 )
1891 1891
1892 @defer.inlineCallbacks 1892 @defer.inlineCallbacks
1893 def unsubscribeReceived(self, entity): 1893 def unsubscribeReceived(self, entity):
1894 log.debug(_("unsubscription asked for [%s]") % entity.userhost()) 1894 log.debug(_("unsubscription asked for [%s]") % entity.userhost())
1895 yield self.parent.roster.got_roster 1895 yield self.parent.roster.got_roster
1896 item = self.parent.roster.getItem(entity) 1896 item = self.parent.roster.get_item(entity)
1897 if item and item.subscriptionFrom: # we automatically remove contact 1897 if item and item.subscriptionFrom: # we automatically remove contact
1898 log.debug(_("automatic contact deletion")) 1898 log.debug(_("automatic contact deletion"))
1899 self.host.delContact(entity, self.parent.profile) 1899 self.host.contact_del(entity, self.parent.profile)
1900 self.host.bridge.subscribe("unsubscribe", entity.userhost(), self.parent.profile) 1900 self.host.bridge.subscribe("unsubscribe", entity.userhost(), self.parent.profile)
1901 1901
1902 1902
1903 @implementer(iwokkel.IDisco) 1903 @implementer(iwokkel.IDisco)
1904 class SatDiscoProtocol(disco.DiscoClientProtocol): 1904 class SatDiscoProtocol(disco.DiscoClientProtocol):