Two small caps enhancements.

* Rename EntityCapabilities to ClientCaps as this seems more intense giving.
 * Add ability to blacklist features where we cannot savely assume that a client, which did not advertise caps, supports them
This commit is contained in:
Stephan Erb 2009-10-26 19:20:16 +01:00
parent 346953fd93
commit 3295b08b26
3 changed files with 55 additions and 46 deletions

View File

@ -28,9 +28,9 @@ Module containing all XEP-115 (Entity Capabilities) related classes
Basic Idea:
CapsCache caches features to hash relationships. The cache is queried
through EntityCapabilities objects which are hold by contact instances.
through ClientCaps objects which are hold by contact instances.
EntityCapabilities represent the client of contacts. It is set on the receive
ClientCaps represent the client of contacts. It is set on the receive
of a presence. The respective jid is then queried with a disco if the advertised
client/hash is unknown.
'''
@ -38,8 +38,13 @@ client/hash is unknown.
import gajim
import helpers
from common.xmpp import NS_XHTML_IM, NS_RECEIPTS, NS_ESESSION, NS_CHATSTATES
class AbstractEntityCapabilities(object):
# Features where we cannot safely assume that the other side supports them
FEATURE_BLACKLIST = [NS_CHATSTATES, NS_XHTML_IM, NS_RECEIPTS, NS_ESESSION]
class AbstractClientCaps(object):
'''
Base class representing a client and its capabilities as advertised by
a caps tag in a presence
@ -62,12 +67,15 @@ class AbstractEntityCapabilities(object):
self._discover(connection, jid)
q.queried = 1
def contains_feature(self, feature):
def contains_feature(self, requested_feature):
''' Returns true if these capabilities contain the given feature '''
features = self._lookup_in_cache().features
if feature in features or features == []:
cach_entry = self._lookup_in_cache()
supported_features = cach_entry.features
if requested_feature in supported_features:
return True
elif supported_features == [] and cach_entry.queried in (0, 1):
# assume feature is supported, if not blacklisted
return requested_feature not in FEATURE_BLACKLIST
else:
return False
@ -80,11 +88,11 @@ class AbstractEntityCapabilities(object):
raise NotImplementedError()
class EntityCapabilities(AbstractEntityCapabilities):
class ClientCaps(AbstractClientCaps):
''' The current XEP-115 implementation '''
def __init__(self, caps_cache, caps_hash, node, hash_method):
AbstractEntityCapabilities.__init__(self, caps_cache, caps_hash, node)
AbstractClientCaps.__init__(self, caps_cache, caps_hash, node)
assert hash_method != 'old'
self._hash_method = hash_method
@ -95,11 +103,11 @@ class EntityCapabilities(AbstractEntityCapabilities):
connection.discoverInfo(jid, '%s#%s' % (self._node, self._hash))
class OldEntityCapabilities(AbstractEntityCapabilities):
class OldClientCaps(AbstractClientCaps):
''' Old XEP-115 implemtation. Kept around for background competability. '''
def __init__(self, caps_cache, caps_hash, node):
AbstractEntityCapabilities.__init__(self, caps_cache, caps_hash, node)
AbstractClientCaps.__init__(self, caps_cache, caps_hash, node)
def _lookup_in_cache(self):
return self._caps_cache[('old', self._node + '#' + self._hash)]
@ -108,12 +116,12 @@ class OldEntityCapabilities(AbstractEntityCapabilities):
connection.discoverInfo(jid)
class NullEntityCapabilities(AbstractEntityCapabilities):
class NullClientCaps(AbstractClientCaps):
'''
This is a NULL-Object to streamline caps handling is a client has not
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 everything is supported.
Assumes (almost) everything is supported.
'''
def __init__(self):
@ -123,7 +131,7 @@ class NullEntityCapabilities(AbstractEntityCapabilities):
pass
def contains_feature(self, feature):
return True
return feature not in FEATURE_BLACKLIST
class CapsCache(object):

View File

@ -8,7 +8,9 @@ lib.setup_env()
from common import helpers
from common.contacts import Contact
from common.caps import CapsCache, EntityCapabilities, OldEntityCapabilities
from common.xmpp import NS_MUC, NS_PING, NS_XHTML_IM
from common.caps import CapsCache, ClientCaps, OldClientCaps
from mock import Mock
@ -17,17 +19,14 @@ class CommonCapsTest(unittest.TestCase):
def setUp(self):
self.caps_method = 'sha-1'
self.caps_hash = 'RNzJvJnTWqczirzu+YF4V8am9ro='
self.caps_hash = 'm3P2WeXPMGVH2tZPe7yITnfY0Dw='
self.caps = (self.caps_method, self.caps_hash)
self.node = "http://gajim.org"
self.identity = {'category': 'client', 'type': 'pc', 'name':'Gajim'}
self.muc = 'http://jabber.org/protocol/muc'
self.chatstates = 'http://jabber.org/protocol/chatstates'
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 = [
@ -46,8 +45,8 @@ class TestCapsCache(CommonCapsTest):
self.cc[self.caps].identities = self.identities
self.cc[self.caps].features = self.features
self.assert_(self.muc in self.cc[self.caps].features)
self.assert_(self.chatstates not in self.cc[self.caps].features)
self.assert_(NS_MUC in self.cc[self.caps].features)
self.assert_(NS_PING not in self.cc[self.caps].features)
identities = self.cc[self.caps].identities
@ -79,8 +78,8 @@ class TestCapsCache(CommonCapsTest):
self.cc.preload(connection, "test@gajim.org", self.node,
self.caps_method, self.caps_hash)
connection.mockCheckCall(0, "discoverInfo", 'test@gajim.org',
'http://gajim.org#RNzJvJnTWqczirzu+YF4V8am9ro=')
connection.mockCheckCall(0, "discoverInfo", "test@gajim.org",
"http://gajim.org#m3P2WeXPMGVH2tZPe7yITnfY0Dw=")
def test_no_preload_query_if_cashed(self):
''' Preload must not send a query if the data is already cached '''
@ -97,15 +96,15 @@ class TestCapsCache(CommonCapsTest):
caps_hash_method=self.caps_method,
caps_hash=self.caps_hash)
self.assertTrue(self.cc.is_supported(contact, self.chatstates),
self.assertTrue(self.cc.is_supported(contact, NS_PING),
msg="Assume everything is supported, if we don't have caps")
self.cc.initialize_from_db()
self.assertFalse(self.cc.is_supported(contact, self.chatstates),
self.assertFalse(self.cc.is_supported(contact, NS_PING),
msg="Must return false on unsupported feature")
self.assertTrue(self.cc.is_supported(contact, self.muc),
self.assertTrue(self.cc.is_supported(contact, NS_MUC),
msg="Must return True on supported feature")
def test_hash(self):
@ -114,11 +113,11 @@ class TestCapsCache(CommonCapsTest):
self.assertEqual(self.caps_hash, computed_hash)
class TestEntityCapabilities(CommonCapsTest):
class TestClientCaps(CommonCapsTest):
def setUp(self):
CommonCapsTest.setUp(self)
self.caps = EntityCapabilities(self.cc, self.caps_hash, self.node,
self.caps = ClientCaps(self.cc, self.caps_hash, self.node,
self.caps_method)
def test_no_query_client_of_jid(self):
@ -134,27 +133,33 @@ class TestEntityCapabilities(CommonCapsTest):
connection = Mock()
self.caps.query_client_of_jid_if_unknown(connection, "test@gajim.org")
connection.mockCheckCall(0, "discoverInfo", 'test@gajim.org',
'http://gajim.org#RNzJvJnTWqczirzu+YF4V8am9ro=')
connection.mockCheckCall(0, "discoverInfo", "test@gajim.org",
"http://gajim.org#m3P2WeXPMGVH2tZPe7yITnfY0Dw=")
def test_is_supported(self):
self.assertTrue(self.caps.contains_feature(self.chatstates),
msg="Assume everything is supported, if we don't have caps")
self.assertTrue(self.caps.contains_feature(NS_PING),
msg="Assume supported, if we don't have caps")
self.assertFalse(self.caps.contains_feature(NS_XHTML_IM),
msg="Must not assume blacklisted feature is supported on default")
self.cc.initialize_from_db()
self.assertFalse(self.caps.contains_feature(self.chatstates),
self.assertFalse(self.caps.contains_feature(NS_PING),
msg="Must return false on unsupported feature")
self.assertTrue(self.caps.contains_feature(self.muc),
self.assertTrue(self.caps.contains_feature(NS_XHTML_IM),
msg="Must return True on supported feature")
self.assertTrue(self.caps.contains_feature(NS_MUC),
msg="Must return True on supported feature")
class TestOldEntityCapabilities(TestEntityCapabilities):
class TestOldClientCaps(TestClientCaps):
def setUp(self):
TestEntityCapabilities.setUp(self)
self.caps = OldEntityCapabilities(self.cc, self.caps_hash, self.node)
TestClientCaps.setUp(self)
self.caps = OldClientCaps(self.cc, self.caps_hash, self.node)
def test_no_query_client_of_jid(self):
''' Client must not be queried if the data is already cached '''

View File

@ -7,14 +7,12 @@ import lib
lib.setup_env()
from common.contacts import Contact
from common.caps import NullEntityCapabilities
from common.caps import NullClientCaps
from mock import Mock
class TestContact(unittest.TestCase):
def test_supports(self):
''' Test the Entity Capabilities part of the contact instance '''
@ -35,12 +33,10 @@ class TestContact(unittest.TestCase):
self.assertFalse(contact.supports(NS_MUC))
# Test with EntityCapabilites to detect API changes
contact.supports = NullEntityCapabilities()
contact.supports = NullClientCaps()
self.assertTrue(contact.supports(NS_MUC),
msg="Default behaviour is to support everything on unknown caps")
if __name__ == "__main__":
unittest.main()