comparison sat/core/xmpp.py @ 2643:189e38fb11ff

core: style improvments (90 chars limit)
author Goffi <goffi@goffi.org>
date Sun, 29 Jul 2018 18:44:27 +0200
parents 56f94936df1e
children f2cf1daa42cb
comparison
equal deleted inserted replaced
2642:755a0b8643bd 2643:189e38fb11ff
52 # else, it's a deferred which fire on disconnection 52 # else, it's a deferred which fire on disconnection
53 self._connected = None 53 self._connected = None
54 self.profile = profile 54 self.profile = profile
55 self.host_app = host_app 55 self.host_app = host_app
56 self.cache = cache.Cache(host_app, profile) 56 self.cache = cache.Cache(host_app, profile)
57 self._mess_id_uid = {} # map from message id to uid used in history. Key: (full_jid,message_id) Value: uid 57 self._mess_id_uid = {} # map from message id to uid used in history.
58 # Key: (full_jid,message_id) Value: uid
58 self.conn_deferred = defer.Deferred() 59 self.conn_deferred = defer.Deferred()
59 self._progress_cb = {} # callback called when a progress is requested (key = progress id) 60 self._progress_cb = {} # callback called when a progress is requested
61 # (key = progress id)
60 self.actions = {} # used to keep track of actions for retrieval (key = action_id) 62 self.actions = {} # used to keep track of actions for retrieval (key = action_id)
61 63
62 ## initialisation ## 64 ## initialisation ##
63 65
64 @defer.inlineCallbacks 66 @defer.inlineCallbacks
111 113
112 @classmethod 114 @classmethod
113 @defer.inlineCallbacks 115 @defer.inlineCallbacks
114 def startConnection(cls, host, profile, max_retries): 116 def startConnection(cls, host, profile, max_retries):
115 """instantiate the entity and start the connection""" 117 """instantiate the entity and start the connection"""
116 # FIXME: reconnection doesn't seems to be handled correclty (client is deleted then recreated from scrash 118 # FIXME: reconnection doesn't seems to be handled correclty
117 # most of methods called here should be called once on first connection (e.g. adding subprotocols) 119 # (client is deleted then recreated from scratch)
118 # but client should not be deleted except if session is finished (independently of connection/deconnection 120 # most of methods called here should be called once on first connection
119 # 121 # (e.g. adding subprotocols)
122 # but client should not be deleted except if session is finished
123 # (independently of connection/deconnection)
120 try: 124 try:
121 port = int( 125 port = int(
122 host.memory.getParamA( 126 host.memory.getParamA(
123 C.FORCE_PORT_PARAM, "Connection", profile_key=profile 127 C.FORCE_PORT_PARAM, "Connection", profile_key=profile
124 ) 128 )
162 166
163 yield entity.getConnectionDeferred() 167 yield entity.getConnectionDeferred()
164 168
165 yield defer.maybeDeferred(entity.entityConnected) 169 yield defer.maybeDeferred(entity.entityConnected)
166 170
167 # Call profileConnected callback for all plugins, and print error message if any of them fails 171 # Call profileConnected callback for all plugins,
172 # and print error message if any of them fails
168 conn_cb_list = [] 173 conn_cb_list = []
169 for dummy, callback in plugin_conn_cb: 174 for __, callback in plugin_conn_cb:
170 conn_cb_list.append(defer.maybeDeferred(callback, entity)) 175 conn_cb_list.append(defer.maybeDeferred(callback, entity))
171 list_d = defer.DeferredList(conn_cb_list) 176 list_d = defer.DeferredList(conn_cb_list)
172 177
173 def logPluginResults(results): 178 def logPluginResults(results):
174 all_succeed = all([success for success, result in results]) 179 all_succeed = all([success for success, result in results])
191 196
192 def getConnectionDeferred(self): 197 def getConnectionDeferred(self):
193 """Return a deferred which fire when the client is connected""" 198 """Return a deferred which fire when the client is connected"""
194 return self.conn_deferred 199 return self.conn_deferred
195 200
196 def _disconnectionCb(self, dummy): 201 def _disconnectionCb(self, __):
197 self._connected = None 202 self._connected = None
198 203
199 def _disconnectionEb(self, failure_): 204 def _disconnectionEb(self, failure_):
200 log.error(_(u"Error while disconnecting: {}".format(failure_))) 205 log.error(_(u"Error while disconnecting: {}".format(failure_)))
201 206
217 self.streamInitialized() 222 self.streamInitialized()
218 self.host_app.bridge.connected( 223 self.host_app.bridge.connected(
219 self.profile, unicode(self.jid) 224 self.profile, unicode(self.jid)
220 ) # we send the signal to the clients 225 ) # we send the signal to the clients
221 226
222 def _finish_connection(self, dummy): 227 def _finish_connection(self, __):
223 self.conn_deferred.callback(None) 228 self.conn_deferred.callback(None)
224 229
225 def streamInitialized(self): 230 def streamInitialized(self):
226 """Called after _authd""" 231 """Called after _authd"""
227 log.debug(_(u"XML stream is initialized")) 232 log.debug(_(u"XML stream is initialized"))
281 self.conn_deferred.errback( 286 self.conn_deferred.errback(
282 error.StreamError(u"Server unexpectedly closed the connection") 287 error.StreamError(u"Server unexpectedly closed the connection")
283 ) 288 )
284 289
285 @defer.inlineCallbacks 290 @defer.inlineCallbacks
286 def _cleanConnection(self, dummy): 291 def _cleanConnection(self, __):
287 """method called on disconnection 292 """method called on disconnection
288 293
289 used to call profileDisconnected* triggers 294 used to call profileDisconnected* triggers
290 """ 295 """
291 trigger_name = "profileDisconnected" 296 trigger_name = "profileDisconnected"
378 383
379 @param post_xml_treatments(D): the same Deferred as in sendMessage trigger 384 @param post_xml_treatments(D): the same Deferred as in sendMessage trigger
380 """ 385 """
381 raise NotImplementedError 386 raise NotImplementedError
382 387
383 def sendMessage( 388 def sendMessage(self, to_jid, message, subject=None, mess_type="auto", extra=None,
384 self, 389 uid=None, no_trigger=False,):
385 to_jid, 390 r"""Send a message to an entity
386 message,
387 subject=None,
388 mess_type="auto",
389 extra=None,
390 uid=None,
391 no_trigger=False,
392 ):
393 """Send a message to an entity
394 391
395 @param to_jid(jid.JID): destinee of the message 392 @param to_jid(jid.JID): destinee of the message
396 @param message(dict): message body, key is the language (use '' when unknown) 393 @param message(dict): message body, key is the language (use '' when unknown)
397 @param subject(dict): message subject, key is the language (use '' when unknown) 394 @param subject(dict): message subject, key is the language (use '' when unknown)
398 @param mess_type(str): one of standard message type (cf RFC 6121 §5.2.2) or: 395 @param mess_type(str): one of standard message type (cf RFC 6121 §5.2.2) or:
468 pre_xml_treatments, 465 pre_xml_treatments,
469 post_xml_treatments, 466 post_xml_treatments,
470 ): 467 ):
471 return defer.succeed(None) 468 return defer.succeed(None)
472 469
473 log.debug( 470 log.debug(_(u"Sending message (type {type}, to {to})")
474 _(u"Sending message (type {type}, to {to})").format( 471 .format(type=data["type"], to=to_jid.full()))
475 type=data["type"], to=to_jid.full() 472
476 ) 473 pre_xml_treatments.addCallback(lambda __: self.generateMessageXML(data))
477 )
478
479 pre_xml_treatments.addCallback(lambda dummy: self.generateMessageXML(data))
480 pre_xml_treatments.chainDeferred(post_xml_treatments) 474 pre_xml_treatments.chainDeferred(post_xml_treatments)
481 post_xml_treatments.addCallback(self.sendMessageData) 475 post_xml_treatments.addCallback(self.sendMessageData)
482 if send_only: 476 if send_only:
483 log.debug( 477 log.debug(_(u"Triggers, storage and echo have been inhibited by the "
484 _( 478 u"'send_only' parameter"))
485 "Triggers, storage and echo have been inhibited by the 'send_only' parameter"
486 )
487 )
488 else: 479 else:
489 self.addPostXmlCallbacks(post_xml_treatments) 480 self.addPostXmlCallbacks(post_xml_treatments)
490 post_xml_treatments.addErrback(self._cancelErrorTrap) 481 post_xml_treatments.addErrback(self._cancelErrorTrap)
491 post_xml_treatments.addErrback(self.host_app.logErrback) 482 post_xml_treatments.addErrback(self.host_app.logErrback)
492 pre_xml_treatments.callback(data) 483 pre_xml_treatments.callback(data)
560 # XXX: DNS SRV records are checked when the host is not specified. 551 # XXX: DNS SRV records are checked when the host is not specified.
561 # If no SRV record is found, the host is directly extracted from the JID. 552 # If no SRV record is found, the host is directly extracted from the JID.
562 self.started = time.time() 553 self.started = time.time()
563 554
564 # Currently, we use "client/pc/Salut à Toi", but as 555 # Currently, we use "client/pc/Salut à Toi", but as
565 # SàT is multi-frontends and can be used on mobile devices, as a bot, with a web frontend, 556 # SàT is multi-frontends and can be used on mobile devices, as a bot,
566 # etc., we should implement a way to dynamically update identities through the bridge 557 # with a web frontend,
558 # etc., we should implement a way to dynamically update identities through the
559 # bridge
567 self.identities = [disco.DiscoIdentity(u"client", u"pc", C.APP_NAME)] 560 self.identities = [disco.DiscoIdentity(u"client", u"pc", C.APP_NAME)]
568 if sys.platform == "android": 561 if sys.platform == "android":
569 # FIXME: temporary hack as SRV is not working on android 562 # FIXME: temporary hack as SRV is not working on android
570 # TODO: remove this hack and fix SRV 563 # TODO: remove this hack and fix SRV
571 log.info(u"FIXME: Android hack, ignoring SRV") 564 log.info(u"FIXME: Android hack, ignoring SRV")
589 _(u"invalid data used for host: {data}").format(data=host_data) 582 _(u"invalid data used for host: {data}").format(data=host_data)
590 ) 583 )
591 host_data = None 584 host_data = None
592 if host_data is not None: 585 if host_data is not None:
593 log.info( 586 log.info(
594 u"using {host}:{port} for host {host_ori} as requested in config".format( 587 u"using {host}:{port} for host {host_ori} as requested in config"
595 host_ori=user_jid.host, host=host, port=port 588 .format(host_ori=user_jid.host, host=host, port=port)
596 )
597 ) 589 )
598 590
599 wokkel_client.XMPPClient.__init__( 591 wokkel_client.XMPPClient.__init__(
600 self, user_jid, password, host or None, port or C.XMPP_C2S_PORT 592 self, user_jid, password, host or None, port or C.XMPP_C2S_PORT
601 ) 593 )
632 # it is intended for things like end 2 end encryption. 624 # it is intended for things like end 2 end encryption.
633 # *DO NOT* cancel (i.e. return False) without very good reason 625 # *DO NOT* cancel (i.e. return False) without very good reason
634 # (out of band transmission for instance). 626 # (out of band transmission for instance).
635 # e2e should have a priority of 0 here, and out of band transmission 627 # e2e should have a priority of 0 here, and out of band transmission
636 # a lower priority 628 # a lower priority
637 #  FIXME: trigger not used yet, can be uncommented when e2e full stanza encryption is implemented 629 #  FIXME: trigger not used yet, can be uncommented when e2e full stanza
630 # encryption is implemented
638 #  if not self.host_app.trigger.point("send", self, obj): 631 #  if not self.host_app.trigger.point("send", self, obj):
639 #   return 632 #   return
640 super(SatXMPPClient, self).send(obj) 633 super(SatXMPPClient, self).send(obj)
641 634
642 def sendMessageData(self, mess_data): 635 def sendMessageData(self, mess_data):
646 The trigger can't be cancelled, it's a good place for e2e encryption which 639 The trigger can't be cancelled, it's a good place for e2e encryption which
647 don't handle full stanza encryption 640 don't handle full stanza encryption
648 @param mess_data(dict): message data as constructed by onMessage workflow 641 @param mess_data(dict): message data as constructed by onMessage workflow
649 @return (dict): mess_data (so it can be used in a deferred chain) 642 @return (dict): mess_data (so it can be used in a deferred chain)
650 """ 643 """
651 # XXX: This is the last trigger before u"send" (last but one globally) for sending message. 644 # XXX: This is the last trigger before u"send" (last but one globally)
652 # This is intented for e2e encryption which doesn't do full stanza encryption (e.g. OTR) 645 # for sending message.
646 # This is intented for e2e encryption which doesn't do full stanza
647 # encryption (e.g. OTR)
653 # This trigger point can't cancel the method 648 # This trigger point can't cancel the method
654 self.host_app.trigger.point("sendMessageData", self, mess_data) 649 self.host_app.trigger.point("sendMessageData", self, mess_data)
655 self.send(mess_data[u"xml"]) 650 self.send(mess_data[u"xml"])
656 return mess_data 651 return mess_data
657 652
673 mess_type=C.MESS_TYPE_INFO, 668 mess_type=C.MESS_TYPE_INFO,
674 extra={}, 669 extra={},
675 profile=self.profile, 670 profile=self.profile,
676 ) 671 )
677 672
678 def _finish_connection(self, dummy): 673 def _finish_connection(self, __):
679 self.roster.requestRoster() 674 self.roster.requestRoster()
680 self.presence.available() 675 self.presence.available()
681 super(SatXMPPClient, self)._finish_connection(dummy) 676 super(SatXMPPClient, self)._finish_connection(__)
682 677
683 678
684 class SatXMPPComponent(SatXMPPEntity, component.Component): 679 class SatXMPPComponent(SatXMPPEntity, component.Component):
685 """XMPP component 680 """XMPP component
686 681
694 "Component" 689 "Component"
695 ) # used for to distinguish some trigger points set in SatXMPPEntity 690 ) # used for to distinguish some trigger points set in SatXMPPEntity
696 is_component = True 691 is_component = True
697 sendHistory = ( 692 sendHistory = (
698 False 693 False
699 ) # XXX: set to True from entry plugin to keep messages in history for received messages 694 ) # XXX: set to True from entry plugin to keep messages in history for received
695 # messages
700 696
701 def __init__( 697 def __init__(
702 self, 698 self,
703 host_app, 699 host_app,
704 profile, 700 profile,
746 @param plugins(list): list of validated plugins, will be filled by the method 742 @param plugins(list): list of validated plugins, will be filled by the method
747 give an empty list for first call 743 give an empty list for first call
748 @param required(bool): True if plugin is mandatory 744 @param required(bool): True if plugin is mandatory
749 for recursive calls only, should not be modified by inital caller 745 for recursive calls only, should not be modified by inital caller
750 @raise InternalError: one of the plugin is not handling components 746 @raise InternalError: one of the plugin is not handling components
751 @raise KeyError: one plugin should be present in self.host_app.plugins but it is not 747 @raise KeyError: one plugin should be present in self.host_app.plugins but it
748 is not
752 """ 749 """
753 if C.PLUG_MODE_COMPONENT not in current._info[u"modes"]: 750 if C.PLUG_MODE_COMPONENT not in current._info[u"modes"]:
754 if not required: 751 if not required:
755 return 752 return
756 else: 753 else:
757 log.error( 754 log.error(
758 _( 755 _(
759 u"Plugin {current_name} is needed for {entry_name}, but it doesn't handle component mode" 756 u"Plugin {current_name} is needed for {entry_name}, "
757 u"but it doesn't handle component mode"
760 ).format( 758 ).format(
761 current_name=current._info[u"import_name"], 759 current_name=current._info[u"import_name"],
762 entry_name=self.entry_plugin._info[u"import_name"], 760 entry_name=self.entry_plugin._info[u"import_name"],
763 ) 761 )
764 ) 762 )
896 log.info(u"history is skipped as requested") 894 log.info(u"history is skipped as requested")
897 data[u"extra"][u"history"] = C.HISTORY_SKIP 895 data[u"extra"][u"history"] = C.HISTORY_SKIP
898 else: 896 else:
899 return self.host.memory.addToHistory(client, data) 897 return self.host.memory.addToHistory(client, data)
900 898
901 def bridgeSignal(self, dummy, client, data): 899 def bridgeSignal(self, __, client, data):
902 try: 900 try:
903 data["extra"]["received_timestamp"] = data["received_timestamp"] 901 data["extra"]["received_timestamp"] = data["received_timestamp"]
904 data["extra"]["delay_sender"] = data["delay_sender"] 902 data["extra"]["delay_sender"] = data["delay_sender"]
905 except KeyError: 903 except KeyError:
906 pass 904 pass
940 if not item.subscriptionTo and not item.subscriptionFrom and not item.ask: 938 if not item.subscriptionTo and not item.subscriptionFrom and not item.ask:
941 # XXX: current behaviour: we don't want contact in our roster list 939 # XXX: current behaviour: we don't want contact in our roster list
942 # if there is no presence subscription 940 # if there is no presence subscription
943 # may change in the future 941 # may change in the future
944 log.info( 942 log.info(
945 u"Removing contact {} from roster because there is no presence subscription".format( 943 u"Removing contact {} from roster because there is no presence "
944 u"subscription".format(
946 item.jid 945 item.jid
947 ) 946 )
948 ) 947 )
949 self.removeItem(item.entity) # FIXME: to be checked 948 self.removeItem(item.entity) # FIXME: to be checked
950 else: 949 else:
957 @param item (RosterIem): item added 956 @param item (RosterIem): item added
958 """ 957 """
959 log.debug(u"registering item: {}".format(item.entity.full())) 958 log.debug(u"registering item: {}".format(item.entity.full()))
960 if item.entity.resource: 959 if item.entity.resource:
961 log.warning( 960 log.warning(
962 u"Received a roster item with a resource, this is not common but not restricted by RFC 6121, this case may be not well tested." 961 u"Received a roster item with a resource, this is not common but not "
962 u"restricted by RFC 6121, this case may be not well tested."
963 ) 963 )
964 if not item.subscriptionTo: 964 if not item.subscriptionTo:
965 if not item.subscriptionFrom: 965 if not item.subscriptionFrom:
966 log.info( 966 log.info(
967 _(u"There's no subscription between you and [{}]!").format( 967 _(u"There's no subscription between you and [{}]!").format(
1042 jids_set.remove(entity) 1042 jids_set.remove(entity)
1043 if not jids_set: 1043 if not jids_set:
1044 del self._groups[group] 1044 del self._groups[group]
1045 except KeyError: 1045 except KeyError:
1046 log.warning( 1046 log.warning(
1047 u"there is no cache for the group [%(group)s] of the removed roster item [%(jid)s]" 1047 u"there is no cache for the group [{group}] of the removed roster "
1048 % {"group": group, "jid": entity} 1048 u"item [{jid_}]".format(group=group, jid=entity)
1049 ) 1049 )
1050 1050
1051 # then we send the bridge signal 1051 # then we send the bridge signal
1052 self.host.bridge.contactDeleted(entity.full(), self.parent.profile) 1052 self.host.bridge.contactDeleted(entity.full(), self.parent.profile)
1053 1053
1137 presence_d.addCallback(lambda __: super(SatPresenceProtocol, self).send(obj)) 1137 presence_d.addCallback(lambda __: super(SatPresenceProtocol, self).send(obj))
1138 1138
1139 def availableReceived(self, entity, show=None, statuses=None, priority=0): 1139 def availableReceived(self, entity, show=None, statuses=None, priority=0):
1140 log.debug( 1140 log.debug(
1141 _( 1141 _(
1142 u"presence update for [%(entity)s] (available, show=%(show)s statuses=%(statuses)s priority=%(priority)d)" 1142 u"presence update for [{entity}] (available, show={show} "
1143 u"statuses={statuses} priority={priority})"
1143 ) 1144 )
1144 % { 1145 .format(
1145 "entity": entity, 1146 entity=entity,
1146 C.PRESENCE_SHOW: show, 1147 show=show,
1147 C.PRESENCE_STATUSES: statuses, 1148 statuses=statuses,
1148 C.PRESENCE_PRIORITY: priority, 1149 priority=priority,
1149 } 1150 )
1150 ) 1151 )
1151 1152
1152 if not statuses: 1153 if not statuses:
1153 statuses = {} 1154 statuses = {}
1154 1155
1185 "presenceReceived", entity, "unavailable", 0, statuses, self.parent.profile 1186 "presenceReceived", entity, "unavailable", 0, statuses, self.parent.profile
1186 ): 1187 ):
1187 return 1188 return
1188 1189
1189 # now it's time to notify frontends 1190 # now it's time to notify frontends
1190 # if the entity is not known yet in this session or is already unavailable, there is no need to send an unavailable signal 1191 # if the entity is not known yet in this session or is already unavailable,
1192 # there is no need to send an unavailable signal
1191 try: 1193 try:
1192 presence = self.host.memory.getEntityDatum( 1194 presence = self.host.memory.getEntityDatum(
1193 entity, "presence", self.parent.profile 1195 entity, "presence", self.parent.profile
1194 ) 1196 )
1195 except (KeyError, exceptions.UnknownEntityError): 1197 except (KeyError, exceptions.UnknownEntityError):
1273 def subscribeReceived(self, entity): 1275 def subscribeReceived(self, entity):
1274 log.debug(_(u"subscription request from [%s]") % entity.userhost()) 1276 log.debug(_(u"subscription request from [%s]") % entity.userhost())
1275 yield self.parent.roster.got_roster 1277 yield self.parent.roster.got_roster
1276 item = self.parent.roster.getItem(entity) 1278 item = self.parent.roster.getItem(entity)
1277 if item and item.subscriptionTo: 1279 if item and item.subscriptionTo:
1278 # We automatically accept subscription if we are already subscribed to contact presence 1280 # We automatically accept subscription if we are already subscribed to
1281 # contact presence
1279 log.debug(_("sending automatic subscription acceptance")) 1282 log.debug(_("sending automatic subscription acceptance"))
1280 self.subscribed(entity) 1283 self.subscribed(entity)
1281 else: 1284 else:
1282 self.host.memory.addWaitingSub( 1285 self.host.memory.addWaitingSub(
1283 "subscribe", entity.userhost(), self.parent.profile 1286 "subscribe", entity.userhost(), self.parent.profile
1312 log.debug(u"iqFallback: xml = [%s]" % (iq.toXml())) 1315 log.debug(u"iqFallback: xml = [%s]" % (iq.toXml()))
1313 generic.FallbackHandler.iqFallback(self, iq) 1316 generic.FallbackHandler.iqFallback(self, iq)
1314 1317
1315 1318
1316 class SatVersionHandler(generic.VersionHandler): 1319 class SatVersionHandler(generic.VersionHandler):
1320
1317 def getDiscoInfo(self, requestor, target, node): 1321 def getDiscoInfo(self, requestor, target, node):
1318 # XXX: We need to work around wokkel's behaviour (namespace not added if there is a 1322 # XXX: We need to work around wokkel's behaviour (namespace not added if there
1319 # node) as it cause issues with XEP-0115 & PEP (XEP-0163): there is a node when server 1323 # is a node) as it cause issues with XEP-0115 & PEP (XEP-0163): there is a
1320 # ask for disco info, and not when we generate the key, so the hash is used with different 1324 # node when server ask for disco info, and not when we generate the key, so
1321 # disco features, and when the server (seen on ejabberd) generate its own hash for security check 1325 # the hash is used with different disco features, and when the server (seen
1322 # it reject our features (resulting in e.g. no notification on PEP) 1326 # on ejabberd) generate its own hash for security check it reject our
1327 # features (resulting in e.g. no notification on PEP)
1323 return generic.VersionHandler.getDiscoInfo(self, requestor, target, None) 1328 return generic.VersionHandler.getDiscoInfo(self, requestor, target, None)
1324 1329
1325 1330
1326 class SatIdentityHandler(XMPPHandler): 1331 class SatIdentityHandler(XMPPHandler):
1327 """ Manage disco Identity of SàT. 1332 """Manage disco Identity of SàT."""
1328
1329 """
1330
1331 # TODO: dynamic identity update (see docstring). Note that a XMPP entity can have several identities
1332 implements(iwokkel.IDisco) 1333 implements(iwokkel.IDisco)
1334 # TODO: dynamic identity update (see docstring). Note that a XMPP entity can have
1335 # several identities
1333 1336
1334 def getDiscoInfo(self, requestor, target, nodeIdentifier=""): 1337 def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
1335 return self.parent.identities 1338 return self.parent.identities
1336 1339
1337 def getDiscoItems(self, requestor, target, nodeIdentifier=""): 1340 def getDiscoItems(self, requestor, target, nodeIdentifier=""):