Mercurial > libervia-backend
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): |