Merge refactoring branch back to default
This commit is contained in:
commit
07d0811439
18 changed files with 563 additions and 382 deletions
|
@ -1,4 +1,5 @@
|
||||||
syntax: glob
|
syntax: glob
|
||||||
|
*.orig
|
||||||
*.gmo
|
*.gmo
|
||||||
*.in
|
*.in
|
||||||
*.la
|
*.la
|
||||||
|
|
|
@ -1356,9 +1356,7 @@ class ChatControl(ChatControlBase):
|
||||||
|
|
||||||
def update_toolbar(self):
|
def update_toolbar(self):
|
||||||
# Formatting
|
# Formatting
|
||||||
if gajim.capscache.is_supported(self.contact, NS_XHTML_IM) \
|
if self.contact.supports(NS_XHTML_IM) and not self.gpg_is_active:
|
||||||
and not gajim.capscache.is_supported(self.contact, 'notexistant') \
|
|
||||||
and not self.gpg_is_active:
|
|
||||||
self._formattings_button.set_sensitive(True)
|
self._formattings_button.set_sensitive(True)
|
||||||
else:
|
else:
|
||||||
self._formattings_button.set_sensitive(False)
|
self._formattings_button.set_sensitive(False)
|
||||||
|
@ -1371,15 +1369,15 @@ class ChatControl(ChatControlBase):
|
||||||
self._add_to_roster_button.hide()
|
self._add_to_roster_button.hide()
|
||||||
|
|
||||||
# Jingle detection
|
# Jingle detection
|
||||||
if gajim.capscache.is_supported(self.contact, NS_JINGLE_ICE_UDP) and \
|
if self.contact.supports(NS_JINGLE_ICE_UDP) and \
|
||||||
gajim.HAVE_FARSIGHT and self.contact.resource:
|
gajim.HAVE_FARSIGHT and self.contact.resource:
|
||||||
if gajim.capscache.is_supported(self.contact, NS_JINGLE_RTP_AUDIO):
|
if self.contact.supports(NS_JINGLE_RTP_AUDIO):
|
||||||
if self.audio_state == self.JINGLE_STATE_NOT_AVAILABLE:
|
if self.audio_state == self.JINGLE_STATE_NOT_AVAILABLE:
|
||||||
self.set_audio_state('available')
|
self.set_audio_state('available')
|
||||||
else:
|
else:
|
||||||
self.set_audio_state('not_available')
|
self.set_audio_state('not_available')
|
||||||
|
|
||||||
if gajim.capscache.is_supported(self.contact, NS_JINGLE_RTP_VIDEO):
|
if self.contact.supports(NS_JINGLE_RTP_VIDEO):
|
||||||
if self.video_state == self.JINGLE_STATE_NOT_AVAILABLE:
|
if self.video_state == self.JINGLE_STATE_NOT_AVAILABLE:
|
||||||
self.set_video_state('available')
|
self.set_video_state('available')
|
||||||
else:
|
else:
|
||||||
|
@ -1403,12 +1401,11 @@ class ChatControl(ChatControlBase):
|
||||||
self._video_button.set_sensitive(True)
|
self._video_button.set_sensitive(True)
|
||||||
|
|
||||||
# Send file
|
# Send file
|
||||||
if gajim.capscache.is_supported(self.contact, NS_FILE) and \
|
if self.contact.supports(NS_FILE) and self.contact.resource:
|
||||||
self.contact.resource:
|
|
||||||
self._send_file_button.set_sensitive(True)
|
self._send_file_button.set_sensitive(True)
|
||||||
else:
|
else:
|
||||||
self._send_file_button.set_sensitive(False)
|
self._send_file_button.set_sensitive(False)
|
||||||
if not gajim.capscache.is_supported(self.contact, NS_FILE):
|
if not self.contact.supports(NS_FILE):
|
||||||
self._send_file_button.set_tooltip_text(_(
|
self._send_file_button.set_tooltip_text(_(
|
||||||
"This contact does not support file transfer."))
|
"This contact does not support file transfer."))
|
||||||
else:
|
else:
|
||||||
|
@ -1417,7 +1414,7 @@ class ChatControl(ChatControlBase):
|
||||||
"her a file."))
|
"her a file."))
|
||||||
|
|
||||||
# Convert to GC
|
# Convert to GC
|
||||||
if gajim.capscache.is_supported(self.contact, NS_MUC):
|
if self.contact.supports(NS_MUC):
|
||||||
self._convert_to_gc_button.set_sensitive(True)
|
self._convert_to_gc_button.set_sensitive(True)
|
||||||
else:
|
else:
|
||||||
self._convert_to_gc_button.set_sensitive(False)
|
self._convert_to_gc_button.set_sensitive(False)
|
||||||
|
@ -1982,10 +1979,7 @@ class ChatControl(ChatControlBase):
|
||||||
self._schedule_activity_timers()
|
self._schedule_activity_timers()
|
||||||
|
|
||||||
def _on_sent(id_, contact, message, encrypted, xhtml):
|
def _on_sent(id_, contact, message, encrypted, xhtml):
|
||||||
# XXX: Once we have fallback to disco, remove notexistant check
|
if contact.supports(NS_RECEIPTS) and gajim.config.get_per('accounts',
|
||||||
if gajim.capscache.is_supported(contact, NS_RECEIPTS) \
|
|
||||||
and not gajim.capscache.is_supported(contact,
|
|
||||||
'notexistant') and gajim.config.get_per('accounts',
|
|
||||||
self.account, 'request_receipt'):
|
self.account, 'request_receipt'):
|
||||||
xep0184_id = id_
|
xep0184_id = id_
|
||||||
else:
|
else:
|
||||||
|
@ -2504,12 +2498,8 @@ class ChatControl(ChatControlBase):
|
||||||
want_e2e = not e2e_is_active and not self.gpg_is_active \
|
want_e2e = not e2e_is_active and not self.gpg_is_active \
|
||||||
and e2e_pref
|
and e2e_pref
|
||||||
|
|
||||||
# XXX: Once we have fallback to disco, remove notexistant check
|
|
||||||
if want_e2e and not self.no_autonegotiation \
|
if want_e2e and not self.no_autonegotiation \
|
||||||
and gajim.HAVE_PYCRYPTO \
|
and gajim.HAVE_PYCRYPTO and self.contact.supports(NS_ESESSION):
|
||||||
and gajim.capscache.is_supported(self.contact,
|
|
||||||
NS_ESESSION) and not gajim.capscache.is_supported(
|
|
||||||
self.contact, 'notexistant'):
|
|
||||||
self.begin_e2e_negotiation()
|
self.begin_e2e_negotiation()
|
||||||
else:
|
else:
|
||||||
self.send_chatstate('active', self.contact)
|
self.send_chatstate('active', self.contact)
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
## Copyright (C) 2007-2008 Yann Leboulanger <asterix AT lagaule.org>
|
## Copyright (C) 2007-2008 Yann Leboulanger <asterix AT lagaule.org>
|
||||||
## Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com>
|
## Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com>
|
||||||
## Jonathan Schleifer <js-gajim AT webkeks.org>
|
## Jonathan Schleifer <js-gajim AT webkeks.org>
|
||||||
## Stephan Erb <steve-e AT h3c.de>
|
## Copyright (C) 2008-2009 Stephan Erb <steve-e AT h3c.de>
|
||||||
##
|
##
|
||||||
## This file is part of Gajim.
|
## This file is part of Gajim.
|
||||||
##
|
##
|
||||||
|
@ -23,55 +23,198 @@
|
||||||
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
||||||
##
|
##
|
||||||
|
|
||||||
from itertools import *
|
'''
|
||||||
|
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 gajim
|
import gajim
|
||||||
import helpers
|
import helpers
|
||||||
|
import base64
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
from common.xmpp import NS_XHTML_IM, NS_RECEIPTS, NS_ESESSION, NS_CHATSTATES
|
||||||
|
# Features where we cannot safely assume that the other side supports them
|
||||||
|
FEATURE_BLACKLIST = [NS_CHATSTATES, NS_XHTML_IM, NS_RECEIPTS, NS_ESESSION]
|
||||||
|
|
||||||
|
|
||||||
|
capscache = None
|
||||||
|
def initialize(logger):
|
||||||
|
''' Initializes the capscache global '''
|
||||||
|
global capscache
|
||||||
|
capscache = CapsCache(logger)
|
||||||
|
|
||||||
|
|
||||||
|
def compute_caps_hash(identities, features, dataforms=[], hash_method='sha-1'):
|
||||||
|
'''Compute caps hash according to XEP-0115, V1.5
|
||||||
|
|
||||||
|
dataforms are xmpp.DataForms objects as common.dataforms don't allow several
|
||||||
|
values without a field type list-multi'''
|
||||||
|
|
||||||
|
def sort_identities_func(i1, i2):
|
||||||
|
cat1 = i1['category']
|
||||||
|
cat2 = i2['category']
|
||||||
|
if cat1 < cat2:
|
||||||
|
return -1
|
||||||
|
if cat1 > cat2:
|
||||||
|
return 1
|
||||||
|
type1 = i1.get('type', '')
|
||||||
|
type2 = i2.get('type', '')
|
||||||
|
if type1 < type2:
|
||||||
|
return -1
|
||||||
|
if type1 > type2:
|
||||||
|
return 1
|
||||||
|
lang1 = i1.get('xml:lang', '')
|
||||||
|
lang2 = i2.get('xml:lang', '')
|
||||||
|
if lang1 < lang2:
|
||||||
|
return -1
|
||||||
|
if lang1 > lang2:
|
||||||
|
return 1
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def sort_dataforms_func(d1, d2):
|
||||||
|
f1 = d1.getField('FORM_TYPE')
|
||||||
|
f2 = d2.getField('FORM_TYPE')
|
||||||
|
if f1 and f2 and (f1.getValue() < f2.getValue()):
|
||||||
|
return -1
|
||||||
|
return 1
|
||||||
|
|
||||||
|
S = ''
|
||||||
|
identities.sort(cmp=sort_identities_func)
|
||||||
|
for i in identities:
|
||||||
|
c = i['category']
|
||||||
|
type_ = i.get('type', '')
|
||||||
|
lang = i.get('xml:lang', '')
|
||||||
|
name = i.get('name', '')
|
||||||
|
S += '%s/%s/%s/%s<' % (c, type_, lang, name)
|
||||||
|
features.sort()
|
||||||
|
for f in features:
|
||||||
|
S += '%s<' % f
|
||||||
|
dataforms.sort(cmp=sort_dataforms_func)
|
||||||
|
for dataform in dataforms:
|
||||||
|
# fields indexed by var
|
||||||
|
fields = {}
|
||||||
|
for f in dataform.getChildren():
|
||||||
|
fields[f.getVar()] = f
|
||||||
|
form_type = fields.get('FORM_TYPE')
|
||||||
|
if form_type:
|
||||||
|
S += form_type.getValue() + '<'
|
||||||
|
del fields['FORM_TYPE']
|
||||||
|
for var in sorted(fields.keys()):
|
||||||
|
S += '%s<' % var
|
||||||
|
values = sorted(fields[var].getValues())
|
||||||
|
for value in values:
|
||||||
|
S += '%s<' % value
|
||||||
|
|
||||||
|
if hash_method == 'sha-1':
|
||||||
|
hash_ = hashlib.sha1(S)
|
||||||
|
elif hash_method == 'md5':
|
||||||
|
hash_ = hashlib.md5(S)
|
||||||
|
else:
|
||||||
|
return ''
|
||||||
|
return base64.b64encode(hash_.digest())
|
||||||
|
|
||||||
|
|
||||||
|
class AbstractClientCaps(object):
|
||||||
|
'''
|
||||||
|
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
|
||||||
|
|
||||||
|
def get_discover_strategy(self):
|
||||||
|
return self._discover
|
||||||
|
|
||||||
|
def _discover(self, connection, jid):
|
||||||
|
''' To be implemented by subclassess '''
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def get_cache_lookup_strategy(self):
|
||||||
|
return self._lookup_in_cache
|
||||||
|
|
||||||
|
def _lookup_in_cache(self, caps_cache):
|
||||||
|
''' To be implemented by subclassess '''
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def get_hash_validation_strategy(self):
|
||||||
|
return self._is_hash_valid
|
||||||
|
|
||||||
|
def _is_hash_valid(self, identities, features, dataforms):
|
||||||
|
''' To be implemented by subclassess '''
|
||||||
|
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.discoverInfo(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)
|
||||||
|
return computed_hash == self._hash
|
||||||
|
|
||||||
|
|
||||||
|
class OldClientCaps(AbstractClientCaps):
|
||||||
|
''' Old XEP-115 implemtation. Kept around for background competability. '''
|
||||||
|
|
||||||
|
def __init__(self, caps_hash, node):
|
||||||
|
AbstractClientCaps.__init__(self, caps_hash, node)
|
||||||
|
|
||||||
|
def _lookup_in_cache(self, caps_cache):
|
||||||
|
return caps_cache[('old', self._node + '#' + self._hash)]
|
||||||
|
|
||||||
|
def _discover(self, connection, jid):
|
||||||
|
connection.discoverInfo(jid)
|
||||||
|
|
||||||
|
def _is_hash_valid(self, identities, features, dataforms):
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
AbstractClientCaps.__init__(self, None, None)
|
||||||
|
|
||||||
|
def _lookup_in_cache(self, caps_cache):
|
||||||
|
# lookup something which does not exist to get a new CacheItem created
|
||||||
|
cache_item = caps_cache[('old', '')]
|
||||||
|
assert cache_item.queried == 0
|
||||||
|
return cache_item
|
||||||
|
|
||||||
|
def _discover(self, connection, jid):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _is_hash_valid(self, identities, features, dataforms):
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
class CapsCache(object):
|
class CapsCache(object):
|
||||||
''' This object keeps the mapping between caps data and real disco
|
'''
|
||||||
|
This object keeps the mapping between caps data and real disco
|
||||||
features they represent, and provides simple way to query that info.
|
features they represent, and provides simple way to query that info.
|
||||||
It is application-wide, that is there's one object for all
|
|
||||||
connections.
|
|
||||||
Goals:
|
|
||||||
* handle storing/retrieving info from database
|
|
||||||
* cache info in memory
|
|
||||||
* expose simple interface
|
|
||||||
Properties:
|
|
||||||
* one object for all connections (move to logger.py?)
|
|
||||||
* store info efficiently (a set() of urls -- we can assume there won't be
|
|
||||||
too much of these, ensure that (X,Y,Z1) and (X,Y,Z2) has different
|
|
||||||
features.
|
|
||||||
|
|
||||||
Connections with other objects: (TODO)
|
|
||||||
|
|
||||||
Interface:
|
|
||||||
|
|
||||||
# object creation
|
|
||||||
>>> cc=CapsCache(logger_object)
|
|
||||||
|
|
||||||
>>> caps = ('sha-1', '66/0NaeaBKkwk85efJTGmU47vXI=')
|
|
||||||
>>> muc = 'http://jabber.org/protocol/muc'
|
|
||||||
>>> chatstates = 'http://jabber.org/protocol/chatstates'
|
|
||||||
|
|
||||||
# setting data
|
|
||||||
>>> cc[caps].identities = [{'category':'client', 'type':'pc'}]
|
|
||||||
>>> cc[caps].features = [muc]
|
|
||||||
|
|
||||||
# retrieving data
|
|
||||||
>>> muc in cc[caps].features
|
|
||||||
True
|
|
||||||
>>> chatstates in cc[caps].features
|
|
||||||
False
|
|
||||||
>>> cc[caps].identities
|
|
||||||
[{'category': 'client', 'type': 'pc'}]
|
|
||||||
>>> x = cc[caps] # more efficient if making several queries for one set of caps
|
|
||||||
ATypicalBlackBoxObject
|
|
||||||
>>> muc in x.features
|
|
||||||
True
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
def __init__(self, logger=None):
|
def __init__(self, logger=None):
|
||||||
''' Create a cache for entity capabilities. '''
|
|
||||||
# our containers:
|
# our containers:
|
||||||
# __cache is a dictionary mapping: pair of hash method and hash maps
|
# __cache is a dictionary mapping: pair of hash method and hash maps
|
||||||
# to CapsCacheItem object
|
# to CapsCacheItem object
|
||||||
|
@ -80,40 +223,39 @@ class CapsCache(object):
|
||||||
self.__cache = {}
|
self.__cache = {}
|
||||||
|
|
||||||
class CacheItem(object):
|
class CacheItem(object):
|
||||||
''' TODO: logging data into db '''
|
|
||||||
# __names is a string cache; every string long enough is given
|
# __names is a string cache; every string long enough is given
|
||||||
# another object, and we will have plenty of identical long
|
# another object, and we will have plenty of identical long
|
||||||
# strings. therefore we can cache them
|
# strings. therefore we can cache them
|
||||||
# TODO: maybe put all known xmpp namespace strings here
|
|
||||||
# (strings given in xmpppy)?
|
|
||||||
__names = {}
|
__names = {}
|
||||||
def __init__(ciself, hash_method, hash_):
|
|
||||||
|
def __init__(self, hash_method, hash_, logger):
|
||||||
# cached into db
|
# cached into db
|
||||||
ciself.hash_method = hash_method
|
self.hash_method = hash_method
|
||||||
ciself.hash = hash_
|
self.hash = hash_
|
||||||
ciself._features = []
|
self._features = []
|
||||||
ciself._identities = []
|
self._identities = []
|
||||||
|
self._logger = logger
|
||||||
|
|
||||||
# not cached into db:
|
# not cached into db:
|
||||||
# have we sent the query?
|
# have we sent the query?
|
||||||
# 0 == not queried
|
# 0 == not queried
|
||||||
# 1 == queried
|
# 1 == queried
|
||||||
# 2 == got the answer
|
# 2 == got the answer
|
||||||
ciself.queried = 0
|
self.queried = 0
|
||||||
|
|
||||||
def _get_features(ciself):
|
def _get_features(self):
|
||||||
return ciself._features
|
return self._features
|
||||||
|
|
||||||
def _set_features(ciself, value):
|
def _set_features(self, value):
|
||||||
ciself._features = []
|
self._features = []
|
||||||
for feature in value:
|
for feature in value:
|
||||||
ciself._features.append(ciself.__names.setdefault(feature,
|
self._features.append(self.__names.setdefault(feature, feature))
|
||||||
feature))
|
|
||||||
features = property(_get_features, _set_features)
|
features = property(_get_features, _set_features)
|
||||||
|
|
||||||
def _get_identities(ciself):
|
def _get_identities(self):
|
||||||
list_ = []
|
list_ = []
|
||||||
for i in ciself._identities:
|
for i in self._identities:
|
||||||
# transforms it back in a dict
|
# transforms it back in a dict
|
||||||
d = dict()
|
d = dict()
|
||||||
d['category'] = i[0]
|
d['category'] = i[0]
|
||||||
|
@ -125,36 +267,27 @@ class CapsCache(object):
|
||||||
d['name'] = i[3]
|
d['name'] = i[3]
|
||||||
list_.append(d)
|
list_.append(d)
|
||||||
return list_
|
return list_
|
||||||
def _set_identities(ciself, value):
|
|
||||||
ciself._identities = []
|
def _set_identities(self, value):
|
||||||
|
self._identities = []
|
||||||
for identity in value:
|
for identity in value:
|
||||||
# dict are not hashable, so transform it into a tuple
|
# dict are not hashable, so transform it into a tuple
|
||||||
t = (identity['category'], identity.get('type'),
|
t = (identity['category'], identity.get('type'),
|
||||||
identity.get('xml:lang'), identity.get('name'))
|
identity.get('xml:lang'), identity.get('name'))
|
||||||
ciself._identities.append(ciself.__names.setdefault(t, t))
|
self._identities.append(self.__names.setdefault(t, t))
|
||||||
|
|
||||||
identities = property(_get_identities, _set_identities)
|
identities = property(_get_identities, _set_identities)
|
||||||
|
|
||||||
def update(ciself, identities, features):
|
def set_and_store(self, identities, features):
|
||||||
# NOTE: self refers to CapsCache object, not to CacheItem
|
self.identities = identities
|
||||||
ciself.identities=identities
|
self.features = features
|
||||||
ciself.features=features
|
self._logger.add_caps_entry(self.hash_method, self.hash,
|
||||||
self.logger.add_caps_entry(ciself.hash_method, ciself.hash,
|
|
||||||
identities, features)
|
identities, features)
|
||||||
|
|
||||||
self.__CacheItem = CacheItem
|
self.__CacheItem = CacheItem
|
||||||
|
|
||||||
# prepopulate data which we are sure of; note: we do not log these info
|
|
||||||
|
|
||||||
for account in gajim.connections:
|
|
||||||
gajimcaps = self[('sha-1', gajim.caps_hash[account])]
|
|
||||||
gajimcaps.identities = [gajim.gajim_identity]
|
|
||||||
gajimcaps.features = gajim.gajim_common_features + \
|
|
||||||
gajim.gajim_optional_features[account]
|
|
||||||
|
|
||||||
# start logging data from the net
|
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
|
|
||||||
def load_from_db(self):
|
def initialize_from_db(self):
|
||||||
# get data from logger...
|
# get data from logger...
|
||||||
if self.logger is not None:
|
if self.logger is not None:
|
||||||
for hash_method, hash_, identities, features in \
|
for hash_method, hash_, identities, features in \
|
||||||
|
@ -170,61 +303,35 @@ class CapsCache(object):
|
||||||
|
|
||||||
hash_method, hash_ = caps
|
hash_method, hash_ = caps
|
||||||
|
|
||||||
x = self.__CacheItem(hash_method, hash_)
|
x = self.__CacheItem(hash_method, hash_, self.logger)
|
||||||
self.__cache[(hash_method, hash_)] = x
|
self.__cache[(hash_method, hash_)] = x
|
||||||
return x
|
return x
|
||||||
|
|
||||||
def preload(self, con, jid, node, hash_method, hash_):
|
def query_client_of_jid_if_unknown(self, connection, jid, client_caps):
|
||||||
''' Preload data about (node, ver, exts) caps using disco
|
'''
|
||||||
query to jid using proper connection. Don't query if
|
Start a disco query to determine caps (node, ver, exts).
|
||||||
the data is already in cache. '''
|
Won't query if the data is already in cache.
|
||||||
if hash_method == 'old':
|
'''
|
||||||
q = self[(hash_method, node + '#' + hash_)]
|
lookup_cache_item = client_caps.get_cache_lookup_strategy()
|
||||||
else:
|
q = lookup_cache_item(self)
|
||||||
q = self[(hash_method, hash_)]
|
|
||||||
|
if q.queried == 0:
|
||||||
if q.queried==0:
|
|
||||||
# do query for bare node+hash pair
|
# do query for bare node+hash pair
|
||||||
# this will create proper object
|
# this will create proper object
|
||||||
q.queried=1
|
q.queried = 1
|
||||||
if hash_method == 'old':
|
discover = client_caps.get_discover_strategy()
|
||||||
con.discoverInfo(jid)
|
discover(connection, jid)
|
||||||
else:
|
|
||||||
con.discoverInfo(jid, '%s#%s' % (node, hash_))
|
|
||||||
|
|
||||||
def is_supported(self, contact, feature):
|
|
||||||
if not contact:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Unfortunately, if all resources are offline, the contact
|
|
||||||
# includes the last resource that was online. Check for its
|
|
||||||
# show, so we can be sure it's existant. Otherwise, we still
|
|
||||||
# return caps for a contact that has no resources left.
|
|
||||||
if contact.show == 'offline':
|
|
||||||
return False
|
|
||||||
|
|
||||||
# FIXME: We assume everything is supported if we got no caps.
|
|
||||||
# This is the "Asterix way", after 0.12 release, I will
|
|
||||||
# likely implement a fallback to disco (could be disabled
|
|
||||||
# for mobile users who pay for traffic)
|
|
||||||
if contact.caps_hash_method == 'old':
|
|
||||||
features = self[(contact.caps_hash_method, contact.caps_node + '#' + \
|
|
||||||
contact.caps_hash)].features
|
|
||||||
else:
|
|
||||||
features = self[(contact.caps_hash_method, contact.caps_hash)].features
|
|
||||||
if feature in features or features == []:
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
gajim.capscache = CapsCache(gajim.logger)
|
|
||||||
|
|
||||||
class ConnectionCaps(object):
|
class ConnectionCaps(object):
|
||||||
''' This class highly depends on that it is a part of Connection class. '''
|
'''
|
||||||
|
This class highly depends on that it is a part of Connection class.
|
||||||
|
'''
|
||||||
def _capsPresenceCB(self, con, presence):
|
def _capsPresenceCB(self, con, presence):
|
||||||
''' Handle incoming presence stanzas... This is a callback
|
'''
|
||||||
for xmpp registered in connection_handlers.py'''
|
Handle incoming presence stanzas... This is a callback for xmpp
|
||||||
|
registered in connection_handlers.py
|
||||||
|
'''
|
||||||
# we will put these into proper Contact object and ask
|
# we will put these into proper Contact object and ask
|
||||||
# for disco... so that disco will learn how to interpret
|
# for disco... so that disco will learn how to interpret
|
||||||
# these caps
|
# these caps
|
||||||
|
@ -245,64 +352,47 @@ class ConnectionCaps(object):
|
||||||
# into Contacts
|
# into Contacts
|
||||||
return
|
return
|
||||||
|
|
||||||
# get the caps element
|
caps_tag = presence.getTag('c')
|
||||||
caps = presence.getTag('c')
|
if not caps_tag:
|
||||||
if not caps:
|
# presence did not contain caps_tag
|
||||||
contact.caps_node = None
|
client_caps = NullClientCaps()
|
||||||
contact.caps_hash = None
|
else:
|
||||||
contact.caps_hash_method = None
|
hash_method, node, caps_hash = caps_tag['hash'], caps_tag['node'], caps_tag['ver']
|
||||||
return
|
|
||||||
|
|
||||||
hash_method, node, hash_ = caps['hash'], caps['node'], caps['ver']
|
if node is None or caps_hash is None:
|
||||||
|
# improper caps in stanza, ignore client capabilities.
|
||||||
|
client_caps = NullClientCaps()
|
||||||
|
elif hash_method is None:
|
||||||
|
client_caps = OldClientCaps(caps_hash, node)
|
||||||
|
else:
|
||||||
|
client_caps = ClientCaps(caps_hash, node, hash_method)
|
||||||
|
|
||||||
|
capscache.query_client_of_jid_if_unknown(self, jid, client_caps)
|
||||||
|
contact.client_caps = client_caps
|
||||||
|
|
||||||
if hash_method is None and node and hash_:
|
|
||||||
# Old XEP-115 implentation
|
|
||||||
hash_method = 'old'
|
|
||||||
|
|
||||||
if hash_method is None or node is None or hash_ is None:
|
|
||||||
# improper caps in stanza, ignoring
|
|
||||||
contact.caps_node = None
|
|
||||||
contact.caps_hash = None
|
|
||||||
contact.hash_method = None
|
|
||||||
return
|
|
||||||
|
|
||||||
# start disco query...
|
|
||||||
gajim.capscache.preload(self, jid, node, hash_method, hash_)
|
|
||||||
|
|
||||||
# overwriting old data
|
|
||||||
contact.caps_node = node
|
|
||||||
contact.caps_hash_method = hash_method
|
|
||||||
contact.caps_hash = hash_
|
|
||||||
if pm_ctrl:
|
if pm_ctrl:
|
||||||
pm_ctrl.update_contact()
|
pm_ctrl.update_contact()
|
||||||
|
|
||||||
def _capsDiscoCB(self, jid, node, identities, features, dataforms):
|
def _capsDiscoCB(self, jid, node, identities, features, dataforms):
|
||||||
contact = gajim.contacts.get_contact_from_full_jid(self.name, jid)
|
contact = gajim.contacts.get_contact_from_full_jid(self.name, jid)
|
||||||
if not contact:
|
if not contact:
|
||||||
room_jid, nick = gajim.get_room_and_nick_from_fjid(jid)
|
room_jid, nick = gajim.get_room_and_nick_from_fjid(jid)
|
||||||
contact = gajim.contacts.get_gc_contact(self.name, room_jid, nick)
|
contact = gajim.contacts.get_gc_contact(self.name, room_jid, nick)
|
||||||
if contact is None:
|
if contact is None:
|
||||||
return
|
return
|
||||||
if not contact.caps_node:
|
|
||||||
return # we didn't asked for that?
|
|
||||||
if contact.caps_hash_method != 'old':
|
|
||||||
computed_hash = helpers.compute_caps_hash(identities, features,
|
|
||||||
dataforms=dataforms, hash_method=contact.caps_hash_method)
|
|
||||||
if computed_hash != contact.caps_hash:
|
|
||||||
# wrong hash, forget it
|
|
||||||
contact.caps_node = ''
|
|
||||||
contact.caps_hash_method = ''
|
|
||||||
contact.caps_hash = ''
|
|
||||||
return
|
|
||||||
# if we don't have this info already...
|
|
||||||
caps = gajim.capscache[(contact.caps_hash_method, contact.caps_hash)]
|
|
||||||
else:
|
|
||||||
# if we don't have this info already...
|
|
||||||
caps = gajim.capscache[(contact.caps_hash_method, contact.caps_node + \
|
|
||||||
'#' + contact.caps_hash)]
|
|
||||||
if caps.queried == 2:
|
|
||||||
return
|
|
||||||
|
|
||||||
caps.update(identities, features)
|
lookup = contact.client_caps.get_cache_lookup_strategy()
|
||||||
|
cache_item = lookup(capscache)
|
||||||
|
|
||||||
|
if cache_item.queried == 2:
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
validate = contact.client_caps.get_hash_validation_strategy()
|
||||||
|
hash_is_valid = validate(identities, features, dataforms)
|
||||||
|
|
||||||
|
if hash_is_valid:
|
||||||
|
cache_item.set_and_store(identities, features)
|
||||||
|
else:
|
||||||
|
contact.client_caps = NullClientCaps()
|
||||||
|
|
||||||
# vim: se ts=3:
|
# vim: se ts=3:
|
||||||
|
|
|
@ -1299,14 +1299,9 @@ class Connection(ConnectionHandlers):
|
||||||
# please note that the only valid tag inside a message containing a <body>
|
# please note that the only valid tag inside a message containing a <body>
|
||||||
# tag is the active event
|
# tag is the active event
|
||||||
if chatstate is not None:
|
if chatstate is not None:
|
||||||
# XXX: Once we have fallback to disco,
|
|
||||||
# remove notexistant check
|
|
||||||
if ((composing_xep == 'XEP-0085' or not composing_xep) \
|
if ((composing_xep == 'XEP-0085' or not composing_xep) \
|
||||||
and composing_xep != 'asked_once') or \
|
and composing_xep != 'asked_once') or \
|
||||||
(gajim.capscache.is_supported(contact,
|
contact.supports(common.xmpp.NS_CHATSTATES):
|
||||||
common.xmpp.NS_CHATSTATES) and \
|
|
||||||
not gajim.capscache.is_supported(contact,
|
|
||||||
'notexistant')):
|
|
||||||
# XEP-0085
|
# XEP-0085
|
||||||
msg_iq.setTag(chatstate, namespace=common.xmpp.NS_CHATSTATES)
|
msg_iq.setTag(chatstate, namespace=common.xmpp.NS_CHATSTATES)
|
||||||
if composing_xep in ('XEP-0022', 'asked_once') or \
|
if composing_xep in ('XEP-0022', 'asked_once') or \
|
||||||
|
@ -1332,8 +1327,7 @@ class Connection(ConnectionHandlers):
|
||||||
|
|
||||||
# XEP-0184
|
# XEP-0184
|
||||||
if msgtxt and gajim.config.get_per('accounts', self.name,
|
if msgtxt and gajim.config.get_per('accounts', self.name,
|
||||||
'request_receipt') and gajim.capscache.is_supported(contact,
|
'request_receipt') and contact.supports(common.xmpp.NS_RECEIPTS):
|
||||||
common.xmpp.NS_RECEIPTS):
|
|
||||||
msg_iq.setTag('request', namespace=common.xmpp.NS_RECEIPTS)
|
msg_iq.setTag('request', namespace=common.xmpp.NS_RECEIPTS)
|
||||||
|
|
||||||
if session:
|
if session:
|
||||||
|
|
|
@ -2469,9 +2469,8 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
|
||||||
if not sess.received_thread_id:
|
if not sess.received_thread_id:
|
||||||
contact = gajim.contacts.get_contact(self.name, jid)
|
contact = gajim.contacts.get_contact(self.name, jid)
|
||||||
|
|
||||||
session_supported = gajim.capscache.is_supported(contact,
|
session_supported = contact.supports(common.xmpp.NS_SSN) or \
|
||||||
common.xmpp.NS_SSN) or gajim.capscache.is_supported(
|
contact.supports(common.xmpp.NS_ESESSION)
|
||||||
contact, common.xmpp.NS_ESESSION)
|
|
||||||
if session_supported:
|
if session_supported:
|
||||||
sess.terminate()
|
sess.terminate()
|
||||||
del self.sessions[jid][sess.thread_id]
|
del self.sessions[jid][sess.thread_id]
|
||||||
|
|
|
@ -30,31 +30,23 @@
|
||||||
|
|
||||||
import common.gajim
|
import common.gajim
|
||||||
|
|
||||||
class Contact:
|
|
||||||
'''Information concerning each contact'''
|
from common import caps
|
||||||
def __init__(self, jid='', name='', groups=[], show='', status='', sub='',
|
|
||||||
ask='', resource='', priority=0, keyID='', caps_node=None,
|
|
||||||
caps_hash_method=None, caps_hash=None, our_chatstate=None, chatstate=None,
|
class CommonContact(object):
|
||||||
last_status_time=None, msg_id = None, composing_xep = None, mood={}, tune={},
|
|
||||||
activity={}):
|
def __init__(self, jid, resource, show, status, name, our_chatstate,
|
||||||
|
composing_xep, chatstate, client_caps=None):
|
||||||
|
|
||||||
self.jid = jid
|
self.jid = jid
|
||||||
self.name = name
|
self.resource = resource
|
||||||
self.contact_name = '' # nick choosen by contact
|
|
||||||
self.groups = groups
|
|
||||||
self.show = show
|
self.show = show
|
||||||
self.status = status
|
self.status = status
|
||||||
self.sub = sub
|
self.name = name
|
||||||
self.ask = ask
|
|
||||||
self.resource = resource
|
self.client_caps = client_caps or caps.NullClientCaps()
|
||||||
self.priority = priority
|
|
||||||
self.keyID = keyID
|
|
||||||
|
|
||||||
# Capabilities; filled by caps.py/ConnectionCaps object
|
|
||||||
# every time it gets these from presence stanzas
|
|
||||||
self.caps_node = caps_node
|
|
||||||
self.caps_hash_method = caps_hash_method
|
|
||||||
self.caps_hash = caps_hash
|
|
||||||
|
|
||||||
# please read xep-85 http://www.xmpp.org/extensions/xep-0085.html
|
# please read xep-85 http://www.xmpp.org/extensions/xep-0085.html
|
||||||
# we keep track of xep85 support with the peer by three extra states:
|
# we keep track of xep85 support with the peer by three extra states:
|
||||||
# None, False and 'ask'
|
# None, False and 'ask'
|
||||||
|
@ -63,14 +55,69 @@ class Contact:
|
||||||
# 'ask' if we sent the first 'active' chatstate and are waiting for reply
|
# 'ask' if we sent the first 'active' chatstate and are waiting for reply
|
||||||
# this holds what WE SEND to contact (our current chatstate)
|
# this holds what WE SEND to contact (our current chatstate)
|
||||||
self.our_chatstate = our_chatstate
|
self.our_chatstate = our_chatstate
|
||||||
self.msg_id = msg_id
|
|
||||||
# tell which XEP we're using for composing state
|
# tell which XEP we're using for composing state
|
||||||
# None = have to ask, XEP-0022 = use this xep,
|
# None = have to ask, XEP-0022 = use this xep,
|
||||||
# XEP-0085 = use this xep, False = no composing support
|
# XEP-0085 = use this xep, False = no composing support
|
||||||
self.composing_xep = composing_xep
|
self.composing_xep = composing_xep
|
||||||
# this is contact's chatstate
|
# this is contact's chatstate
|
||||||
self.chatstate = chatstate
|
self.chatstate = chatstate
|
||||||
|
|
||||||
|
def get_full_jid(self):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def get_shown_name(self):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def supports(self, requested_feature):
|
||||||
|
'''
|
||||||
|
Returns True if the contact has advertised to support the feature
|
||||||
|
identified by the given namespace. False otherwise.
|
||||||
|
'''
|
||||||
|
if self.show == 'offline':
|
||||||
|
# Unfortunately, if all resources are offline, the contact
|
||||||
|
# includes the last resource that was online. Check for its
|
||||||
|
# show, so we can be sure it's existant. Otherwise, we still
|
||||||
|
# return caps for a contact that has no resources left.
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return self._client_supports(requested_feature)
|
||||||
|
|
||||||
|
def _client_supports(self, requested_feature):
|
||||||
|
lookup_item = self.client_caps.get_cache_lookup_strategy()
|
||||||
|
cache_item = lookup_item(caps.capscache)
|
||||||
|
|
||||||
|
supported_features = cache_item.features
|
||||||
|
if requested_feature in supported_features:
|
||||||
|
return True
|
||||||
|
elif supported_features == [] and cache_item.queried in (0, 1):
|
||||||
|
# assume feature is supported, if we don't know yet, what the client
|
||||||
|
# is capable of
|
||||||
|
return requested_feature not in caps.FEATURE_BLACKLIST
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class Contact(CommonContact):
|
||||||
|
'''Information concerning each contact'''
|
||||||
|
def __init__(self, jid='', name='', groups=[], show='', status='', sub='',
|
||||||
|
ask='', resource='', priority=0, keyID='', client_caps=None,
|
||||||
|
our_chatstate=None, chatstate=None, last_status_time=None, msg_id = None,
|
||||||
|
composing_xep=None, mood={}, tune={}, activity={}):
|
||||||
|
|
||||||
|
CommonContact.__init__(self, jid, resource, show, status, name,
|
||||||
|
our_chatstate, composing_xep, chatstate, client_caps=client_caps)
|
||||||
|
|
||||||
|
self.contact_name = '' # nick choosen by contact
|
||||||
|
self.groups = groups
|
||||||
|
|
||||||
|
self.sub = sub
|
||||||
|
self.ask = ask
|
||||||
|
|
||||||
|
self.priority = priority
|
||||||
|
self.keyID = keyID
|
||||||
|
self.msg_id = msg_id
|
||||||
self.last_status_time = last_status_time
|
self.last_status_time = last_status_time
|
||||||
|
|
||||||
self.mood = mood.copy()
|
self.mood = mood.copy()
|
||||||
self.tune = tune.copy()
|
self.tune = tune.copy()
|
||||||
self.activity = activity.copy()
|
self.activity = activity.copy()
|
||||||
|
@ -135,31 +182,25 @@ class Contact:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
class GC_Contact:
|
class GC_Contact(CommonContact):
|
||||||
'''Information concerning each groupchat contact'''
|
'''Information concerning each groupchat contact'''
|
||||||
def __init__(self, room_jid='', name='', show='', status='', role='',
|
def __init__(self, room_jid='', name='', show='', status='', role='',
|
||||||
affiliation='', jid = '', resource = '', our_chatstate = None,
|
affiliation='', jid='', resource='', our_chatstate=None,
|
||||||
composing_xep = None, chatstate = None):
|
composing_xep=None, chatstate=None):
|
||||||
|
|
||||||
|
CommonContact.__init__(self, jid, resource, show, status, name,
|
||||||
|
our_chatstate, composing_xep, chatstate)
|
||||||
|
|
||||||
self.room_jid = room_jid
|
self.room_jid = room_jid
|
||||||
self.name = name
|
|
||||||
self.show = show
|
|
||||||
self.status = status
|
|
||||||
self.role = role
|
self.role = role
|
||||||
self.affiliation = affiliation
|
self.affiliation = affiliation
|
||||||
self.jid = jid
|
|
||||||
self.resource = resource
|
|
||||||
self.caps_node = None
|
|
||||||
self.caps_hash_method = None
|
|
||||||
self.caps_hash = None
|
|
||||||
self.our_chatstate = our_chatstate
|
|
||||||
self.composing_xep = composing_xep
|
|
||||||
self.chatstate = chatstate
|
|
||||||
|
|
||||||
def get_full_jid(self):
|
def get_full_jid(self):
|
||||||
return self.room_jid + '/' + self.name
|
return self.room_jid + '/' + self.name
|
||||||
|
|
||||||
def get_shown_name(self):
|
def get_shown_name(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
class Contacts:
|
class Contacts:
|
||||||
'''Information concerning all contacts and groupchat contacts'''
|
'''Information concerning all contacts and groupchat contacts'''
|
||||||
|
@ -193,10 +234,9 @@ class Contacts:
|
||||||
del self._metacontacts_tags[account]
|
del self._metacontacts_tags[account]
|
||||||
|
|
||||||
def create_contact(self, jid='', name='', groups=[], show='', status='',
|
def create_contact(self, jid='', name='', groups=[], show='', status='',
|
||||||
sub='', ask='', resource='', priority=0, keyID='', caps_node=None,
|
sub='', ask='', resource='', priority=0, keyID='', client_caps=None,
|
||||||
caps_hash_method=None, caps_hash=None, our_chatstate=None,
|
our_chatstate=None, chatstate=None, last_status_time=None,
|
||||||
chatstate=None, last_status_time=None, composing_xep=None,
|
composing_xep=None, mood={}, tune={}, activity={}):
|
||||||
mood={}, tune={}, activity={}):
|
|
||||||
|
|
||||||
# We don't want duplicated group values
|
# We don't want duplicated group values
|
||||||
groups_unique = []
|
groups_unique = []
|
||||||
|
@ -206,18 +246,16 @@ class Contacts:
|
||||||
|
|
||||||
return Contact(jid=jid, name=name, groups=groups_unique, show=show,
|
return Contact(jid=jid, name=name, groups=groups_unique, show=show,
|
||||||
status=status, sub=sub, ask=ask, resource=resource, priority=priority,
|
status=status, sub=sub, ask=ask, resource=resource, priority=priority,
|
||||||
keyID=keyID, caps_node=caps_node, caps_hash_method=caps_hash_method,
|
keyID=keyID, client_caps=client_caps, our_chatstate=our_chatstate,
|
||||||
caps_hash=caps_hash, our_chatstate=our_chatstate, chatstate=chatstate,
|
chatstate=chatstate, last_status_time=last_status_time,
|
||||||
last_status_time=last_status_time, composing_xep=composing_xep,
|
composing_xep=composing_xep, mood=mood, tune=tune, activity=activity)
|
||||||
mood=mood, tune=tune, activity=activity)
|
|
||||||
|
|
||||||
def copy_contact(self, contact):
|
def copy_contact(self, contact):
|
||||||
return self.create_contact(jid=contact.jid, name=contact.name,
|
return self.create_contact(jid=contact.jid, name=contact.name,
|
||||||
groups=contact.groups, show=contact.show, status=contact.status,
|
groups=contact.groups, show=contact.show, status=contact.status,
|
||||||
sub=contact.sub, ask=contact.ask, resource=contact.resource,
|
sub=contact.sub, ask=contact.ask, resource=contact.resource,
|
||||||
priority=contact.priority, keyID=contact.keyID,
|
priority=contact.priority, keyID=contact.keyID,
|
||||||
caps_node=contact.caps_node, caps_hash_method=contact.caps_hash_method,
|
client_caps=contact.client_caps, our_chatstate=contact.our_chatstate,
|
||||||
caps_hash=contact.caps_hash, our_chatstate=contact.our_chatstate,
|
|
||||||
chatstate=contact.chatstate, last_status_time=contact.last_status_time)
|
chatstate=contact.chatstate, last_status_time=contact.last_status_time)
|
||||||
|
|
||||||
def add_contact(self, account, contact):
|
def add_contact(self, account, contact):
|
||||||
|
@ -587,9 +625,7 @@ class Contacts:
|
||||||
jid = gc_contact.get_full_jid()
|
jid = gc_contact.get_full_jid()
|
||||||
return Contact(jid=jid, resource=gc_contact.resource,
|
return Contact(jid=jid, resource=gc_contact.resource,
|
||||||
name=gc_contact.name, groups=[], show=gc_contact.show,
|
name=gc_contact.name, groups=[], show=gc_contact.show,
|
||||||
status=gc_contact.status, sub='none', caps_node=gc_contact.caps_node,
|
status=gc_contact.status, sub='none', client_caps=gc_contact.client_caps)
|
||||||
caps_hash_method=gc_contact.caps_hash_method,
|
|
||||||
caps_hash=gc_contact.caps_hash)
|
|
||||||
|
|
||||||
def create_gc_contact(self, room_jid='', name='', show='', status='',
|
def create_gc_contact(self, room_jid='', name='', show='', status='',
|
||||||
role='', affiliation='', jid='', resource=''):
|
role='', affiliation='', jid='', resource=''):
|
||||||
|
|
|
@ -32,8 +32,6 @@ import logging
|
||||||
import locale
|
import locale
|
||||||
|
|
||||||
import config
|
import config
|
||||||
from contacts import Contacts
|
|
||||||
from events import Events
|
|
||||||
import xmpp
|
import xmpp
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -101,6 +99,9 @@ else:
|
||||||
|
|
||||||
os_info = None # used to cache os information
|
os_info = None # used to cache os information
|
||||||
|
|
||||||
|
from contacts import Contacts
|
||||||
|
from events import Events
|
||||||
|
|
||||||
gmail_domains = ['gmail.com', 'googlemail.com']
|
gmail_domains = ['gmail.com', 'googlemail.com']
|
||||||
|
|
||||||
transport_type = {} # list the type of transport
|
transport_type = {} # list the type of transport
|
||||||
|
@ -209,6 +210,9 @@ gajim_optional_features = {}
|
||||||
# Capabilities hash per account
|
# Capabilities hash per account
|
||||||
caps_hash = {}
|
caps_hash = {}
|
||||||
|
|
||||||
|
import caps
|
||||||
|
caps.initialize(logger)
|
||||||
|
|
||||||
def get_nick_from_jid(jid):
|
def get_nick_from_jid(jid):
|
||||||
pos = jid.find('@')
|
pos = jid.find('@')
|
||||||
return jid[:pos]
|
return jid[:pos]
|
||||||
|
|
|
@ -37,8 +37,8 @@ import urllib
|
||||||
import errno
|
import errno
|
||||||
import select
|
import select
|
||||||
import base64
|
import base64
|
||||||
import sys
|
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import caps
|
||||||
|
|
||||||
from encodings.punycode import punycode_encode
|
from encodings.punycode import punycode_encode
|
||||||
|
|
||||||
|
@ -566,74 +566,6 @@ def datetime_tuple(timestamp):
|
||||||
from time import strptime
|
from time import strptime
|
||||||
return strptime(timestamp, '%Y%m%dT%H:%M:%S')
|
return strptime(timestamp, '%Y%m%dT%H:%M:%S')
|
||||||
|
|
||||||
def sort_identities_func(i1, i2):
|
|
||||||
cat1 = i1['category']
|
|
||||||
cat2 = i2['category']
|
|
||||||
if cat1 < cat2:
|
|
||||||
return -1
|
|
||||||
if cat1 > cat2:
|
|
||||||
return 1
|
|
||||||
type1 = i1.get('type', '')
|
|
||||||
type2 = i2.get('type', '')
|
|
||||||
if type1 < type2:
|
|
||||||
return -1
|
|
||||||
if type1 > type2:
|
|
||||||
return 1
|
|
||||||
lang1 = i1.get('xml:lang', '')
|
|
||||||
lang2 = i2.get('xml:lang', '')
|
|
||||||
if lang1 < lang2:
|
|
||||||
return -1
|
|
||||||
if lang1 > lang2:
|
|
||||||
return 1
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def sort_dataforms_func(d1, d2):
|
|
||||||
f1 = d1.getField('FORM_TYPE')
|
|
||||||
f2 = d2.getField('FORM_TYPE')
|
|
||||||
if f1 and f2 and (f1.getValue() < f2.getValue()):
|
|
||||||
return -1
|
|
||||||
return 1
|
|
||||||
|
|
||||||
def compute_caps_hash(identities, features, dataforms=[], hash_method='sha-1'):
|
|
||||||
'''Compute caps hash according to XEP-0115, V1.5
|
|
||||||
|
|
||||||
dataforms are xmpp.DataForms objects as common.dataforms don't allow several
|
|
||||||
values without a field type list-multi'''
|
|
||||||
S = ''
|
|
||||||
identities.sort(cmp=sort_identities_func)
|
|
||||||
for i in identities:
|
|
||||||
c = i['category']
|
|
||||||
type_ = i.get('type', '')
|
|
||||||
lang = i.get('xml:lang', '')
|
|
||||||
name = i.get('name', '')
|
|
||||||
S += '%s/%s/%s/%s<' % (c, type_, lang, name)
|
|
||||||
features.sort()
|
|
||||||
for f in features:
|
|
||||||
S += '%s<' % f
|
|
||||||
dataforms.sort(cmp=sort_dataforms_func)
|
|
||||||
for dataform in dataforms:
|
|
||||||
# fields indexed by var
|
|
||||||
fields = {}
|
|
||||||
for f in dataform.getChildren():
|
|
||||||
fields[f.getVar()] = f
|
|
||||||
form_type = fields.get('FORM_TYPE')
|
|
||||||
if form_type:
|
|
||||||
S += form_type.getValue() + '<'
|
|
||||||
del fields['FORM_TYPE']
|
|
||||||
for var in sorted(fields.keys()):
|
|
||||||
S += '%s<' % var
|
|
||||||
values = sorted(fields[var].getValues())
|
|
||||||
for value in values:
|
|
||||||
S += '%s<' % value
|
|
||||||
|
|
||||||
if hash_method == 'sha-1':
|
|
||||||
hash_ = hashlib.sha1(S)
|
|
||||||
elif hash_method == 'md5':
|
|
||||||
hash_ = hashlib.md5(S)
|
|
||||||
else:
|
|
||||||
return ''
|
|
||||||
return base64.b64encode(hash_.digest())
|
|
||||||
|
|
||||||
# import gajim only when needed (after decode_string is defined) see #4764
|
# import gajim only when needed (after decode_string is defined) see #4764
|
||||||
|
|
||||||
import gajim
|
import gajim
|
||||||
|
@ -1363,7 +1295,7 @@ def update_optional_features(account = None):
|
||||||
gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_RTP_AUDIO)
|
gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_RTP_AUDIO)
|
||||||
gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_RTP_VIDEO)
|
gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_RTP_VIDEO)
|
||||||
gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_ICE_UDP)
|
gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_ICE_UDP)
|
||||||
gajim.caps_hash[a] = compute_caps_hash([gajim.gajim_identity],
|
gajim.caps_hash[a] = caps.compute_caps_hash([gajim.gajim_identity],
|
||||||
gajim.gajim_common_features + gajim.gajim_optional_features[a])
|
gajim.gajim_common_features + gajim.gajim_optional_features[a])
|
||||||
# re-send presence with new hash
|
# re-send presence with new hash
|
||||||
connected = gajim.connections[a].connected
|
connected = gajim.connections[a].connected
|
||||||
|
|
|
@ -31,6 +31,7 @@ import locale
|
||||||
import re
|
import re
|
||||||
from common import gajim
|
from common import gajim
|
||||||
from common import helpers
|
from common import helpers
|
||||||
|
from common import caps
|
||||||
|
|
||||||
import exceptions
|
import exceptions
|
||||||
try:
|
try:
|
||||||
|
@ -219,7 +220,7 @@ class OptionsParser:
|
||||||
gajim.logger.init_vars()
|
gajim.logger.init_vars()
|
||||||
gajim.config.set('version', new_version)
|
gajim.config.set('version', new_version)
|
||||||
|
|
||||||
gajim.capscache.load_from_db()
|
caps.capscache.initialize_from_db()
|
||||||
|
|
||||||
def update_config_x_to_09(self):
|
def update_config_x_to_09(self):
|
||||||
# Var name that changed:
|
# Var name that changed:
|
||||||
|
|
|
@ -223,7 +223,7 @@ class EncryptedStanzaSession(StanzaSession):
|
||||||
|
|
||||||
def _is_buggy_gajim(self):
|
def _is_buggy_gajim(self):
|
||||||
c = self._get_contact()
|
c = self._get_contact()
|
||||||
if gajim.capscache.is_supported(c, xmpp.NS_ROSTERX):
|
if c and c.supports(xmpp.NS_ROSTERX):
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
|
@ -255,6 +255,7 @@ from common import optparser
|
||||||
from common import dataforms
|
from common import dataforms
|
||||||
from common import passwords
|
from common import passwords
|
||||||
from common import pep
|
from common import pep
|
||||||
|
from common import caps
|
||||||
|
|
||||||
gajimpaths = common.configpaths.gajimpaths
|
gajimpaths = common.configpaths.gajimpaths
|
||||||
|
|
||||||
|
@ -3641,6 +3642,12 @@ class Interface:
|
||||||
gajim.caps_hash[a] = ''
|
gajim.caps_hash[a] = ''
|
||||||
|
|
||||||
helpers.update_optional_features()
|
helpers.update_optional_features()
|
||||||
|
# prepopulate data which we are sure of; note: we do not log these info
|
||||||
|
for account in gajim.connections:
|
||||||
|
gajimcaps = caps.capscache[('sha-1', gajim.caps_hash[account])]
|
||||||
|
gajimcaps.identities = [gajim.gajim_identity]
|
||||||
|
gajimcaps.features = gajim.gajim_common_features + \
|
||||||
|
gajim.gajim_optional_features[account]
|
||||||
|
|
||||||
self.remote_ctrl = None
|
self.remote_ctrl = None
|
||||||
|
|
||||||
|
|
|
@ -55,7 +55,7 @@ room_account=None, cap=None):
|
||||||
else: # start_chat, execute_command, send_file
|
else: # start_chat, execute_command, send_file
|
||||||
item.connect('activate', action, c, account, c.resource)
|
item.connect('activate', action, c, account, c.resource)
|
||||||
|
|
||||||
if cap and not gajim.capscache.is_supported(c, cap):
|
if cap and not c.supports(cap):
|
||||||
item.set_sensitive(False)
|
item.set_sensitive(False)
|
||||||
|
|
||||||
return sub_menu
|
return sub_menu
|
||||||
|
@ -92,7 +92,7 @@ def build_invite_submenu(invite_menuitem, list_):
|
||||||
if len(contact_list) > 1: # several resources
|
if len(contact_list) > 1: # several resources
|
||||||
invite_to_new_room_menuitem.set_submenu(build_resources_submenu(
|
invite_to_new_room_menuitem.set_submenu(build_resources_submenu(
|
||||||
contact_list, account, roster.on_invite_to_new_room, cap=NS_MUC))
|
contact_list, account, roster.on_invite_to_new_room, cap=NS_MUC))
|
||||||
elif len(list_) == 1 and gajim.capscache.is_supported(contact, NS_MUC):
|
elif len(list_) == 1 and contact.supports(NS_MUC):
|
||||||
invite_menuitem.set_sensitive(True)
|
invite_menuitem.set_sensitive(True)
|
||||||
# use resource if it's self contact
|
# use resource if it's self contact
|
||||||
if contact.jid == gajim.get_jid_from_account(account):
|
if contact.jid == gajim.get_jid_from_account(account):
|
||||||
|
@ -222,14 +222,14 @@ control=None):
|
||||||
else:
|
else:
|
||||||
start_chat_menuitem.connect('activate',
|
start_chat_menuitem.connect('activate',
|
||||||
gajim.interface.on_open_chat_window, contact, account)
|
gajim.interface.on_open_chat_window, contact, account)
|
||||||
if gajim.capscache.is_supported(contact, NS_FILE):
|
if contact.supports(NS_FILE):
|
||||||
send_file_menuitem.set_sensitive(True)
|
send_file_menuitem.set_sensitive(True)
|
||||||
send_file_menuitem.connect('activate',
|
send_file_menuitem.connect('activate',
|
||||||
roster.on_send_file_menuitem_activate, contact, account)
|
roster.on_send_file_menuitem_activate, contact, account)
|
||||||
else:
|
else:
|
||||||
send_file_menuitem.set_sensitive(False)
|
send_file_menuitem.set_sensitive(False)
|
||||||
|
|
||||||
if gajim.capscache.is_supported(contact, NS_COMMANDS):
|
if contact.supports(NS_COMMANDS):
|
||||||
execute_command_menuitem.set_sensitive(True)
|
execute_command_menuitem.set_sensitive(True)
|
||||||
execute_command_menuitem.connect('activate', roster.on_execute_command,
|
execute_command_menuitem.connect('activate', roster.on_execute_command,
|
||||||
contact, account, contact.resource)
|
contact, account, contact.resource)
|
||||||
|
@ -294,10 +294,7 @@ control=None):
|
||||||
control._on_toggle_gpg_menuitem_activate)
|
control._on_toggle_gpg_menuitem_activate)
|
||||||
|
|
||||||
# disable esessions if we or the other client don't support them
|
# disable esessions if we or the other client don't support them
|
||||||
# XXX: Once we have fallback to disco, remove notexistant check
|
if not gajim.HAVE_PYCRYPTO or not contact.supports(NS_ESESSION) or \
|
||||||
if not gajim.HAVE_PYCRYPTO or \
|
|
||||||
not gajim.capscache.is_supported(contact, NS_ESESSION) or \
|
|
||||||
gajim.capscache.is_supported(contact, 'notexistant') or \
|
|
||||||
not gajim.config.get_per('accounts', account, 'enable_esessions'):
|
not gajim.config.get_per('accounts', account, 'enable_esessions'):
|
||||||
toggle_e2e_menuitem.set_sensitive(False)
|
toggle_e2e_menuitem.set_sensitive(False)
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -4061,7 +4061,7 @@ class RosterWindow:
|
||||||
return
|
return
|
||||||
c_dest = gajim.contacts.get_contact_with_highest_priority(account_dest,
|
c_dest = gajim.contacts.get_contact_with_highest_priority(account_dest,
|
||||||
jid_dest)
|
jid_dest)
|
||||||
if not gajim.capscache.is_supported(c_dest, NS_FILE):
|
if not c_dest.supports(NS_FILE):
|
||||||
return
|
return
|
||||||
uri = data.strip()
|
uri = data.strip()
|
||||||
uri_splitted = uri.split() # we may have more than one file dropped
|
uri_splitted = uri.split() # we may have more than one file dropped
|
||||||
|
@ -5157,17 +5157,6 @@ class RosterWindow:
|
||||||
|
|
||||||
zeroconf_properties_menuitem.connect('activate',
|
zeroconf_properties_menuitem.connect('activate',
|
||||||
self.on_zeroconf_properties, account)
|
self.on_zeroconf_properties, account)
|
||||||
#gc_sub_menu = gtk.Menu() # gc is always a submenu
|
|
||||||
#join_group_chat_menuitem.set_submenu(gc_sub_menu)
|
|
||||||
#self.add_bookmarks_list(gc_sub_menu, account)
|
|
||||||
#new_message_menuitem.connect('activate',
|
|
||||||
# self.on_new_message_menuitem_activate, account)
|
|
||||||
|
|
||||||
# make some items insensitive if account is offline
|
|
||||||
#if gajim.connections[account].connected < 2:
|
|
||||||
# for widget in [join_group_chat_menuitem, new_message_menuitem]:
|
|
||||||
# widget.set_sensitive(False)
|
|
||||||
# new_message_menuitem.set_sensitive(False)
|
|
||||||
|
|
||||||
return account_context_menu
|
return account_context_menu
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,6 @@ import threading, time
|
||||||
from mock import Mock
|
from mock import Mock
|
||||||
|
|
||||||
from common.xmpp import idlequeue
|
from common.xmpp import idlequeue
|
||||||
from common.xmpp.plugin import PlugIn
|
|
||||||
|
|
||||||
IDLEQUEUE_INTERVAL = 0.2 # polling interval. 200ms is used in Gajim as default
|
IDLEQUEUE_INTERVAL = 0.2 # polling interval. 200ms is used in Gajim as default
|
||||||
IDLEMOCK_TIMEOUT = 30 # how long we wait for an event
|
IDLEMOCK_TIMEOUT = 30 # how long we wait for an event
|
||||||
|
|
|
@ -40,6 +40,7 @@ modules = ( 'test_xmpp_dispatcher_nb',
|
||||||
'test_xmpp_transports_nb',
|
'test_xmpp_transports_nb',
|
||||||
'test_resolver',
|
'test_resolver',
|
||||||
'test_caps',
|
'test_caps',
|
||||||
|
'test_contacts',
|
||||||
)
|
)
|
||||||
#modules = ()
|
#modules = ()
|
||||||
|
|
||||||
|
|
|
@ -6,57 +6,148 @@ import unittest
|
||||||
import lib
|
import lib
|
||||||
lib.setup_env()
|
lib.setup_env()
|
||||||
|
|
||||||
from common import gajim
|
from common.xmpp import NS_MUC, NS_PING, NS_XHTML_IM
|
||||||
from common import xmpp
|
from common import caps
|
||||||
from common import helpers
|
from common.contacts import Contact
|
||||||
|
|
||||||
from common.caps import CapsCache
|
|
||||||
|
|
||||||
from mock import Mock
|
from mock import Mock
|
||||||
|
|
||||||
class MockLogger(Mock):
|
|
||||||
def __init__(self, *args):
|
class CommonCapsTest(unittest.TestCase):
|
||||||
Mock.__init__(self, *args)
|
|
||||||
|
|
||||||
class TestCapsCache(unittest.TestCase):
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.logger = MockLogger()
|
|
||||||
self.cc = CapsCache(self.logger)
|
|
||||||
|
|
||||||
self.caps_method = 'sha-1'
|
self.caps_method = 'sha-1'
|
||||||
self.caps_hash = 'zaQfb22o0UCwYDIk8KZOnoZTnrs='
|
self.caps_hash = 'm3P2WeXPMGVH2tZPe7yITnfY0Dw='
|
||||||
self.caps = (self.caps_method, self.caps_hash)
|
self.client_caps = (self.caps_method, self.caps_hash)
|
||||||
self.identity = {'category': 'client', 'type': 'pc'}
|
|
||||||
|
self.node = "http://gajim.org"
|
||||||
self.muc = 'http://jabber.org/protocol/muc'
|
self.identity = {'category': 'client', 'type': 'pc', 'name':'Gajim'}
|
||||||
self.chatstates = 'http://jabber.org/protocol/chatstates'
|
|
||||||
|
|
||||||
self.identities = [self.identity]
|
self.identities = [self.identity]
|
||||||
self.features = [self.muc]
|
self.features = [NS_MUC, NS_XHTML_IM] # NS_MUC not supported!
|
||||||
|
|
||||||
|
# Simulate a filled db
|
||||||
|
db_caps_cache = [
|
||||||
|
(self.caps_method, self.caps_hash, self.identities, self.features),
|
||||||
|
('old', self.node + '#' + self.caps_hash, self.identities, self.features)]
|
||||||
|
self.logger = Mock(returnValues={"iter_caps_data":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 '''
|
||||||
|
|
||||||
def test_examples(self):
|
self.cc[self.client_caps].identities = self.identities
|
||||||
'''tests the examples given in common/caps.py'''
|
self.cc[self.client_caps].features = self.features
|
||||||
|
|
||||||
self.cc[self.caps].identities = self.identities
|
self.assert_(NS_MUC in self.cc[self.client_caps].features)
|
||||||
self.cc[self.caps].features = self.features
|
self.assert_(NS_PING not in self.cc[self.client_caps].features)
|
||||||
|
|
||||||
self.assert_(self.muc in self.cc[self.caps].features)
|
identities = self.cc[self.client_caps].identities
|
||||||
self.assert_(self.chatstates not in self.cc[self.caps].features)
|
|
||||||
|
|
||||||
id = self.cc[self.caps].identities
|
self.assertEqual(1, len(identities))
|
||||||
|
|
||||||
self.assertEqual(1, len(id))
|
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 '''
|
||||||
|
|
||||||
|
item = self.cc[self.client_caps]
|
||||||
|
item.set_and_store(self.identities, self.features)
|
||||||
|
|
||||||
|
self.logger.mockCheckCall(0, "add_caps_entry", self.caps_method,
|
||||||
|
self.caps_hash, self.identities, self.features)
|
||||||
|
|
||||||
|
def test_initialize_from_db(self):
|
||||||
|
''' Read cashed dummy data from db '''
|
||||||
|
self.assertEqual(self.cc[self.client_caps].queried, 0)
|
||||||
|
self.cc.initialize_from_db()
|
||||||
|
self.assertEqual(self.cc[self.client_caps].queried, 2)
|
||||||
|
|
||||||
id = id[0]
|
def test_preload_triggering_query(self):
|
||||||
self.assertEqual('client', id['category'])
|
''' Make sure that preload issues a disco '''
|
||||||
self.assertEqual('pc', id['type'])
|
connection = Mock()
|
||||||
|
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, len(connection.mockGetAllCalls()))
|
||||||
|
|
||||||
|
def test_no_preload_query_if_cashed(self):
|
||||||
|
''' Preload must not send a query if the data is already cached '''
|
||||||
|
connection = Mock()
|
||||||
|
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, len(connection.mockGetAllCalls()))
|
||||||
|
|
||||||
def test_hash(self):
|
def test_hash(self):
|
||||||
'''tests the hash computation'''
|
'''tests the hash computation'''
|
||||||
computed_hash = helpers.compute_caps_hash(self.identities, self.features)
|
computed_hash = caps.compute_caps_hash(self.identities, self.features)
|
||||||
|
|
||||||
self.assertEqual(self.caps_hash, computed_hash)
|
self.assertEqual(self.caps_hash, computed_hash)
|
||||||
|
|
||||||
|
|
||||||
|
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 = Mock()
|
||||||
|
discover = self.client_caps.get_discover_strategy()
|
||||||
|
discover(connection, "test@gajim.org")
|
||||||
|
|
||||||
|
connection.mockCheckCall(0, "discoverInfo", "test@gajim.org",
|
||||||
|
"http://gajim.org#m3P2WeXPMGVH2tZPe7yITnfY0Dw=")
|
||||||
|
|
||||||
|
def test_client_supports(self):
|
||||||
|
contact = Contact(client_caps=self.client_caps)
|
||||||
|
|
||||||
|
self.assertTrue(contact.supports(NS_PING),
|
||||||
|
msg="Assume supported, if we don't have caps")
|
||||||
|
|
||||||
|
self.assertFalse(contact.supports(NS_XHTML_IM),
|
||||||
|
msg="Must not assume blacklisted feature is supported on default")
|
||||||
|
|
||||||
|
self.cc.initialize_from_db()
|
||||||
|
|
||||||
|
self.assertFalse(contact.supports(NS_PING),
|
||||||
|
msg="Must return false on unsupported feature")
|
||||||
|
|
||||||
|
self.assertTrue(contact.supports(NS_XHTML_IM),
|
||||||
|
msg="Must return True on supported feature")
|
||||||
|
|
||||||
|
self.assertTrue(contact.supports(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 = Mock()
|
||||||
|
discover = self.client_caps.get_discover_strategy()
|
||||||
|
discover(connection, "test@gajim.org")
|
||||||
|
|
||||||
|
connection.mockCheckCall(0, "discoverInfo", "test@gajim.org")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
||||||
|
|
51
test/test_contacts.py
Normal file
51
test/test_contacts.py
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
'''
|
||||||
|
Test for Contact, GC_Contact and Contacts
|
||||||
|
'''
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import lib
|
||||||
|
lib.setup_env()
|
||||||
|
|
||||||
|
from common.contacts import CommonContact, Contact, GC_Contact
|
||||||
|
from common.xmpp import NS_MUC
|
||||||
|
|
||||||
|
from common import caps
|
||||||
|
|
||||||
|
class TestCommonContact(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.contact = CommonContact(jid='', resource='', show='', status='',
|
||||||
|
name='', our_chatstate=None, composing_xep=None, 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.capscache = caps.CapsCache()
|
||||||
|
self.assertTrue(self.contact.supports(NS_MUC),
|
||||||
|
msg="Must not backtrace on simple check for supported feature")
|
||||||
|
|
||||||
|
self.contact.client_caps = caps.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()
|
||||||
|
|
||||||
|
|
||||||
|
class TestGC_Contact(TestCommonContact):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
TestCommonContact.setUp(self)
|
||||||
|
self.contact = GC_Contact()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
|
@ -9,7 +9,6 @@ from mock import Mock, expectParams
|
||||||
from gajim_mocks import *
|
from gajim_mocks import *
|
||||||
|
|
||||||
from common import gajim
|
from common import gajim
|
||||||
from common import zeroconf
|
|
||||||
import roster_window
|
import roster_window
|
||||||
|
|
||||||
gajim.get_jid_from_account = lambda acc: 'myjid@' + acc
|
gajim.get_jid_from_account = lambda acc: 'myjid@' + acc
|
||||||
|
|
Loading…
Add table
Reference in a new issue