From 07c87a4194baafc557f43b64c657e9ff2b87c7b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20H=C3=B6rist?= Date: Sun, 22 Jul 2018 12:18:24 +0200 Subject: [PATCH] Rewrite discovery code and move it into own module --- gajim/common/caps_cache.py | 12 +- gajim/common/connection.py | 129 +------- gajim/common/connection_handlers.py | 220 +------------ gajim/common/connection_handlers_events.py | 114 ------- gajim/common/modules/__init__.py | 5 +- gajim/common/modules/blocking.py | 15 +- gajim/common/modules/bookmarks.py | 6 +- gajim/common/modules/carbons.py | 32 ++ gajim/common/modules/discovery.py | 295 ++++++++++++++++++ gajim/common/modules/httpupload.py | 33 +- gajim/common/modules/mam.py | 14 + gajim/common/modules/muc.py | 12 + gajim/common/modules/pep.py | 12 +- gajim/common/modules/privacylists.py | 20 +- gajim/common/modules/pubsub.py | 10 + gajim/common/modules/security_labels.py | 7 + gajim/common/modules/vcard_temp.py | 13 + gajim/common/protocol/bytestream.py | 15 +- gajim/common/protocol/caps.py | 37 ++- gajim/common/zeroconf/client_zeroconf.py | 2 - .../zeroconf/connection_handlers_zeroconf.py | 17 - gajim/config.py | 21 +- gajim/disco.py | 93 ++---- gajim/groupchat_control.py | 4 +- gajim/gtk/join_groupchat.py | 49 +-- gajim/gtk/server_info.py | 27 +- gajim/gui_interface.py | 10 +- gajim/gui_menu_builder.py | 6 +- gajim/roster_window.py | 34 +- test/lib/mock.py | 3 + test/unit/test_caps_cache.py | 33 +- 31 files changed, 621 insertions(+), 679 deletions(-) create mode 100644 gajim/common/modules/discovery.py diff --git a/gajim/common/caps_cache.py b/gajim/common/caps_cache.py index 659f2f88b..a4539535b 100644 --- a/gajim/common/caps_cache.py +++ b/gajim/common/caps_cache.py @@ -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 diff --git a/gajim/common/connection.py b/gajim/common/connection.py index b0947a9fe..e2ad9bb61 100644 --- a/gajim/common/connection.py +++ b/gajim/common/connection.py @@ -103,7 +103,6 @@ class CommonConnection: self.priority = app.get_priority(name, 'offline') self.time_to_reconnect = None - self.pep_supported = False self.pep = {} # Do we continue connection when we get roster (send presence,get vcard..) self.continue_connect_info = None @@ -115,13 +114,9 @@ class CommonConnection: # the fake jid self.groupchat_jids = {} # {ID : groupchat_jid} - self.privacy_rules_supported = False - self.vcard_supported = False self.private_storage_supported = False self.roster_supported = True - self.blocking_supported = False self.addressing_supported = False - self.carbons_available = False self.muc_jid = {} # jid of muc server for each transport type self._stun_servers = [] # STUN servers of our jabber server @@ -582,7 +577,6 @@ class Connection(CommonConnection, ConnectionHandlers): self.music_track_info = 0 self.register_supported = False - self.pubsub_publish_options_supported = False # Do we auto accept insecure connection self.connection_auto_accepted = False self.pasword_callback = None @@ -611,10 +605,6 @@ class Connection(CommonConnection, ConnectionHandlers): # Register all modules modules.register(self) - app.ged.register_event_handler('agent-info-error-received', ged.CORE, - self._nec_agent_info_error_received) - app.ged.register_event_handler('agent-info-received', ged.CORE, - self._nec_agent_info_received) app.ged.register_event_handler('message-outgoing', ged.OUT_CORE, self._nec_message_outgoing) app.ged.register_event_handler('gc-message-outgoing', ged.OUT_CORE, @@ -629,10 +619,6 @@ class Connection(CommonConnection, ConnectionHandlers): ConnectionHandlers.cleanup(self) modules.unregister(self) - app.ged.remove_event_handler('agent-info-error-received', ged.CORE, - self._nec_agent_info_error_received) - app.ged.remove_event_handler('agent-info-received', ged.CORE, - self._nec_agent_info_received) app.ged.remove_event_handler('message-outgoing', ged.OUT_CORE, self._nec_message_outgoing) app.ged.remove_event_handler('gc-message-outgoing', ged.OUT_CORE, @@ -699,7 +685,7 @@ class Connection(CommonConnection, ConnectionHandlers): self.on_purpose = on_purpose self.connected = 0 self.time_to_reconnect = None - self.privacy_rules_supported = False + self.get_module('PrivacyLists').supported = False self.get_module('VCardAvatars').avatar_advertised = False if on_purpose: self.sm = Smacks(self) @@ -1430,7 +1416,7 @@ class Connection(CommonConnection, ConnectionHandlers): def send_invisible_presence(self, msg, signed, initial = False): if not app.account_is_connected(self.name): return - if not self.privacy_rules_supported: + if not self.get_module('PrivacyLists').supported: app.nec.push_incoming_event(OurShowEvent(None, conn=self, show=app.SHOW_LIST[self.connected])) app.nec.push_incoming_event(InformationEvent( @@ -1438,7 +1424,7 @@ class Connection(CommonConnection, ConnectionHandlers): return # If we are already connected, and privacy rules are supported, send # offline presence first as it's required by XEP-0126 - if self.connected > 1 and self.privacy_rules_supported: + if self.connected > 1 and self.get_module('PrivacyLists').supported: self.on_purpose = True p = nbxmpp.Presence(typ='unavailable') p = self.add_sha(p, False) @@ -1515,10 +1501,9 @@ class Connection(CommonConnection, ConnectionHandlers): # If we are not resuming, we ask for discovery info # and archiving preferences if not self.sm.supports_sm or (not self.sm.resuming and self.sm.enabled): - our_jid = app.get_jid_from_account(self.name) our_server = app.config.get_per('accounts', self.name, 'hostname') - self.discoverInfo(our_jid, id_prefix='Gajim_') - self.discoverInfo(our_server, id_prefix='Gajim_') + self.get_module('Discovery').discover_account_info() + self.get_module('Discovery').discover_server_info() else: self.request_roster(resume=True) @@ -1534,7 +1519,7 @@ class Connection(CommonConnection, ConnectionHandlers): self._stun_servers = self._hosts = [i for i in result_array] def _continue_connection_request_privacy(self): - if self.privacy_rules_supported: + if self.get_module('PrivacyLists').supported: if not self.privacy_rules_requested: self.privacy_rules_requested = True self.get_module('PrivacyLists').get_privacy_lists( @@ -1557,104 +1542,12 @@ class Connection(CommonConnection, ConnectionHandlers): None, dialog_name='invisibility-not-supported', args=self.name)) return - if self.blocking_supported: - self.get_module('Blocking').get_blocking_list() + + self.get_module('Blocking').get_blocking_list() # Ask metacontacts before roster self.get_metacontacts() - def _nec_agent_info_error_received(self, obj): - if obj.conn.name != self.name: - return - hostname = app.config.get_per('accounts', self.name, 'hostname') - if obj.id_[:6] == 'Gajim_' and obj.fjid == hostname: - self._continue_connection_request_privacy() - - def _nec_agent_info_received(self, obj): - if obj.conn.name != self.name: - return - is_muc = False - transport_type = '' - for identity in obj.identities: - if 'category' in identity and identity['category'] in ('gateway', - 'headline') and 'type' in identity: - transport_type = identity['type'] - if 'category' in identity and identity['category'] == 'server' and \ - 'type' in identity and identity['type'] == 'im': - transport_type = 'jabber' # it's a jabber server - if 'category' in identity and identity['category'] == 'conference' \ - and 'type' in identity and identity['type'] == 'text': - is_muc = True - - if transport_type != '' and obj.fjid not in app.transport_type: - app.transport_type[obj.fjid] = transport_type - app.logger.save_transport_type(obj.fjid, transport_type) - - if obj.id_[:6] == 'Gajim_': - hostname = app.config.get_per('accounts', self.name, 'hostname') - our_jid = app.get_jid_from_account(self.name) - - if obj.fjid == our_jid: - if nbxmpp.NS_MAM_2 in obj.features: - self.get_module('MAM').archiving_namespace = nbxmpp.NS_MAM_2 - elif nbxmpp.NS_MAM_1 in obj.features: - self.get_module('MAM').archiving_namespace = nbxmpp.NS_MAM_1 - if self.get_module('MAM').archiving_namespace: - self.get_module('MAM').available = True - get_action(self.name + '-archive').set_enabled(True) - for identity in obj.identities: - if identity['category'] == 'pubsub': - self.pep_supported = identity.get('type') == 'pep' - break - if nbxmpp.NS_PUBSUB_PUBLISH_OPTIONS in obj.features: - self.pubsub_publish_options_supported = True - else: - # Remove stored bookmarks accessible to everyone. - self.get_module('Bookmarks').purge_pubsub_bookmarks() - - if obj.fjid == hostname: - if nbxmpp.NS_SECLABEL in obj.features: - self.get_module('SecLabels').supported = True - if nbxmpp.NS_VCARD in obj.features: - self.vcard_supported = True - get_action(self.name + '-profile').set_enabled(True) - if nbxmpp.NS_REGISTER in obj.features: - self.register_supported = True - if nbxmpp.NS_BLOCKING in obj.features: - self.blocking_supported = True - if nbxmpp.NS_ADDRESS in obj.features: - self.addressing_supported = True - if nbxmpp.NS_CARBONS in obj.features: - self.carbons_available = True - if app.config.get_per('accounts', self.name, - 'enable_message_carbons'): - # Server supports carbons, activate it - iq = nbxmpp.Iq('set') - iq.setTag('enable', namespace=nbxmpp.NS_CARBONS) - self.connection.send(iq) - if nbxmpp.NS_PRIVACY in obj.features: - self.privacy_rules_supported = True - get_action(self.name + '-privacylists').set_enabled(True) - - self._continue_connection_request_privacy() - - if nbxmpp.NS_BYTESTREAM in obj.features and \ - app.config.get_per('accounts', self.name, 'use_ft_proxies'): - our_fjid = helpers.parse_jid(our_jid + '/' + \ - self.server_resource) - testit = app.config.get_per('accounts', self.name, - 'test_ft_proxies_on_startup') - app.proxy65_manager.resolve(obj.fjid, self.connection, - our_fjid, default=self.name, testit=testit) - if nbxmpp.NS_MUC in obj.features and is_muc: - type_ = transport_type or 'jabber' - self.muc_jid[type_] = obj.fjid - if transport_type: - if transport_type in self.available_transports: - self.available_transports[transport_type].append(obj.fjid) - else: - self.available_transports[transport_type] = [obj.fjid] - def send_custom_status(self, show, msg, jid): if not show in app.SHOW_LIST: return -1 @@ -1684,7 +1577,7 @@ class Connection(CommonConnection, ConnectionHandlers): self.send_invisible_presence(msg, signed) def _change_from_invisible(self): - if self.privacy_rules_supported: + if self.get_module('PrivacyLists').supported: self.get_module('PrivacyLists').set_active_list(None) def _update_status(self, show, msg, idle_time=None): @@ -1897,7 +1790,7 @@ class Connection(CommonConnection, ConnectionHandlers): def bookmarks_available(self): if self.private_storage_supported: return True - if self.pubsub_publish_options_supported: + if self.get_module('PubSub').publish_options: return True return False @@ -2022,7 +1915,7 @@ class Connection(CommonConnection, ConnectionHandlers): # Never join a room when invisible return - self.discoverMUC( + self.get_module('Discovery').disco_muc( room_jid, partial(self._join_gc, nick, show, room_jid, password, change_nick, rejoin)) diff --git a/gajim/common/connection_handlers.py b/gajim/common/connection_handlers.py index c4f4ce1d5..cb18a894b 100644 --- a/gajim/common/connection_handlers.py +++ b/gajim/common/connection_handlers.py @@ -66,51 +66,6 @@ PRIVACY_ARRIVED = 'privacy_arrived' class ConnectionDisco: - """ - Holds xmpppy handlers and public methods for discover services - """ - - def discoverItems(self, jid, node=None, id_prefix=None): - """ - According to XEP-0030: - jid is mandatory; - name, node, action is optional. - """ - id_ = self._discover(nbxmpp.NS_DISCO_ITEMS, jid, node, id_prefix) - self.disco_items_ids.append(id_) - - def discoverInfo(self, jid, node=None, id_prefix=None): - """ - According to XEP-0030: - For identity: category, type is mandatory, name is optional. - For feature: var is mandatory. - """ - id_ = self._discover(nbxmpp.NS_DISCO_INFO, jid, node, id_prefix) - self.disco_info_ids.append(id_) - - def discoverMUC(self, jid, callback, update=False): - if muc_caps_cache.is_cached(jid) and not update: - callback() - return - disco_info = nbxmpp.Iq(typ='get', to=jid, queryNS=nbxmpp.NS_DISCO_INFO) - self.connection.SendAndCallForResponse( - disco_info, self.received_muc_info, {'callback': callback}) - - def received_muc_info(self, conn, stanza, callback): - if nbxmpp.isResultNode(stanza): - app.log('gajim.muc').info( - 'Received MUC DiscoInfo for %s', stanza.getFrom()) - muc_caps_cache.append(stanza) - callback() - else: - error = stanza.getError() - if error == 'item-not-found': - # Groupchat does not exist - callback() - return - app.nec.push_incoming_event( - InformationEvent( - None, dialog_name='unable-join-groupchat', args=error)) def request_register_agent_info(self, agent): if not self.connection or self.connected < 2: @@ -159,117 +114,10 @@ class ConnectionDisco: self.agent_registrations[agent] = {'roster_push': False, 'sub_received': False} - def _discover(self, ns, jid, node=None, id_prefix=None): - if not self.connection or self.connected < 2: - return - iq = nbxmpp.Iq(typ='get', to=jid, queryNS=ns) - id_ = self.connection.getAnID() - if id_prefix: - id_ = id_prefix + id_ - iq.setID(id_) - if node: - iq.setQuerynode(node) - self.connection.send(iq) - return id_ - def _ReceivedRegInfo(self, con, resp, agent): nbxmpp.features_nb._ReceivedRegInfo(con, resp, agent) self._IqCB(con, resp) - def _discoGetCB(self, con, iq_obj): - """ - Get disco info - """ - if not self.connection or self.connected < 2: - return - frm = helpers.get_full_jid_from_iq(iq_obj) - to = iq_obj.getAttr('to') - id_ = iq_obj.getAttr('id') - iq = nbxmpp.Iq(to=frm, typ='result', queryNS=nbxmpp.NS_DISCO, frm=to) - iq.setAttr('id', id_) - query = iq.setTag('query') - query.setAttr('node', 'http://gajim.org#' + app.version.split('-', 1)[ - 0]) - for f in (nbxmpp.NS_BYTESTREAM, nbxmpp.NS_SI, nbxmpp.NS_FILE, - nbxmpp.NS_COMMANDS, nbxmpp.NS_JINGLE_FILE_TRANSFER_5, - nbxmpp.NS_JINGLE_XTLS, nbxmpp.NS_PUBKEY_PUBKEY, nbxmpp.NS_PUBKEY_REVOKE, - nbxmpp.NS_PUBKEY_ATTEST): - feature = nbxmpp.Node('feature') - feature.setAttr('var', f) - query.addChild(node=feature) - - self.connection.send(iq) - raise nbxmpp.NodeProcessed - - def _DiscoverItemsErrorCB(self, con, iq_obj): - log.debug('DiscoverItemsErrorCB') - app.nec.push_incoming_event(AgentItemsErrorReceivedEvent(None, - conn=self, stanza=iq_obj)) - - def _DiscoverItemsCB(self, con, iq_obj): - log.debug('DiscoverItemsCB') - app.nec.push_incoming_event(AgentItemsReceivedEvent(None, conn=self, - stanza=iq_obj)) - - def _DiscoverItemsGetCB(self, con, iq_obj): - log.debug('DiscoverItemsGetCB') - - if not self.connection or self.connected < 2: - return - - if self.get_module('AdHocCommands').command_items_query(iq_obj): - raise nbxmpp.NodeProcessed - node = iq_obj.getTagAttr('query', 'node') - if node is None: - result = iq_obj.buildReply('result') - self.connection.send(result) - raise nbxmpp.NodeProcessed - if node == nbxmpp.NS_COMMANDS: - self.get_module('AdHocCommands').command_list_query(iq_obj) - raise nbxmpp.NodeProcessed - - def _DiscoverInfoGetCB(self, con, iq_obj): - log.debug('DiscoverInfoGetCB') - if not self.connection or self.connected < 2: - return - node = iq_obj.getQuerynode() - - if self.get_module('AdHocCommands').command_info_query(iq_obj): - raise nbxmpp.NodeProcessed - - id_ = iq_obj.getAttr('id') - if id_[:6] == 'Gajim_': - # We get this request from echo.server - raise nbxmpp.NodeProcessed - - iq = iq_obj.buildReply('result') - q = iq.setQuery() - if node: - q.setAttr('node', node) - q.addChild('identity', attrs=app.gajim_identity) - client_version = 'http://gajim.org#' + app.caps_hash[self.name] - - if node in (None, client_version): - for f in app.gajim_common_features: - q.addChild('feature', attrs={'var': f}) - for f in app.gajim_optional_features[self.name]: - q.addChild('feature', attrs={'var': f}) - - if q.getChildren(): - self.connection.send(iq) - raise nbxmpp.NodeProcessed - - def _DiscoverInfoErrorCB(self, con, iq_obj): - log.debug('DiscoverInfoErrorCB') - app.nec.push_incoming_event(AgentInfoErrorReceivedEvent(None, - conn=self, stanza=iq_obj)) - - def _DiscoverInfoCB(self, con, iq_obj): - log.debug('DiscoverInfoCB') - if not self.connection or self.connected < 2: - return - app.nec.push_incoming_event(AgentInfoReceivedEvent(None, conn=self, - stanza=iq_obj)) # basic connection handlers used here and in zeroconf class ConnectionHandlersBase: @@ -292,25 +140,17 @@ class ConnectionHandlersBase: # We decrypt GPG messages one after the other. Keep queue in mem self.gpg_messages_to_decrypt = [] - app.ged.register_event_handler('iq-error-received', ged.CORE, - self._nec_iq_error_received) app.ged.register_event_handler('presence-received', ged.CORE, self._nec_presence_received) app.ged.register_event_handler('gc-message-received', ged.CORE, self._nec_gc_message_received) def cleanup(self): - app.ged.remove_event_handler('iq-error-received', ged.CORE, - self._nec_iq_error_received) app.ged.remove_event_handler('presence-received', ged.CORE, self._nec_presence_received) app.ged.remove_event_handler('gc-message-received', ged.CORE, self._nec_gc_message_received) - def _nec_iq_error_received(self, obj): - if obj.conn.name != self.name: - return - def _nec_presence_received(self, obj): account = obj.conn.name if account != self.name: @@ -647,8 +487,6 @@ class ConnectionHandlers(ConnectionSocks5Bytestream, ConnectionDisco, ged.CORE, self._nec_roster_set_received) app.ged.register_event_handler('roster-received', ged.CORE, self._nec_roster_received) - app.ged.register_event_handler('iq-error-received', ged.CORE, - self._nec_iq_error_received) app.ged.register_event_handler('subscribe-presence-received', ged.CORE, self._nec_subscribe_presence_received) app.ged.register_event_handler('subscribed-presence-received', @@ -669,8 +507,6 @@ class ConnectionHandlers(ConnectionSocks5Bytestream, ConnectionDisco, ged.CORE, self._nec_roster_set_received) app.ged.remove_event_handler('roster-received', ged.CORE, self._nec_roster_received) - app.ged.remove_event_handler('iq-error-received', ged.CORE, - self._nec_iq_error_received) app.ged.remove_event_handler('subscribe-presence-received', ged.CORE, self._nec_subscribe_presence_received) app.ged.remove_event_handler('subscribed-presence-received', @@ -762,28 +598,14 @@ class ConnectionHandlers(ConnectionSocks5Bytestream, ConnectionDisco, self._getRoster() elif iq_obj.getType() == 'error': self.roster_supported = False - self.discoverItems(app.config.get_per('accounts', self.name, - 'hostname'), id_prefix='Gajim_') + self.get_module('Discovery').discover_server_items() if app.config.get_per('accounts', self.name, 'use_ft_proxies'): self.discover_ft_proxies() app.nec.push_incoming_event(RosterReceivedEvent(None, conn=self)) - GLib.timeout_add_seconds(10, self.discover_servers) del self.awaiting_answers[id_] - def _nec_iq_error_received(self, obj): - if obj.conn.name != self.name: - return - if obj.id_ in self.disco_items_ids: - app.nec.push_incoming_event(AgentItemsErrorReceivedEvent(None, - conn=self, stanza=obj.stanza)) - return True - if obj.id_ in self.disco_info_ids: - app.nec.push_incoming_event(AgentInfoErrorReceivedEvent(None, - conn=self, stanza=obj.stanza)) - return True - def _rosterSetCB(self, con, iq_obj): log.debug('rosterSetCB') app.nec.push_incoming_event(RosterSetReceivedEvent(None, conn=self, @@ -972,8 +794,7 @@ class ConnectionHandlers(ConnectionSocks5Bytestream, ConnectionDisco, if not self.connection: return self.connection.getRoster(self._on_roster_set) - self.discoverItems(app.config.get_per('accounts', self.name, - 'hostname'), id_prefix='Gajim_') + self.get_module('Discovery').discover_server_items() if app.config.get_per('accounts', self.name, 'use_ft_proxies'): self.discover_ft_proxies() @@ -990,17 +811,6 @@ class ConnectionHandlers(ConnectionSocks5Bytestream, ConnectionDisco, app.proxy65_manager.resolve(proxy, self.connection, our_jid, testit=testit) - def discover_servers(self): - if not self.connection: - return - servers = [] - for c in app.contacts.iter_contacts(self.name): - s = app.get_server_from_jid(c.jid) - if s not in servers and s not in app.transport_type: - servers.append(s) - for s in servers: - self.discoverInfo(s) - def _on_roster_set(self, roster): app.nec.push_incoming_event(RosterReceivedEvent(None, conn=self, xmpp_roster=roster)) @@ -1027,13 +837,6 @@ class ConnectionHandlers(ConnectionSocks5Bytestream, ConnectionDisco, if send_first_presence: self._send_first_presence(signed) - if obj.received_from_server: - for jid in obj.roster: - if jid != our_jid and app.jid_is_transport(jid) and \ - not app.get_transport_name_from_jid(jid): - # we can't determine which iconset to use - self.discoverInfo(jid) - app.logger.replace_roster(self.name, obj.version, obj.roster) for contact in app.contacts.iter_contacts(self.name): @@ -1080,9 +883,9 @@ class ConnectionHandlers(ConnectionSocks5Bytestream, ConnectionDisco, self.priority = priority app.nec.push_incoming_event(OurShowEvent(None, conn=self, show=show)) - if self.vcard_supported: - # ask our VCard - self.get_module('VCardTemp').request_vcard() + + # ask our VCard + self.get_module('VCardTemp').request_vcard() # 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._siErrorCB, 'error', nbxmpp.NS_SI) con.RegisterHandler('iq', self._siResultCB, 'result', nbxmpp.NS_SI) - con.RegisterHandler('iq', self._discoGetCB, 'get', nbxmpp.NS_DISCO) con.RegisterHandler('iq', self._bytestreamSetCB, 'set', nbxmpp.NS_BYTESTREAM) con.RegisterHandler('iq', self._bytestreamResultCB, 'result', @@ -1130,18 +932,6 @@ class ConnectionHandlers(ConnectionSocks5Bytestream, ConnectionDisco, con.RegisterHandlerOnce('iq', self.IBBAllIqHandler) con.RegisterHandler('iq', self.IBBIqHandler, ns=nbxmpp.NS_IBB) con.RegisterHandler('message', self.IBBMessageHandler, ns=nbxmpp.NS_IBB) - con.RegisterHandler('iq', self._DiscoverItemsCB, 'result', - nbxmpp.NS_DISCO_ITEMS) - con.RegisterHandler('iq', self._DiscoverItemsErrorCB, 'error', - nbxmpp.NS_DISCO_ITEMS) - con.RegisterHandler('iq', self._DiscoverInfoCB, 'result', - nbxmpp.NS_DISCO_INFO) - con.RegisterHandler('iq', self._DiscoverInfoErrorCB, 'error', - nbxmpp.NS_DISCO_INFO) - con.RegisterHandler('iq', self._DiscoverInfoGetCB, 'get', - nbxmpp.NS_DISCO_INFO) - con.RegisterHandler('iq', self._DiscoverItemsGetCB, 'get', - nbxmpp.NS_DISCO_ITEMS) con.RegisterHandler('iq', self._JingleCB, 'result') con.RegisterHandler('iq', self._JingleCB, 'error') diff --git a/gajim/common/connection_handlers_events.py b/gajim/common/connection_handlers_events.py index 78bd46749..17d2a18c1 100644 --- a/gajim/common/connection_handlers_events.py +++ b/gajim/common/connection_handlers_events.py @@ -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 = [] diff --git a/gajim/common/modules/__init__.py b/gajim/common/modules/__init__.py index 7c0997e98..880e607d9 100644 --- a/gajim/common/modules/__init__.py +++ b/gajim/common/modules/__init__.py @@ -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 diff --git a/gajim/common/modules/blocking.py b/gajim/common/modules/blocking.py index d35efdc60..39dd2d6b8 100644 --- a/gajim/common/modules/blocking.py +++ b/gajim/common/modules/blocking.py @@ -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') diff --git a/gajim/common/modules/bookmarks.py b/gajim/common/modules/bookmarks.py index bdd0dc982..d95b0594f 100644 --- a/gajim/common/modules/bookmarks.py +++ b/gajim/common/modules/bookmarks.py @@ -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() diff --git a/gajim/common/modules/carbons.py b/gajim/common/modules/carbons.py index 99d576406..0d9d67822 100644 --- a/gajim/common/modules/carbons.py +++ b/gajim/common/modules/carbons.py @@ -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' diff --git a/gajim/common/modules/discovery.py b/gajim/common/modules/discovery.py new file mode 100644 index 000000000..94d55ef0e --- /dev/null +++ b/gajim/common/modules/discovery.py @@ -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 . + +# 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' diff --git a/gajim/common/modules/httpupload.py b/gajim/common/modules/httpupload.py index db454adb8..a6c1de7db 100644 --- a/gajim/common/modules/httpupload.py +++ b/gajim/common/modules/httpupload.py @@ -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: diff --git a/gajim/common/modules/mam.py b/gajim/common/modules/mam.py index a3c32656a..0f24df109 100644 --- a/gajim/common/modules/mam.py +++ b/gajim/common/modules/mam.py @@ -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() diff --git a/gajim/common/modules/muc.py b/gajim/common/modules/muc.py index 029f19c56..d7088f408 100644 --- a/gajim/common/modules/muc.py +++ b/gajim/common/modules/muc.py @@ -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 diff --git a/gajim/common/modules/pep.py b/gajim/common/modules/pep.py index 01edf4833..029824cb2 100644 --- a/gajim/common/modules/pep.py +++ b/gajim/common/modules/pep.py @@ -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( diff --git a/gajim/common/modules/privacylists.py b/gajim/common/modules/privacylists.py index 78702a4ec..4bab95bdc 100644 --- a/gajim/common/modules/privacylists.py +++ b/gajim/common/modules/privacylists.py @@ -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: diff --git a/gajim/common/modules/pubsub.py b/gajim/common/modules/pubsub.py index 14d093099..3009c16a5 100644 --- a/gajim/common/modules/pubsub.py +++ b/gajim/common/modules/pubsub.py @@ -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 diff --git a/gajim/common/modules/security_labels.py b/gajim/common/modules/security_labels.py index d99389a11..94d3e13ad 100644 --- a/gajim/common/modules/security_labels.py +++ b/gajim/common/modules/security_labels.py @@ -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) diff --git a/gajim/common/modules/vcard_temp.py b/gajim/common/modules/vcard_temp.py index 9b532a6f4..15a00fa34 100644 --- a/gajim/common/modules/vcard_temp.py +++ b/gajim/common/modules/vcard_temp.py @@ -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 diff --git a/gajim/common/protocol/bytestream.py b/gajim/common/protocol/bytestream.py index 86d30c294..bddaf41c1 100644 --- a/gajim/common/protocol/bytestream.py +++ b/gajim/common/protocol/bytestream.py @@ -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, diff --git a/gajim/common/protocol/caps.py b/gajim/common/protocol/caps.py index 12a2d2d4c..21a27e01c 100644 --- a/gajim/common/protocol/caps.py +++ b/gajim/common/protocol/caps.py @@ -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()) + 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)) + app.nec.push_incoming_event( + CapsDiscoReceivedEvent(None, + conn=self, + fjid=fjid, + jid=bare_jid, + resource=resource, + client_caps=contact.client_caps)) diff --git a/gajim/common/zeroconf/client_zeroconf.py b/gajim/common/zeroconf/client_zeroconf.py index 24fd1a654..29ff2b27a 100644 --- a/gajim/common/zeroconf/client_zeroconf.py +++ b/gajim/common/zeroconf/client_zeroconf.py @@ -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', diff --git a/gajim/common/zeroconf/connection_handlers_zeroconf.py b/gajim/common/zeroconf/connection_handlers_zeroconf.py index 1258d0218..d9333aaee 100644 --- a/gajim/common/zeroconf/connection_handlers_zeroconf.py +++ b/gajim/common/zeroconf/connection_handlers_zeroconf.py @@ -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 diff --git a/gajim/config.py b/gajim/config.py index 9c26b7d55..9a6bb3453 100644 --- a/gajim/config.py +++ b/gajim/config.py @@ -2845,6 +2845,7 @@ class ManagePEPServicesWindow: self.xml.get_object('delete_button').set_sensitive(False) self.xml.connect_signals(self) self.account = account + self._con = app.connections[self.account] self.init_services() self.xml.get_object('services_treeview').get_selection().connect( @@ -2852,8 +2853,6 @@ class ManagePEPServicesWindow: app.ged.register_event_handler('pubsub-config-received', ged.GUI1, self._nec_pep_config_received) - app.ged.register_event_handler('agent-items-received', ged.GUI1, - self._nec_agent_items_received) self.window.show_all() @@ -2862,8 +2861,6 @@ class ManagePEPServicesWindow: del app.interface.instances[self.account]['pep_services'] app.ged.remove_event_handler('pubsub-config-received', ged.GUI1, self._nec_pep_config_received) - app.ged.remove_event_handler('agent-items-received', ged.GUI1, - self._nec_agent_items_received) def on_close_button_clicked(self, widget): self.window.destroy() @@ -2885,15 +2882,19 @@ class ManagePEPServicesWindow: col.pack_start(cellrenderer_text, True) col.add_attribute(cellrenderer_text, 'text', 0) - our_jid = app.get_jid_from_account(self.account) - app.connections[self.account].discoverItems(our_jid) + jid = self._con.get_own_jid().getStripped() + self._con.get_module('Discovery').disco_items( + jid, success_cb=self._items_received, error_cb=self._items_error) - def _nec_agent_items_received(self, obj): - our_jid = app.get_jid_from_account(self.account) - for item in obj.items: - if 'jid' in item and item['jid'] == our_jid and 'node' in item: + def _items_received(self, from_, node, items): + jid = self._con.get_own_jid().getStripped() + for item in items: + if item['jid'] == jid and 'node' in item: self.treestore.append([item['node']]) + def _items_error(self, from_, error): + ErrorDialog('Error', error) + def node_removed(self, jid, node): if jid != app.get_jid_from_account(self.account): return diff --git a/gajim/disco.py b/gajim/disco.py index d3fd47295..af0a96c0a 100644 --- a/gajim/disco.py +++ b/gajim/disco.py @@ -258,24 +258,6 @@ class ServicesCache: self._info = CacheDictionary(0, getrefresh = False) self._subscriptions = CacheDictionary(5, getrefresh=False) self._cbs = {} - app.ged.register_event_handler('agent-items-received', ged.GUI1, - self._nec_agent_items_received) - app.ged.register_event_handler('agent-items-error-received', ged.GUI1, - self._nec_agent_items_error_received) - app.ged.register_event_handler('agent-info-received', ged.GUI1, - self._nec_agent_info_received) - app.ged.register_event_handler('agent-info-error-received', ged.GUI1, - self._nec_agent_info_error_received) - - def __del__(self): - app.ged.remove_event_handler('agent-items-received', ged.GUI1, - self._nec_agent_items_received) - app.ged.remove_event_handler('agent-items-error-received', ged.GUI1, - self._nec_agent_items_error_received) - app.ged.remove_event_handler('agent-info-received', ged.GUI1, - self._nec_agent_info_received) - app.ged.remove_event_handler('agent-info-error-received', ged.GUI1, - self._nec_agent_info_error_received) def cleanup(self): self._items.cleanup() @@ -391,7 +373,9 @@ class ServicesCache: self._cbs[cbkey].append(cb) else: self._cbs[cbkey] = [cb] - app.connections[self.account].discoverInfo(jid, node) + con = app.connections[self.account] + con.get_module('Discovery').disco_info( + jid, node, self._disco_info_received, self._disco_info_error) def get_items(self, jid, node, cb, force=False, nofetch=False, args=()): """ @@ -409,24 +393,38 @@ class ServicesCache: # Create a closure object cbkey = ('items', addr) cb = Closure(cb, userargs=args, remove=self._clean_closure, - removeargs=cbkey) + removeargs=cbkey) # Are we already fetching this? if cbkey in self._cbs: self._cbs[cbkey].append(cb) else: self._cbs[cbkey] = [cb] - app.connections[self.account].discoverItems(jid, node) + con = app.connections[self.account] + con.get_module('Discovery').disco_items( + jid, node, self._disco_items_received, self._disco_items_error) - def _nec_agent_info_received(self, obj): + def _disco_info_received(self, from_, identities, features, data, node): """ Callback for when we receive an agent's info array is (agent, node, identities, features, data) """ - # We receive events from all accounts from GED - if obj.conn.name != self.account: - return - self._on_agent_info(obj.fjid, obj.node, obj.identities, obj.features, - obj.data) + self._on_agent_info(str(from_), node, identities, features, data) + + def _disco_info_error(self, from_, error): + """ + Callback for when a query fails. Even after the browse and agents + namespaces + """ + addr = get_agent_address(from_) + + # Call callbacks + cbkey = ('info', addr) + if cbkey in self._cbs: + for cb in self._cbs[cbkey]: + cb(str(from_), '', 0, 0, 0) + # clean_closure may have beaten us to it + if cbkey in self._cbs: + del self._cbs[cbkey] def _on_agent_info(self, fjid, node, identities, features, data): addr = get_agent_address(fjid, node) @@ -443,67 +441,42 @@ class ServicesCache: if cbkey in self._cbs: del self._cbs[cbkey] - def _nec_agent_items_received(self, obj): + def _disco_items_received(self, from_, node, items): """ Callback for when we receive an agent's items array is (agent, node, items) """ - # We receive events from all accounts from GED - if obj.conn.name != self.account: - return - - addr = get_agent_address(obj.fjid, obj.node) + addr = get_agent_address(from_, node) # Store in cache - self._items[addr] = obj.items + self._items[addr] = items # Call callbacks cbkey = ('items', addr) if cbkey in self._cbs: for cb in self._cbs[cbkey]: - cb(obj.fjid, obj.node, obj.items) + cb(str(from_), node, items) # clean_closure may have beaten us to it if cbkey in self._cbs: del self._cbs[cbkey] - def _nec_agent_info_error_received(self, obj): + def _disco_items_error(self, from_, error): """ Callback for when a query fails. Even after the browse and agents namespaces """ - # We receive events from all accounts from GED - if obj.conn.name != self.account: - return - addr = get_agent_address(obj.fjid) - - # Call callbacks - cbkey = ('info', addr) - if cbkey in self._cbs: - for cb in self._cbs[cbkey]: - cb(obj.fjid, '', 0, 0, 0) - # clean_closure may have beaten us to it - if cbkey in self._cbs: - del self._cbs[cbkey] - - def _nec_agent_items_error_received(self, obj): - """ - Callback for when a query fails. Even after the browse and agents - namespaces - """ - # We receive events from all accounts from GED - if obj.conn.name != self.account: - return - addr = get_agent_address(obj.fjid) + addr = get_agent_address(from_) # Call callbacks cbkey = ('items', addr) if cbkey in self._cbs: for cb in self._cbs[cbkey]: - cb(obj.fjid, '', 0) + cb(str(from_), '', 0) # clean_closure may have beaten us to it if cbkey in self._cbs: del self._cbs[cbkey] + # object is needed so that @property works class ServiceDiscoveryWindow(object): """ diff --git a/gajim/groupchat_control.py b/gajim/groupchat_control.py index 5fe1ce40c..64967e8c4 100644 --- a/gajim/groupchat_control.py +++ b/gajim/groupchat_control.py @@ -1533,7 +1533,7 @@ class GroupchatControl(ChatControlBase): if '104' in obj.status_code: changes.append(_('A setting not related to privacy has been ' 'changed')) - app.connections[self.account].discoverMUC( + app.connections[self.account].get_module('Discovery').disco_muc( self.room_jid, self.update_actions, update=True) if '170' in obj.status_code: # Can be a presence (see chg_contact_status in groupchat_control.py) @@ -2693,7 +2693,7 @@ class GroupchatControl(ChatControlBase): item = xml.get_object('block_menuitem') item2 = xml.get_object('unblock_menuitem') - if not app.connections[self.account].privacy_rules_supported: + if not app.connections[self.account].get_module('PrivacyLists').supported: item2.set_no_show_all(True) item.set_no_show_all(True) item.hide() diff --git a/gajim/gtk/join_groupchat.py b/gajim/gtk/join_groupchat.py index bb4072a33..22dc2e7c7 100644 --- a/gajim/gtk/join_groupchat.py +++ b/gajim/gtk/join_groupchat.py @@ -80,13 +80,6 @@ class JoinGroupchatWindow(Gtk.ApplicationWindow): self.builder.connect_signals(self) self.connect('key-press-event', self._on_key_press_event) - self.connect('destroy', self._on_destroy) - - if not self.minimal_mode: - app.ged.register_event_handler('agent-info-received', ged.GUI1, - self._nec_agent_info_received) - app.ged.register_event_handler('agent-info-error-received', ged.GUI1, - self._nec_agent_info_error_received) # Hide account combobox if there is only one account if len(accounts) == 1: @@ -277,48 +270,36 @@ class JoinGroupchatWindow(Gtk.ApplicationWindow): con.get_module('Bookmarks').add_bookmark( name, self.room_jid, autojoin, 1, password, nickname) - def _on_destroy(self, *args): - if not self.minimal_mode: - app.ged.remove_event_handler('agent-info-received', ged.GUI1, - self._nec_agent_info_received) - app.ged.remove_event_handler('agent-info-error-received', ged.GUI1, - self._nec_agent_info_error_received) - def _on_search_clicked(self, widget): server = self.server_combo.get_active_text().strip() - self.requested_jid = server - app.connections[self.account].discoverInfo(server) + con = app.connections[self.account] + con.get_module('Discovery').disco_info( + server, + success_cb=self._disco_info_received, + error_cb=self._disco_info_error) - def _nec_agent_info_error_received(self, obj): - if obj.conn.name != self.account: - return - if obj.jid != self.requested_jid: - return - self.requested_jid = None + def _disco_info_error(self, from_, error): ErrorDialog(_('Wrong server'), - _('%s is not a groupchat server') % obj.jid, + _('%s is not a groupchat server') % from_, transient_for=self) - def _nec_agent_info_received(self, obj): - if obj.conn.name != self.account: - return - if obj.jid != self.requested_jid: - return - self.requested_jid = None - if nbxmpp.NS_MUC not in obj.features: + def _disco_info_received(self, from_, identities, features, data, node): + if nbxmpp.NS_MUC not in features: ErrorDialog(_('Wrong server'), - _('%s is not a groupchat server') % obj.jid, + _('%s is not a groupchat server') % from_, transient_for=self) return - if obj.jid in app.interface.instances[self.account]['disco']: - app.interface.instances[self.account]['disco'][obj.jid].window.\ + + jid = str(from_) + if jid in app.interface.instances[self.account]['disco']: + app.interface.instances[self.account]['disco'][jid].window.\ present() else: try: # Object will add itself to the window dict from gajim.disco import ServiceDiscoveryWindow ServiceDiscoveryWindow( - self.account, obj.jid, + self.account, jid, initial_identities=[{'category': 'conference', 'type': 'text'}]) except GajimGeneralException: diff --git a/gajim/gtk/server_info.py b/gajim/gtk/server_info.py index 4f8e82280..cb81256f7 100644 --- a/gajim/gtk/server_info.py +++ b/gajim/gtk/server_info.py @@ -61,9 +61,9 @@ class ServerInfoDialog(Gtk.Dialog): ged.CORE, self._nec_version_result_received) - app.ged.register_event_handler('agent-info-received', + app.ged.register_event_handler('server-disco-received', ged.GUI1, - self._nec_agent_info_received) + self._server_disco_received) self.version = '' self.uptime = '' @@ -132,9 +132,7 @@ class ServerInfoDialog(Gtk.Dialog): self.version = obj.client_info self.update(self.get_infos, self.info_listbox) - def _nec_agent_info_received(self, obj): - if 'Gajim_' not in obj.id_: - return + def _server_disco_received(self, obj): self.update(self.get_features, self.feature_listbox) def add_feature(self, feature): @@ -154,22 +152,25 @@ class ServerInfoDialog(Gtk.Dialog): return [ Feature('XEP-0016: Privacy Lists', - con.privacy_rules_supported, '', None), + con.get_module('PrivacyLists').supported, '', None), Feature('XEP-0045: Multi-User Chat', con.muc_jid, '', None), - Feature('XEP-0054: vcard-temp', con.vcard_supported, '', None), + Feature('XEP-0054: vcard-temp', + con.get_module('VCardTemp').supported, '', None), Feature('XEP-0163: Personal Eventing Protocol', - con.pep_supported, '', None), + con.get_module('PEP').supported, '', None), Feature('XEP-0163: #publish-options', - con.pubsub_publish_options_supported, '', None), + con.get_module('PubSub').publish_options, '', None), Feature('XEP-0191: Blocking Command', - con.blocking_supported, nbxmpp.NS_BLOCKING, None), + con.get_module('Blocking').supported, + nbxmpp.NS_BLOCKING, None), Feature('XEP-0198: Stream Management', con.sm.enabled, nbxmpp.NS_STREAM_MGMT, None), Feature('XEP-0258: Security Labels in XMPP', con.get_module('SecLabels').supported, nbxmpp.NS_SECLABEL, None), Feature('XEP-0280: Message Carbons', - con.carbons_available, nbxmpp.NS_CARBONS, carbons_enabled), + con.get_module('Carbons').supported, + nbxmpp.NS_CARBONS, carbons_enabled), Feature('XEP-0313: Message Archive Management', con.get_module('MAM').archiving_namespace, con.get_module('MAM').archiving_namespace, @@ -198,9 +199,9 @@ class ServerInfoDialog(Gtk.Dialog): ged.CORE, self._nec_version_result_received) - app.ged.remove_event_handler('agent-info-received', + app.ged.remove_event_handler('server-disco-received', ged.GUI1, - self._nec_agent_info_received) + self._server_disco_received) class FeatureItem(Gtk.Grid): diff --git a/gajim/gui_interface.py b/gajim/gui_interface.py index 17acd067e..a99f2f076 100644 --- a/gajim/gui_interface.py +++ b/gajim/gui_interface.py @@ -1116,6 +1116,8 @@ class Interface: app.block_signed_in_notifications[account] = True connected = obj.conn.connected + pep_supported = obj.conn.get_module('PEP').supported + if not idle.Monitor.is_unknown() and connected in (2, 3): # we go online or free for chat, so we activate auto status app.sleeper_state[account] = 'online' @@ -1135,11 +1137,11 @@ class Interface: if connected == invisible_show: return # send currently played music - if (obj.conn.pep_supported and sys.platform == 'linux' and + if (pep_supported and sys.platform == 'linux' and app.config.get_per('accounts', account, 'publish_tune')): self.enable_music_listener() # enable location listener - if (obj.conn.pep_supported and app.is_installed('GEOCLUE') and + if (pep_supported and app.is_installed('GEOCLUE') and app.config.get_per('accounts', account, 'publish_location')): location_listener.enable() @@ -1785,7 +1787,7 @@ class Interface: transient_for=transient_for) disco_account = connected_accounts[0] if account is None else account - app.connections[disco_account].discoverMUC( + app.connections[disco_account].get_module('Discovery').disco_muc( room_jid, _on_discover_result) ################################################################################ @@ -2187,7 +2189,7 @@ class Interface: for acct in accounts: if not app.account_is_connected(acct): continue - if not app.connections[acct].pep_supported: + if not app.connections[acct].get_module('PEP').supported: continue if not app.config.get_per('accounts', acct, 'publish_tune'): continue diff --git a/gajim/gui_menu_builder.py b/gajim/gui_menu_builder.py index 3baac0f1a..0826f7db7 100644 --- a/gajim/gui_menu_builder.py +++ b/gajim/gui_menu_builder.py @@ -460,8 +460,10 @@ control=None, gc_contact=None, is_anonymous=True): execute_command_menuitem, send_custom_status_menuitem): widget.set_sensitive(False) - if app.connections[account] and (app.connections[account].\ - privacy_rules_supported or app.connections[account].blocking_supported): + + con = app.connections[account] + if con and (con.get_module('PrivacyLists').supported or + con.get_module('Blocking').supported): if helpers.jid_is_blocked(account, jid): block_menuitem.set_no_show_all(True) block_menuitem.hide() diff --git a/gajim/roster_window.py b/gajim/roster_window.py index e6779481a..73a7ab391 100644 --- a/gajim/roster_window.py +++ b/gajim/roster_window.py @@ -2840,9 +2840,14 @@ class RosterWindow: if msg is None: # user pressed Cancel to change status message dialog return - accounts = set(i[1] for i in list_ if (app.connections[i[1]].\ - privacy_rules_supported or (group is None and app.\ - connections[i[1]].blocking_supported))) + + accounts = [] + for _, account in list_: + con = app.connections[account] + if con.get_module('PrivacyLists').supported or ( + group is None and con.get_module('Blocking').supported): + accounts.append(account) + if group is None: for acct in accounts: l_ = [i[0] for i in list_ if i[1] == acct] @@ -2882,9 +2887,13 @@ class RosterWindow: """ When clicked on the 'unblock' button in context menu. """ - accounts = set(i[1] for i in list_ if (app.connections[i[1]].\ - privacy_rules_supported or (group is None and app.\ - connections[i[1]].blocking_supported))) + accounts = [] + for _, account in list_: + con = app.connections[account] + if con.get_module('PrivacyLists').supported or ( + group is None and con.get_module('Blocking').supported): + accounts.append(account) + if group is None: for acct in accounts: l_ = [i[0] for i in list_ if i[1] == acct] @@ -4916,7 +4925,7 @@ class RosterWindow: sub_menu.append(item) con = app.connections[account] if show == 'invisible' and con.connected > 1 and \ - not con.privacy_rules_supported: + not con.get_module('PrivacyLists').supported: item.set_sensitive(False) else: item.connect('activate', self.change_status, account, show) @@ -4940,7 +4949,7 @@ class RosterWindow: item.connect('activate', self.change_status, account, 'offline') pep_menuitem = xml.get_object('pep_menuitem') - if app.connections[account].pep_supported: + if app.connections[account].get_module('PEP').supported: pep_submenu = Gtk.Menu() pep_menuitem.set_submenu(pep_submenu) @@ -5183,8 +5192,7 @@ class RosterWindow: if helpers.group_is_blocked(account, group): is_blocked = True - if is_blocked and app.connections[account].\ - privacy_rules_supported: + if is_blocked and app.connections[account].get_module('PrivacyLists').supported: unblock_menuitem = Gtk.MenuItem.new_with_mnemonic(_('_Unblock')) unblock_menuitem.connect('activate', self.on_unblock, list_, group) @@ -5193,7 +5201,7 @@ class RosterWindow: block_menuitem = Gtk.MenuItem.new_with_mnemonic(_('_Block')) block_menuitem.connect('activate', self.on_block, list_, group) menu.append(block_menuitem) - if not app.connections[account].privacy_rules_supported: + if not app.connections[account].get_module('PrivacyLists').supported: block_menuitem.set_sensitive(False) # Remove group @@ -5245,7 +5253,7 @@ class RosterWindow: account = model[titer][Column.ACCOUNT] if app.connections[account].connected < 2: one_account_offline = True - if not app.connections[account].privacy_rules_supported: + if not app.connections[account].get_module('PrivacyLists').supported: privacy_rules_supported = False contact = app.contacts.get_contact_with_highest_priority(account, jid) @@ -5411,7 +5419,7 @@ class RosterWindow: self.on_xml_console_menuitem_activate, account) if app.connections[account]: - if app.connections[account].privacy_rules_supported: + if app.connections[account].get_module('PrivacyLists').supported: privacy_lists_menuitem.connect('activate', self.on_privacy_lists_menuitem_activate, account) else: diff --git a/test/lib/mock.py b/test/lib/mock.py index e2a2b6c03..5f54a1209 100644 --- a/test/lib/mock.py +++ b/test/lib/mock.py @@ -102,6 +102,9 @@ class Mock(object): if not name in baseMethods: self.__dict__[name] = MockCallable(name, self, handcrafted=True) + def get_module(self, name): + return Mock() + def __getattr__(self, name): return MockCallable(name, self) diff --git a/test/unit/test_caps_cache.py b/test/unit/test_caps_cache.py index 66fcc02c7..8d57deba1 100644 --- a/test/unit/test_caps_cache.py +++ b/test/unit/test_caps_cache.py @@ -6,10 +6,10 @@ import unittest import lib lib.setup_env() +from unittest.mock import MagicMock from nbxmpp import NS_MUC, NS_PING, NS_XHTML_IM, Iq from gajim.common import caps_cache as caps -from gajim.common.contacts import Contact -from gajim.common.connection_handlers_events import AgentInfoReceivedEvent +from gajim.common.modules.discovery import Discovery from mock import Mock @@ -106,29 +106,29 @@ class TestCapsCache(CommonCapsTest): def test_preload_triggering_query(self): ''' Make sure that preload issues a disco ''' - connection = Mock() + connection = MagicMock() client_caps = caps.ClientCaps(self.caps_hash, self.node, self.caps_method) - self.cc.query_client_of_jid_if_unknown(connection, "test@gajim.org", - client_caps) + self.cc.query_client_of_jid_if_unknown( + connection, "test@gajim.org", client_caps) - self.assertEqual(1, len(connection.mockGetAllCalls())) + self.assertEqual(1, connection.get_module('Discovery').disco_contact.call_count) def test_no_preload_query_if_cashed(self): ''' Preload must not send a query if the data is already cached ''' - connection = Mock() + connection = MagicMock() client_caps = caps.ClientCaps(self.caps_hash, self.node, self.caps_method) self.cc.initialize_from_db() - self.cc.query_client_of_jid_if_unknown(connection, "test@gajim.org", - client_caps) + self.cc.query_client_of_jid_if_unknown( + connection, "test@gajim.org", client_caps) - self.assertEqual(0, len(connection.mockGetAllCalls())) + self.assertEqual(0, connection.get_module('Discovery').disco_contact.call_count) def test_hash(self): '''tests the hash computation''' stanza = Iq(node=COMPLEX_EXAMPLE) - identities, features, data, _ = AgentInfoReceivedEvent.parse_stanza(stanza) + identities, features, data, _ = Discovery.parse_info_response(stanza) computed_hash = caps.compute_caps_hash(identities, features, data) self.assertEqual('q07IKJEyjvHSyhy//CH0CxmKi8w=', computed_hash) @@ -141,12 +141,11 @@ class TestClientCaps(CommonCapsTest): def test_query_by_get_discover_strategy(self): ''' Client must be queried if the data is unkown ''' - connection = Mock() + connection = MagicMock() discover = self.client_caps.get_discover_strategy() discover(connection, "test@gajim.org") - - connection.mockCheckCall(0, "discoverInfo", "test@gajim.org", - "http://gajim.org#m3P2WeXPMGVH2tZPe7yITnfY0Dw=") + connection.get_module('Discovery').disco_contact.assert_called_once_with( + 'test@gajim.org', 'http://gajim.org#m3P2WeXPMGVH2tZPe7yITnfY0Dw=') def test_client_supports(self): self.assertTrue(caps.client_supports(self.client_caps, NS_PING), @@ -175,11 +174,11 @@ class TestOldClientCaps(TestClientCaps): def test_query_by_get_discover_strategy(self): ''' Client must be queried if the data is unknown ''' - connection = Mock() + connection = MagicMock() discover = self.client_caps.get_discover_strategy() discover(connection, "test@gajim.org") - connection.mockCheckCall(0, "discoverInfo", "test@gajim.org") + connection.get_module('Discovery').disco_contact.assert_called_once_with('test@gajim.org') if __name__ == '__main__': unittest.main()