Philipp Hörist pushed to branch master at gajim / gajim
Commits: 9c53fcbd by Philipp Hörist at 2019-06-28T21:32:02Z Use nbxmpp's Discovery module
- - - - -
21 changed files:
- gajim/common/app.py - gajim/common/caps_cache.py - gajim/common/logger.py - gajim/common/modules/blocking.py - gajim/common/modules/bookmarks.py - gajim/common/modules/bytestream.py - gajim/common/modules/caps.py - gajim/common/modules/carbons.py - gajim/common/modules/discovery.py - gajim/common/modules/httpupload.py - gajim/common/modules/mam.py - gajim/common/modules/muc.py - gajim/common/modules/pep.py - gajim/common/modules/privacylists.py - gajim/common/modules/pubsub.py - gajim/common/modules/security_labels.py - gajim/common/modules/vcard_temp.py - gajim/gtk/discovery.py - gajim/gtk/join_groupchat.py - gajim/gtk/pep_config.py - test/no_gui/unit/test_caps_cache.py
Changes:
===================================== gajim/common/app.py ===================================== @@ -38,6 +38,7 @@ from pathlib import Path from collections import namedtuple
import nbxmpp +from nbxmpp.structs import DiscoIdentity from gi.repository import Gdk
import gajim @@ -143,7 +144,9 @@ socks5queue = None
gupnp_igd = None
-gajim_identity = {'type': 'pc', 'category': 'client', 'name': 'Gajim'} +gajim_identity = DiscoIdentity(category='client', + type='pc', + name='Gajim')
gajim_common_features = [ nbxmpp.NS_BYTESTREAM,
===================================== gajim/common/caps_cache.py ===================================== @@ -30,7 +30,6 @@ through ClientCaps objects which are hold by contact instances. import base64 import hashlib import logging -from collections import namedtuple
import nbxmpp from nbxmpp.const import Affiliation @@ -114,19 +113,18 @@ def compute_caps_hash(identities, features, dataforms=None, hash_method='sha-1') dataforms = []
def sort_identities_key(i): - return (i['category'], i.get('type', ''), i.get('xml:lang', '')) + return (i.category, i.type, i.lang or '')
def sort_dataforms_key(dataform): - f = dataform.getField('FORM_TYPE') - return (bool(f), f.getValue()) + return dataform['FORM_TYPE'].value
S = '' identities.sort(key=sort_identities_key) for i in identities: - c = i['category'] - type_ = i.get('type', '') - lang = i.get('xml:lang', '') - name = i.get('name', '') + c = i.category + type_ = i.type + lang = i.lang or '' + name = i.name or '' S += '%s/%s/%s/%s<' % (c, type_, lang, name) features.sort() for f in features: @@ -135,15 +133,16 @@ def compute_caps_hash(identities, features, dataforms=None, hash_method='sha-1') for dataform in dataforms: # fields indexed by var fields = {} - for f in dataform.getChildren(): - fields[f.getVar()] = f + for f in dataform.iter_fields(): + values = f.getTags('value') + fields[f.var] = [value.getData() for value in values] form_type = fields.get('FORM_TYPE') if form_type: - S += form_type.getValue() + '<' + S += form_type[0] + '<' del fields['FORM_TYPE'] for var in sorted(fields.keys()): S += '%s<' % var - values = sorted(fields[var].getValues()) + values = sorted(fields[var]) for value in values: S += '%s<' % value
@@ -331,27 +330,10 @@ class CapsCache: features = property(_get_features, _set_features)
def _get_identities(self): - list_ = [] - for i in self._identities: - # transforms it back in a dict - d = dict() - d['category'] = i[0] - if i[1]: - d['type'] = i[1] - if i[2]: - d['xml:lang'] = i[2] - if i[3]: - d['name'] = i[3] - list_.append(d) - return list_ + return self._identities
def _set_identities(self, value): - self._identities = [] - for identity in value: - # dict are not hashable, so transform it into a tuple - t = (identity['category'], identity.get('type'), - identity.get('xml:lang'), identity.get('name')) - self._identities.append(self.__names.setdefault(t, t)) + self._identities = value
identities = property(_get_identities, _set_identities)
@@ -432,37 +414,15 @@ class CapsCache:
class MucCapsCache:
- DiscoInfo = namedtuple('DiscoInfo', ['identities', 'features', 'data']) - def __init__(self): self.cache = {}
- def append(self, stanza): - jid = stanza.getFrom() - identities, features, data = [], [], [] - query_childs = stanza.getQueryChildren() - if not query_childs: - log.warning('%s returned empty disco info', jid) - return - - for child in query_childs: - if child.getName() == 'identity': - attr = {} - for key in child.getAttrs().keys(): - attr[key] = child.getAttr(key) - identities.append(attr) - elif child.getName() == 'feature': - features.append(child.getAttr('var')) - elif child.getName() == 'x': - if child.getNamespace() == nbxmpp.NS_DATA: - from gajim.common.modules import dataforms - data.append(dataforms.extend_form(node=child)) - - if nbxmpp.NS_MUC not in features: + def append(self, info): + if nbxmpp.NS_MUC not in info.features: # Not a MUC, don't cache info return
- self.cache[jid] = self.DiscoInfo(identities, features, data) + self.cache[info.jid] = info
def is_cached(self, jid): return jid in self.cache @@ -498,7 +458,7 @@ class MucCapsCache: return allowed
if jid in self.cache: - for form in self.cache[jid].data: + for form in self.cache[jid].dataforms: try: allowed = form['muc#roominfo_changesubject'].value except KeyError: @@ -520,7 +480,7 @@ class MucCapsCache: def get_room_infos(self, jid): room_info = {} if jid in self.cache: - for form in self.cache[jid].data: + for form in self.cache[jid].dataforms: try: room_info['name'] = form['muc#roomconfig_roomname'].value except KeyError:
===================================== gajim/common/logger.py ===================================== @@ -38,6 +38,8 @@ from gzip import GzipFile from io import BytesIO from gi.repository import GLib
+from nbxmpp.structs import DiscoIdentity + from gajim.common import exceptions from gajim.common import app from gajim.common import configpaths @@ -1027,8 +1029,10 @@ class Logger: type_ = data[i + 1] lang = data[i + 2] name = data[i + 3] - identities.append({'category': category, 'type': type_, - 'xml:lang': lang, 'name': name}) + identities.append(DiscoIdentity(category=category, + type=type_, + lang=lang, + name=name)) i += 4 i += 1 while i < len(data): @@ -1046,10 +1050,12 @@ class Logger: data = [] for identity in identities: # there is no FEAT category - if identity['category'] == 'FEAT': + if identity.category == 'FEAT': return - data.extend((identity.get('category'), identity.get('type', ''), - identity.get('xml:lang', ''), identity.get('name', ''))) + data.extend((identity.category, + identity.type, + identity.lang or '', + identity.name or '')) data.append('FEAT') data.extend(features) data = '\0'.join(data)
===================================== gajim/common/modules/blocking.py ===================================== @@ -50,8 +50,8 @@ class Blocking(BaseModule):
self.supported = False
- def pass_disco(self, from_, _identities, features, _data, _node): - if nbxmpp.NS_BLOCKING not in features: + def pass_disco(self, info): + if nbxmpp.NS_BLOCKING not in info.features: return
self.supported = True @@ -60,7 +60,7 @@ class Blocking(BaseModule): account=self._account, feature=nbxmpp.NS_BLOCKING))
- self._log.info('Discovered blocking: %s', from_) + self._log.info('Discovered blocking: %s', info.jid)
def _blocking_list_received(self, result): if is_error_result(result):
===================================== gajim/common/modules/bookmarks.py ===================================== @@ -82,11 +82,11 @@ class Bookmarks(BaseModule): app.nec.push_incoming_event( NetworkEvent('bookmarks-received', account=self._account))
- def pass_disco(self, from_, _identities, features, _data, _node): - if nbxmpp.NS_BOOKMARK_CONVERSION not in features: + def pass_disco(self, info): + if nbxmpp.NS_BOOKMARK_CONVERSION not in info.features: return self._conversion = True - self._log.info('Discovered Bookmarks Conversion: %s', from_) + self._log.info('Discovered Bookmarks Conversion: %s', info.jid)
def _act_on_changed_bookmarks(self, old_bookmarks): new_bookmarks = self._convert_to_set(self._bookmarks)
===================================== gajim/common/modules/bytestream.py ===================================== @@ -95,16 +95,16 @@ class Bytestream(BaseModule): callback=self._ResultCB), ]
- def pass_disco(self, from_, _identities, features, _data, _node): - if nbxmpp.NS_BYTESTREAM not in features: + def pass_disco(self, info): + if nbxmpp.NS_BYTESTREAM not in info.features: return if app.config.get_per('accounts', self._account, 'use_ft_proxies'): - log.info('Discovered proxy: %s', from_) + log.info('Discovered proxy: %s', info.jid) our_fjid = self._con.get_own_jid() testit = app.config.get_per( 'accounts', self._account, 'test_ft_proxies_on_startup') app.proxy65_manager.resolve( - from_, self._con.connection, str(our_fjid), + info.jid, self._con.connection, str(our_fjid), default=self._account, testit=testit) raise nbxmpp.NodeProcessed
===================================== gajim/common/modules/caps.py ===================================== @@ -19,6 +19,7 @@
import nbxmpp from nbxmpp.structs import StanzaHandler +from nbxmpp.util import is_error_result
from gajim.common import caps_cache from gajim.common import app @@ -94,16 +95,21 @@ class Caps(BaseModule): self._account, room_jid, resource) return contact
- def contact_info_received(self, from_, identities, features, data, node): + def contact_info_received(self, info): """ callback to update our caps cache with queried information after we have retrieved an unknown caps hash via a disco """ - bare_jid = from_.getStripped()
- contact = self._get_contact_or_gc_contact_for_jid(from_) + if is_error_result(info): + self._log.info(info) + return + + bare_jid = info.jid.getBare() + + contact = self._get_contact_or_gc_contact_for_jid(info.jid) if not contact: - self._log.info('Received Disco from unknown contact %s', from_) + self._log.info('Received Disco from unknown contact %s', info.jid) return
lookup = contact.client_caps.get_cache_lookup_strategy() @@ -115,10 +121,10 @@ class Caps(BaseModule): return
validate = contact.client_caps.get_hash_validation_strategy() - hash_is_valid = validate(identities, features, data) + hash_is_valid = validate(info.identities, info.features, info.dataforms)
if hash_is_valid: - cache_item.set_and_store(identities, features) + cache_item.set_and_store(info.identities, info.features) else: node = caps_hash = hash_method = None contact.client_caps = self._create_suitable_client_caps( @@ -126,11 +132,10 @@ class Caps(BaseModule): self._log.warning( 'Computed and retrieved caps hash differ. Ignoring ' 'caps of contact %s', contact.get_full_jid()) - app.nec.push_incoming_event( NetworkEvent('caps-update', conn=self._con, - fjid=str(from_), + fjid=str(info.jid), jid=bare_jid))
===================================== gajim/common/modules/carbons.py ===================================== @@ -25,12 +25,12 @@ class Carbons(BaseModule):
self.supported = False
- def pass_disco(self, from_, _identities, features, _data, _node): - if nbxmpp.NS_CARBONS not in features: + def pass_disco(self, info): + if nbxmpp.NS_CARBONS not in info.features: return
self.supported = True - self._log.info('Discovered carbons: %s', from_) + self._log.info('Discovered carbons: %s', info.jid)
iq = nbxmpp.Iq('set') iq.setTag('enable', namespace=nbxmpp.NS_CARBONS)
===================================== gajim/common/modules/discovery.py ===================================== @@ -14,13 +14,11 @@
# XEP-0030: Service Discovery
-import weakref - import nbxmpp from nbxmpp.structs import StanzaHandler +from nbxmpp.util import is_error_result
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.modules.base import BaseModule @@ -28,6 +26,13 @@ from gajim.common.connection_handlers_events import InformationEvent
class Discovery(BaseModule): + + _nbxmpp_extends = 'Discovery' + _nbxmpp_methods = [ + 'disco_info', + 'disco_items', + ] + def __init__(self, con): BaseModule.__init__(self, con)
@@ -44,125 +49,38 @@ class Discovery(BaseModule):
def disco_contact(self, jid, node=None): success_cb = self._con.get_module('Caps').contact_info_received - 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' - self._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, _con, stanza, success_cb, error_cb): - if not nbxmpp.isResultNode(stanza): - if error_cb is not None: - error_cb()(stanza.getFrom(), stanza.getError()) - else: - self._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: - self._log.warning('Wrong query namespace: %s', stanza) - - def parse_items_response(self, 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: - self._log.warning('No jid attr in disco items: %s', stanza) - continue - try: - attr['jid'] = helpers.parse_jid(attr['jid']) - except helpers.InvalidFormat: - self._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 - query = stanza.getTag('query') - node = query.getAttr('node') - if not node: - node = '' - - childs = stanza.getQueryChildren() - if not childs: - childs = [] - - for i in childs: - 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 + self.disco_info(jid, node, callback=success_cb)
def discover_server_items(self): server = self._con.get_own_jid().getDomain() - self.disco_items(server, success_cb=self._server_items_received) + self.disco_items(server, callback=self._server_items_received) + + def _server_items_received(self, result): + if is_error_result(result): + self._log.warning('Server disco failed') + self._log.error(result) + return
- def _server_items_received(self, _from, _node, items): self._log.info('Server items received') - for item in items: - if 'node' in item: + self._log.debug(result) + for item in result.items: + if item.node is not None: # Only disco components continue - self.disco_info(item['jid'], - success_cb=self._server_items_info_received) + self.disco_info(item.jid, callback=self._server_items_info_received) + + def _server_items_info_received(self, result): + if is_error_result(result): + self._log.warning('Server item disco info failed') + self._log.warning(result) + return
- def _server_items_info_received(self, from_, *args): - from_ = from_.getStripped() - self._log.info('Server item info received: %s', from_) - self._parse_transports(from_, *args) + self._log.info('Server item info received: %s', result.jid) + self._parse_transports(result) try: - self._con.get_module('MUC').pass_disco(from_, *args) - self._con.get_module('HTTPUpload').pass_disco(from_, *args) - self._con.get_module('Bytestream').pass_disco(from_, *args) + self._con.get_module('MUC').pass_disco(result) + self._con.get_module('HTTPUpload').pass_disco(result) + self._con.get_module('Bytestream').pass_disco(result) except nbxmpp.NodeProcessed: pass
@@ -171,62 +89,69 @@ class Discovery(BaseModule):
def discover_account_info(self): own_jid = self._con.get_own_jid().getStripped() - self.disco_info(own_jid, success_cb=self._account_info_received) + self.disco_info(own_jid, callback=self._account_info_received)
- def _account_info_received(self, from_, *args): - from_ = from_.getStripped() - self._log.info('Account info received: %s', from_) + def _account_info_received(self, result): + if is_error_result(result): + self._log.warning('Account disco info failed') + self._log.warning(result) + return + + self._log.info('Account info received: %s', result.jid)
- 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) - self._con.get_module('Bookmarks').pass_disco(from_, *args) + self._con.get_module('MAM').pass_disco(result) + self._con.get_module('PEP').pass_disco(result) + self._con.get_module('PubSub').pass_disco(result) + self._con.get_module('Bookmarks').pass_disco(result)
- features = args[1] - if 'urn:xmpp:pep-vcard-conversion:0' in features: + if 'urn:xmpp:pep-vcard-conversion:0' in result.features: self._con.avatar_conversion = True
def discover_server_info(self): # Calling this method starts the connect_maschine() server = self._con.get_own_jid().getDomain() - self.disco_info(server, success_cb=self._server_info_received) + self.disco_info(server, callback=self._server_info_received) + + def _server_info_received(self, result): + if is_error_result(result): + self._log.error('Server disco info failed') + self._log.error(result) + return
- def _server_info_received(self, from_, *args): - self._log.info('Server info received: %s', from_) + self._log.info('Server info received: %s', result.jid)
- 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) - self._con.get_module('HTTPUpload').pass_disco(from_, *args) + self._con.get_module('SecLabels').pass_disco(result) + self._con.get_module('Blocking').pass_disco(result) + self._con.get_module('VCardTemp').pass_disco(result) + self._con.get_module('Carbons').pass_disco(result) + self._con.get_module('PrivacyLists').pass_disco(result) + self._con.get_module('HTTPUpload').pass_disco(result)
- features = args[1] - if nbxmpp.NS_REGISTER in features: + if nbxmpp.NS_REGISTER in result.features: self._con.register_supported = True
- if nbxmpp.NS_ADDRESS in features: + if nbxmpp.NS_ADDRESS in result.features: self._con.addressing_supported = True
self._con.connect_machine(restart=True)
- def _parse_transports(self, from_, identities, _features, _data, _node): - for identity in identities: - category = identity.get('category') - if category not in ('gateway', 'headline'): + def _parse_transports(self, info): + for identity in info.identities: + if identity.category not in ('gateway', 'headline'): continue - transport_type = identity.get('type') + self._log.info('Found transport: %s %s %s', - from_, category, transport_type) - jid = str(from_) + info.jid, info.category, identity.type) + + jid = str(info.jid) if jid not in app.transport_type: - app.transport_type[jid] = transport_type - app.logger.save_transport_type(jid, transport_type) + app.transport_type[jid] = identity.type + app.logger.save_transport_type(jid, identity.type)
- if transport_type in self._con.available_transports: - self._con.available_transports[transport_type].append(jid) + if identity.type in self._con.available_transports: + self._con.available_transports[identity.type].append(jid) else: - self._con.available_transports[transport_type] = [jid] + self._con.available_transports[identity.type] = [jid]
def _answer_disco_items(self, _con, stanza, _properties): from_ = stanza.getFrom() @@ -260,7 +185,7 @@ class Discovery(BaseModule): query = iq.setQuery() if node: query.setAttr('node', node) - query.addChild('identity', attrs=app.gajim_identity) + query.addChild('identity', attrs=app.gajim_identity._asdict()) client_version = 'http://gajim.org#' + app.caps_hash[self._account]
if node in (None, client_version): @@ -279,32 +204,31 @@ class Discovery(BaseModule): callback() return
- iq = nbxmpp.Iq(typ='get', to=jid, queryNS=nbxmpp.NS_DISCO_INFO) self._log.info('Request MUC info %s', jid)
- self._con.connection.SendAndCallForResponse( - iq, self._muc_info_response, {'callback': callback}) + self.disco_info(jid, + callback=self._muc_info_response, + user_data=callback)
- def _muc_info_response(self, _con, stanza, callback): - if not nbxmpp.isResultNode(stanza): - error = stanza.getError() - if error == 'item-not-found': + def _muc_info_response(self, result, callback): + if is_error_result(result): + if result.type == 'item-not-found': # Groupchat does not exist - self._log.info('MUC does not exist: %s', stanza.getFrom()) + self._log.info('MUC does not exist: %s', result.jid) callback() else: - self._log.info('MUC disco error: %s', error) + self._log.info('MUC disco error: %s', result) app.nec.push_incoming_event( InformationEvent( None, dialog_name='unable-join-groupchat', kwargs={ - 'server':stanza.getFrom(), - 'error': error})) + 'server': result.jid, + 'error': str(result)})) return
- self._log.info('MUC info received: %s', stanza.getFrom()) - muc_caps_cache.append(stanza) + self._log.info('MUC info received: %s', result.jid) + muc_caps_cache.append(result) callback()
===================================== gajim/common/modules/httpupload.py ===================================== @@ -71,22 +71,22 @@ class HTTPUpload(BaseModule): ged.OUT_PREGUI, self.handle_outgoing_stanza)
- def pass_disco(self, from_, _identities, features, data, _node): - if NS_HTTPUPLOAD_0 in features: + def pass_disco(self, info): + if NS_HTTPUPLOAD_0 in info.features: self.httpupload_namespace = NS_HTTPUPLOAD_0 - elif NS_HTTPUPLOAD in features: + elif NS_HTTPUPLOAD in info.features: self.httpupload_namespace = NS_HTTPUPLOAD else: return
- self.component = from_ - self._log.info('Discovered component: %s', from_) + self.component = info.jid + self._log.info('Discovered component: %s', info.jid)
- for form in data: + for form in info.dataforms: form_dict = form.asDict() - if form_dict.get('FORM_TYPE', None) != self.httpupload_namespace: + if form_dict.get('FORM_TYPE') != self.httpupload_namespace: continue - size = form_dict.get('max-file-size', None) + size = form_dict.get('max-file-size') if size is not None: self.max_file_size = int(size) break
===================================== gajim/common/modules/mam.py ===================================== @@ -52,16 +52,17 @@ class MAM(BaseModule): # Holds archive jids where catch up was successful self._catch_up_finished = []
- def pass_disco(self, from_, _identities, features, _data, _node): - if nbxmpp.NS_MAM_2 in features: + def pass_disco(self, info): + if nbxmpp.NS_MAM_2 in info.features: self.archiving_namespace = nbxmpp.NS_MAM_2 - elif nbxmpp.NS_MAM_1 in features: + elif nbxmpp.NS_MAM_1 in info.features: self.archiving_namespace = nbxmpp.NS_MAM_1 else: return
self.available = True - self._log.info('Discovered MAM %s: %s', self.archiving_namespace, from_) + self._log.info('Discovered MAM %s: %s', + self.archiving_namespace, info.jid)
app.nec.push_incoming_event( NetworkEvent('feature-discovered',
===================================== gajim/common/modules/muc.py ===================================== @@ -103,16 +103,16 @@ class MUC(BaseModule):
self._muc_data = {}
- def pass_disco(self, from_, identities, features, _data, _node): - for identity in identities: - if identity.get('category') != 'conference': + def pass_disco(self, info): + for identity in info.identities: + if identity.category != 'conference': continue - if identity.get('type') != 'text': + if identity.type != 'text': continue - if nbxmpp.NS_MUC in features: - self._log.info('Discovered MUC: %s', from_) + if nbxmpp.NS_MUC in info.features: + self._log.info('Discovered MUC: %s', info.jid) # TODO: make this nicer - self._con.muc_jid['jabber'] = from_ + self._con.muc_jid['jabber'] = str(info.jid) raise nbxmpp.NodeProcessed
def _get_muc_data(self, room_jid):
===================================== gajim/common/modules/pep.py ===================================== @@ -15,12 +15,8 @@ # XEP-0163: Personal Eventing Protocol
from typing import Any -from typing import Dict -from typing import List from typing import Tuple
-import nbxmpp - from gajim.common.types import ConnectionT from gajim.common.modules.base import BaseModule
@@ -31,16 +27,11 @@ class PEP(BaseModule):
self.supported = False
- def pass_disco(self, - from_: nbxmpp.JID, - identities: List[Dict[str, str]], - _features: List[str], - _data: List[nbxmpp.DataForm], - _node: str) -> None: - for identity in identities: - if identity['category'] == 'pubsub': - if identity.get('type') == 'pep': - self._log.info('Discovered PEP support: %s', from_) + def pass_disco(self, info): + for identity in info.identities: + if identity.category == 'pubsub': + if identity.type == 'pep': + self._log.info('Discovered PEP support: %s', info.jid) self.supported = True
===================================== gajim/common/modules/privacylists.py ===================================== @@ -45,12 +45,12 @@ class PrivacyLists(BaseModule):
self.supported = False
- def pass_disco(self, from_, _identities, features, _data, _node): - if nbxmpp.NS_PRIVACY not in features: + def pass_disco(self, info): + if nbxmpp.NS_PRIVACY not in info.features: return
self.supported = True - self._log.info('Discovered XEP-0016: Privacy Lists: %s', from_) + self._log.info('Discovered XEP-0016: Privacy Lists: %s', info.jid)
app.nec.push_incoming_event( NetworkEvent('feature-discovered',
===================================== gajim/common/modules/pubsub.py ===================================== @@ -34,12 +34,12 @@ class PubSub(BaseModule):
self.publish_options = False
- def pass_disco(self, from_, _identities, features, _data, _node): - if nbxmpp.NS_PUBSUB_PUBLISH_OPTIONS not in features: + def pass_disco(self, info): + if nbxmpp.NS_PUBSUB_PUBLISH_OPTIONS not in info.features: # Remove stored bookmarks accessible to everyone. self._con.get_module('Bookmarks').purge_pubsub_bookmarks() return - self._log.info('Discovered Pubsub publish options: %s', from_) + self._log.info('Discovered Pubsub publish options: %s', info.jid) self.publish_options = True
def send_pb_subscription_query(self, jid, cb, **kwargs):
===================================== gajim/common/modules/security_labels.py ===================================== @@ -28,12 +28,12 @@ class SecLabels(BaseModule): self._catalogs = {} self.supported = False
- def pass_disco(self, from_, _identities, features, _data, _node): - if nbxmpp.NS_SECLABEL not in features: + def pass_disco(self, info): + if nbxmpp.NS_SECLABEL not in info.features: return
self.supported = True - self._log.info('Discovered security labels: %s', from_) + self._log.info('Discovered security labels: %s', info.jid)
def request_catalog(self, jid): server = app.get_jid_from_account(self._account).split("@")[1]
===================================== gajim/common/modules/vcard_temp.py ===================================== @@ -36,12 +36,12 @@ class VCardTemp(BaseModule): self.room_jids = [] self.supported = False
- def pass_disco(self, from_, _identities, features, _data, _node): - if nbxmpp.NS_VCARD not in features: + def pass_disco(self, info): + if nbxmpp.NS_VCARD not in info.features: return
self.supported = True - self._log.info('Discovered vcard-temp: %s', from_) + self._log.info('Discovered vcard-temp: %s', info.jid)
app.nec.push_incoming_event(NetworkEvent('feature-discovered', account=self._account,
===================================== gajim/gtk/discovery.py ===================================== @@ -46,6 +46,8 @@ import weakref import uuid
import nbxmpp +from nbxmpp.util import is_error_result +from nbxmpp.structs import DiscoIdentity
from gi.repository import GLib from gi.repository import Gtk @@ -290,7 +292,7 @@ class ServicesCache: # Grab the first identity with an icon for identity in identities: try: - cat, type_ = identity['category'], identity['type'] + cat, type_ = identity.category, identity.type info = _agent_type_info[(cat, type_)] except KeyError: continue @@ -318,7 +320,7 @@ class ServicesCache: # First pass, we try to find a ToplevelAgentBrowser for identity in identities: try: - cat, type_ = identity['category'], identity['type'] + cat, type_ = identity.category, identity.type info = _agent_type_info[(cat, type_)] except KeyError: continue @@ -329,7 +331,7 @@ class ServicesCache: # Second pass, we haven't found a ToplevelAgentBrowser for identity in identities: try: - cat, type_ = identity['category'], identity['type'] + cat, type_ = identity.category, identity.type info = _agent_type_info[(cat, type_)] except KeyError: continue @@ -369,7 +371,7 @@ class ServicesCache: self._cbs[cbkey] = [cb] con = app.connections[self.account] con.get_module('Discovery').disco_info( - jid, node, self._disco_info_received, self._disco_info_error) + jid, node, callback=self._disco_info_received)
def get_items(self, jid, node, cb, force=False, nofetch=False, args=()): """ @@ -395,56 +397,73 @@ class ServicesCache: self._cbs[cbkey] = [cb] con = app.connections[self.account] con.get_module('Discovery').disco_items( - jid, node, self._disco_items_received, self._disco_items_error) + jid, node, callback=self._disco_items_received)
- def _disco_info_received(self, from_, identities, features, data, node): + def _disco_info_received(self, result): """ Callback for when we receive an agent's info array is (agent, node, identities, features, data) """ + + if is_error_result(result): + self._disco_info_error(result) + return + + identities = result.identities if not identities: # Ejabberd doesn't send identities when using admin nodes - identities = [{'category': 'server', 'type': 'im', 'name': node}] + identities = [DiscoIdentity(category='server', + type='im', + name=result.node)]
- self._on_agent_info(str(from_), node, identities, features, data) + self._on_agent_info(str(result.jid), result.node, result.identities, + result.features, result.dataforms)
- def _disco_info_error(self, from_, error): + def _disco_info_error(self, result): """ Callback for when a query fails. Even after the browse and agents namespaces """ - addr = get_agent_address(from_) + addr = get_agent_address(result.jid)
# Call callbacks cbkey = ('info', addr) if cbkey in self._cbs: for cb in self._cbs[cbkey]: - cb(str(from_), '', 0, 0, 0) + cb(str(result.jid), '', 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, dataforms): addr = get_agent_address(fjid, node)
# Store in cache - self._info[addr] = (identities, features, data) + self._info[addr] = (identities, features, dataforms)
# Call callbacks cbkey = ('info', addr) if cbkey in self._cbs: for cb in self._cbs[cbkey]: - cb(fjid, node, identities, features, data) + cb(fjid, node, identities, features, dataforms) # clean_closure may have beaten us to it if cbkey in self._cbs: del self._cbs[cbkey]
- def _disco_items_received(self, from_, node, items): + def _disco_items_received(self, result): """ Callback for when we receive an agent's items array is (agent, node, items) """ - addr = get_agent_address(from_, node) + if is_error_result(result): + self._disco_items_error(result) + return + + addr = get_agent_address(result.jid, result.node) + + items = [] + for item in result.items: + items.append(item._asdict())
# Store in cache self._items[addr] = items @@ -453,23 +472,23 @@ class ServicesCache: cbkey = ('items', addr) if cbkey in self._cbs: for cb in self._cbs[cbkey]: - cb(str(from_), node, items) + cb(str(result.jid), result.node, items) # clean_closure may have beaten us to it if cbkey in self._cbs: del self._cbs[cbkey]
- def _disco_items_error(self, from_, error): + def _disco_items_error(self, result): """ Callback for when a query fails. Even after the browse and agents namespaces """ - addr = get_agent_address(from_) + addr = get_agent_address(result.jid)
# Call callbacks cbkey = ('items', addr) if cbkey in self._cbs: for cb in self._cbs[cbkey]: - cb(str(from_), '', 0) + cb(str(result.jid), '', 0) # clean_closure may have beaten us to it if cbkey in self._cbs: del self._cbs[cbkey] @@ -479,13 +498,13 @@ class ServiceDiscoveryWindow: """ Class that represents the Services Discovery window """ - def __init__(self, account, jid='', node='', address_entry=False, + def __init__(self, account, jid='', node=None, address_entry=False, parent=None, initial_identities=None): self._account = account self.parent = parent if not jid: jid = app.config.get_per('accounts', account, 'hostname') - node = '' + node = None
self.jid = None self.browser = None @@ -718,7 +737,7 @@ class ServiceDiscoveryWindow: except helpers.InvalidFormat as s: ErrorDialog(_('Invalid Server Name'), str(s)) return - self.travel(jid, '') + self.travel(jid, None)
def _on_go_button_clicked(self, widget): jid = self._ui.address_comboboxtext_entry.get_text() @@ -741,7 +760,7 @@ class ServiceDiscoveryWindow: self.address_comboboxtext.append_text(j) app.config.set('latest_disco_addresses', ' '.join(self.latest_addresses)) - self.travel(jid, '') + self.travel(jid, None)
def _on_services_treeview_row_activated(self, widget, path, col=0): if self.browser: @@ -849,11 +868,11 @@ class AgentBrowser: if len(identities) > 1: # Check if an identity with server category is present for _index, identity in enumerate(identities): - if identity['category'] == 'server' and 'name' in identity: - name = identity['name'] + if identity.category == 'server' and identity.name is not None: + name = identity.name break - elif 'name' in identities[0]: - name = identities[0]['name'] + elif identities[0].name is not None: + name = identities[0].name
if name: self.window._set_window_banner_text(self._get_agent_address(), name) @@ -1016,7 +1035,7 @@ class AgentBrowser: GLib.source_remove(self._pulse_timeout) self.window.progressbar.hide() # The server returned an error - if items == 0: + if not items: if not self.window.address_comboboxtext: # We can't travel anywhere else. self.window.destroy() @@ -1088,7 +1107,7 @@ class AgentBrowser: Called when an item should be updated in the model with further info. The result of a disco#info query """ - name = identities[0].get('name', '') + name = identities[0].name or '' if name: self.model[iter_][2] = name
@@ -1123,7 +1142,9 @@ class ToplevelAgentBrowser(AgentBrowser): type_ = app.get_transport_name_from_jid(self.jid, use_config_setting=False) if type_: - identity = {'category': '_jid', 'type': type_} + identity = DiscoIdentity(category='_jid', + type=type_, + name=None) identities.append(identity) # Set the pixmap for the row icon_name = self.cache.get_icon(identities, addr=addr) @@ -1321,7 +1342,6 @@ class ToplevelAgentBrowser(AgentBrowser): jid = model[iter_][0] if jid: ServiceRegistration(self.account, jid) - self.window.destroy(chain=True)
def _on_join_button_clicked(self, widget): """ @@ -1366,7 +1386,7 @@ class ToplevelAgentBrowser(AgentBrowser): type_ = app.get_transport_name_from_jid( jid, use_config_setting=False) if type_: - identity = {'category': '_jid', 'type': type_} + identity = DiscoIdentity(category='_jid', type=type_) klass = self.cache.get_browser([identity]) if klass: self.browse_button.set_sensitive(True) @@ -1530,7 +1550,7 @@ class ToplevelAgentBrowser(AgentBrowser): type_ = app.get_transport_name_from_jid( jid, use_config_setting=False) if type_: - identity = {'category': '_jid', 'type': type_} + identity = DiscoIdentity(category='_jid', type=type_) identities.append(identity) cat_args = ('_jid', type_) else: @@ -1561,7 +1581,7 @@ class ToplevelAgentBrowser(AgentBrowser): if not identities: descr = "<b>%s</b>" % addr else: - name = identities[0].get('name', '') + name = identities[0].name or '' if name: descr = "<b>%s</b>\n%s" % (name, addr) else: @@ -1576,7 +1596,7 @@ class ToplevelAgentBrowser(AgentBrowser): cat, type_ = None, None for identity in identities: try: - cat, type_ = identity['category'], identity['type'] + cat, type_ = identity.category, identity.type except KeyError: continue break @@ -1839,19 +1859,19 @@ class MucBrowser(AgentBrowser): self._fetch_source = GLib.idle_add(self._start_info_query)
def _update_info(self, iter_, jid, node, identities, features, data): - name = identities[0].get('name', '') + name = identities[0].name or '' for form in data: - typefield = form.getField('FORM_TYPE') - if typefield and typefield.getValue() == \ + typefield = form.vars.get('FORM_TYPE') + if typefield and typefield.value == \ 'http://jabber.org/protocol/muc#roominfo': # Fill model row from the form's fields - users = form.getField('muc#roominfo_occupants') - descr = form.getField('muc#roominfo_description') + users = form.vars.get('muc#roominfo_occupants') + descr = form.vars.get('muc#roominfo_description') if users: - self.model[iter_][3] = int(users.getValue()) - self.model[iter_][4] = users.getValue() - if descr and descr.getValue(): - self.model[iter_][5] = descr.getValue() + self.model[iter_][3] = int(users.value) + self.model[iter_][4] = users.value + if descr and descr.value: + self.model[iter_][5] = descr.value # Only set these when we find a form with additional info # Some servers don't support forms and put extra info in # the name attribute, so we preserve it in that case. @@ -1963,7 +1983,8 @@ class DiscussionGroupsBrowser(AgentBrowser): Called when we got basic information about new node from query. Show the item """ - name = item.get('name', '') + + name = item['name'] or ''
if self.subscriptions is not None: dunno = False
===================================== gajim/gtk/join_groupchat.py ===================================== @@ -12,10 +12,14 @@ # You should have received a copy of the GNU General Public License # along with Gajim. If not, see http://www.gnu.org/licenses/.
-import nbxmpp + from gi.repository import Gtk from gi.repository import Gdk
+import nbxmpp +from nbxmpp.util import is_error_result +from nbxmpp.structs import DiscoIdentity + from gajim.common import app from gajim.common import helpers from gajim.common.i18n import _ @@ -272,23 +276,26 @@ class JoinGroupchatWindow(Gtk.ApplicationWindow): server = self.server_combo.get_active_text().strip() con = app.connections[self.account] con.get_module('Discovery').disco_info( - server, - success_cb=self._disco_info_received, - error_cb=self._disco_info_error) + server, callback=self._disco_info_received)
def _disco_info_error(self, from_, error): ErrorDialog(_('Wrong server'), _('%s is not a groupchat server') % from_, transient_for=self)
- def _disco_info_received(self, from_, identities, features, data, node): - if nbxmpp.NS_MUC not in features: + def _disco_info_received(self, result): + if is_error_result(result): + ErrorDialog(_('Wrong server'), + _('%s is not a groupchat server') % result.jid, + transient_for=self) + return + if nbxmpp.NS_MUC not in result.features: ErrorDialog(_('Wrong server'), - _('%s is not a groupchat server') % from_, + _('%s is not a groupchat server') % result.jid, transient_for=self) return
- jid = str(from_) + jid = str(result.jid) if jid in app.interface.instances[self.account]['disco']: app.interface.instances[self.account]['disco'][jid].window.\ present() @@ -297,7 +304,8 @@ class JoinGroupchatWindow(Gtk.ApplicationWindow): # Object will add itself to the window dict ServiceDiscoveryWindow( self.account, jid, - initial_identities=[{'category': 'conference', - 'type': 'text'}]) + initial_identities=[DiscoIdentity(category='conference', + type='text', + name=None)]) except GajimGeneralException: pass
===================================== gajim/gtk/pep_config.py ===================================== @@ -14,6 +14,7 @@
from gi.repository import Gdk from gi.repository import Gtk +from nbxmpp.util import is_error_result
from gajim.common import app from gajim.common import ged @@ -83,18 +84,17 @@ class ManagePEPServicesWindow(Gtk.ApplicationWindow):
jid = self._con.get_own_jid().getStripped() self._con.get_module('Discovery').disco_items( - jid, - success_cb=self._items_received, - error_cb=self._items_error) + jid, callback=self._items_received)
- def _items_received(self, from_, node, items): - jid = self._con.get_own_jid().getStripped() - for item in items: - if item['jid'] == jid and 'node' in item: - self.treestore.append([item['node']]) + def _items_received(self, result): + if is_error_result(result): + ErrorDialog('Error', str(result)) + return
- def _items_error(self, from_, error): - ErrorDialog('Error', error) + jid = result.jid.getBare() + for item in result.items: + if item.jid == jid and item.node is not None: + self.treestore.append([item.node])
def _node_removed(self, jid, node): if jid != app.get_jid_from_account(self.account):
===================================== test/no_gui/unit/test_caps_cache.py ===================================== @@ -2,14 +2,16 @@ Tests for capabilities and the capabilities cache ''' import unittest +import weakref
from unittest.mock import MagicMock, Mock from nbxmpp import NS_MUC, NS_PING, NS_XHTML_IM, Iq +from nbxmpp.structs import DiscoIdentity +from nbxmpp.modules.discovery import Discovery from gajim.common import caps_cache as caps -from gajim.common.modules.discovery import Discovery
EXAMPLES = [ -'''<iq> +'''<iq type='result'> <query xmlns='http://jabber.org/protocol/disco#info' node='http://psi-im.org#q07IKJEyjvHSyhy//CH0CxmKi8w='> <identity xml:lang='en' category='client' name='Psi 0.11' type='pc'/> <identity xml:lang='el' category='client' name='Ψ 0.11' type='pc'/> @@ -42,7 +44,7 @@ EXAMPLES = [ </iq> ''',
-'''<iq> +'''<iq type='result'> <query node="http://bombusmod.net.ru/caps#tbBQGBMv8g8U7kW55TEZZRnMCJ4=" xmlns="http://jabber.org/protocol/disco#info"> <identity category="client" name="BombusMod" type="mobile"/> <feature var="http://jabber.org/protocol/chatstates"/> @@ -62,7 +64,7 @@ EXAMPLES = [ </iq> ''',
-'''<iq> +'''<iq type='result'> <query node="http://jappix.org/#qRsaGbKTz8EwAOakYO00InkZUxM=" xmlns="http://jabber.org/protocol/disco#info"> <identity category="client" name="Jappix" type="web"/> <feature var="http://jabber.org/protocol/activity"/> @@ -137,10 +139,12 @@ class CommonCapsTest(unittest.TestCase): self.client_caps = (self.caps_method, self.caps_hash)
self.node = "http://gajim.org" - self.identity = {'category': 'client', 'type': 'pc', 'name':'Gajim'} + self.identity = DiscoIdentity(category='client', + type='pc', + name='Gajim')
self.identities = [self.identity] - self.features = [NS_MUC, NS_XHTML_IM] # NS_MUC not supported! + self.features = [NS_MUC, NS_XHTML_IM]
# Simulate a filled db db_caps_cache = [ @@ -169,8 +173,8 @@ class TestCapsCache(CommonCapsTest): self.assertEqual(1, len(identities))
identity = identities[0] - self.assertEqual('client', identity['category']) - self.assertEqual('pc', identity['type']) + self.assertEqual('client', identity.category) + self.assertEqual('pc', identity.type)
def test_set_and_store(self): ''' Test client_caps update gets logged into db ''' @@ -214,10 +218,14 @@ class TestCapsCache(CommonCapsTest): '''tests the hash computation''' for example in EXAMPLES: stanza = Iq(node=example) - identities, features, data, node = Discovery.parse_info_response(stanza) - computed_hash = caps.compute_caps_hash(identities, features, data) - hash_ = node.split('#')[1] - self.assertEqual(hash_, computed_hash) + disco_module = Discovery(None) + weak_callback = weakref.WeakMethod(self.disco_info_received) + disco_module._disco_info_received(None, stanza, callback=weak_callback) + + def disco_info_received(self, info): + computed_hash = caps.compute_caps_hash(info.identities, info.features, info.dataforms) + hash_ = info.node.split('#')[1] + self.assertEqual(hash_, computed_hash)
class TestClientCaps(CommonCapsTest):
View it on GitLab: https://dev.gajim.org/gajim/gajim/commit/9c53fcbd898bd0d61e5d16f8eac586fda86...