Make detecting the features supported by the remote entity less awful
no more stalling while waiting for a disco info while sending a message.
This commit is contained in:
parent
916416a019
commit
97ef9d7fb9
7 changed files with 99 additions and 180 deletions
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
97
src/fixes.py
97
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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue