diff --git a/gajim/application.py b/gajim/application.py index 71e5b5508250a91984c7270e18e63a58b57cbbcc..7c719f703ac9f2f0afc6b33a1bc9efae1c1ce232 100644 --- a/gajim/application.py +++ b/gajim/application.py @@ -52,9 +52,9 @@ from gajim.common import configpaths from gajim.common import logging_helpers from gajim.common import exceptions -from gajim.common import caps_cache from gajim.common import logger from gajim.common.i18n import _ +from gajim.common.contacts import LegacyContactsAPI class GajimApplication(Gtk.Application): @@ -199,7 +199,7 @@ def _startup(self, _application): configpaths.create_paths() try: app.logger = logger.Logger() - caps_cache.initialize(app.logger) + app.contacts = LegacyContactsAPI() except exceptions.DatabaseMalformed as error: dlg = Gtk.MessageDialog( transient_for=None, diff --git a/gajim/common/app.py b/gajim/common/app.py index 2a452428231e5b76aab1229465b99facd5f82efc..ed8f145b7373f0fa82b2f01e59b3f2de21adbd36 100644 --- a/gajim/common/app.py +++ b/gajim/common/app.py @@ -46,7 +46,6 @@ from gajim.common import ged as ged_module from gajim.common.i18n import LANG from gajim.common.const import Display -from gajim.common.contacts import LegacyContactsAPI from gajim.common.events import Events from gajim.common.types import NetworkEventsControllerT # pylint: disable=unused-import from gajim.common.types import InterfaceT # pylint: disable=unused-import @@ -79,7 +78,7 @@ # {acct1: {jid1: time1, jid2: time2}, } last_message_time = {} # type: Dict[str, Dict[str, float]] -contacts = LegacyContactsAPI() +contacts = None # tell if we are connected to the room or not # {acct: {room_jid: True}} diff --git a/gajim/common/caps_cache.py b/gajim/common/caps_cache.py deleted file mode 100644 index fc8530c003493f56bd785eeb0f5e3bf778752fcc..0000000000000000000000000000000000000000 --- a/gajim/common/caps_cache.py +++ /dev/null @@ -1,335 +0,0 @@ -# Copyright (C) 2007 Tomasz Melcer <liori AT exroot.org> -# Travis Shirk <travis AT pobox.com> -# Copyright (C) 2007-2014 Yann Leboulanger <asterix AT lagaule.org> -# Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com> -# Jonathan Schleifer <js-gajim AT webkeks.org> -# Copyright (C) 2008-2009 Stephan Erb <steve-e AT h3c.de> -# -# This file is part of Gajim. -# -# Gajim is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published -# by the Free Software Foundation; version 3 only. -# -# Gajim is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Gajim. If not, see <http://www.gnu.org/licenses/>. - -""" -Module containing all XEP-115 (Entity Capabilities) related classes - -Basic Idea: -CapsCache caches features to hash relationships. The cache is queried -through ClientCaps objects which are hold by contact instances. -""" - -import logging - -from nbxmpp import (NS_ESESSION, NS_CHATSTATES, - NS_JINGLE_ICE_UDP, NS_JINGLE_RTP_AUDIO, NS_JINGLE_RTP_VIDEO, - NS_JINGLE_FILE_TRANSFER_5) -# Features where we cannot safely assume that the other side supports them -FEATURE_BLACKLIST = [NS_CHATSTATES, NS_ESESSION, - NS_JINGLE_ICE_UDP, NS_JINGLE_RTP_AUDIO, NS_JINGLE_RTP_VIDEO, - NS_JINGLE_FILE_TRANSFER_5] - -log = logging.getLogger('gajim.c.caps_cache') - -# Query entry status codes -NEW = 0 -QUERIED = 1 -CACHED = 2 # got the answer -FAKED = 3 # allow NullClientCaps to behave as it has a cached item - -################################################################################ -### Public API of this module -################################################################################ - -capscache = None - -def initialize(logger): - """ - Initialize this module - """ - global capscache - capscache = CapsCache(logger) - -def client_supports(client_caps, requested_feature): - lookup_item = client_caps.get_cache_lookup_strategy() - cache_item = lookup_item(capscache) - - supported_features = cache_item.features - if requested_feature in supported_features: - return True - if not supported_features and cache_item.status in (NEW, QUERIED, FAKED): - # assume feature is supported, if we don't know yet, what the client - # is capable of - return requested_feature not in FEATURE_BLACKLIST - return False - -def get_client_identity(client_caps): - lookup_item = client_caps.get_cache_lookup_strategy() - cache_item = lookup_item(capscache) - - for identity in cache_item.identities: - if identity.category == 'client': - return identity.type - -def create_suitable_client_caps(node, caps_hash, hash_method, fjid=None): - """ - Create and return a suitable ClientCaps object for the given node, - caps_hash, hash_method combination. - """ - if not node or not caps_hash: - if fjid: - client_caps = NoClientCaps(fjid) - else: - # improper caps, ignore client capabilities. - client_caps = NullClientCaps() - elif not hash_method: - client_caps = OldClientCaps(caps_hash, node) - else: - client_caps = ClientCaps(caps_hash, node, hash_method) - return client_caps - - -################################################################################ -### Internal classes of this module -################################################################################ - -class AbstractClientCaps: - """ - Base class representing a client and its capabilities as advertised by a - caps tag in a presence - """ - def __init__(self, caps_hash, node): - self._hash = caps_hash - self._node = node - self._hash_method = None - - @property - def hash_method(self): - return self._hash_method - - def get_discover_strategy(self): - return self._discover - - def _discover(self, connection, jid): - """ - To be implemented by subclasses - """ - raise NotImplementedError - - def get_cache_lookup_strategy(self): - return self._lookup_in_cache - - def _lookup_in_cache(self, caps_cache): - """ - To be implemented by subclasses - """ - raise NotImplementedError - - -class ClientCaps(AbstractClientCaps): - """ - The current XEP-115 implementation - """ - def __init__(self, caps_hash, node, hash_method): - AbstractClientCaps.__init__(self, caps_hash, node) - assert hash_method != 'old' - self._hash_method = hash_method - - def _lookup_in_cache(self, caps_cache): - return caps_cache[(self._hash_method, self._hash)] - - def _discover(self, connection, jid): - connection.get_module('Discovery').disco_contact( - jid, '%s#%s' % (self._node, self._hash)) - - -class OldClientCaps(AbstractClientCaps): - """ - Old XEP-115 implementation. Kept around for background compatibility - """ - def __init__(self, caps_hash, node): - AbstractClientCaps.__init__(self, caps_hash, node) - self._hash_method = 'old' - - def _lookup_in_cache(self, caps_cache): - return caps_cache[('old', self._node + '#' + self._hash)] - - def _discover(self, connection, jid): - connection.get_module('Discovery').disco_contact(jid) - - -class NoClientCaps(AbstractClientCaps): - """ - For clients that don't support XEP-0115 - """ - def __init__(self, fjid): - AbstractClientCaps.__init__(self, fjid, fjid) - self._hash_method = 'no' - - def _lookup_in_cache(self, caps_cache): - return caps_cache[('no', self._node)] - - def _discover(self, connection, jid): - connection.get_module('Discovery').disco_contact(jid) - - -class NullClientCaps(AbstractClientCaps): - """ - This is a NULL-Object to streamline caps handling if a client has not - advertised any caps or has advertised them in an improper way - - Assumes (almost) everything is supported. - """ - _instance = None - def __new__(cls, *args, **kwargs): - """ - Make it a singleton. - """ - if not cls._instance: - cls._instance = super(NullClientCaps, cls).__new__( - cls, *args, **kwargs) - return cls._instance - - def __init__(self): - AbstractClientCaps.__init__(self, None, None) - self._hash_method = 'dummy' - - def _lookup_in_cache(self, caps_cache): - # lookup something which does not exist to get a new CacheItem created - cache_item = caps_cache[('dummy', '')] - # Mark the item as cached so that protocol/caps.py does not update it - cache_item.status = FAKED - return cache_item - - def _discover(self, connection, jid): - pass - - -class CapsCache: - """ - This object keeps the mapping between caps data and real disco features they - represent, and provides simple way to query that info - """ - def __init__(self, logger=None): - # our containers: - # __cache is a dictionary mapping: pair of hash method and hash maps - # to CapsCacheItem object - # __CacheItem is a class that stores data about particular - # client (hash method/hash pair) - self.__cache = {} - - class CacheItem: - # __names is a string cache; every string long enough is given - # another object, and we will have plenty of identical long - # strings. therefore we can cache them - __names = {} - - def __init__(self, hash_method, hash_, logger): - # cached into db - self.hash_method = hash_method - self.hash = hash_ - self._features = [] - self._identities = [] - self._logger = logger - - self.status = NEW - self._recently_seen = False - - def _get_features(self): - return self._features - - def _set_features(self, value): - self._features = [] - for feature in value: - self._features.append(self.__names.setdefault(feature, feature)) - - features = property(_get_features, _set_features) - - def _get_identities(self): - return self._identities - - def _set_identities(self, value): - self._identities = value - - identities = property(_get_identities, _set_identities) - - def set_and_store(self, info): - self.identities = info.identities - self.features = info.features - if self.hash_method != 'no': - self._logger.add_caps_entry(self.hash_method, self.hash, info) - self.status = CACHED - - def update_last_seen(self): - if not self._recently_seen: - self._recently_seen = True - if self.hash_method != 'no': - self._logger.update_caps_time(self.hash_method, - self.hash) - - def is_valid(self): - """ - Returns True if identities and features for this cache item - are known. - """ - return self.status in (CACHED, FAKED) - - self.__CacheItem = CacheItem - self.logger = logger - - def initialize_from_db(self): - self._remove_outdated_caps() - data = self.logger.load_caps_data() - for key, item in data.items(): - x = self[key] - x.identities = item.identities - x.features = item.features - x.status = CACHED - - def _remove_outdated_caps(self): - """ - Remove outdated values from the db - """ - self.logger.clean_caps_table() - - def __getitem__(self, caps): - if caps in self.__cache: - return self.__cache[caps] - - hash_method, hash_ = caps - - x = self.__CacheItem(hash_method, hash_, self.logger) - self.__cache[(hash_method, hash_)] = x - return x - - def query_client_of_jid_if_unknown(self, connection, jid, client_caps): - """ - Start a disco query to determine caps (node, ver, exts). Won't query if - the data is already in cache - """ - lookup_cache_item = client_caps.get_cache_lookup_strategy() - q = lookup_cache_item(self) - - if q.status == NEW: - # do query for bare node+hash pair - # this will create proper object - q.status = QUERIED - discover = client_caps.get_discover_strategy() - discover(connection, jid) - else: - q.update_last_seen() - - def forget_caps(self, client_caps): - hash_method = client_caps._hash_method - hash_ = client_caps._hash - key = (hash_method, hash_) - if key in self.__cache: - del self.__cache[key] diff --git a/gajim/common/contacts.py b/gajim/common/contacts.py index c628b1f5d667d5f801be631b0d3d060285afd866..6f2ae7df1504de86de501d4fa207c2b8cd458642 100644 --- a/gajim/common/contacts.py +++ b/gajim/common/contacts.py @@ -25,7 +25,7 @@ # along with Gajim. If not, see <http://www.gnu.org/licenses/>. try: - from gajim.common import caps_cache + from gajim.common import app from gajim.common.i18n import _ from gajim.common.account import Account from gajim import common @@ -47,7 +47,7 @@ def __init__(self, jid, account, resource): class CommonContact(XMPPEntity): def __init__(self, jid, account, resource, show, presence, status, name, - chatstate, client_caps=None): + chatstate): XMPPEntity.__init__(self, jid, account, resource) @@ -56,8 +56,6 @@ def __init__(self, jid, account, resource, show, presence, status, name, self.status = status self.name = name - self.client_caps = client_caps or caps_cache.NullClientCaps() - # this is contact's chatstate self._chatstate = chatstate @@ -129,11 +127,20 @@ def supports(self, requested_feature): # show, so we can be sure it's existant. Otherwise, we still # return caps for a contact that has no resources left. return False - return caps_cache.client_supports(self.client_caps, requested_feature) + + disco_info = app.logger.get_last_disco_info(self.get_full_jid()) + if disco_info is None: + return False + + return disco_info.supports(requested_feature) @property def uses_phone(self): - return caps_cache.get_client_identity(self.client_caps) == 'phone' + disco_info = app.logger.get_last_disco_info(self.get_full_jid()) + if disco_info is None: + return False + + return disco_info.has_category('phone') class Contact(CommonContact): @@ -141,7 +148,7 @@ class Contact(CommonContact): Information concerning a contact """ def __init__(self, jid, account, name='', groups=None, show='', status='', - sub='', ask='', resource='', priority=0, client_caps=None, + sub='', ask='', resource='', priority=0, chatstate=None, idle_time=None, avatar_sha=None, groupchat=False, is_pm_contact=False): if not isinstance(jid, str): @@ -150,8 +157,7 @@ def __init__(self, jid, account, name='', groups=None, show='', status='', groups = [] CommonContact.__init__(self, jid, account, resource, show, - None, status, name, - chatstate, client_caps=client_caps) + None, status, name, chatstate) self.contact_name = '' # nick choosen by contact self.groups = [i if i else _('General') for i in set(groups)] # filter duplicate values @@ -182,7 +188,6 @@ def get_shown_name(self): return self.jid.split('@')[0] def _get_groupchat_name(self): - from gajim.common import app from gajim.common.helpers import get_groupchat_name con = app.connections[self.account.name] return get_groupchat_name(con, self.jid) @@ -229,7 +234,6 @@ def is_groupchat(self): @property def is_connected(self): - from gajim.common import app try: return app.gc_connected[self.account.name][self.jid] except Exception: @@ -272,7 +276,7 @@ def as_contact(self): """ return Contact(jid=self.get_full_jid(), account=self.account, name=self.name, groups=[], show=self.show, status=self.status, - sub='none', client_caps=self.client_caps, avatar_sha=self.avatar_sha, + sub='none', avatar_sha=self.avatar_sha, is_pm_contact=True) @@ -312,7 +316,7 @@ def remove_account(self, account): def create_contact(self, jid, account, name='', groups=None, show='', status='', sub='', ask='', resource='', priority=0, - client_caps=None, chatstate=None, idle_time=None, + chatstate=None, idle_time=None, avatar_sha=None, groupchat=False): if groups is None: groups = [] @@ -320,7 +324,7 @@ def create_contact(self, jid, account, name='', groups=None, show='', account = self._accounts.get(account, account) return Contact(jid=jid, account=account, name=name, groups=groups, show=show, status=status, sub=sub, ask=ask, resource=resource, - priority=priority, client_caps=client_caps, + priority=priority, chatstate=chatstate, idle_time=idle_time, avatar_sha=avatar_sha, groupchat=groupchat) @@ -349,7 +353,6 @@ def copy_contact(self, contact): name=contact.name, groups=contact.groups, show=contact.show, status=contact.status, sub=contact.sub, ask=contact.ask, resource=contact.resource, priority=contact.priority, - client_caps=contact.client_caps, chatstate=contact.chatstate_enum, idle_time=contact.idle_time, avatar_sha=contact.avatar_sha) diff --git a/gajim/common/logger.py b/gajim/common/logger.py index 92eb7f74ce0c5e9798065b98971b7f7ac9d4130c..232d41a0c21f7f6068c4b8a8181ea21fdd0d71e4 100644 --- a/gajim/common/logger.py +++ b/gajim/common/logger.py @@ -38,11 +38,9 @@ from gi.repository import GLib -from nbxmpp.protocol import Node from nbxmpp.protocol import Iq from nbxmpp.structs import DiscoInfo from nbxmpp.structs import CommonError -from nbxmpp.modules.dataforms import extend_form from nbxmpp.modules.discovery import parse_disco_info from gajim.common import exceptions @@ -53,8 +51,6 @@ from gajim.common.const import ( JIDConstant, KindConstant, ShowConstant, TypeConstant, SubscriptionConstant) -from gajim.common.structs import CapsData -from gajim.common.structs import CapsIdentity LOGS_SQL_STATEMENT = ''' @@ -135,55 +131,12 @@ jid TEXT PRIMARY KEY UNIQUE, avatar_sha TEXT ); - PRAGMA user_version=4; + PRAGMA user_version=5; ''' log = logging.getLogger('gajim.c.logger') - -class CapsEncoder(json.JSONEncoder): - def encode(self, obj): - if isinstance(obj, DiscoInfo): - identities = [] - for identity in obj.identities: - identities.append( - {'category': identity.category, - 'type': identity.type, - 'name': identity.name, - 'lang': identity.lang}) - - dataforms = [] - for dataform in obj.dataforms: - # Filter out invalid forms according to XEP-0115 - form_type = dataform.vars.get('FORM_TYPE') - if form_type is None or form_type.type_ != 'hidden': - continue - dataforms.append(str(dataform)) - - obj = {'identities': identities, - 'features': obj.features, - 'dataforms': dataforms} - return json.JSONEncoder.encode(self, obj) - - -def caps_decoder(dict_): - if 'identities' not in dict_: - return dict_ - - identities = [] - for identity in dict_['identities']: - identities.append(CapsIdentity(**identity)) - - features = dict_['features'] - - dataforms = [] - for dataform in dict_['dataforms']: - dataforms.append(extend_form(node=Node(node=dataform))) - return CapsData(identities=identities, - features=features, - dataforms=dataforms) - def timeit(func): def func_wrapper(self, *args, **kwargs): start = time.time() @@ -228,6 +181,7 @@ def __init__(self): self._log_db_path = configpaths.get('LOG_DB') self._cache_db_path = configpaths.get('CACHE_DB') + self._entity_caps_cache = {} self._disco_info_cache = {} self._muc_avatar_sha_cache = {} @@ -237,6 +191,8 @@ def __init__(self): self._get_jid_ids_from_db() self._fill_disco_info_cache() self._fill_muc_avatar_sha_cache() + self._clean_caps_table() + self._load_caps_data() def _create_databases(self): if os.path.isdir(self._log_db_path): @@ -388,6 +344,13 @@ def _migrate_cache(self, con): ] self._execute_multiple(con, statements) + if self._get_user_version(con) < 5: + statements = [ + 'DELETE FROM caps_cache', + 'PRAGMA user_version=5' + ] + self._execute_multiple(con, statements) + @staticmethod def _execute_multiple(con, statements): """ @@ -1083,32 +1046,32 @@ def get_transports_type(self): return answer @timeit - def load_caps_data(self): + def _load_caps_data(self): ''' Load caps cache data ''' rows = self._con.execute( - 'SELECT hash_method, hash, data FROM caps_cache') + 'SELECT hash_method, hash, data as "data [disco_info]" ' + 'FROM caps_cache') - cache = {} for row in rows: - try: - data = json.loads(row.data, object_hook=caps_decoder) - except Exception: - log.exception('') - continue - cache[(row.hash_method, row.hash)] = data - return cache + self._entity_caps_cache[(row.hash_method, row.hash)] = row.data @timeit - def add_caps_entry(self, hash_method, hash_, caps_data): - serialized = json.dumps(caps_data, cls=CapsEncoder) + def add_caps_entry(self, jid, hash_method, hash_, caps_data): + self._entity_caps_cache[(hash_method, hash_)] = caps_data + + self._disco_info_cache[jid] = caps_data + self._con.execute(''' INSERT INTO caps_cache (hash_method, hash, data, last_seen) VALUES (?, ?, ?, ?) - ''', (hash_method, hash_, serialized, int(time.time()))) + ''', (hash_method, hash_, caps_data, int(time.time()))) self._timeout_commit() + def get_caps_entry(self, hash_method, hash_): + return self._entity_caps_cache.get((hash_method, hash_)) + @timeit def update_caps_time(self, method, hash_): sql = '''UPDATE caps_cache SET last_seen = ? @@ -1117,7 +1080,7 @@ def update_caps_time(self, method, hash_): self._timeout_commit() @timeit - def clean_caps_table(self): + def _clean_caps_table(self): """ Remove caps which was not seen for 3 months """ @@ -1689,7 +1652,7 @@ def get_last_disco_info(self, jid, max_age=0): return disco_info @timeit - def set_last_disco_info(self, jid, disco_info): + def set_last_disco_info(self, jid, disco_info, cache_only=False): """ Get last disco info from jid @@ -1701,6 +1664,10 @@ def set_last_disco_info(self, jid, disco_info): log.info('Save disco info from %s', jid) + if cache_only: + self._disco_info_cache[jid] = disco_info + return + disco_exists = self.get_last_disco_info(jid) is not None if disco_exists: sql = '''UPDATE last_seen_disco_info SET diff --git a/gajim/common/modules/caps.py b/gajim/common/modules/caps.py index 6faf63cee51c62f8002fc70187bfcbfe2faed36a..51834c47c42433fba3f3d0ba6eddc0ea74c199e1 100644 --- a/gajim/common/modules/caps.py +++ b/gajim/common/modules/caps.py @@ -23,7 +23,6 @@ from nbxmpp.util import is_error_result from nbxmpp.util import compute_caps_hash -from gajim.common import caps_cache from gajim.common import app from gajim.common.const import COMMON_FEATURES from gajim.common.helpers import get_optional_features @@ -49,10 +48,6 @@ def __init__(self, con): priority=51), ] - self._capscache = caps_cache.capscache - self._create_suitable_client_caps = \ - caps_cache.create_suitable_client_caps - self._identities = [ DiscoIdentity(category='client', type='pc', name='Gajim') ] @@ -74,83 +69,46 @@ def _entity_caps(self, _con, _stanza, properties): 'Received from %s, type: %s, method: %s, node: %s, hash: %s', jid, properties.type, hash_method, node, caps_hash) - client_caps = self._create_suitable_client_caps( - node, caps_hash, hash_method, jid) - - # Type is None means 'available' - if properties.type.is_available and client_caps.hash_method == 'no': - self._capscache.forget_caps(client_caps) - client_caps = self._create_suitable_client_caps( - node, caps_hash, hash_method) - else: - self._capscache.query_client_of_jid_if_unknown( - self._con, jid, client_caps) - - self._update_client_caps_of_contact(properties.jid, client_caps) - - app.nec.push_incoming_event( - NetworkEvent('caps-update', - account=self._account, - fjid=jid, - jid=properties.jid.getBare())) + disco_info = app.logger.get_caps_entry(hash_method, caps_hash) + if disco_info is None: + self._con.get_module('Discovery').disco_info( + jid, + '%s#%s' % (node, caps_hash), + callback=self._on_disco_info, + user_data=hash_method) - def _update_client_caps_of_contact(self, from_, client_caps): - contact = self._get_contact_or_gc_contact_for_jid(from_) - if contact is not None: - contact.client_caps = client_caps else: - self._log.info('Received Caps from unknown contact %s', from_) - - def _get_contact_or_gc_contact_for_jid(self, from_): - contact = app.contacts.get_contact_from_full_jid(self._account, - str(from_)) - - if contact is None: - room_jid, resource = from_.getStripped(), from_.getResource() - contact = app.contacts.get_gc_contact( - self._account, room_jid, resource) - return contact - - 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 - """ - - if is_error_result(info): - self._log.info(info) + app.logger.set_last_disco_info(jid, disco_info, cache_only=True) + app.nec.push_incoming_event( + NetworkEvent('caps-update', + account=self._account, + fjid=jid, + jid=properties.jid.getBare())) + + def _on_disco_info(self, disco_info, hash_method): + if is_error_result(disco_info): + self._log.info(disco_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', info.jid) - return - - lookup = contact.client_caps.get_cache_lookup_strategy() - cache_item = lookup(self._capscache) - - if cache_item.is_valid(): - # we already know that the hash is fine and have already cached - # the identities and features - return + bare_jid = disco_info.jid.getBare() try: - compute_caps_hash(info) + compute_caps_hash(disco_info) except Exception as error: self._log.warning('Disco info malformed: %s %s', - contact.get_full_jid(), error) - node = caps_hash = hash_method = None - contact.client_caps = self._create_suitable_client_caps( - node, caps_hash, hash_method) - else: - cache_item.set_and_store(info) + disco_info.jid, error) + return + + app.logger.add_caps_entry( + str(disco_info.jid), + hash_method, + disco_info.get_caps_hash(), + disco_info) app.nec.push_incoming_event( NetworkEvent('caps-update', account=self._account, - fjid=str(info.jid), + fjid=str(disco_info.jid), jid=bare_jid)) def update_caps(self): diff --git a/gajim/common/modules/discovery.py b/gajim/common/modules/discovery.py index 7dbf8feafad497e9ba631708b3db2d24e73b322d..b443bcc2d3c21d49f5de00857ed7ef06c434e81e 100644 --- a/gajim/common/modules/discovery.py +++ b/gajim/common/modules/discovery.py @@ -57,10 +57,6 @@ def account_info(self): def server_info(self): return self._server_info - def disco_contact(self, jid, node=None): - success_cb = self._con.get_module('Caps').contact_info_received - self.disco_info(jid, node, callback=success_cb) - def discover_server_items(self): server = self._con.get_own_jid().getDomain() self.disco_items(server, callback=self._server_items_received) diff --git a/gajim/common/optparser.py b/gajim/common/optparser.py index 4f4d79ede42dabdabeeffb1c5730b6e663d72933..cf22fb686af440fc4afbe8386cafdd5aaecb9cb1 100644 --- a/gajim/common/optparser.py +++ b/gajim/common/optparser.py @@ -30,7 +30,6 @@ from pathlib import Path from gajim.common import app -from gajim.common import caps_cache from gajim.common.i18n import _ @@ -153,8 +152,6 @@ def update_config(self, old_version, new_version): app.config.set('version', new_version) - caps_cache.capscache.initialize_from_db() - @staticmethod def update_ft_proxies(to_remove=None, to_add=None): if to_remove is None: diff --git a/gajim/common/structs.py b/gajim/common/structs.py index c92a4cb7e46a599fd101dd2cc5c4cae1b710e75a..651969701a85dba2d3656060ec50af0eb721bd38 100644 --- a/gajim/common/structs.py +++ b/gajim/common/structs.py @@ -23,9 +23,6 @@ URI = namedtuple('URI', 'type action data') URI.__new__.__defaults__ = (None, None) # type: ignore -CapsData = namedtuple('CapsData', 'identities features dataforms') -CapsIdentity = namedtuple('CapsIdentity', 'category type name lang') - class MUCData: def __init__(self, room_jid, nick, password, config=None): diff --git a/test/broken/no_gui/test_protocol_caps.py b/test/broken/no_gui/test_protocol_caps.py deleted file mode 100644 index eef096a43d581f7a425d9f527a0de2de4517d50b..0000000000000000000000000000000000000000 --- a/test/broken/no_gui/test_protocol_caps.py +++ /dev/null @@ -1,55 +0,0 @@ -''' -Tests for caps network coding -''' - -import unittest -from unittest.mock import MagicMock - -import nbxmpp - -from gajim.common import app -from gajim.common import nec -from gajim.common import ged -from gajim.common import caps_cache -from gajim.common.modules.caps import Caps - - -class TestConnectionCaps(unittest.TestCase): - - def setUp(self): - app.contacts.add_account('account') - contact = app.contacts.create_contact( - 'user@server.com', 'account', resource='a') - app.contacts.add_contact('account', contact) - - app.nec = nec.NetworkEventsController() - app.ged.register_event_handler( - 'caps-presence-received', ged.GUI2, - self._nec_caps_presence_received) - - self.module = Caps(MagicMock()) - self.module._account = 'account' - self.module._capscache = MagicMock() - - def tearDown(self): - app.contacts.remove_account('account') - - def _nec_caps_presence_received(self, obj): - self.assertTrue( - isinstance(obj.client_caps, caps_cache.ClientCaps), - msg="On receive of valid caps, ClientCaps should be returned") - - def test_capsPresenceCB(self): - fjid = "user@server.com/a" - - xml = """<presence from='user@server.com/a' to='%s' id='123'> - <c node='http://gajim.org' ver='pRCD6cgQ4SDqNMCjdhRV6TECx5o=' - hash='sha-1' xmlns='http://jabber.org/protocol/caps'/> - </presence> - """ % (fjid) - msg = nbxmpp.protocol.Presence(node=nbxmpp.simplexml.XML2Node(xml)) - self.module._presence_received(None, msg) - - -if __name__ == '__main__': - unittest.main() diff --git a/test/gtk/htmltextview.py b/test/gtk/htmltextview.py index a1069254290f9230e1db1eeb20ed6cdcfea71f41..cdc28fa51659ff90aa382e4bdbc6b5ccd442f91b 100644 --- a/test/gtk/htmltextview.py +++ b/test/gtk/htmltextview.py @@ -7,13 +7,11 @@ from gajim.common import app from gajim.common import configpaths configpaths.init() -from gajim.common import caps_cache from gajim.common.helpers import AdditionalDataDict from gajim.conversation_textview import ConversationTextview from gajim.gui_interface import Interface -caps_cache.capscache = MagicMock() app.plugin_manager = MagicMock() app.logger = MagicMock() app.cert_store = MagicMock() diff --git a/test/no_gui/unit/test_account.py b/test/no_gui/unit/test_account.py deleted file mode 100644 index 75d94542e6fda2c7d3a4bf5f5fe0ab5e7afc07b6..0000000000000000000000000000000000000000 --- a/test/no_gui/unit/test_account.py +++ /dev/null @@ -1,19 +0,0 @@ -''' -Tests for Account classes -''' -import unittest - -from gajim.common.account import Account - - -class Test(unittest.TestCase): - - def testInstantiate(self): - account = Account(name='MyAcc', contacts=None, gc_contacts=None) - - self.assertEqual('MyAcc', account.name) - self.assertTrue(account.gc_contacts is None) - self.assertTrue(account.contacts is None) - -if __name__ == "__main__": - unittest.main() diff --git a/test/no_gui/unit/test_caps_cache.py b/test/no_gui/unit/test_caps_cache.py deleted file mode 100644 index bf086761af7f4bcaa3622d9a9c529cc23834de65..0000000000000000000000000000000000000000 --- a/test/no_gui/unit/test_caps_cache.py +++ /dev/null @@ -1,149 +0,0 @@ -''' -Tests for capabilities and the capabilities cache -''' -import unittest -from unittest.mock import MagicMock, Mock - -from nbxmpp import NS_MUC, NS_PING, NS_XHTML_IM, NS_JINGLE_FILE_TRANSFER_5 -from nbxmpp.structs import DiscoIdentity -from nbxmpp.structs import DiscoInfo -from gajim.common import caps_cache as caps -from gajim.common.structs import CapsData - - -class CommonCapsTest(unittest.TestCase): - - def setUp(self): - self.caps_method = 'sha-1' - self.caps_hash = 'm3P2WeXPMGVH2tZPe7yITnfY0Dw=' - self.client_caps = (self.caps_method, self.caps_hash) - - self.node = "http://gajim.org" - self.identity = DiscoIdentity(category='client', - type='pc', - name='Gajim') - - self.identities = [self.identity] - self.features = [NS_MUC, NS_XHTML_IM, NS_JINGLE_FILE_TRANSFER_5] - - # Simulate a filled db - db_caps_cache = { - (self.caps_method, self.caps_hash): CapsData(self.identities, self.features, []), - ('old', self.node + '#' + self.caps_hash): CapsData(self.identities, self.features, []) - } - - self.logger = Mock() - self.logger.load_caps_data = Mock(return_value=db_caps_cache) - - self.cc = caps.CapsCache(self.logger) - caps.capscache = self.cc - - -class TestCapsCache(CommonCapsTest): - - def test_set_retrieve(self): - ''' Test basic set / retrieve cycle ''' - - self.cc[self.client_caps].identities = self.identities - self.cc[self.client_caps].features = self.features - - self.assertTrue(NS_MUC in self.cc[self.client_caps].features) - self.assertTrue(NS_PING not in self.cc[self.client_caps].features) - - identities = self.cc[self.client_caps].identities - - self.assertEqual(1, len(identities)) - - identity = identities[0] - self.assertEqual('client', identity.category) - self.assertEqual('pc', identity.type) - - def test_set_and_store(self): - ''' Test client_caps update gets logged into db ''' - - disco_info = DiscoInfo(None, self.identities, self.features, []) - - item = self.cc[self.client_caps] - item.set_and_store(disco_info) - - self.logger.add_caps_entry.assert_called_once_with(self.caps_method, - self.caps_hash, - disco_info) - - def test_initialize_from_db(self): - ''' Read cashed dummy data from db ''' - self.assertEqual(self.cc[self.client_caps].status, caps.NEW) - self.cc.initialize_from_db() - self.assertEqual(self.cc[self.client_caps].status, caps.CACHED) - - def test_preload_triggering_query(self): - ''' Make sure that preload issues a disco ''' - 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.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 = 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.assertEqual(0, connection.get_module('Discovery').disco_contact.call_count) - - -class TestClientCaps(CommonCapsTest): - - def setUp(self): - CommonCapsTest.setUp(self) - self.client_caps = caps.ClientCaps(self.caps_hash, self.node, self.caps_method) - - def test_query_by_get_discover_strategy(self): - ''' Client must be queried if the data is unkown ''' - connection = MagicMock() - discover = self.client_caps.get_discover_strategy() - discover(connection, "test@gajim.org") - 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), - msg="Assume supported, if we don't have caps") - - self.assertFalse(caps.client_supports(self.client_caps, NS_JINGLE_FILE_TRANSFER_5), - msg="Must not assume blacklisted feature is supported on default") - - self.cc.initialize_from_db() - - self.assertFalse(caps.client_supports(self.client_caps, NS_PING), - msg="Must return false on unsupported feature") - - self.assertTrue(caps.client_supports(self.client_caps, NS_XHTML_IM), - msg="Must return True on supported feature") - - self.assertTrue(caps.client_supports(self.client_caps, NS_MUC), - msg="Must return True on supported feature") - - -class TestOldClientCaps(TestClientCaps): - - def setUp(self): - TestClientCaps.setUp(self) - self.client_caps = caps.OldClientCaps(self.caps_hash, self.node) - - def test_query_by_get_discover_strategy(self): - ''' Client must be queried if the data is unknown ''' - connection = MagicMock() - discover = self.client_caps.get_discover_strategy() - discover(connection, "test@gajim.org") - - connection.get_module('Discovery').disco_contact.assert_called_once_with('test@gajim.org') - -if __name__ == '__main__': - unittest.main() diff --git a/test/no_gui/unit/test_contacts.py b/test/no_gui/unit/test_contacts.py deleted file mode 100644 index b3233a422fd9490a37e961c84a29151890b7eda0..0000000000000000000000000000000000000000 --- a/test/no_gui/unit/test_contacts.py +++ /dev/null @@ -1,117 +0,0 @@ -''' -Test for Contact, GC_Contact and Contacts -''' -import unittest -from nbxmpp import NS_MUC - -from gajim.common.contacts import CommonContact, Contact, GC_Contact, LegacyContactsAPI - -from gajim.common import caps_cache - -class TestCommonContact(unittest.TestCase): - - def setUp(self): - self.contact = CommonContact( - jid='', account="", resource='', show='', presence=None, - status='', name='', chatstate=None, client_caps=None) - - def test_default_client_supports(self): - ''' - Test the caps support method of contacts. - See test_caps for more enhanced tests. - ''' - caps_cache.capscache = caps_cache.CapsCache() - self.assertTrue(self.contact.supports(NS_MUC), - msg="Must not backtrace on simple check for supported feature") - - self.contact.client_caps = caps_cache.NullClientCaps() - - self.assertTrue(self.contact.supports(NS_MUC), - msg="Must not backtrace on simple check for supported feature") - - -class TestContact(TestCommonContact): - - def setUp(self): - TestCommonContact.setUp(self) - self.contact = Contact(jid="test@gajim.org", account="account") - - def test_attributes_available(self): - '''This test supports the migration from the old to the new contact - domain model by smoke testing that no attribute values are lost''' - - attributes = ["jid", "resource", "show", "status", "name", - "chatstate", "client_caps", "priority", "sub"] - for attr in attributes: - self.assertTrue(hasattr(self.contact, attr), msg="expected: " + attr) - - -class TestGC_Contact(TestCommonContact): - - def setUp(self): - TestCommonContact.setUp(self) - self.contact = GC_Contact(room_jid="confernce@gajim.org", account="account") - - def test_attributes_available(self): - '''This test supports the migration from the old to the new contact - domain model by asserting no attributes have been lost''' - - attributes = ["jid", "resource", "show", "status", "name", - "chatstate", "client_caps", "role", "room_jid"] - for attr in attributes: - self.assertTrue(hasattr(self.contact, attr), msg="expected: " + attr) - - -class TestContacts(unittest.TestCase): - - def setUp(self): - self.contacts = LegacyContactsAPI() - - def test_create_add_get_contact(self): - jid = 'test@gajim.org' - account = "account" - - contact = self.contacts.create_contact(jid=jid, account=account) - self.contacts.add_contact(account, contact) - - retrieved_contact = self.contacts.get_contact(account, jid) - self.assertEqual(contact, retrieved_contact, "Contact must be known") - - self.contacts.remove_contact(account, contact) - - retrieved_contact = self.contacts.get_contact(account, jid) - self.assertNotEqual(contact, retrieved_contact, - msg="Contact must not be known any longer") - - - def test_copy_contact(self): - jid = 'test@gajim.org' - account = "account" - - contact = self.contacts.create_contact(jid=jid, account=account) - copy = self.contacts.copy_contact(contact) - self.assertFalse(contact is copy, msg="Must not be the same") - - # Not yet implemented to remain backwart compatible - # self.assertEqual(contact, copy, msg="Must be equal") - - def test_legacy_contacts_from_groups(self): - jid1 = "test1@gajim.org" - jid2 = "test2@gajim.org" - account = "account" - group = "GroupA" - - contact1 = self.contacts.create_contact(jid=jid1, account=account, - groups=[group]) - self.contacts.add_contact(account, contact1) - - contact2 = self.contacts.create_contact(jid=jid2, account=account, - groups=[group]) - self.contacts.add_contact(account, contact2) - - self.assertEqual(2, len(self.contacts.get_contacts_from_group(account, group))) - self.assertEqual(0, len(self.contacts.get_contacts_from_group(account, ''))) - - -if __name__ == "__main__": - unittest.main()