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)] return caps_cache[(self._hash_method, self._hash)]
def _discover(self, connection, jid): 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): def _is_hash_valid(self, identities, features, dataforms):
computed_hash = compute_caps_hash(identities, features, computed_hash = compute_caps_hash(
dataforms=dataforms, hash_method=self._hash_method) identities, features, dataforms=dataforms,
hash_method=self._hash_method)
return computed_hash == self._hash return computed_hash == self._hash
@ -227,7 +229,7 @@ class OldClientCaps(AbstractClientCaps):
return caps_cache[('old', self._node + '#' + self._hash)] return caps_cache[('old', self._node + '#' + self._hash)]
def _discover(self, connection, jid): def _discover(self, connection, jid):
connection.discoverInfo(jid) connection.get_module('Discovery').disco_contact(jid)
def _is_hash_valid(self, identities, features, dataforms): def _is_hash_valid(self, identities, features, dataforms):
return True return True
@ -244,7 +246,7 @@ class NoClientCaps(AbstractClientCaps):
return caps_cache[('no', self._node)] return caps_cache[('no', self._node)]
def _discover(self, connection, jid): def _discover(self, connection, jid):
connection.discoverInfo(jid) connection.get_module('Discovery').disco_contact(jid)
def _is_hash_valid(self, identities, features, dataforms): def _is_hash_valid(self, identities, features, dataforms):
return True return True

View File

@ -103,7 +103,6 @@ class CommonConnection:
self.priority = app.get_priority(name, 'offline') self.priority = app.get_priority(name, 'offline')
self.time_to_reconnect = None self.time_to_reconnect = None
self.pep_supported = False
self.pep = {} self.pep = {}
# Do we continue connection when we get roster (send presence,get vcard..) # Do we continue connection when we get roster (send presence,get vcard..)
self.continue_connect_info = None self.continue_connect_info = None
@ -115,13 +114,9 @@ class CommonConnection:
# the fake jid # the fake jid
self.groupchat_jids = {} # {ID : groupchat_jid} self.groupchat_jids = {} # {ID : groupchat_jid}
self.privacy_rules_supported = False
self.vcard_supported = False
self.private_storage_supported = False self.private_storage_supported = False
self.roster_supported = True self.roster_supported = True
self.blocking_supported = False
self.addressing_supported = False self.addressing_supported = False
self.carbons_available = False
self.muc_jid = {} # jid of muc server for each transport type self.muc_jid = {} # jid of muc server for each transport type
self._stun_servers = [] # STUN servers of our jabber server self._stun_servers = [] # STUN servers of our jabber server
@ -582,7 +577,6 @@ class Connection(CommonConnection, ConnectionHandlers):
self.music_track_info = 0 self.music_track_info = 0
self.register_supported = False self.register_supported = False
self.pubsub_publish_options_supported = False
# Do we auto accept insecure connection # Do we auto accept insecure connection
self.connection_auto_accepted = False self.connection_auto_accepted = False
self.pasword_callback = None self.pasword_callback = None
@ -611,10 +605,6 @@ class Connection(CommonConnection, ConnectionHandlers):
# Register all modules # Register all modules
modules.register(self) 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, app.ged.register_event_handler('message-outgoing', ged.OUT_CORE,
self._nec_message_outgoing) self._nec_message_outgoing)
app.ged.register_event_handler('gc-message-outgoing', ged.OUT_CORE, app.ged.register_event_handler('gc-message-outgoing', ged.OUT_CORE,
@ -629,10 +619,6 @@ class Connection(CommonConnection, ConnectionHandlers):
ConnectionHandlers.cleanup(self) ConnectionHandlers.cleanup(self)
modules.unregister(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, app.ged.remove_event_handler('message-outgoing', ged.OUT_CORE,
self._nec_message_outgoing) self._nec_message_outgoing)
app.ged.remove_event_handler('gc-message-outgoing', ged.OUT_CORE, 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.on_purpose = on_purpose
self.connected = 0 self.connected = 0
self.time_to_reconnect = None self.time_to_reconnect = None
self.privacy_rules_supported = False self.get_module('PrivacyLists').supported = False
self.get_module('VCardAvatars').avatar_advertised = False self.get_module('VCardAvatars').avatar_advertised = False
if on_purpose: if on_purpose:
self.sm = Smacks(self) self.sm = Smacks(self)
@ -1430,7 +1416,7 @@ class Connection(CommonConnection, ConnectionHandlers):
def send_invisible_presence(self, msg, signed, initial = False): def send_invisible_presence(self, msg, signed, initial = False):
if not app.account_is_connected(self.name): if not app.account_is_connected(self.name):
return return
if not self.privacy_rules_supported: if not self.get_module('PrivacyLists').supported:
app.nec.push_incoming_event(OurShowEvent(None, conn=self, app.nec.push_incoming_event(OurShowEvent(None, conn=self,
show=app.SHOW_LIST[self.connected])) show=app.SHOW_LIST[self.connected]))
app.nec.push_incoming_event(InformationEvent( app.nec.push_incoming_event(InformationEvent(
@ -1438,7 +1424,7 @@ class Connection(CommonConnection, ConnectionHandlers):
return return
# If we are already connected, and privacy rules are supported, send # If we are already connected, and privacy rules are supported, send
# offline presence first as it's required by XEP-0126 # 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 self.on_purpose = True
p = nbxmpp.Presence(typ='unavailable') p = nbxmpp.Presence(typ='unavailable')
p = self.add_sha(p, False) p = self.add_sha(p, False)
@ -1515,10 +1501,9 @@ class Connection(CommonConnection, ConnectionHandlers):
# If we are not resuming, we ask for discovery info # If we are not resuming, we ask for discovery info
# and archiving preferences # and archiving preferences
if not self.sm.supports_sm or (not self.sm.resuming and self.sm.enabled): 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') our_server = app.config.get_per('accounts', self.name, 'hostname')
self.discoverInfo(our_jid, id_prefix='Gajim_') self.get_module('Discovery').discover_account_info()
self.discoverInfo(our_server, id_prefix='Gajim_') self.get_module('Discovery').discover_server_info()
else: else:
self.request_roster(resume=True) self.request_roster(resume=True)
@ -1534,7 +1519,7 @@ class Connection(CommonConnection, ConnectionHandlers):
self._stun_servers = self._hosts = [i for i in result_array] self._stun_servers = self._hosts = [i for i in result_array]
def _continue_connection_request_privacy(self): def _continue_connection_request_privacy(self):
if self.privacy_rules_supported: if self.get_module('PrivacyLists').supported:
if not self.privacy_rules_requested: if not self.privacy_rules_requested:
self.privacy_rules_requested = True self.privacy_rules_requested = True
self.get_module('PrivacyLists').get_privacy_lists( self.get_module('PrivacyLists').get_privacy_lists(
@ -1557,104 +1542,12 @@ class Connection(CommonConnection, ConnectionHandlers):
None, dialog_name='invisibility-not-supported', None, dialog_name='invisibility-not-supported',
args=self.name)) args=self.name))
return return
if self.blocking_supported:
self.get_module('Blocking').get_blocking_list() self.get_module('Blocking').get_blocking_list()
# Ask metacontacts before roster # Ask metacontacts before roster
self.get_metacontacts() 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): def send_custom_status(self, show, msg, jid):
if not show in app.SHOW_LIST: if not show in app.SHOW_LIST:
return -1 return -1
@ -1684,7 +1577,7 @@ class Connection(CommonConnection, ConnectionHandlers):
self.send_invisible_presence(msg, signed) self.send_invisible_presence(msg, signed)
def _change_from_invisible(self): def _change_from_invisible(self):
if self.privacy_rules_supported: if self.get_module('PrivacyLists').supported:
self.get_module('PrivacyLists').set_active_list(None) self.get_module('PrivacyLists').set_active_list(None)
def _update_status(self, show, msg, idle_time=None): def _update_status(self, show, msg, idle_time=None):
@ -1897,7 +1790,7 @@ class Connection(CommonConnection, ConnectionHandlers):
def bookmarks_available(self): def bookmarks_available(self):
if self.private_storage_supported: if self.private_storage_supported:
return True return True
if self.pubsub_publish_options_supported: if self.get_module('PubSub').publish_options:
return True return True
return False return False
@ -2022,7 +1915,7 @@ class Connection(CommonConnection, ConnectionHandlers):
# Never join a room when invisible # Never join a room when invisible
return return
self.discoverMUC( self.get_module('Discovery').disco_muc(
room_jid, partial(self._join_gc, nick, show, room_jid, room_jid, partial(self._join_gc, nick, show, room_jid,
password, change_nick, rejoin)) password, change_nick, rejoin))

View File

@ -66,51 +66,6 @@ PRIVACY_ARRIVED = 'privacy_arrived'
class ConnectionDisco: 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): def request_register_agent_info(self, agent):
if not self.connection or self.connected < 2: if not self.connection or self.connected < 2:
@ -159,117 +114,10 @@ class ConnectionDisco:
self.agent_registrations[agent] = {'roster_push': False, self.agent_registrations[agent] = {'roster_push': False,
'sub_received': 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): def _ReceivedRegInfo(self, con, resp, agent):
nbxmpp.features_nb._ReceivedRegInfo(con, resp, agent) nbxmpp.features_nb._ReceivedRegInfo(con, resp, agent)
self._IqCB(con, resp) 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 # basic connection handlers used here and in zeroconf
class ConnectionHandlersBase: class ConnectionHandlersBase:
@ -292,25 +140,17 @@ class ConnectionHandlersBase:
# We decrypt GPG messages one after the other. Keep queue in mem # We decrypt GPG messages one after the other. Keep queue in mem
self.gpg_messages_to_decrypt = [] 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, app.ged.register_event_handler('presence-received', ged.CORE,
self._nec_presence_received) self._nec_presence_received)
app.ged.register_event_handler('gc-message-received', ged.CORE, app.ged.register_event_handler('gc-message-received', ged.CORE,
self._nec_gc_message_received) self._nec_gc_message_received)
def cleanup(self): 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, app.ged.remove_event_handler('presence-received', ged.CORE,
self._nec_presence_received) self._nec_presence_received)
app.ged.remove_event_handler('gc-message-received', ged.CORE, app.ged.remove_event_handler('gc-message-received', ged.CORE,
self._nec_gc_message_received) 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): def _nec_presence_received(self, obj):
account = obj.conn.name account = obj.conn.name
if account != self.name: if account != self.name:
@ -647,8 +487,6 @@ class ConnectionHandlers(ConnectionSocks5Bytestream, ConnectionDisco,
ged.CORE, self._nec_roster_set_received) ged.CORE, self._nec_roster_set_received)
app.ged.register_event_handler('roster-received', ged.CORE, app.ged.register_event_handler('roster-received', ged.CORE,
self._nec_roster_received) 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', app.ged.register_event_handler('subscribe-presence-received',
ged.CORE, self._nec_subscribe_presence_received) ged.CORE, self._nec_subscribe_presence_received)
app.ged.register_event_handler('subscribed-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) ged.CORE, self._nec_roster_set_received)
app.ged.remove_event_handler('roster-received', ged.CORE, app.ged.remove_event_handler('roster-received', ged.CORE,
self._nec_roster_received) 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', app.ged.remove_event_handler('subscribe-presence-received',
ged.CORE, self._nec_subscribe_presence_received) ged.CORE, self._nec_subscribe_presence_received)
app.ged.remove_event_handler('subscribed-presence-received', app.ged.remove_event_handler('subscribed-presence-received',
@ -762,28 +598,14 @@ class ConnectionHandlers(ConnectionSocks5Bytestream, ConnectionDisco,
self._getRoster() self._getRoster()
elif iq_obj.getType() == 'error': elif iq_obj.getType() == 'error':
self.roster_supported = False self.roster_supported = False
self.discoverItems(app.config.get_per('accounts', self.name, self.get_module('Discovery').discover_server_items()
'hostname'), id_prefix='Gajim_')
if app.config.get_per('accounts', self.name, if app.config.get_per('accounts', self.name,
'use_ft_proxies'): 'use_ft_proxies'):
self.discover_ft_proxies() self.discover_ft_proxies()
app.nec.push_incoming_event(RosterReceivedEvent(None, app.nec.push_incoming_event(RosterReceivedEvent(None,
conn=self)) conn=self))
GLib.timeout_add_seconds(10, self.discover_servers)
del self.awaiting_answers[id_] 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): def _rosterSetCB(self, con, iq_obj):
log.debug('rosterSetCB') log.debug('rosterSetCB')
app.nec.push_incoming_event(RosterSetReceivedEvent(None, conn=self, app.nec.push_incoming_event(RosterSetReceivedEvent(None, conn=self,
@ -972,8 +794,7 @@ class ConnectionHandlers(ConnectionSocks5Bytestream, ConnectionDisco,
if not self.connection: if not self.connection:
return return
self.connection.getRoster(self._on_roster_set) self.connection.getRoster(self._on_roster_set)
self.discoverItems(app.config.get_per('accounts', self.name, self.get_module('Discovery').discover_server_items()
'hostname'), id_prefix='Gajim_')
if app.config.get_per('accounts', self.name, 'use_ft_proxies'): if app.config.get_per('accounts', self.name, 'use_ft_proxies'):
self.discover_ft_proxies() self.discover_ft_proxies()
@ -990,17 +811,6 @@ class ConnectionHandlers(ConnectionSocks5Bytestream, ConnectionDisco,
app.proxy65_manager.resolve(proxy, self.connection, our_jid, app.proxy65_manager.resolve(proxy, self.connection, our_jid,
testit=testit) 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): def _on_roster_set(self, roster):
app.nec.push_incoming_event(RosterReceivedEvent(None, conn=self, app.nec.push_incoming_event(RosterReceivedEvent(None, conn=self,
xmpp_roster=roster)) xmpp_roster=roster))
@ -1027,13 +837,6 @@ class ConnectionHandlers(ConnectionSocks5Bytestream, ConnectionDisco,
if send_first_presence: if send_first_presence:
self._send_first_presence(signed) 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) app.logger.replace_roster(self.name, obj.version, obj.roster)
for contact in app.contacts.iter_contacts(self.name): for contact in app.contacts.iter_contacts(self.name):
@ -1080,9 +883,9 @@ class ConnectionHandlers(ConnectionSocks5Bytestream, ConnectionDisco,
self.priority = priority self.priority = priority
app.nec.push_incoming_event(OurShowEvent(None, conn=self, app.nec.push_incoming_event(OurShowEvent(None, conn=self,
show=show)) show=show))
if self.vcard_supported:
# ask our VCard # ask our VCard
self.get_module('VCardTemp').request_vcard() self.get_module('VCardTemp').request_vcard()
# Get bookmarks # Get bookmarks
self.get_module('Bookmarks').get_bookmarks() self.get_module('Bookmarks').get_bookmarks()
@ -1120,7 +923,6 @@ class ConnectionHandlers(ConnectionSocks5Bytestream, ConnectionDisco,
con.RegisterHandler('iq', self._siSetCB, 'set', nbxmpp.NS_SI) con.RegisterHandler('iq', self._siSetCB, 'set', nbxmpp.NS_SI)
con.RegisterHandler('iq', self._siErrorCB, 'error', 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._siResultCB, 'result', nbxmpp.NS_SI)
con.RegisterHandler('iq', self._discoGetCB, 'get', nbxmpp.NS_DISCO)
con.RegisterHandler('iq', self._bytestreamSetCB, 'set', con.RegisterHandler('iq', self._bytestreamSetCB, 'set',
nbxmpp.NS_BYTESTREAM) nbxmpp.NS_BYTESTREAM)
con.RegisterHandler('iq', self._bytestreamResultCB, 'result', con.RegisterHandler('iq', self._bytestreamResultCB, 'result',
@ -1130,18 +932,6 @@ class ConnectionHandlers(ConnectionSocks5Bytestream, ConnectionDisco,
con.RegisterHandlerOnce('iq', self.IBBAllIqHandler) con.RegisterHandlerOnce('iq', self.IBBAllIqHandler)
con.RegisterHandler('iq', self.IBBIqHandler, ns=nbxmpp.NS_IBB) con.RegisterHandler('iq', self.IBBIqHandler, ns=nbxmpp.NS_IBB)
con.RegisterHandler('message', self.IBBMessageHandler, 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, 'result')
con.RegisterHandler('iq', self._JingleCB, 'error') 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 app
from gajim.common import i18n from gajim.common import i18n
from gajim.common.modules import dataforms from gajim.common.modules import dataforms
from gajim.common.zeroconf.zeroconf import Constant
from gajim.common.const import KindConstant, SSLError from gajim.common.const import KindConstant, SSLError
from gajim.common.pep import SUPPORTED_PERSONAL_USER_EVENTS from gajim.common.pep import SUPPORTED_PERSONAL_USER_EVENTS
from gajim.common.jingle_transport import JingleTransportSocks5 from gajim.common.jingle_transport import JingleTransportSocks5
@ -1080,119 +1079,6 @@ class RegisterAgentInfoReceivedEvent(nec.NetworkIncomingEvent):
name = 'register-agent-info-received' name = 'register-agent-info-received'
base_network_events = [] 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): class FileRequestReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
name = 'file-request-received' name = 'file-request-received'
base_network_events = [] base_network_events = []

View File

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

View File

@ -35,7 +35,18 @@ class Blocking:
('iq', self._blocking_push_received, 'set', nbxmpp.NS_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): def get_blocking_list(self):
if not self.supported:
return
iq = nbxmpp.Iq('get', nbxmpp.NS_BLOCKING) iq = nbxmpp.Iq('get', nbxmpp.NS_BLOCKING)
iq.setQuery('blocklist') iq.setQuery('blocklist')
log.info('Request list') log.info('Request list')
@ -119,7 +130,7 @@ class Blocking:
self._con.connection.send(probe) self._con.connection.send(probe)
def block(self, contact_list): def block(self, contact_list):
if not self._con.blocking_supported: if not self.supported:
return return
iq = nbxmpp.Iq('set', nbxmpp.NS_BLOCKING) iq = nbxmpp.Iq('set', nbxmpp.NS_BLOCKING)
query = iq.setQuery(name='block') query = iq.setQuery(name='block')
@ -131,7 +142,7 @@ class Blocking:
iq, self._default_result_handler, {}) iq, self._default_result_handler, {})
def unblock(self, contact_list): def unblock(self, contact_list):
if not self._con.blocking_supported: if not self.supported:
return return
iq = nbxmpp.Iq('set', nbxmpp.NS_BLOCKING) iq = nbxmpp.Iq('set', nbxmpp.NS_BLOCKING)
query = iq.setQuery(name='unblock') query = iq.setQuery(name='unblock')

View File

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

View File

@ -18,9 +18,37 @@ import logging
import nbxmpp import nbxmpp
from gajim.common import app
log = logging.getLogger('gajim.c.m.carbons') 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): def parse_carbon(con, stanza):
carbon = stanza.getTag( carbon = stanza.getTag(
'received', namespace=nbxmpp.NS_CARBONS, protocol=True) 'received', namespace=nbxmpp.NS_CARBONS, protocol=True)
@ -75,3 +103,7 @@ def parse_carbon(con, stanza):
raise nbxmpp.NodeProcessed raise nbxmpp.NodeProcessed
return message, sent, True 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._allowed_headers = ['Authorization', 'Cookie', 'Expires']
self.max_file_size = None # maximum file size in bytes 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', app.ged.register_event_handler('stanza-message-outgoing',
ged.OUT_PREGUI, ged.OUT_PREGUI,
self.handle_outgoing_stanza) self.handle_outgoing_stanza)
@ -72,9 +69,6 @@ class HTTPUpload:
self.messages = [] self.messages = []
def cleanup(self): 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', app.ged.remove_event_handler('stanza-message-outgoing',
ged.OUT_PREGUI, ged.OUT_PREGUI,
self.handle_outgoing_stanza) self.handle_outgoing_stanza)
@ -82,27 +76,18 @@ class HTTPUpload:
ged.OUT_PREGUI, ged.OUT_PREGUI,
self.handle_outgoing_stanza) self.handle_outgoing_stanza)
def handle_agent_info_received(self, event): def pass_disco(self, from_, identities, features, data, node):
account = event.conn.name if NS_HTTPUPLOAD_0 in features:
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:
self.httpupload_namespace = NS_HTTPUPLOAD_0 self.httpupload_namespace = NS_HTTPUPLOAD_0
elif NS_HTTPUPLOAD in event.features: elif NS_HTTPUPLOAD in features:
self.httpupload_namespace = NS_HTTPUPLOAD self.httpupload_namespace = NS_HTTPUPLOAD
else: else:
return 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() form_dict = form.asDict()
if form_dict.get('FORM_TYPE', None) != self.httpupload_namespace: if form_dict.get('FORM_TYPE', None) != self.httpupload_namespace:
continue continue
@ -112,14 +97,16 @@ class HTTPUpload:
break break
if self.max_file_size is None: 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: else:
log.info('%s has a maximum file size of: %s MiB', 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 self.available = True
for ctrl in app.interface.msg_win_mgr.get_controls(acct=self._account): for ctrl in app.interface.msg_win_mgr.get_controls(acct=self._account):
ctrl.update_actions() ctrl.update_actions()
raise nbxmpp.NodeProcessed
def handle_outgoing_stanza(self, event): def handle_outgoing_stanza(self, event):
if event.conn.name != self._account: if event.conn.name != self._account:

View File

@ -47,6 +47,20 @@ class MAM:
self.archiving_namespace = None self.archiving_namespace = None
self._mam_query_ids = {} 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): def _from_valid_archive(self, stanza, message, groupchat):
if groupchat: if groupchat:
expected_archive = message.getFrom() expected_archive = message.getFrom()

View File

@ -38,6 +38,18 @@ class MUC:
('message', self._direct_invite, '', nbxmpp.NS_CONFERENCE), ('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): def set_subject(self, room_jid, subject):
if not app.account_is_connected(self._account): if not app.account_is_connected(self._account):
return return

View File

@ -36,9 +36,17 @@ class PEP:
'headline', nbxmpp.NS_PUBSUB_EVENT) 'headline', nbxmpp.NS_PUBSUB_EVENT)
] ]
self.supported = False
self._pep_handlers = {} self._pep_handlers = {}
self._store_publish_modules = [] 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): def register_pep_handler(self, namespace, notify_handler, retract_handler):
if namespace in self._pep_handlers: if namespace in self._pep_handlers:
self._pep_handlers[namespace].append( self._pep_handlers[namespace].append(
@ -165,7 +173,7 @@ class AbstractPEPModule:
self._stored_publish = None self._stored_publish = None
def send(self, data): def send(self, data):
if not self._con.pep_supported: if not self._con.get_module('PEP').supported:
return return
if self._con.connected == 1: if self._con.connected == 1:
@ -184,7 +192,7 @@ class AbstractPEPModule:
'', self.namespace, item, 'current') '', self.namespace, item, 'current')
def retract(self): def retract(self):
if not self._con.pep_supported: if not self._con.get_module('PEP').supported:
return return
self.send(None) self.send(None)
self._con.get_module('PubSub').send_pb_retract( 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) ('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): def _list_push_received(self, con, stanza):
result = stanza.buildReply('result') result = stanza.buildReply('result')
result.delChild(result.getTag('query')) result.delChild(result.getTag('query'))
@ -287,7 +299,7 @@ class PrivacyLists:
self.set_default_list(self.default_list) self.set_default_list(self.default_list)
def block_contacts(self, contact_list, message): 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) self._con.get_module('Blocking').block(contact_list)
return return
@ -332,7 +344,7 @@ class PrivacyLists:
self.set_privacy_list(self.default_list, new_blocked_list) self.set_privacy_list(self.default_list, new_blocked_list)
def unblock_contacts(self, contact_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) self._con.get_module('Blocking').unblock(contact_list)
return return
@ -367,7 +379,7 @@ class PrivacyLists:
self._presence_probe(contact.jid) self._presence_probe(contact.jid)
def block_group(self, group, contact_list, message): def block_group(self, group, contact_list, message):
if not self._con.privacy_rules_supported: if not self.supported:
return return
if group in self.blocked_groups: if group in self.blocked_groups:
return return
@ -393,7 +405,7 @@ class PrivacyLists:
self.set_default_list(self.default_list) self.set_default_list(self.default_list)
def unblock_group(self, group, contact_list): def unblock_group(self, group, contact_list):
if not self._con.privacy_rules_supported: if not self.supported:
return return
if group not in self.blocked_groups: if group not in self.blocked_groups:

View File

@ -38,6 +38,16 @@ class PubSub:
self.handlers = [] 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): def send_pb_subscription_query(self, jid, cb, **kwargs):
if not app.account_is_connected(self._account): if not app.account_is_connected(self._account):
return return

View File

@ -34,6 +34,13 @@ class SecLabels:
self._catalogs = {} self._catalogs = {}
self.supported = False 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): def request_catalog(self, jid):
server = app.get_jid_from_account(self._account).split("@")[1] server = app.get_jid_from_account(self._account).split("@")[1]
iq = nbxmpp.Iq(typ='get', to=server) iq = nbxmpp.Iq(typ='get', to=server)

View File

@ -41,6 +41,17 @@ class VCardTemp:
self._own_vcard = None self._own_vcard = None
self.own_vcard_received = False self.own_vcard_received = False
self.room_jids = [] 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): def _node_to_dict(self, node):
dict_ = {} dict_ = {}
@ -67,6 +78,8 @@ class VCardTemp:
if isinstance(callback, RequestAvatar): if isinstance(callback, RequestAvatar):
if callback == RequestAvatar.SELF: if callback == RequestAvatar.SELF:
if not self.supported:
return
callback = self._on_own_avatar_received callback = self._on_own_avatar_received
elif callback == RequestAvatar.ROOM: elif callback == RequestAvatar.ROOM:
callback = self._on_room_avatar_received callback = self._on_room_avatar_received

View File

@ -80,7 +80,20 @@ class ConnectionBytestream:
def __init__(self): def __init__(self):
app.ged.register_event_handler('file-request-received', ged.GUI1, app.ged.register_event_handler('file-request-received', ged.GUI1,
self._nec_file_request_received) 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): def cleanup(self):
app.ged.remove_event_handler('file-request-received', ged.GUI1, app.ged.remove_event_handler('file-request-received', ged.GUI1,

View File

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

View File

@ -191,20 +191,3 @@ connection_handlers.ConnectionJingle):
# serverside metacontacts are not supported with zeroconf # serverside metacontacts are not supported with zeroconf
# (there is no server) # (there is no server)
pass 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.get_object('delete_button').set_sensitive(False)
self.xml.connect_signals(self) self.xml.connect_signals(self)
self.account = account self.account = account
self._con = app.connections[self.account]
self.init_services() self.init_services()
self.xml.get_object('services_treeview').get_selection().connect( 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, app.ged.register_event_handler('pubsub-config-received', ged.GUI1,
self._nec_pep_config_received) self._nec_pep_config_received)
app.ged.register_event_handler('agent-items-received', ged.GUI1,
self._nec_agent_items_received)
self.window.show_all() self.window.show_all()
@ -2862,8 +2861,6 @@ class ManagePEPServicesWindow:
del app.interface.instances[self.account]['pep_services'] del app.interface.instances[self.account]['pep_services']
app.ged.remove_event_handler('pubsub-config-received', ged.GUI1, app.ged.remove_event_handler('pubsub-config-received', ged.GUI1,
self._nec_pep_config_received) 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): def on_close_button_clicked(self, widget):
self.window.destroy() self.window.destroy()
@ -2885,15 +2882,19 @@ class ManagePEPServicesWindow:
col.pack_start(cellrenderer_text, True) col.pack_start(cellrenderer_text, True)
col.add_attribute(cellrenderer_text, 'text', 0) col.add_attribute(cellrenderer_text, 'text', 0)
our_jid = app.get_jid_from_account(self.account) jid = self._con.get_own_jid().getStripped()
app.connections[self.account].discoverItems(our_jid) 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): def _items_received(self, from_, node, items):
our_jid = app.get_jid_from_account(self.account) jid = self._con.get_own_jid().getStripped()
for item in obj.items: for item in items:
if 'jid' in item and item['jid'] == our_jid and 'node' in item: if item['jid'] == jid and 'node' in item:
self.treestore.append([item['node']]) self.treestore.append([item['node']])
def _items_error(self, from_, error):
ErrorDialog('Error', error)
def node_removed(self, jid, node): def node_removed(self, jid, node):
if jid != app.get_jid_from_account(self.account): if jid != app.get_jid_from_account(self.account):
return return

View File

@ -258,24 +258,6 @@ class ServicesCache:
self._info = CacheDictionary(0, getrefresh = False) self._info = CacheDictionary(0, getrefresh = False)
self._subscriptions = CacheDictionary(5, getrefresh=False) self._subscriptions = CacheDictionary(5, getrefresh=False)
self._cbs = {} 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): def cleanup(self):
self._items.cleanup() self._items.cleanup()
@ -391,7 +373,9 @@ class ServicesCache:
self._cbs[cbkey].append(cb) self._cbs[cbkey].append(cb)
else: else:
self._cbs[cbkey] = [cb] 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=()): def get_items(self, jid, node, cb, force=False, nofetch=False, args=()):
""" """
@ -409,24 +393,38 @@ class ServicesCache:
# Create a closure object # Create a closure object
cbkey = ('items', addr) cbkey = ('items', addr)
cb = Closure(cb, userargs=args, remove=self._clean_closure, cb = Closure(cb, userargs=args, remove=self._clean_closure,
removeargs=cbkey) removeargs=cbkey)
# Are we already fetching this? # Are we already fetching this?
if cbkey in self._cbs: if cbkey in self._cbs:
self._cbs[cbkey].append(cb) self._cbs[cbkey].append(cb)
else: else:
self._cbs[cbkey] = [cb] 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 Callback for when we receive an agent's info
array is (agent, node, identities, features, data) array is (agent, node, identities, features, data)
""" """
# We receive events from all accounts from GED self._on_agent_info(str(from_), node, identities, features, data)
if obj.conn.name != self.account:
return def _disco_info_error(self, from_, error):
self._on_agent_info(obj.fjid, obj.node, obj.identities, obj.features, """
obj.data) 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): def _on_agent_info(self, fjid, node, identities, features, data):
addr = get_agent_address(fjid, node) addr = get_agent_address(fjid, node)
@ -443,67 +441,42 @@ class ServicesCache:
if cbkey in self._cbs: if cbkey in self._cbs:
del self._cbs[cbkey] 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 Callback for when we receive an agent's items
array is (agent, node, items) array is (agent, node, items)
""" """
# We receive events from all accounts from GED addr = get_agent_address(from_, node)
if obj.conn.name != self.account:
return
addr = get_agent_address(obj.fjid, obj.node)
# Store in cache # Store in cache
self._items[addr] = obj.items self._items[addr] = items
# Call callbacks # Call callbacks
cbkey = ('items', addr) cbkey = ('items', addr)
if cbkey in self._cbs: if cbkey in self._cbs:
for cb in self._cbs[cbkey]: 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 # clean_closure may have beaten us to it
if cbkey in self._cbs: if cbkey in self._cbs:
del self._cbs[cbkey] 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 Callback for when a query fails. Even after the browse and agents
namespaces namespaces
""" """
# We receive events from all accounts from GED addr = get_agent_address(from_)
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)
# Call callbacks # Call callbacks
cbkey = ('items', addr) cbkey = ('items', addr)
if cbkey in self._cbs: if cbkey in self._cbs:
for cb in self._cbs[cbkey]: for cb in self._cbs[cbkey]:
cb(obj.fjid, '', 0) cb(str(from_), '', 0)
# clean_closure may have beaten us to it # clean_closure may have beaten us to it
if cbkey in self._cbs: if cbkey in self._cbs:
del self._cbs[cbkey] del self._cbs[cbkey]
# object is needed so that @property works # object is needed so that @property works
class ServiceDiscoveryWindow(object): class ServiceDiscoveryWindow(object):
""" """

View File

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

View File

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

View File

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

View File

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

View File

@ -460,8 +460,10 @@ control=None, gc_contact=None, is_anonymous=True):
execute_command_menuitem, send_custom_status_menuitem): execute_command_menuitem, send_custom_status_menuitem):
widget.set_sensitive(False) 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): if helpers.jid_is_blocked(account, jid):
block_menuitem.set_no_show_all(True) block_menuitem.set_no_show_all(True)
block_menuitem.hide() block_menuitem.hide()

View File

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

View File

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

View File

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