diff --git a/src/connection.py b/src/connection.py index a0e9a395..b06a013b 100644 --- a/src/connection.py +++ b/src/connection.py @@ -16,12 +16,17 @@ log = logging.getLogger(__name__) import getpass import sleekxmpp from sleekxmpp.plugins.xep_0184 import XEP_0184 +from sleekxmpp.plugins.xep_0030 import StaticDisco +from sleekxmpp.plugins.xep_0115 import StaticCaps import common import fixes from common import safeJID from config import config, options +StaticDisco.supports = fixes.xep_30_supports +StaticCaps.supports = fixes.xep_115_supports + class Connection(sleekxmpp.ClientXMPP): """ Receives everything from Jabber and emits the @@ -72,8 +77,6 @@ class Connection(sleekxmpp.ClientXMPP): self.whitespace_keepalive_interval = int(interval) else: self.whitespace_keepalive_interval = 300 - # Hack to check the sleekxmpp version - # TODO: Remove that when a sufficient time has passed since the move self.register_plugin('xep_0004') self.register_plugin('xep_0012') self.register_plugin('xep_0030') diff --git a/src/core/core.py b/src/core/core.py index e340bd5d..74930083 100644 --- a/src/core/core.py +++ b/src/core/core.py @@ -25,6 +25,7 @@ import bookmark import connection import decorators import events +import fixes import singleton import tabs import theming @@ -297,6 +298,7 @@ class Core(object): self.on_theme_config_change) self.add_configuration_handler("", self.on_any_config_change) + self.reset_iq_errors() def on_any_config_change(self, option, value): """ @@ -739,6 +741,12 @@ class Core(object): self.timed_events.remove(event) break + def reset_iq_errors(self): + "Reset the iq error cache periodically" + fixes.reset_iq_errors() + self.add_timed_event( + timed_events.DelayedEvent(7200, self.reset_iq_errors)) + ####################### XMPP-related actions ################################## diff --git a/src/fixes.py b/src/fixes.py index 75ac6343..cb992e88 100644 --- a/src/fixes.py +++ b/src/fixes.py @@ -5,12 +5,15 @@ upstream. TODO: Check that they are fixed and remove those hacks """ - from sleekxmpp.stanza import Message from sleekxmpp.xmlstream import ET import logging +# used to avoid doing numerous useless disco#info requests +# especially with message receipts +IQ_ERRORS = set() + log = logging.getLogger(__name__) def has_identity(xmpp, jid, identity): @@ -96,3 +99,89 @@ def _filter_add_receipt_request(self, stanza): stanza['request_receipt'] = True return stanza + +def xep_30_supports(self, jid, node, ifrom, data): + """ + Check if a JID supports a given feature. + + The data parameter may provide: + feature -- The feature to check for support. + local -- If true, then the query is for a JID/node + combination handled by this Sleek instance and + no stanzas need to be sent. + Otherwise, a disco stanza must be sent to the + remove JID to retrieve the info. + cached -- If true, then look for the disco info data from + the local cache system. If no results are found, + send the query as usual. The self.use_cache + setting must be set to true for this option to + be useful. If set to false, then the cache will + be skipped, even if a result has already been + cached. Defaults to false. + """ + feature = data.get('feature', None) + + data = {'local': data.get('local', False), + 'cached': data.get('cached', True)} + + if not feature or jid.full in IQ_ERRORS: + return False + + try: + info = self.disco.get_info(jid=jid, node=node, + ifrom=ifrom, **data) + info = self.disco._wrap(ifrom, jid, info, True) + features = info['disco_info']['features'] + return feature in features + except: + IQ_ERRORS.add(jid.full) + log.debug('%s added to the list of entities that do' + 'not honor disco#info', jid.full) + return False + +def xep_115_supports(self, jid, node, ifrom, data): + """ + Check if a JID supports a given feature. + + The data parameter may provide: + feature -- The feature to check for support. + local -- If true, then the query is for a JID/node + combination handled by this Sleek instance and + no stanzas need to be sent. + Otherwise, a disco stanza must be sent to the + remove JID to retrieve the info. + cached -- If true, then look for the disco info data from + the local cache system. If no results are found, + send the query as usual. The self.use_cache + setting must be set to true for this option to + be useful. If set to false, then the cache will + be skipped, even if a result has already been + cached. Defaults to false. + """ + feature = data.get('feature', None) + + data = {'local': data.get('local', False), + 'cached': data.get('cached', True)} + + if not feature or jid.full in IQ_ERRORS: + return False + + if node in (None, ''): + info = self.caps.get_caps(jid) + if info and feature in info['features']: + return True + + try: + info = self.disco.get_info(jid=jid, node=node, + ifrom=ifrom, **data) + info = self.disco._wrap(ifrom, jid, info, True) + return feature in info['disco_info']['features'] + except: + IQ_ERRORS.add(jid.full) + log.debug('%s added to the list of entities that do' + 'not honor disco#info', jid.full) + return False + +def reset_iq_errors(): + "reset the iq error cache" + IQ_ERRORS.clear()