Rewrite discovery code and move it into own module

This commit is contained in:
Philipp Hörist 2018-07-22 12:18:24 +02:00
parent 5ff9e9febf
commit 07c87a4194
31 changed files with 621 additions and 679 deletions

View File

@ -207,11 +207,13 @@ class ClientCaps(AbstractClientCaps):
return caps_cache[(self._hash_method, self._hash)]
def _discover(self, connection, jid):
connection.discoverInfo(jid, '%s#%s' % (self._node, self._hash))
connection.get_module('Discovery').disco_contact(
jid, '%s#%s' % (self._node, self._hash))
def _is_hash_valid(self, identities, features, dataforms):
computed_hash = compute_caps_hash(identities, features,
dataforms=dataforms, hash_method=self._hash_method)
computed_hash = compute_caps_hash(
identities, features, dataforms=dataforms,
hash_method=self._hash_method)
return computed_hash == self._hash
@ -227,7 +229,7 @@ class OldClientCaps(AbstractClientCaps):
return caps_cache[('old', self._node + '#' + self._hash)]
def _discover(self, connection, jid):
connection.discoverInfo(jid)
connection.get_module('Discovery').disco_contact(jid)
def _is_hash_valid(self, identities, features, dataforms):
return True
@ -244,7 +246,7 @@ class NoClientCaps(AbstractClientCaps):
return caps_cache[('no', self._node)]
def _discover(self, connection, jid):
connection.discoverInfo(jid)
connection.get_module('Discovery').disco_contact(jid)
def _is_hash_valid(self, identities, features, dataforms):
return True

View File

@ -103,7 +103,6 @@ class CommonConnection:
self.priority = app.get_priority(name, 'offline')
self.time_to_reconnect = None
self.pep_supported = False
self.pep = {}
# Do we continue connection when we get roster (send presence,get vcard..)
self.continue_connect_info = None
@ -115,13 +114,9 @@ class CommonConnection:
# the fake jid
self.groupchat_jids = {} # {ID : groupchat_jid}
self.privacy_rules_supported = False
self.vcard_supported = False
self.private_storage_supported = False
self.roster_supported = True
self.blocking_supported = False
self.addressing_supported = False
self.carbons_available = False
self.muc_jid = {} # jid of muc server for each transport type
self._stun_servers = [] # STUN servers of our jabber server
@ -582,7 +577,6 @@ class Connection(CommonConnection, ConnectionHandlers):
self.music_track_info = 0
self.register_supported = False
self.pubsub_publish_options_supported = False
# Do we auto accept insecure connection
self.connection_auto_accepted = False
self.pasword_callback = None
@ -611,10 +605,6 @@ class Connection(CommonConnection, ConnectionHandlers):
# Register all modules
modules.register(self)
app.ged.register_event_handler('agent-info-error-received', ged.CORE,
self._nec_agent_info_error_received)
app.ged.register_event_handler('agent-info-received', ged.CORE,
self._nec_agent_info_received)
app.ged.register_event_handler('message-outgoing', ged.OUT_CORE,
self._nec_message_outgoing)
app.ged.register_event_handler('gc-message-outgoing', ged.OUT_CORE,
@ -629,10 +619,6 @@ class Connection(CommonConnection, ConnectionHandlers):
ConnectionHandlers.cleanup(self)
modules.unregister(self)
app.ged.remove_event_handler('agent-info-error-received', ged.CORE,
self._nec_agent_info_error_received)
app.ged.remove_event_handler('agent-info-received', ged.CORE,
self._nec_agent_info_received)
app.ged.remove_event_handler('message-outgoing', ged.OUT_CORE,
self._nec_message_outgoing)
app.ged.remove_event_handler('gc-message-outgoing', ged.OUT_CORE,
@ -699,7 +685,7 @@ class Connection(CommonConnection, ConnectionHandlers):
self.on_purpose = on_purpose
self.connected = 0
self.time_to_reconnect = None
self.privacy_rules_supported = False
self.get_module('PrivacyLists').supported = False
self.get_module('VCardAvatars').avatar_advertised = False
if on_purpose:
self.sm = Smacks(self)
@ -1430,7 +1416,7 @@ class Connection(CommonConnection, ConnectionHandlers):
def send_invisible_presence(self, msg, signed, initial = False):
if not app.account_is_connected(self.name):
return
if not self.privacy_rules_supported:
if not self.get_module('PrivacyLists').supported:
app.nec.push_incoming_event(OurShowEvent(None, conn=self,
show=app.SHOW_LIST[self.connected]))
app.nec.push_incoming_event(InformationEvent(
@ -1438,7 +1424,7 @@ class Connection(CommonConnection, ConnectionHandlers):
return
# If we are already connected, and privacy rules are supported, send
# offline presence first as it's required by XEP-0126
if self.connected > 1 and self.privacy_rules_supported:
if self.connected > 1 and self.get_module('PrivacyLists').supported:
self.on_purpose = True
p = nbxmpp.Presence(typ='unavailable')
p = self.add_sha(p, False)
@ -1515,10 +1501,9 @@ class Connection(CommonConnection, ConnectionHandlers):
# If we are not resuming, we ask for discovery info
# and archiving preferences
if not self.sm.supports_sm or (not self.sm.resuming and self.sm.enabled):
our_jid = app.get_jid_from_account(self.name)
our_server = app.config.get_per('accounts', self.name, 'hostname')
self.discoverInfo(our_jid, id_prefix='Gajim_')
self.discoverInfo(our_server, id_prefix='Gajim_')
self.get_module('Discovery').discover_account_info()
self.get_module('Discovery').discover_server_info()
else:
self.request_roster(resume=True)
@ -1534,7 +1519,7 @@ class Connection(CommonConnection, ConnectionHandlers):
self._stun_servers = self._hosts = [i for i in result_array]
def _continue_connection_request_privacy(self):
if self.privacy_rules_supported:
if self.get_module('PrivacyLists').supported:
if not self.privacy_rules_requested:
self.privacy_rules_requested = True
self.get_module('PrivacyLists').get_privacy_lists(
@ -1557,104 +1542,12 @@ class Connection(CommonConnection, ConnectionHandlers):
None, dialog_name='invisibility-not-supported',
args=self.name))
return
if self.blocking_supported:
self.get_module('Blocking').get_blocking_list()
# Ask metacontacts before roster
self.get_metacontacts()
def _nec_agent_info_error_received(self, obj):
if obj.conn.name != self.name:
return
hostname = app.config.get_per('accounts', self.name, 'hostname')
if obj.id_[:6] == 'Gajim_' and obj.fjid == hostname:
self._continue_connection_request_privacy()
def _nec_agent_info_received(self, obj):
if obj.conn.name != self.name:
return
is_muc = False
transport_type = ''
for identity in obj.identities:
if 'category' in identity and identity['category'] in ('gateway',
'headline') and 'type' in identity:
transport_type = identity['type']
if 'category' in identity and identity['category'] == 'server' and \
'type' in identity and identity['type'] == 'im':
transport_type = 'jabber' # it's a jabber server
if 'category' in identity and identity['category'] == 'conference' \
and 'type' in identity and identity['type'] == 'text':
is_muc = True
if transport_type != '' and obj.fjid not in app.transport_type:
app.transport_type[obj.fjid] = transport_type
app.logger.save_transport_type(obj.fjid, transport_type)
if obj.id_[:6] == 'Gajim_':
hostname = app.config.get_per('accounts', self.name, 'hostname')
our_jid = app.get_jid_from_account(self.name)
if obj.fjid == our_jid:
if nbxmpp.NS_MAM_2 in obj.features:
self.get_module('MAM').archiving_namespace = nbxmpp.NS_MAM_2
elif nbxmpp.NS_MAM_1 in obj.features:
self.get_module('MAM').archiving_namespace = nbxmpp.NS_MAM_1
if self.get_module('MAM').archiving_namespace:
self.get_module('MAM').available = True
get_action(self.name + '-archive').set_enabled(True)
for identity in obj.identities:
if identity['category'] == 'pubsub':
self.pep_supported = identity.get('type') == 'pep'
break
if nbxmpp.NS_PUBSUB_PUBLISH_OPTIONS in obj.features:
self.pubsub_publish_options_supported = True
else:
# Remove stored bookmarks accessible to everyone.
self.get_module('Bookmarks').purge_pubsub_bookmarks()
if obj.fjid == hostname:
if nbxmpp.NS_SECLABEL in obj.features:
self.get_module('SecLabels').supported = True
if nbxmpp.NS_VCARD in obj.features:
self.vcard_supported = True
get_action(self.name + '-profile').set_enabled(True)
if nbxmpp.NS_REGISTER in obj.features:
self.register_supported = True
if nbxmpp.NS_BLOCKING in obj.features:
self.blocking_supported = True
if nbxmpp.NS_ADDRESS in obj.features:
self.addressing_supported = True
if nbxmpp.NS_CARBONS in obj.features:
self.carbons_available = True
if app.config.get_per('accounts', self.name,
'enable_message_carbons'):
# Server supports carbons, activate it
iq = nbxmpp.Iq('set')
iq.setTag('enable', namespace=nbxmpp.NS_CARBONS)
self.connection.send(iq)
if nbxmpp.NS_PRIVACY in obj.features:
self.privacy_rules_supported = True
get_action(self.name + '-privacylists').set_enabled(True)
self._continue_connection_request_privacy()
if nbxmpp.NS_BYTESTREAM in obj.features and \
app.config.get_per('accounts', self.name, 'use_ft_proxies'):
our_fjid = helpers.parse_jid(our_jid + '/' + \
self.server_resource)
testit = app.config.get_per('accounts', self.name,
'test_ft_proxies_on_startup')
app.proxy65_manager.resolve(obj.fjid, self.connection,
our_fjid, default=self.name, testit=testit)
if nbxmpp.NS_MUC in obj.features and is_muc:
type_ = transport_type or 'jabber'
self.muc_jid[type_] = obj.fjid
if transport_type:
if transport_type in self.available_transports:
self.available_transports[transport_type].append(obj.fjid)
else:
self.available_transports[transport_type] = [obj.fjid]
def send_custom_status(self, show, msg, jid):
if not show in app.SHOW_LIST:
return -1
@ -1684,7 +1577,7 @@ class Connection(CommonConnection, ConnectionHandlers):
self.send_invisible_presence(msg, signed)
def _change_from_invisible(self):
if self.privacy_rules_supported:
if self.get_module('PrivacyLists').supported:
self.get_module('PrivacyLists').set_active_list(None)
def _update_status(self, show, msg, idle_time=None):
@ -1897,7 +1790,7 @@ class Connection(CommonConnection, ConnectionHandlers):
def bookmarks_available(self):
if self.private_storage_supported:
return True
if self.pubsub_publish_options_supported:
if self.get_module('PubSub').publish_options:
return True
return False
@ -2022,7 +1915,7 @@ class Connection(CommonConnection, ConnectionHandlers):
# Never join a room when invisible
return
self.discoverMUC(
self.get_module('Discovery').disco_muc(
room_jid, partial(self._join_gc, nick, show, room_jid,
password, change_nick, rejoin))

View File

@ -66,51 +66,6 @@ PRIVACY_ARRIVED = 'privacy_arrived'
class ConnectionDisco:
"""
Holds xmpppy handlers and public methods for discover services
"""
def discoverItems(self, jid, node=None, id_prefix=None):
"""
According to XEP-0030:
jid is mandatory;
name, node, action is optional.
"""
id_ = self._discover(nbxmpp.NS_DISCO_ITEMS, jid, node, id_prefix)
self.disco_items_ids.append(id_)
def discoverInfo(self, jid, node=None, id_prefix=None):
"""
According to XEP-0030:
For identity: category, type is mandatory, name is optional.
For feature: var is mandatory.
"""
id_ = self._discover(nbxmpp.NS_DISCO_INFO, jid, node, id_prefix)
self.disco_info_ids.append(id_)
def discoverMUC(self, jid, callback, update=False):
if muc_caps_cache.is_cached(jid) and not update:
callback()
return
disco_info = nbxmpp.Iq(typ='get', to=jid, queryNS=nbxmpp.NS_DISCO_INFO)
self.connection.SendAndCallForResponse(
disco_info, self.received_muc_info, {'callback': callback})
def received_muc_info(self, conn, stanza, callback):
if nbxmpp.isResultNode(stanza):
app.log('gajim.muc').info(
'Received MUC DiscoInfo for %s', stanza.getFrom())
muc_caps_cache.append(stanza)
callback()
else:
error = stanza.getError()
if error == 'item-not-found':
# Groupchat does not exist
callback()
return
app.nec.push_incoming_event(
InformationEvent(
None, dialog_name='unable-join-groupchat', args=error))
def request_register_agent_info(self, agent):
if not self.connection or self.connected < 2:
@ -159,117 +114,10 @@ class ConnectionDisco:
self.agent_registrations[agent] = {'roster_push': False,
'sub_received': False}
def _discover(self, ns, jid, node=None, id_prefix=None):
if not self.connection or self.connected < 2:
return
iq = nbxmpp.Iq(typ='get', to=jid, queryNS=ns)
id_ = self.connection.getAnID()
if id_prefix:
id_ = id_prefix + id_
iq.setID(id_)
if node:
iq.setQuerynode(node)
self.connection.send(iq)
return id_
def _ReceivedRegInfo(self, con, resp, agent):
nbxmpp.features_nb._ReceivedRegInfo(con, resp, agent)
self._IqCB(con, resp)
def _discoGetCB(self, con, iq_obj):
"""
Get disco info
"""
if not self.connection or self.connected < 2:
return
frm = helpers.get_full_jid_from_iq(iq_obj)
to = iq_obj.getAttr('to')
id_ = iq_obj.getAttr('id')
iq = nbxmpp.Iq(to=frm, typ='result', queryNS=nbxmpp.NS_DISCO, frm=to)
iq.setAttr('id', id_)
query = iq.setTag('query')
query.setAttr('node', 'http://gajim.org#' + app.version.split('-', 1)[
0])
for f in (nbxmpp.NS_BYTESTREAM, nbxmpp.NS_SI, nbxmpp.NS_FILE,
nbxmpp.NS_COMMANDS, nbxmpp.NS_JINGLE_FILE_TRANSFER_5,
nbxmpp.NS_JINGLE_XTLS, nbxmpp.NS_PUBKEY_PUBKEY, nbxmpp.NS_PUBKEY_REVOKE,
nbxmpp.NS_PUBKEY_ATTEST):
feature = nbxmpp.Node('feature')
feature.setAttr('var', f)
query.addChild(node=feature)
self.connection.send(iq)
raise nbxmpp.NodeProcessed
def _DiscoverItemsErrorCB(self, con, iq_obj):
log.debug('DiscoverItemsErrorCB')
app.nec.push_incoming_event(AgentItemsErrorReceivedEvent(None,
conn=self, stanza=iq_obj))
def _DiscoverItemsCB(self, con, iq_obj):
log.debug('DiscoverItemsCB')
app.nec.push_incoming_event(AgentItemsReceivedEvent(None, conn=self,
stanza=iq_obj))
def _DiscoverItemsGetCB(self, con, iq_obj):
log.debug('DiscoverItemsGetCB')
if not self.connection or self.connected < 2:
return
if self.get_module('AdHocCommands').command_items_query(iq_obj):
raise nbxmpp.NodeProcessed
node = iq_obj.getTagAttr('query', 'node')
if node is None:
result = iq_obj.buildReply('result')
self.connection.send(result)
raise nbxmpp.NodeProcessed
if node == nbxmpp.NS_COMMANDS:
self.get_module('AdHocCommands').command_list_query(iq_obj)
raise nbxmpp.NodeProcessed
def _DiscoverInfoGetCB(self, con, iq_obj):
log.debug('DiscoverInfoGetCB')
if not self.connection or self.connected < 2:
return
node = iq_obj.getQuerynode()
if self.get_module('AdHocCommands').command_info_query(iq_obj):
raise nbxmpp.NodeProcessed
id_ = iq_obj.getAttr('id')
if id_[:6] == 'Gajim_':
# We get this request from echo.server
raise nbxmpp.NodeProcessed
iq = iq_obj.buildReply('result')
q = iq.setQuery()
if node:
q.setAttr('node', node)
q.addChild('identity', attrs=app.gajim_identity)
client_version = 'http://gajim.org#' + app.caps_hash[self.name]
if node in (None, client_version):
for f in app.gajim_common_features:
q.addChild('feature', attrs={'var': f})
for f in app.gajim_optional_features[self.name]:
q.addChild('feature', attrs={'var': f})
if q.getChildren():
self.connection.send(iq)
raise nbxmpp.NodeProcessed
def _DiscoverInfoErrorCB(self, con, iq_obj):
log.debug('DiscoverInfoErrorCB')
app.nec.push_incoming_event(AgentInfoErrorReceivedEvent(None,
conn=self, stanza=iq_obj))
def _DiscoverInfoCB(self, con, iq_obj):
log.debug('DiscoverInfoCB')
if not self.connection or self.connected < 2:
return
app.nec.push_incoming_event(AgentInfoReceivedEvent(None, conn=self,
stanza=iq_obj))
# basic connection handlers used here and in zeroconf
class ConnectionHandlersBase:
@ -292,25 +140,17 @@ class ConnectionHandlersBase:
# We decrypt GPG messages one after the other. Keep queue in mem
self.gpg_messages_to_decrypt = []
app.ged.register_event_handler('iq-error-received', ged.CORE,
self._nec_iq_error_received)
app.ged.register_event_handler('presence-received', ged.CORE,
self._nec_presence_received)
app.ged.register_event_handler('gc-message-received', ged.CORE,
self._nec_gc_message_received)
def cleanup(self):
app.ged.remove_event_handler('iq-error-received', ged.CORE,
self._nec_iq_error_received)
app.ged.remove_event_handler('presence-received', ged.CORE,
self._nec_presence_received)
app.ged.remove_event_handler('gc-message-received', ged.CORE,
self._nec_gc_message_received)
def _nec_iq_error_received(self, obj):
if obj.conn.name != self.name:
return
def _nec_presence_received(self, obj):
account = obj.conn.name
if account != self.name:
@ -647,8 +487,6 @@ class ConnectionHandlers(ConnectionSocks5Bytestream, ConnectionDisco,
ged.CORE, self._nec_roster_set_received)
app.ged.register_event_handler('roster-received', ged.CORE,
self._nec_roster_received)
app.ged.register_event_handler('iq-error-received', ged.CORE,
self._nec_iq_error_received)
app.ged.register_event_handler('subscribe-presence-received',
ged.CORE, self._nec_subscribe_presence_received)
app.ged.register_event_handler('subscribed-presence-received',
@ -669,8 +507,6 @@ class ConnectionHandlers(ConnectionSocks5Bytestream, ConnectionDisco,
ged.CORE, self._nec_roster_set_received)
app.ged.remove_event_handler('roster-received', ged.CORE,
self._nec_roster_received)
app.ged.remove_event_handler('iq-error-received', ged.CORE,
self._nec_iq_error_received)
app.ged.remove_event_handler('subscribe-presence-received',
ged.CORE, self._nec_subscribe_presence_received)
app.ged.remove_event_handler('subscribed-presence-received',
@ -762,28 +598,14 @@ class ConnectionHandlers(ConnectionSocks5Bytestream, ConnectionDisco,
self._getRoster()
elif iq_obj.getType() == 'error':
self.roster_supported = False
self.discoverItems(app.config.get_per('accounts', self.name,
'hostname'), id_prefix='Gajim_')
self.get_module('Discovery').discover_server_items()
if app.config.get_per('accounts', self.name,
'use_ft_proxies'):
self.discover_ft_proxies()
app.nec.push_incoming_event(RosterReceivedEvent(None,
conn=self))
GLib.timeout_add_seconds(10, self.discover_servers)
del self.awaiting_answers[id_]
def _nec_iq_error_received(self, obj):
if obj.conn.name != self.name:
return
if obj.id_ in self.disco_items_ids:
app.nec.push_incoming_event(AgentItemsErrorReceivedEvent(None,
conn=self, stanza=obj.stanza))
return True
if obj.id_ in self.disco_info_ids:
app.nec.push_incoming_event(AgentInfoErrorReceivedEvent(None,
conn=self, stanza=obj.stanza))
return True
def _rosterSetCB(self, con, iq_obj):
log.debug('rosterSetCB')
app.nec.push_incoming_event(RosterSetReceivedEvent(None, conn=self,
@ -972,8 +794,7 @@ class ConnectionHandlers(ConnectionSocks5Bytestream, ConnectionDisco,
if not self.connection:
return
self.connection.getRoster(self._on_roster_set)
self.discoverItems(app.config.get_per('accounts', self.name,
'hostname'), id_prefix='Gajim_')
self.get_module('Discovery').discover_server_items()
if app.config.get_per('accounts', self.name, 'use_ft_proxies'):
self.discover_ft_proxies()
@ -990,17 +811,6 @@ class ConnectionHandlers(ConnectionSocks5Bytestream, ConnectionDisco,
app.proxy65_manager.resolve(proxy, self.connection, our_jid,
testit=testit)
def discover_servers(self):
if not self.connection:
return
servers = []
for c in app.contacts.iter_contacts(self.name):
s = app.get_server_from_jid(c.jid)
if s not in servers and s not in app.transport_type:
servers.append(s)
for s in servers:
self.discoverInfo(s)
def _on_roster_set(self, roster):
app.nec.push_incoming_event(RosterReceivedEvent(None, conn=self,
xmpp_roster=roster))
@ -1027,13 +837,6 @@ class ConnectionHandlers(ConnectionSocks5Bytestream, ConnectionDisco,
if send_first_presence:
self._send_first_presence(signed)
if obj.received_from_server:
for jid in obj.roster:
if jid != our_jid and app.jid_is_transport(jid) and \
not app.get_transport_name_from_jid(jid):
# we can't determine which iconset to use
self.discoverInfo(jid)
app.logger.replace_roster(self.name, obj.version, obj.roster)
for contact in app.contacts.iter_contacts(self.name):
@ -1080,7 +883,7 @@ class ConnectionHandlers(ConnectionSocks5Bytestream, ConnectionDisco,
self.priority = priority
app.nec.push_incoming_event(OurShowEvent(None, conn=self,
show=show))
if self.vcard_supported:
# ask our VCard
self.get_module('VCardTemp').request_vcard()
@ -1120,7 +923,6 @@ class ConnectionHandlers(ConnectionSocks5Bytestream, ConnectionDisco,
con.RegisterHandler('iq', self._siSetCB, 'set', nbxmpp.NS_SI)
con.RegisterHandler('iq', self._siErrorCB, 'error', nbxmpp.NS_SI)
con.RegisterHandler('iq', self._siResultCB, 'result', nbxmpp.NS_SI)
con.RegisterHandler('iq', self._discoGetCB, 'get', nbxmpp.NS_DISCO)
con.RegisterHandler('iq', self._bytestreamSetCB, 'set',
nbxmpp.NS_BYTESTREAM)
con.RegisterHandler('iq', self._bytestreamResultCB, 'result',
@ -1130,18 +932,6 @@ class ConnectionHandlers(ConnectionSocks5Bytestream, ConnectionDisco,
con.RegisterHandlerOnce('iq', self.IBBAllIqHandler)
con.RegisterHandler('iq', self.IBBIqHandler, ns=nbxmpp.NS_IBB)
con.RegisterHandler('message', self.IBBMessageHandler, ns=nbxmpp.NS_IBB)
con.RegisterHandler('iq', self._DiscoverItemsCB, 'result',
nbxmpp.NS_DISCO_ITEMS)
con.RegisterHandler('iq', self._DiscoverItemsErrorCB, 'error',
nbxmpp.NS_DISCO_ITEMS)
con.RegisterHandler('iq', self._DiscoverInfoCB, 'result',
nbxmpp.NS_DISCO_INFO)
con.RegisterHandler('iq', self._DiscoverInfoErrorCB, 'error',
nbxmpp.NS_DISCO_INFO)
con.RegisterHandler('iq', self._DiscoverInfoGetCB, 'get',
nbxmpp.NS_DISCO_INFO)
con.RegisterHandler('iq', self._DiscoverItemsGetCB, 'get',
nbxmpp.NS_DISCO_ITEMS)
con.RegisterHandler('iq', self._JingleCB, 'result')
con.RegisterHandler('iq', self._JingleCB, 'error')

View File

@ -37,7 +37,6 @@ from gajim.common import helpers
from gajim.common import app
from gajim.common import i18n
from gajim.common.modules import dataforms
from gajim.common.zeroconf.zeroconf import Constant
from gajim.common.const import KindConstant, SSLError
from gajim.common.pep import SUPPORTED_PERSONAL_USER_EVENTS
from gajim.common.jingle_transport import JingleTransportSocks5
@ -1080,119 +1079,6 @@ class RegisterAgentInfoReceivedEvent(nec.NetworkIncomingEvent):
name = 'register-agent-info-received'
base_network_events = []
class AgentItemsReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
name = 'agent-items-received'
base_network_events = []
def generate(self):
q = self.stanza.getTag('query')
self.node = q.getAttr('node')
if not self.node:
self.node = ''
qp = self.stanza.getQueryPayload()
self.items = []
if not qp:
qp = []
for i in qp:
# CDATA payload is not processed, only nodes
if not isinstance(i, nbxmpp.simplexml.Node):
continue
attr = {}
for key in i.getAttrs():
attr[key] = i.getAttrs()[key]
if 'jid' not in attr:
continue
try:
attr['jid'] = helpers.parse_jid(attr['jid'])
except helpers.InvalidFormat:
# jid is not conform
continue
self.items.append(attr)
self.get_jid_resource()
hostname = app.config.get_per('accounts', self.conn.name, 'hostname')
self.get_id()
if self.id_ in self.conn.disco_items_ids:
self.conn.disco_items_ids.remove(self.id_)
if self.fjid == hostname and self.id_[:6] == 'Gajim_':
for item in self.items:
self.conn.discoverInfo(item['jid'], id_prefix='Gajim_')
else:
return True
class AgentItemsErrorReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
name = 'agent-items-error-received'
base_network_events = []
def generate(self):
self.get_jid_resource()
self.get_id()
if self.id_ in self.conn.disco_items_ids:
self.conn.disco_items_ids.remove(self.id_)
return True
class AgentInfoReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
name = 'agent-info-received'
base_network_events = []
def generate(self):
self.get_id()
if self.id_ in self.conn.disco_info_ids:
self.conn.disco_info_ids.remove(self.id_)
if self.id_ is None:
log.warning('Invalid IQ received without an ID. '
'Ignoring it: %s', self.stanza)
return
# According to XEP-0030:
# For identity: category, type is mandatory, name is optional.
# For feature: var is mandatory
self.identities, self.features, self.data, self.node = self.parse_stanza(self.stanza)
if not self.identities:
# ejabberd doesn't send identities when we browse online users
# see http://www.jabber.ru/bugzilla/show_bug.cgi?id=225
self.identities = [{'category': 'server', 'type': 'im',
'name': self.node}]
self.get_jid_resource()
return True
@classmethod
def parse_stanza(cls, stanza):
identities, features, data, node = [], [], [], None
q = stanza.getTag('query')
node = q.getAttr('node')
if not node:
node = ''
qc = stanza.getQueryChildren()
if not qc:
qc = []
for i in qc:
if i.getName() == 'identity':
attr = {}
for key in i.getAttrs().keys():
attr[key] = i.getAttr(key)
identities.append(attr)
elif i.getName() == 'feature':
var = i.getAttr('var')
if var:
features.append(var)
elif i.getName() == 'x' and i.getNamespace() == nbxmpp.NS_DATA:
data.append(nbxmpp.DataForm(node=i))
return identities, features, data, node
class AgentInfoErrorReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
name = 'agent-info-error-received'
base_network_events = []
def generate(self):
self.get_jid_resource()
self.get_id()
if self.id_ in self.conn.disco_info_ids:
self.conn.disco_info_ids.remove(self.id_)
return True
class FileRequestReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
name = 'file-request-received'
base_network_events = []

View File

@ -18,7 +18,7 @@ from pathlib import Path
log = logging.getLogger('gajim.c.m')
ZEROCONF_MODULES = ['adhoc_commands', 'receipts']
ZEROCONF_MODULES = ['adhoc_commands', 'receipts', 'discovery']
imported_modules = []
_modules = {}
@ -56,6 +56,9 @@ class ModuleMock:
# Bookmarks
self.bookmarks = {}
# Various Modules
self.supported = False
def __getattr__(self, key):
def _mock(self, *args, **kwargs):
return

View File

@ -35,7 +35,18 @@ class Blocking:
('iq', self._blocking_push_received, 'set', nbxmpp.NS_BLOCKING)
]
self.supported = False
def pass_disco(self, from_, identities, features, data, node):
if nbxmpp.NS_BLOCKING not in features:
return
self.supported = True
log.info('Discovered blocking: %s', from_)
def get_blocking_list(self):
if not self.supported:
return
iq = nbxmpp.Iq('get', nbxmpp.NS_BLOCKING)
iq.setQuery('blocklist')
log.info('Request list')
@ -119,7 +130,7 @@ class Blocking:
self._con.connection.send(probe)
def block(self, contact_list):
if not self._con.blocking_supported:
if not self.supported:
return
iq = nbxmpp.Iq('set', nbxmpp.NS_BLOCKING)
query = iq.setQuery(name='block')
@ -131,7 +142,7 @@ class Blocking:
iq, self._default_result_handler, {})
def unblock(self, contact_list):
if not self._con.blocking_supported:
if not self.supported:
return
iq = nbxmpp.Iq('set', nbxmpp.NS_BLOCKING)
query = iq.setQuery(name='unblock')

View File

@ -35,8 +35,8 @@ class Bookmarks:
self.handlers = []
def _pubsub_support(self):
return (self._con.pep_supported and
self._con.pubsub_publish_options_supported)
return (self._con.get_module('PEP').supported and
self._con.get_module('PubSub').publish_options)
def get_bookmarks(self, storage_type=None):
if not app.account_is_connected(self._account):
@ -86,7 +86,7 @@ class Bookmarks:
else:
log.info('Received Bookmarks (PrivateStorage)')
merged = self._parse_bookmarks(stanza, check_merge=True)
if merged:
if merged and self._pubsub_support():
log.info('Merge PrivateStorage with PubSub')
self.store_bookmarks(BookmarkStorageType.PUBSUB)
self.auto_join_bookmarks()

View File

@ -18,9 +18,37 @@ import logging
import nbxmpp
from gajim.common import app
log = logging.getLogger('gajim.c.m.carbons')
class Carbons:
def __init__(self, con):
self._con = con
self._account = con.name
self.handlers = []
self.supported = False
def pass_disco(self, from_, identities, features, data, node):
if nbxmpp.NS_CARBONS not in features:
return
self.supported = True
log.info('Discovered carbons: %s', from_)
if app.config.get_per('accounts', self._account,
'enable_message_carbons'):
iq = nbxmpp.Iq('set')
iq.setTag('enable', namespace=nbxmpp.NS_CARBONS)
log.info('Activate')
self._con.connection.send(iq)
else:
log.warning('Carbons deactivated (user setting)')
def parse_carbon(con, stanza):
carbon = stanza.getTag(
'received', namespace=nbxmpp.NS_CARBONS, protocol=True)
@ -75,3 +103,7 @@ def parse_carbon(con, stanza):
raise nbxmpp.NodeProcessed
return message, sent, True
def get_instance(*args, **kwargs):
return Carbons(*args, **kwargs), 'Carbons'

View File

@ -0,0 +1,295 @@
# This file is part of Gajim.
#
# Gajim is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published
# by the Free Software Foundation; version 3 only.
#
# Gajim is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
# XEP-0030: Service Discovery
import logging
import weakref
import nbxmpp
from gajim.common import app
from gajim.common import helpers
from gajim.common.caps_cache import muc_caps_cache
from gajim.common.nec import NetworkIncomingEvent
from gajim.common.connection_handlers_events import InformationEvent
log = logging.getLogger('gajim.c.m.discovery')
class Discovery:
def __init__(self, con):
self._con = con
self._account = con.name
self.handlers = [
('iq', self._answer_disco_info, 'get', nbxmpp.NS_DISCO_INFO),
('iq', self._answer_disco_items, 'get', nbxmpp.NS_DISCO_ITEMS),
]
def disco_contact(self, jid, node=None):
success_cb = self._con._nec_agent_info_received_caps
self._disco(nbxmpp.NS_DISCO_INFO, jid, node, success_cb, None)
def disco_items(self, jid, node=None, success_cb=None, error_cb=None):
self._disco(nbxmpp.NS_DISCO_ITEMS, jid, node, success_cb, error_cb)
def disco_info(self, jid, node=None, success_cb=None, error_cb=None):
self._disco(nbxmpp.NS_DISCO_INFO, jid, node, success_cb, error_cb)
def _disco(self, namespace, jid, node, success_cb, error_cb):
if success_cb is None:
raise ValueError('success_cb is required')
if not app.account_is_connected(self._account):
return
iq = nbxmpp.Iq(typ='get', to=jid, queryNS=namespace)
if node:
iq.setQuerynode(node)
log_str = 'Request info: %s %s'
if namespace == nbxmpp.NS_DISCO_ITEMS:
log_str = 'Request items: %s %s'
log.info(log_str, jid, node or '')
# Create weak references so we can pass GUI object methods
weak_success_cb = weakref.WeakMethod(success_cb)
if error_cb is not None:
weak_error_cb = weakref.WeakMethod(error_cb)
else:
weak_error_cb = None
self._con.connection.SendAndCallForResponse(
iq, self._disco_response, {'success_cb': weak_success_cb,
'error_cb': weak_error_cb})
def _disco_response(self, conn, stanza, success_cb, error_cb):
if not nbxmpp.isResultNode(stanza):
if error_cb is not None:
error_cb()(stanza.getFrom(), stanza.getError())
else:
log.info('Error: %s', stanza.getError())
return
from_ = stanza.getFrom()
node = stanza.getQuerynode()
if stanza.getQueryNS() == nbxmpp.NS_DISCO_INFO:
identities, features, data, node = self.parse_info_response(stanza)
success_cb()(from_, identities, features, data, node)
elif stanza.getQueryNS() == nbxmpp.NS_DISCO_ITEMS:
items = self.parse_items_response(stanza)
success_cb()(from_, node, items)
else:
log.warning('Wrong query namespace: %s', stanza)
@classmethod
def parse_items_response(cls, stanza):
payload = stanza.getQueryPayload()
items = []
for item in payload:
# CDATA payload is not processed, only nodes
if not isinstance(item, nbxmpp.simplexml.Node):
continue
attr = item.getAttrs()
if 'jid' not in attr:
log.warning('No jid attr in disco items: %s', stanza)
continue
try:
attr['jid'] = helpers.parse_jid(attr['jid'])
except helpers.InvalidFormat:
log.warning('Invalid jid attr in disco items: %s', stanza)
continue
items.append(attr)
return items
@classmethod
def parse_info_response(cls, stanza):
identities, features, data, node = [], [], [], None
q = stanza.getTag('query')
node = q.getAttr('node')
if not node:
node = ''
qc = stanza.getQueryChildren()
if not qc:
qc = []
for i in qc:
if i.getName() == 'identity':
attr = {}
for key in i.getAttrs().keys():
attr[key] = i.getAttr(key)
identities.append(attr)
elif i.getName() == 'feature':
var = i.getAttr('var')
if var:
features.append(var)
elif i.getName() == 'x' and i.getNamespace() == nbxmpp.NS_DATA:
data.append(nbxmpp.DataForm(node=i))
return identities, features, data, node
def discover_server_items(self):
server = self._con.get_own_jid().getDomain()
self.disco_items(server, success_cb=self._server_items_received)
def _server_items_received(self, from_, node, items):
log.info('Server items received')
for item in items:
self.disco_info(item['jid'],
success_cb=self._server_items_info_received)
def _server_items_info_received(self, from_, *args):
from_ = from_.getStripped()
log.info('Server item info received: %s', from_)
self._parse_transports(from_, *args)
try:
self._con.get_module('MUC').pass_disco(from_, *args)
self._con.get_module('HTTPUpload').pass_disco(from_, *args)
self._con.pass_bytestream_disco(from_, *args)
except nbxmpp.NodeProcessed:
pass
app.nec.push_incoming_event(
NetworkIncomingEvent('server-disco-received'))
def discover_account_info(self):
own_jid = self._con.get_own_jid().getStripped()
self.disco_info(own_jid, success_cb=self._account_info_received)
def _account_info_received(self, from_, *args):
from_ = from_.getStripped()
log.info('Account info received: %s', from_)
self._con.get_module('MAM').pass_disco(from_, *args)
self._con.get_module('PEP').pass_disco(from_, *args)
self._con.get_module('PubSub').pass_disco(from_, *args)
def discover_server_info(self):
server = self._con.get_own_jid().getDomain()
self.disco_info(server, success_cb=self._server_info_received)
def _server_info_received(self, from_, *args):
log.info('Server info received: %s', from_)
self._con.get_module('SecLabels').pass_disco(from_, *args)
self._con.get_module('Blocking').pass_disco(from_, *args)
self._con.get_module('VCardTemp').pass_disco(from_, *args)
self._con.get_module('Carbons').pass_disco(from_, *args)
self._con.get_module('PrivacyLists').pass_disco(from_, *args)
identities, features, data, node = args
if nbxmpp.NS_REGISTER in features:
self._con.register_supported = True
if nbxmpp.NS_ADDRESS in features:
self._con.addressing_supported = True
self._con._continue_connection_request_privacy()
def _parse_transports(self, from_, identities, features, data, node):
for identity in identities:
category = identity.get('category')
if category not in ('gateway', 'headline'):
continue
transport_type = identity.get('type')
log.info('Found transport: %s %s %s',
from_, category, transport_type)
jid = str(from_)
if jid not in app.transport_type:
app.transport_type[jid] = transport_type
app.logger.save_transport_type(jid, transport_type)
if transport_type in self._con.available_transports:
self._con.available_transports[transport_type].append(jid)
else:
self._con.available_transports[transport_type] = [jid]
def _answer_disco_items(self, con, stanza):
from_ = stanza.getFrom()
log.info('Answer disco items to %s', from_)
if self._con.get_module('AdHocCommands').command_items_query(stanza):
raise nbxmpp.NodeProcessed
node = stanza.getTagAttr('query', 'node')
if node is None:
result = stanza.buildReply('result')
self._con.connection.send(result)
raise nbxmpp.NodeProcessed
if node == nbxmpp.NS_COMMANDS:
self._con.get_module('AdHocCommands').command_list_query(stanza)
raise nbxmpp.NodeProcessed
def _answer_disco_info(self, con, stanza):
from_ = stanza.getFrom()
log.info('Answer disco info %s', from_)
if str(from_).startswith('echo.'):
# Service that echos all stanzas, ignore it
raise nbxmpp.NodeProcessed
if self._con.get_module('AdHocCommands').command_info_query(stanza):
raise nbxmpp.NodeProcessed
node = stanza.getQuerynode()
iq = stanza.buildReply('result')
query = iq.setQuery()
if node:
query.setAttr('node', node)
query.addChild('identity', attrs=app.gajim_identity)
client_version = 'http://gajim.org#' + app.caps_hash[self._account]
if node in (None, client_version):
for f in app.gajim_common_features:
query.addChild('feature', attrs={'var': f})
for f in app.gajim_optional_features[self._account]:
query.addChild('feature', attrs={'var': f})
self._con.connection.send(iq)
raise nbxmpp.NodeProcessed
def disco_muc(self, jid, callback, update=False):
if not app.account_is_connected(self._account):
return
if muc_caps_cache.is_cached(jid) and not update:
callback()
return
iq = nbxmpp.Iq(typ='get', to=jid, queryNS=nbxmpp.NS_DISCO_INFO)
log.info('Request MUC info %s', jid)
self._con.connection.SendAndCallForResponse(
iq, self._muc_info_response, {'callback': callback})
def _muc_info_response(self, conn, stanza, callback):
if not nbxmpp.isResultNode(stanza):
error = stanza.getError()
if error == 'item-not-found':
# Groupchat does not exist
log.info('MUC does not exist: %s', stanza.getFrom())
callback()
else:
log.info('MUC disco error: %s', error)
app.nec.push_incoming_event(
InformationEvent(
None, dialog_name='unable-join-groupchat', args=error))
return
log.info('MUC info received: %s', stanza.getFrom())
muc_caps_cache.append(stanza)
callback()
def get_instance(*args, **kwargs):
return Discovery(*args, **kwargs), 'Discovery'

View File

@ -59,9 +59,6 @@ class HTTPUpload:
self._allowed_headers = ['Authorization', 'Cookie', 'Expires']
self.max_file_size = None # maximum file size in bytes
app.ged.register_event_handler('agent-info-received',
ged.GUI1,
self.handle_agent_info_received)
app.ged.register_event_handler('stanza-message-outgoing',
ged.OUT_PREGUI,
self.handle_outgoing_stanza)
@ -72,9 +69,6 @@ class HTTPUpload:
self.messages = []
def cleanup(self):
app.ged.remove_event_handler('agent-info-received',
ged.GUI1,
self.handle_agent_info_received)
app.ged.remove_event_handler('stanza-message-outgoing',
ged.OUT_PREGUI,
self.handle_outgoing_stanza)
@ -82,27 +76,18 @@ class HTTPUpload:
ged.OUT_PREGUI,
self.handle_outgoing_stanza)
def handle_agent_info_received(self, event):
account = event.conn.name
if account != self._account:
return
if not app.jid_is_transport(event.jid):
return
if not event.id_.startswith('Gajim_'):
return
if NS_HTTPUPLOAD_0 in event.features:
def pass_disco(self, from_, identities, features, data, node):
if NS_HTTPUPLOAD_0 in features:
self.httpupload_namespace = NS_HTTPUPLOAD_0
elif NS_HTTPUPLOAD in event.features:
elif NS_HTTPUPLOAD in features:
self.httpupload_namespace = NS_HTTPUPLOAD
else:
return
self.component = event.jid
self.component = from_
log.info('Discovered component: %s', from_)
for form in event.data:
for form in data:
form_dict = form.asDict()
if form_dict.get('FORM_TYPE', None) != self.httpupload_namespace:
continue
@ -112,14 +97,16 @@ class HTTPUpload:
break
if self.max_file_size is None:
log.warning('%s does not provide maximum file size', account)
log.warning('%s does not provide maximum file size', self._account)
else:
log.info('%s has a maximum file size of: %s MiB',
account, self.max_file_size / (1024 * 1024))
self._account, self.max_file_size / (1024 * 1024))
self.available = True
for ctrl in app.interface.msg_win_mgr.get_controls(acct=self._account):
ctrl.update_actions()
raise nbxmpp.NodeProcessed
def handle_outgoing_stanza(self, event):
if event.conn.name != self._account:

View File

@ -47,6 +47,20 @@ class MAM:
self.archiving_namespace = None
self._mam_query_ids = {}
def pass_disco(self, from_, identities, features, data, node):
if nbxmpp.NS_MAM_2 in features:
self.archiving_namespace = nbxmpp.NS_MAM_2
elif nbxmpp.NS_MAM_1 in features:
self.archiving_namespace = nbxmpp.NS_MAM_1
else:
return
self.available = True
log.info('Discovered MAM %s: %s', self.archiving_namespace, from_)
# TODO: Move this GUI code out
action = app.app.lookup_action('%s-archive' % self._account)
action.set_enabled(True)
def _from_valid_archive(self, stanza, message, groupchat):
if groupchat:
expected_archive = message.getFrom()

View File

@ -38,6 +38,18 @@ class MUC:
('message', self._direct_invite, '', nbxmpp.NS_CONFERENCE),
]
def pass_disco(self, from_, identities, features, data, node):
for identity in identities:
if identity.get('category') != 'conference':
continue
if identity.get('type') != 'text':
continue
if nbxmpp.NS_MUC in features:
log.info('Discovered MUC: %s', from_)
# TODO: make this nicer
self._con.muc_jid['jabber'] = from_
raise nbxmpp.NodeProcessed
def set_subject(self, room_jid, subject):
if not app.account_is_connected(self._account):
return

View File

@ -36,9 +36,17 @@ class PEP:
'headline', nbxmpp.NS_PUBSUB_EVENT)
]
self.supported = False
self._pep_handlers = {}
self._store_publish_modules = []
def pass_disco(self, from_, identities, features, data, node):
for identity in identities:
if identity['category'] == 'pubsub':
if identity.get('type') == 'pep':
log.info('Discovered PEP support: %s', from_)
self.supported = True
def register_pep_handler(self, namespace, notify_handler, retract_handler):
if namespace in self._pep_handlers:
self._pep_handlers[namespace].append(
@ -165,7 +173,7 @@ class AbstractPEPModule:
self._stored_publish = None
def send(self, data):
if not self._con.pep_supported:
if not self._con.get_module('PEP').supported:
return
if self._con.connected == 1:
@ -184,7 +192,7 @@ class AbstractPEPModule:
'', self.namespace, item, 'current')
def retract(self):
if not self._con.pep_supported:
if not self._con.get_module('PEP').supported:
return
self.send(None)
self._con.get_module('PubSub').send_pb_retract(

View File

@ -43,6 +43,18 @@ class PrivacyLists:
('iq', self._list_push_received, 'set', nbxmpp.NS_PRIVACY)
]
self.supported = False
def pass_disco(self, from_, identities, features, data, node):
if nbxmpp.NS_PRIVACY not in features:
return
self.supported = True
log.info('Discovered XEP-0016: Privacy Lists: %s', from_)
# TODO: Move this GUI code out
action = app.app.lookup_action('%s-privacylists' % self._account)
action.set_enabled(True)
def _list_push_received(self, con, stanza):
result = stanza.buildReply('result')
result.delChild(result.getTag('query'))
@ -287,7 +299,7 @@ class PrivacyLists:
self.set_default_list(self.default_list)
def block_contacts(self, contact_list, message):
if not self._con.privacy_rules_supported:
if not self.supported:
self._con.get_module('Blocking').block(contact_list)
return
@ -332,7 +344,7 @@ class PrivacyLists:
self.set_privacy_list(self.default_list, new_blocked_list)
def unblock_contacts(self, contact_list):
if not self._con.privacy_rules_supported:
if not self.supported:
self._con.get_module('Blocking').unblock(contact_list)
return
@ -367,7 +379,7 @@ class PrivacyLists:
self._presence_probe(contact.jid)
def block_group(self, group, contact_list, message):
if not self._con.privacy_rules_supported:
if not self.supported:
return
if group in self.blocked_groups:
return
@ -393,7 +405,7 @@ class PrivacyLists:
self.set_default_list(self.default_list)
def unblock_group(self, group, contact_list):
if not self._con.privacy_rules_supported:
if not self.supported:
return
if group not in self.blocked_groups:

View File

@ -38,6 +38,16 @@ class PubSub:
self.handlers = []
self.publish_options = False
def pass_disco(self, from_, identities, features, data, node):
if nbxmpp.NS_PUBSUB_PUBLISH_OPTIONS not in features:
# Remove stored bookmarks accessible to everyone.
self._con.get_module('Bookmarks').purge_pubsub_bookmarks()
return
log.info('Discovered Pubsub publish options: %s', from_)
self.publish_options = True
def send_pb_subscription_query(self, jid, cb, **kwargs):
if not app.account_is_connected(self._account):
return

View File

@ -34,6 +34,13 @@ class SecLabels:
self._catalogs = {}
self.supported = False
def pass_disco(self, from_, identities, features, data, node):
if nbxmpp.NS_SECLABEL not in features:
return
self.supported = True
log.info('Discovered security labels: %s', from_)
def request_catalog(self, jid):
server = app.get_jid_from_account(self._account).split("@")[1]
iq = nbxmpp.Iq(typ='get', to=server)

View File

@ -41,6 +41,17 @@ class VCardTemp:
self._own_vcard = None
self.own_vcard_received = False
self.room_jids = []
self.supported = False
def pass_disco(self, from_, identities, features, data, node):
if nbxmpp.NS_VCARD not in features:
return
self.supported = True
log.info('Discovered vcard-temp: %s', from_)
# TODO: Move this GUI code out
action = app.app.lookup_action('%s-profile' % self._account)
action.set_enabled(True)
def _node_to_dict(self, node):
dict_ = {}
@ -67,6 +78,8 @@ class VCardTemp:
if isinstance(callback, RequestAvatar):
if callback == RequestAvatar.SELF:
if not self.supported:
return
callback = self._on_own_avatar_received
elif callback == RequestAvatar.ROOM:
callback = self._on_room_avatar_received

View File

@ -82,6 +82,19 @@ class ConnectionBytestream:
app.ged.register_event_handler('file-request-received', ged.GUI1,
self._nec_file_request_received)
def pass_bytestream_disco(self, from_, identities, features, data, node):
if nbxmpp.NS_BYTESTREAM not in features:
return
if app.config.get_per('accounts', self.name, 'use_ft_proxies'):
log.info('Discovered proxy: %s', from_)
our_fjid = self.get_own_jid()
testit = app.config.get_per(
'accounts', self.name, 'test_ft_proxies_on_startup')
app.proxy65_manager.resolve(
from_, self.connection, str(our_fjid),
default=self.name, testit=testit)
raise nbxmpp.NodeProcessed
def cleanup(self):
app.ged.remove_event_handler('file-request-received', ged.GUI1,
self._nec_file_request_received)

View File

@ -41,14 +41,10 @@ class ConnectionCaps(object):
app.nec.register_incoming_event(CapsReceivedEvent)
app.ged.register_event_handler('caps-presence-received', ged.GUI1,
self._nec_caps_presence_received)
app.ged.register_event_handler('agent-info-received', ged.GUI1,
self._nec_agent_info_received_caps)
def cleanup(self):
app.ged.remove_event_handler('caps-presence-received', ged.GUI1,
self._nec_caps_presence_received)
app.ged.remove_event_handler('agent-info-received', ged.GUI1,
self._nec_agent_info_received_caps)
def caps_change_account_name(self, new_name):
self._account = new_name
@ -81,16 +77,19 @@ class ConnectionCaps(object):
contact = app.contacts.get_gc_contact(self._account, room_jid, nick)
return contact
def _nec_agent_info_received_caps(self, obj):
def _nec_agent_info_received_caps(self, from_, identities, features,
data, node):
"""
callback to update our caps cache with queried information after
we have retrieved an unknown caps hash and issued a disco
"""
if obj.conn.name != self._account:
return
contact = self._get_contact_or_gc_contact_for_jid(obj.fjid)
fjid = str(from_)
bare_jid = from_.getStripped()
resource = from_.getResource()
contact = self._get_contact_or_gc_contact_for_jid(fjid)
if not contact:
log.info('Received Disco from unknown contact %s' % obj.fjid)
log.info('Received Disco from unknown contact %s' % fjid)
return
lookup = contact.client_caps.get_cache_lookup_strategy()
@ -102,17 +101,21 @@ class ConnectionCaps(object):
return
else:
validate = contact.client_caps.get_hash_validation_strategy()
hash_is_valid = validate(obj.identities, obj.features, obj.data)
hash_is_valid = validate(identities, features, data)
if hash_is_valid:
cache_item.set_and_store(obj.identities, obj.features)
cache_item.set_and_store(identities, features)
else:
node = caps_hash = hash_method = None
contact.client_caps = self._create_suitable_client_caps(
obj.node, caps_hash, hash_method)
log.info('Computed and retrieved caps hash differ.' +
node, caps_hash, hash_method)
log.info('Computed and retrieved caps hash differ.'
'Ignoring caps of contact %s' % contact.get_full_jid())
app.nec.push_incoming_event(CapsDiscoReceivedEvent(None,
conn=self, fjid=obj.fjid, jid=obj.jid, resource=obj.resource,
app.nec.push_incoming_event(
CapsDiscoReceivedEvent(None,
conn=self,
fjid=fjid,
jid=bare_jid,
resource=resource,
client_caps=contact.client_caps))

View File

@ -329,8 +329,6 @@ class P2PClient(IdleObject):
nbxmpp.NS_BYTESTREAM)
self.RegisterHandler('iq', self._caller._bytestreamErrorCB, 'error',
nbxmpp.NS_BYTESTREAM)
self.RegisterHandler('iq', self._caller._DiscoverItemsGetCB, 'get',
nbxmpp.NS_DISCO_ITEMS)
self.RegisterHandler('iq', self._caller._JingleCB, 'result')
self.RegisterHandler('iq', self._caller._JingleCB, 'error')
self.RegisterHandler('iq', self._caller._JingleCB, 'set',

View File

@ -191,20 +191,3 @@ connection_handlers.ConnectionJingle):
# serverside metacontacts are not supported with zeroconf
# (there is no server)
pass
def _DiscoverItemsGetCB(self, con, iq_obj):
log.debug('DiscoverItemsGetCB')
if not self.connection or self.connected < 2:
return
if self.get_module('AdHocCommands').command_items_query(iq_obj):
raise nbxmpp.NodeProcessed
node = iq_obj.getTagAttr('query', 'node')
if node is None:
result = iq_obj.buildReply('result')
self.connection.send(result)
raise nbxmpp.NodeProcessed
if node == nbxmpp.NS_COMMANDS:
self.get_module('AdHocCommands').command_list_query(iq_obj)
raise nbxmpp.NodeProcessed

View File

@ -2845,6 +2845,7 @@ class ManagePEPServicesWindow:
self.xml.get_object('delete_button').set_sensitive(False)
self.xml.connect_signals(self)
self.account = account
self._con = app.connections[self.account]
self.init_services()
self.xml.get_object('services_treeview').get_selection().connect(
@ -2852,8 +2853,6 @@ class ManagePEPServicesWindow:
app.ged.register_event_handler('pubsub-config-received', ged.GUI1,
self._nec_pep_config_received)
app.ged.register_event_handler('agent-items-received', ged.GUI1,
self._nec_agent_items_received)
self.window.show_all()
@ -2862,8 +2861,6 @@ class ManagePEPServicesWindow:
del app.interface.instances[self.account]['pep_services']
app.ged.remove_event_handler('pubsub-config-received', ged.GUI1,
self._nec_pep_config_received)
app.ged.remove_event_handler('agent-items-received', ged.GUI1,
self._nec_agent_items_received)
def on_close_button_clicked(self, widget):
self.window.destroy()
@ -2885,15 +2882,19 @@ class ManagePEPServicesWindow:
col.pack_start(cellrenderer_text, True)
col.add_attribute(cellrenderer_text, 'text', 0)
our_jid = app.get_jid_from_account(self.account)
app.connections[self.account].discoverItems(our_jid)
jid = self._con.get_own_jid().getStripped()
self._con.get_module('Discovery').disco_items(
jid, success_cb=self._items_received, error_cb=self._items_error)
def _nec_agent_items_received(self, obj):
our_jid = app.get_jid_from_account(self.account)
for item in obj.items:
if 'jid' in item and item['jid'] == our_jid and 'node' in item:
def _items_received(self, from_, node, items):
jid = self._con.get_own_jid().getStripped()
for item in items:
if item['jid'] == jid and 'node' in item:
self.treestore.append([item['node']])
def _items_error(self, from_, error):
ErrorDialog('Error', error)
def node_removed(self, jid, node):
if jid != app.get_jid_from_account(self.account):
return

View File

@ -258,24 +258,6 @@ class ServicesCache:
self._info = CacheDictionary(0, getrefresh = False)
self._subscriptions = CacheDictionary(5, getrefresh=False)
self._cbs = {}
app.ged.register_event_handler('agent-items-received', ged.GUI1,
self._nec_agent_items_received)
app.ged.register_event_handler('agent-items-error-received', ged.GUI1,
self._nec_agent_items_error_received)
app.ged.register_event_handler('agent-info-received', ged.GUI1,
self._nec_agent_info_received)
app.ged.register_event_handler('agent-info-error-received', ged.GUI1,
self._nec_agent_info_error_received)
def __del__(self):
app.ged.remove_event_handler('agent-items-received', ged.GUI1,
self._nec_agent_items_received)
app.ged.remove_event_handler('agent-items-error-received', ged.GUI1,
self._nec_agent_items_error_received)
app.ged.remove_event_handler('agent-info-received', ged.GUI1,
self._nec_agent_info_received)
app.ged.remove_event_handler('agent-info-error-received', ged.GUI1,
self._nec_agent_info_error_received)
def cleanup(self):
self._items.cleanup()
@ -391,7 +373,9 @@ class ServicesCache:
self._cbs[cbkey].append(cb)
else:
self._cbs[cbkey] = [cb]
app.connections[self.account].discoverInfo(jid, node)
con = app.connections[self.account]
con.get_module('Discovery').disco_info(
jid, node, self._disco_info_received, self._disco_info_error)
def get_items(self, jid, node, cb, force=False, nofetch=False, args=()):
"""
@ -415,18 +399,32 @@ class ServicesCache:
self._cbs[cbkey].append(cb)
else:
self._cbs[cbkey] = [cb]
app.connections[self.account].discoverItems(jid, node)
con = app.connections[self.account]
con.get_module('Discovery').disco_items(
jid, node, self._disco_items_received, self._disco_items_error)
def _nec_agent_info_received(self, obj):
def _disco_info_received(self, from_, identities, features, data, node):
"""
Callback for when we receive an agent's info
array is (agent, node, identities, features, data)
"""
# We receive events from all accounts from GED
if obj.conn.name != self.account:
return
self._on_agent_info(obj.fjid, obj.node, obj.identities, obj.features,
obj.data)
self._on_agent_info(str(from_), node, identities, features, data)
def _disco_info_error(self, from_, error):
"""
Callback for when a query fails. Even after the browse and agents
namespaces
"""
addr = get_agent_address(from_)
# Call callbacks
cbkey = ('info', addr)
if cbkey in self._cbs:
for cb in self._cbs[cbkey]:
cb(str(from_), '', 0, 0, 0)
# clean_closure may have beaten us to it
if cbkey in self._cbs:
del self._cbs[cbkey]
def _on_agent_info(self, fjid, node, identities, features, data):
addr = get_agent_address(fjid, node)
@ -443,67 +441,42 @@ class ServicesCache:
if cbkey in self._cbs:
del self._cbs[cbkey]
def _nec_agent_items_received(self, obj):
def _disco_items_received(self, from_, node, items):
"""
Callback for when we receive an agent's items
array is (agent, node, items)
"""
# We receive events from all accounts from GED
if obj.conn.name != self.account:
return
addr = get_agent_address(obj.fjid, obj.node)
addr = get_agent_address(from_, node)
# Store in cache
self._items[addr] = obj.items
self._items[addr] = items
# Call callbacks
cbkey = ('items', addr)
if cbkey in self._cbs:
for cb in self._cbs[cbkey]:
cb(obj.fjid, obj.node, obj.items)
cb(str(from_), node, items)
# clean_closure may have beaten us to it
if cbkey in self._cbs:
del self._cbs[cbkey]
def _nec_agent_info_error_received(self, obj):
def _disco_items_error(self, from_, error):
"""
Callback for when a query fails. Even after the browse and agents
namespaces
"""
# We receive events from all accounts from GED
if obj.conn.name != self.account:
return
addr = get_agent_address(obj.fjid)
# Call callbacks
cbkey = ('info', addr)
if cbkey in self._cbs:
for cb in self._cbs[cbkey]:
cb(obj.fjid, '', 0, 0, 0)
# clean_closure may have beaten us to it
if cbkey in self._cbs:
del self._cbs[cbkey]
def _nec_agent_items_error_received(self, obj):
"""
Callback for when a query fails. Even after the browse and agents
namespaces
"""
# We receive events from all accounts from GED
if obj.conn.name != self.account:
return
addr = get_agent_address(obj.fjid)
addr = get_agent_address(from_)
# Call callbacks
cbkey = ('items', addr)
if cbkey in self._cbs:
for cb in self._cbs[cbkey]:
cb(obj.fjid, '', 0)
cb(str(from_), '', 0)
# clean_closure may have beaten us to it
if cbkey in self._cbs:
del self._cbs[cbkey]
# object is needed so that @property works
class ServiceDiscoveryWindow(object):
"""

View File

@ -1533,7 +1533,7 @@ class GroupchatControl(ChatControlBase):
if '104' in obj.status_code:
changes.append(_('A setting not related to privacy has been '
'changed'))
app.connections[self.account].discoverMUC(
app.connections[self.account].get_module('Discovery').disco_muc(
self.room_jid, self.update_actions, update=True)
if '170' in obj.status_code:
# Can be a presence (see chg_contact_status in groupchat_control.py)
@ -2693,7 +2693,7 @@ class GroupchatControl(ChatControlBase):
item = xml.get_object('block_menuitem')
item2 = xml.get_object('unblock_menuitem')
if not app.connections[self.account].privacy_rules_supported:
if not app.connections[self.account].get_module('PrivacyLists').supported:
item2.set_no_show_all(True)
item.set_no_show_all(True)
item.hide()

View File

@ -80,13 +80,6 @@ class JoinGroupchatWindow(Gtk.ApplicationWindow):
self.builder.connect_signals(self)
self.connect('key-press-event', self._on_key_press_event)
self.connect('destroy', self._on_destroy)
if not self.minimal_mode:
app.ged.register_event_handler('agent-info-received', ged.GUI1,
self._nec_agent_info_received)
app.ged.register_event_handler('agent-info-error-received', ged.GUI1,
self._nec_agent_info_error_received)
# Hide account combobox if there is only one account
if len(accounts) == 1:
@ -277,48 +270,36 @@ class JoinGroupchatWindow(Gtk.ApplicationWindow):
con.get_module('Bookmarks').add_bookmark(
name, self.room_jid, autojoin, 1, password, nickname)
def _on_destroy(self, *args):
if not self.minimal_mode:
app.ged.remove_event_handler('agent-info-received', ged.GUI1,
self._nec_agent_info_received)
app.ged.remove_event_handler('agent-info-error-received', ged.GUI1,
self._nec_agent_info_error_received)
def _on_search_clicked(self, widget):
server = self.server_combo.get_active_text().strip()
self.requested_jid = server
app.connections[self.account].discoverInfo(server)
con = app.connections[self.account]
con.get_module('Discovery').disco_info(
server,
success_cb=self._disco_info_received,
error_cb=self._disco_info_error)
def _nec_agent_info_error_received(self, obj):
if obj.conn.name != self.account:
return
if obj.jid != self.requested_jid:
return
self.requested_jid = None
def _disco_info_error(self, from_, error):
ErrorDialog(_('Wrong server'),
_('%s is not a groupchat server') % obj.jid,
_('%s is not a groupchat server') % from_,
transient_for=self)
def _nec_agent_info_received(self, obj):
if obj.conn.name != self.account:
return
if obj.jid != self.requested_jid:
return
self.requested_jid = None
if nbxmpp.NS_MUC not in obj.features:
def _disco_info_received(self, from_, identities, features, data, node):
if nbxmpp.NS_MUC not in features:
ErrorDialog(_('Wrong server'),
_('%s is not a groupchat server') % obj.jid,
_('%s is not a groupchat server') % from_,
transient_for=self)
return
if obj.jid in app.interface.instances[self.account]['disco']:
app.interface.instances[self.account]['disco'][obj.jid].window.\
jid = str(from_)
if jid in app.interface.instances[self.account]['disco']:
app.interface.instances[self.account]['disco'][jid].window.\
present()
else:
try:
# Object will add itself to the window dict
from gajim.disco import ServiceDiscoveryWindow
ServiceDiscoveryWindow(
self.account, obj.jid,
self.account, jid,
initial_identities=[{'category': 'conference',
'type': 'text'}])
except GajimGeneralException:

View File

@ -61,9 +61,9 @@ class ServerInfoDialog(Gtk.Dialog):
ged.CORE,
self._nec_version_result_received)
app.ged.register_event_handler('agent-info-received',
app.ged.register_event_handler('server-disco-received',
ged.GUI1,
self._nec_agent_info_received)
self._server_disco_received)
self.version = ''
self.uptime = ''
@ -132,9 +132,7 @@ class ServerInfoDialog(Gtk.Dialog):
self.version = obj.client_info
self.update(self.get_infos, self.info_listbox)
def _nec_agent_info_received(self, obj):
if 'Gajim_' not in obj.id_:
return
def _server_disco_received(self, obj):
self.update(self.get_features, self.feature_listbox)
def add_feature(self, feature):
@ -154,22 +152,25 @@ class ServerInfoDialog(Gtk.Dialog):
return [
Feature('XEP-0016: Privacy Lists',
con.privacy_rules_supported, '', None),
con.get_module('PrivacyLists').supported, '', None),
Feature('XEP-0045: Multi-User Chat', con.muc_jid, '', None),
Feature('XEP-0054: vcard-temp', con.vcard_supported, '', None),
Feature('XEP-0054: vcard-temp',
con.get_module('VCardTemp').supported, '', None),
Feature('XEP-0163: Personal Eventing Protocol',
con.pep_supported, '', None),
con.get_module('PEP').supported, '', None),
Feature('XEP-0163: #publish-options',
con.pubsub_publish_options_supported, '', None),
con.get_module('PubSub').publish_options, '', None),
Feature('XEP-0191: Blocking Command',
con.blocking_supported, nbxmpp.NS_BLOCKING, None),
con.get_module('Blocking').supported,
nbxmpp.NS_BLOCKING, None),
Feature('XEP-0198: Stream Management',
con.sm.enabled, nbxmpp.NS_STREAM_MGMT, None),
Feature('XEP-0258: Security Labels in XMPP',
con.get_module('SecLabels').supported,
nbxmpp.NS_SECLABEL, None),
Feature('XEP-0280: Message Carbons',
con.carbons_available, nbxmpp.NS_CARBONS, carbons_enabled),
con.get_module('Carbons').supported,
nbxmpp.NS_CARBONS, carbons_enabled),
Feature('XEP-0313: Message Archive Management',
con.get_module('MAM').archiving_namespace,
con.get_module('MAM').archiving_namespace,
@ -198,9 +199,9 @@ class ServerInfoDialog(Gtk.Dialog):
ged.CORE,
self._nec_version_result_received)
app.ged.remove_event_handler('agent-info-received',
app.ged.remove_event_handler('server-disco-received',
ged.GUI1,
self._nec_agent_info_received)
self._server_disco_received)
class FeatureItem(Gtk.Grid):

View File

@ -1116,6 +1116,8 @@ class Interface:
app.block_signed_in_notifications[account] = True
connected = obj.conn.connected
pep_supported = obj.conn.get_module('PEP').supported
if not idle.Monitor.is_unknown() and connected in (2, 3):
# we go online or free for chat, so we activate auto status
app.sleeper_state[account] = 'online'
@ -1135,11 +1137,11 @@ class Interface:
if connected == invisible_show:
return
# send currently played music
if (obj.conn.pep_supported and sys.platform == 'linux' and
if (pep_supported and sys.platform == 'linux' and
app.config.get_per('accounts', account, 'publish_tune')):
self.enable_music_listener()
# enable location listener
if (obj.conn.pep_supported and app.is_installed('GEOCLUE') and
if (pep_supported and app.is_installed('GEOCLUE') and
app.config.get_per('accounts', account, 'publish_location')):
location_listener.enable()
@ -1785,7 +1787,7 @@ class Interface:
transient_for=transient_for)
disco_account = connected_accounts[0] if account is None else account
app.connections[disco_account].discoverMUC(
app.connections[disco_account].get_module('Discovery').disco_muc(
room_jid, _on_discover_result)
################################################################################
@ -2187,7 +2189,7 @@ class Interface:
for acct in accounts:
if not app.account_is_connected(acct):
continue
if not app.connections[acct].pep_supported:
if not app.connections[acct].get_module('PEP').supported:
continue
if not app.config.get_per('accounts', acct, 'publish_tune'):
continue

View File

@ -460,8 +460,10 @@ control=None, gc_contact=None, is_anonymous=True):
execute_command_menuitem, send_custom_status_menuitem):
widget.set_sensitive(False)
if app.connections[account] and (app.connections[account].\
privacy_rules_supported or app.connections[account].blocking_supported):
con = app.connections[account]
if con and (con.get_module('PrivacyLists').supported or
con.get_module('Blocking').supported):
if helpers.jid_is_blocked(account, jid):
block_menuitem.set_no_show_all(True)
block_menuitem.hide()

View File

@ -2840,9 +2840,14 @@ class RosterWindow:
if msg is None:
# user pressed Cancel to change status message dialog
return
accounts = set(i[1] for i in list_ if (app.connections[i[1]].\
privacy_rules_supported or (group is None and app.\
connections[i[1]].blocking_supported)))
accounts = []
for _, account in list_:
con = app.connections[account]
if con.get_module('PrivacyLists').supported or (
group is None and con.get_module('Blocking').supported):
accounts.append(account)
if group is None:
for acct in accounts:
l_ = [i[0] for i in list_ if i[1] == acct]
@ -2882,9 +2887,13 @@ class RosterWindow:
"""
When clicked on the 'unblock' button in context menu.
"""
accounts = set(i[1] for i in list_ if (app.connections[i[1]].\
privacy_rules_supported or (group is None and app.\
connections[i[1]].blocking_supported)))
accounts = []
for _, account in list_:
con = app.connections[account]
if con.get_module('PrivacyLists').supported or (
group is None and con.get_module('Blocking').supported):
accounts.append(account)
if group is None:
for acct in accounts:
l_ = [i[0] for i in list_ if i[1] == acct]
@ -4916,7 +4925,7 @@ class RosterWindow:
sub_menu.append(item)
con = app.connections[account]
if show == 'invisible' and con.connected > 1 and \
not con.privacy_rules_supported:
not con.get_module('PrivacyLists').supported:
item.set_sensitive(False)
else:
item.connect('activate', self.change_status, account, show)
@ -4940,7 +4949,7 @@ class RosterWindow:
item.connect('activate', self.change_status, account, 'offline')
pep_menuitem = xml.get_object('pep_menuitem')
if app.connections[account].pep_supported:
if app.connections[account].get_module('PEP').supported:
pep_submenu = Gtk.Menu()
pep_menuitem.set_submenu(pep_submenu)
@ -5183,8 +5192,7 @@ class RosterWindow:
if helpers.group_is_blocked(account, group):
is_blocked = True
if is_blocked and app.connections[account].\
privacy_rules_supported:
if is_blocked and app.connections[account].get_module('PrivacyLists').supported:
unblock_menuitem = Gtk.MenuItem.new_with_mnemonic(_('_Unblock'))
unblock_menuitem.connect('activate', self.on_unblock, list_,
group)
@ -5193,7 +5201,7 @@ class RosterWindow:
block_menuitem = Gtk.MenuItem.new_with_mnemonic(_('_Block'))
block_menuitem.connect('activate', self.on_block, list_, group)
menu.append(block_menuitem)
if not app.connections[account].privacy_rules_supported:
if not app.connections[account].get_module('PrivacyLists').supported:
block_menuitem.set_sensitive(False)
# Remove group
@ -5245,7 +5253,7 @@ class RosterWindow:
account = model[titer][Column.ACCOUNT]
if app.connections[account].connected < 2:
one_account_offline = True
if not app.connections[account].privacy_rules_supported:
if not app.connections[account].get_module('PrivacyLists').supported:
privacy_rules_supported = False
contact = app.contacts.get_contact_with_highest_priority(account,
jid)
@ -5411,7 +5419,7 @@ class RosterWindow:
self.on_xml_console_menuitem_activate, account)
if app.connections[account]:
if app.connections[account].privacy_rules_supported:
if app.connections[account].get_module('PrivacyLists').supported:
privacy_lists_menuitem.connect('activate',
self.on_privacy_lists_menuitem_activate, account)
else:

View File

@ -102,6 +102,9 @@ class Mock(object):
if not name in baseMethods:
self.__dict__[name] = MockCallable(name, self, handcrafted=True)
def get_module(self, name):
return Mock()
def __getattr__(self, name):
return MockCallable(name, self)

View File

@ -6,10 +6,10 @@ import unittest
import lib
lib.setup_env()
from unittest.mock import MagicMock
from nbxmpp import NS_MUC, NS_PING, NS_XHTML_IM, Iq
from gajim.common import caps_cache as caps
from gajim.common.contacts import Contact
from gajim.common.connection_handlers_events import AgentInfoReceivedEvent
from gajim.common.modules.discovery import Discovery
from mock import Mock
@ -106,29 +106,29 @@ class TestCapsCache(CommonCapsTest):
def test_preload_triggering_query(self):
''' Make sure that preload issues a disco '''
connection = Mock()
connection = MagicMock()
client_caps = caps.ClientCaps(self.caps_hash, self.node, self.caps_method)
self.cc.query_client_of_jid_if_unknown(connection, "test@gajim.org",
client_caps)
self.cc.query_client_of_jid_if_unknown(
connection, "test@gajim.org", client_caps)
self.assertEqual(1, len(connection.mockGetAllCalls()))
self.assertEqual(1, connection.get_module('Discovery').disco_contact.call_count)
def test_no_preload_query_if_cashed(self):
''' Preload must not send a query if the data is already cached '''
connection = Mock()
connection = MagicMock()
client_caps = caps.ClientCaps(self.caps_hash, self.node, self.caps_method)
self.cc.initialize_from_db()
self.cc.query_client_of_jid_if_unknown(connection, "test@gajim.org",
client_caps)
self.cc.query_client_of_jid_if_unknown(
connection, "test@gajim.org", client_caps)
self.assertEqual(0, len(connection.mockGetAllCalls()))
self.assertEqual(0, connection.get_module('Discovery').disco_contact.call_count)
def test_hash(self):
'''tests the hash computation'''
stanza = Iq(node=COMPLEX_EXAMPLE)
identities, features, data, _ = AgentInfoReceivedEvent.parse_stanza(stanza)
identities, features, data, _ = Discovery.parse_info_response(stanza)
computed_hash = caps.compute_caps_hash(identities, features, data)
self.assertEqual('q07IKJEyjvHSyhy//CH0CxmKi8w=', computed_hash)
@ -141,12 +141,11 @@ class TestClientCaps(CommonCapsTest):
def test_query_by_get_discover_strategy(self):
''' Client must be queried if the data is unkown '''
connection = Mock()
connection = MagicMock()
discover = self.client_caps.get_discover_strategy()
discover(connection, "test@gajim.org")
connection.mockCheckCall(0, "discoverInfo", "test@gajim.org",
"http://gajim.org#m3P2WeXPMGVH2tZPe7yITnfY0Dw=")
connection.get_module('Discovery').disco_contact.assert_called_once_with(
'test@gajim.org', 'http://gajim.org#m3P2WeXPMGVH2tZPe7yITnfY0Dw=')
def test_client_supports(self):
self.assertTrue(caps.client_supports(self.client_caps, NS_PING),
@ -175,11 +174,11 @@ class TestOldClientCaps(TestClientCaps):
def test_query_by_get_discover_strategy(self):
''' Client must be queried if the data is unknown '''
connection = Mock()
connection = MagicMock()
discover = self.client_caps.get_discover_strategy()
discover(connection, "test@gajim.org")
connection.mockCheckCall(0, "discoverInfo", "test@gajim.org")
connection.get_module('Discovery').disco_contact.assert_called_once_with('test@gajim.org')
if __name__ == '__main__':
unittest.main()