From 97ef9d7fb90fd4a3a408868f0aa996728ad382cd Mon Sep 17 00:00:00 2001 From: mathieui Date: Thu, 15 May 2014 23:33:51 +0200 Subject: [PATCH] Make detecting the features supported by the remote entity less awful no more stalling while waiting for a disco info while sending a message. --- src/connection.py | 5 -- src/core/core.py | 8 --- src/fixes.py | 97 ++----------------------------------- src/tabs/__init__.py | 2 +- src/tabs/basetabs.py | 94 ++++++++++++++++++++++++++++++----- src/tabs/conversationtab.py | 36 +++----------- src/tabs/privatetab.py | 37 +++----------- 7 files changed, 99 insertions(+), 180 deletions(-) diff --git a/src/connection.py b/src/connection.py index b06a013b..b5b7e12e 100644 --- a/src/connection.py +++ b/src/connection.py @@ -16,17 +16,12 @@ 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 diff --git a/src/core/core.py b/src/core/core.py index 74930083..2e7a3c64 100644 --- a/src/core/core.py +++ b/src/core/core.py @@ -298,7 +298,6 @@ 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): """ @@ -741,13 +740,6 @@ 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 ################################## def get_status(self): diff --git a/src/fixes.py b/src/fixes.py index cb992e88..18c117d8 100644 --- a/src/fixes.py +++ b/src/fixes.py @@ -10,10 +10,6 @@ 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): @@ -91,97 +87,10 @@ def _filter_add_receipt_request(self, stanza): if not stanza['body']: return stanza - if stanza['to'].resource: - if not self.xmpp['xep_0030'].supports(stanza['to'], - feature='urn:xmpp:receipts', - cached=True): - return stanza + # hack + if stanza['to'].resource and not hasattr(stanza, '_add_receipt'): + return 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() diff --git a/src/tabs/__init__.py b/src/tabs/__init__.py index 5f09bf38..eaf41a2f 100644 --- a/src/tabs/__init__.py +++ b/src/tabs/__init__.py @@ -1,4 +1,4 @@ -from . basetabs import Tab, ChatTab, GapTab +from . basetabs import Tab, ChatTab, GapTab, OneToOneTab from . basetabs import STATE_PRIORITY from . rostertab import RosterInfoTab from . muctab import MucTab, NS_MUC_USER diff --git a/src/tabs/basetabs.py b/src/tabs/basetabs.py index c472bd60..6fb8a329 100644 --- a/src/tabs/basetabs.py +++ b/src/tabs/basetabs.py @@ -433,9 +433,6 @@ class ChatTab(Tab): self.name = jid self.text_win = None self._text_buffer = TextBuffer() - self.remote_wants_chatstates = None # change this to True or False when - # we know that the remote user wants chatstates, or not. - # None means we don’t know yet, and we send only "active" chatstates self.chatstate = None # can be "active", "composing", "paused", "gone", "inactive" # We keep a weakref of the event that will set our chatstate to "paused", so that # we can delete it or change it if we need to @@ -443,7 +440,6 @@ class ChatTab(Tab): # if that’s None, then no paused chatstate was sent recently # if that’s a weakref returning None, then a paused chatstate was sent # since the last input - self.remote_supports_attention = False # Keeps the last sent message to complete it easily in completion_correct, and to replace it. self.last_sent_message = None self.key_func['M-v'] = self.move_separator @@ -503,15 +499,6 @@ class ChatTab(Tab): identifier=identifier, jid=jid) - def ack_message(self, msg_id): - """ - Ack a message - """ - new_msg = self._text_buffer.ack_message(msg_id) - if new_msg: - self.text_win.modify_message(msg_id, new_msg) - self.core.refresh_window() - def modify_message(self, txt, old_id, new_id, user=None, jid=None, nickname=None): self.log_message(txt, nickname, typ=1) message = self._text_buffer.modify_message(txt, old_id, new_id, time=time, user=user, jid=jid) @@ -709,3 +696,84 @@ class ChatTab(Tab): def scroll_separator(self): self.text_win.scroll_to_separator() +class OneToOneTab(ChatTab): + + def __init__(self, jid=''): + ChatTab.__init__(self, jid) + + # change this to True or False when + # we know that the remote user wants chatstates, or not. + # None means we don’t know yet, and we send only "active" chatstates + self.remote_wants_chatstates = None + self.remote_supports_attention = False + self.remote_supports_receipts = True + self.check_features() + + def ack_message(self, msg_id): + """ + Ack a message + """ + new_msg = self._text_buffer.ack_message(msg_id) + if new_msg: + self.text_win.modify_message(msg_id, new_msg) + self.core.refresh_window() + + def check_features(self): + "check the features supported by the other party" + self.core.xmpp.plugin['xep_0030'].get_info( + jid=self.get_dest_jid(), block=False, timeout=5, + callback=self.features_checked) + + def command_attention(self, message=''): + "/attention [message]" + if message is not '': + self.command_say(message, attention=True) + else: + msg = self.core.xmpp.make_message(self.get_dest_jid()) + msg['type'] = 'chat' + msg['attention'] = True + msg.send() + + def command_say(self, line, correct=False, attention=False): + pass + + def _feature_attention(self, features): + "Check for the 'attention' features" + if 'urn:xmpp:attention:0' in features: + self.remote_supports_attention = True + self.register_command('attention', self.command_attention, + usage=_('[message]'), + shortdesc=_('Request the attention.'), + desc=_('Attention: Request the attention of ' + 'the contact. Can also send a message' + ' along with the attention.')) + else: + self.remote_supports_attention = False + + def _feature_correct(self, features): + "Check for the 'correction' feature" + if not 'urn:xmpp:message-correct:0' in features: + if 'correct' in self.commands: + del self.commands['correct'] + elif not 'correct' in self.commands: + self.register_command('correct', self.command_correct, + desc=_('Fix the last message with whatever you want.'), + shortdesc=_('Correct the last message.'), + completion=self.completion_correct) + + def _feature_receipts(self, features): + "Check for the 'receipts' feature" + if 'urn:xmpp:receipts' in features: + self.remote_supports_receipts = True + else: + self.remote_supports_receipts = False + + def features_checked(self, iq): + "Features check callback" + features = iq['disco_info'].get_features() or [] + log.debug('\n\nFEATURES:\n%s\n\n%s\n\n', iq, features) + self._feature_correct(features) + self._feature_attention(features) + self._feature_receipts(features) + + diff --git a/src/tabs/conversationtab.py b/src/tabs/conversationtab.py index 7480906b..e6324182 100644 --- a/src/tabs/conversationtab.py +++ b/src/tabs/conversationtab.py @@ -18,7 +18,7 @@ log = logging.getLogger(__name__) import curses -from . import ChatTab, Tab +from . basetabs import OneToOneTab, Tab import common import fixes @@ -30,7 +30,7 @@ from decorators import refresh_wrapper from roster import roster from theming import get_theme, dump_tuple -class ConversationTab(ChatTab): +class ConversationTab(OneToOneTab): """ The tab containg a normal conversation (not from a MUC) Must not be instantiated, use Static or Dynamic version only. @@ -40,7 +40,7 @@ class ConversationTab(ChatTab): additional_informations = {} message_type = 'chat' def __init__(self, jid): - ChatTab.__init__(self, jid) + OneToOneTab.__init__(self, jid) self.nick = None self.nick_sent = False self.state = 'normal' @@ -49,7 +49,6 @@ class ConversationTab(ChatTab): self._text_buffer.add_window(self.text_win) self.upper_bar = windows.ConversationStatusMessageWin() self.input = windows.MessageInput() - self.check_attention() # keys self.key_func['^I'] = self.completion # commands @@ -142,6 +141,8 @@ class ConversationTab(ChatTab): typ=1) self.last_sent_message = msg + if self.remote_supports_receipts: + msg._add_receipt = True msg.send() self.cancel_paused_delay() self.text_win.refresh() @@ -208,32 +209,6 @@ class ConversationTab(ChatTab): self._text_buffer.add_message("\x19%(info_col)s}No information available\x19o" % {'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}) return True - - def command_attention(self, message=''): - if message is not '': - self.command_say(message, attention=True) - else: - msg = self.core.xmpp.make_message(self.get_dest_jid()) - msg['type'] = 'chat' - msg['attention'] = True - msg.send() - - def check_attention(self): - self.core.xmpp.plugin['xep_0030'].get_info( - jid=self.get_dest_jid(), block=False, timeout=5, - callback=self.on_attention_checked) - - def on_attention_checked(self, iq): - if 'urn:xmpp:attention:0' in iq['disco_info'].get_features(): - self.core.information('Attention is supported', 'Info') - self.remote_supports_attention = True - self.commands['attention'] = (self.command_attention, - _('Usage: /attention [message]\nAttention: Require' - ' the attention of the contact. Can also send a ' - 'message along with the attention.'), None) - else: - self.remote_supports_attention = False - def command_unquery(self, arg): self.core.close_tab() @@ -416,6 +391,7 @@ class DynamicConversationTab(ConversationTab): self.name, resource) self.add_message(message, typ=0) + self.check_features() def unlock_command(self, arg=None): self.unlock() diff --git a/src/tabs/privatetab.py b/src/tabs/privatetab.py index b8699bd2..6ab06744 100644 --- a/src/tabs/privatetab.py +++ b/src/tabs/privatetab.py @@ -17,7 +17,7 @@ log = logging.getLogger(__name__) import curses -from . import ChatTab, MucTab, Tab +from . import OneToOneTab, MucTab, Tab import fixes import windows @@ -28,7 +28,7 @@ from decorators import refresh_wrapper from logger import logger from theming import get_theme, dump_tuple -class PrivateTab(ChatTab): +class PrivateTab(OneToOneTab): """ The tab containg a private conversation (someone from a MUC) """ @@ -37,14 +37,13 @@ class PrivateTab(ChatTab): additional_informations = {} plugin_keys = {} def __init__(self, name, nick): - ChatTab.__init__(self, name) + OneToOneTab.__init__(self, name) self.own_nick = nick self.name = name self.text_win = windows.TextWin() self._text_buffer.add_window(self.text_win) self.info_header = windows.PrivateInfoWin() self.input = windows.MessageInput() - self.check_attention() # keys self.key_func['^I'] = self.completion # commands @@ -68,6 +67,9 @@ class PrivateTab(ChatTab): def general_jid(self): return self.name + def get_dest_jid(self): + return self.name + @property def nick(self): return self.get_nick() @@ -172,36 +174,13 @@ class PrivateTab(ChatTab): typ=1) self.last_sent_message = msg + if self.remote_supports_receipts: + msg._add_receipt = True msg.send() self.cancel_paused_delay() self.text_win.refresh() self.input.refresh() - def command_attention(self, message=''): - if message is not '': - self.command_say(message, attention=True) - else: - msg = self.core.xmpp.make_message(self.name) - msg['type'] = 'chat' - msg['attention'] = True - msg.send() - - def check_attention(self): - self.core.xmpp.plugin['xep_0030'].get_info(jid=self.name, block=False, timeout=5, callback=self.on_attention_checked) - - def on_attention_checked(self, iq): - if 'urn:xmpp:attention:0' in iq['disco_info'].get_features(): - self.core.information('Attention is supported', 'Info') - self.remote_supports_attention = True - self.commands['attention'] = ( - self.command_attention, - _('Usage: /attention [message]\nAttention:' - 'Require the attention of the contact. Can' - ' also send a message along with the attention.'), - None) - else: - self.remote_supports_attention = False - def command_unquery(self, arg): """ /unquery