Commit 07c87a41 authored by Philipp Hörist's avatar Philipp Hörist

Rewrite discovery code and move it into own module

parent 5ff9e9fe
......@@ -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
......
This diff is collapsed.
This diff is collapsed.
......@@ -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 = []
......
......@@ -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
......
......@@ -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')
......
......@@ -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()
......
......@@ -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'
This diff is collapsed.
......@@ -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:
......
......@@ -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()
......
......@@ -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
......
......@@ -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(
......
......@@ -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:
......
......@@ -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
......
......@@ -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)
......
......@@ -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
......
......@@ -80,7 +80,20 @@ class ConnectionBytestream:
def __init__(self):
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):
app.ged.remove_event_handler('file-request-received', ged.GUI1,
......
......@@ -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.' +
'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,
client_caps=contact.client_caps))
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=fjid,
jid=bare_jid,
resource=resource,
client_caps=contact.client_caps))
......@@ -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',
......
......@@ -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
......@@ -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)