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: Basic Idea:
CapsCache caches features to hash relationships. The cache is queried 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 of a presence. The respective jid is then queried with a disco if the advertised
client/hash is unknown. client/hash is unknown.
''' '''
@ -38,8 +38,13 @@ client/hash is unknown.
import gajim import gajim
import helpers 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 Base class representing a client and its capabilities as advertised by
a caps tag in a presence a caps tag in a presence
@ -62,12 +67,15 @@ class AbstractEntityCapabilities(object):
self._discover(connection, jid) self._discover(connection, jid)
q.queried = 1 q.queried = 1
def contains_feature(self, feature): def contains_feature(self, requested_feature):
''' Returns true if these capabilities contain the given feature ''' ''' Returns true if these capabilities contain the given feature '''
features = self._lookup_in_cache().features cach_entry = self._lookup_in_cache()
supported_features = cach_entry.features
if feature in features or features == []: if requested_feature in supported_features:
return True 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: else:
return False return False
@ -80,11 +88,11 @@ class AbstractEntityCapabilities(object):
raise NotImplementedError() raise NotImplementedError()
class EntityCapabilities(AbstractEntityCapabilities): class ClientCaps(AbstractClientCaps):
''' The current XEP-115 implementation ''' ''' The current XEP-115 implementation '''
def __init__(self, caps_cache, caps_hash, node, hash_method): 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' assert hash_method != 'old'
self._hash_method = hash_method self._hash_method = hash_method
@ -95,11 +103,11 @@ class EntityCapabilities(AbstractEntityCapabilities):
connection.discoverInfo(jid, '%s#%s' % (self._node, self._hash)) connection.discoverInfo(jid, '%s#%s' % (self._node, self._hash))
class OldEntityCapabilities(AbstractEntityCapabilities): class OldClientCaps(AbstractClientCaps):
''' Old XEP-115 implemtation. Kept around for background competability. ''' ''' Old XEP-115 implemtation. Kept around for background competability. '''
def __init__(self, caps_cache, caps_hash, node): 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): def _lookup_in_cache(self):
return self._caps_cache[('old', self._node + '#' + self._hash)] return self._caps_cache[('old', self._node + '#' + self._hash)]
@ -108,12 +116,12 @@ class OldEntityCapabilities(AbstractEntityCapabilities):
connection.discoverInfo(jid) 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. advertised any caps or has advertised them in an improper way.
Assumes everything is supported. Assumes (almost) everything is supported.
''' '''
def __init__(self): def __init__(self):
@ -123,7 +131,7 @@ class NullEntityCapabilities(AbstractEntityCapabilities):
pass pass
def contains_feature(self, feature): def contains_feature(self, feature):
return True return feature not in FEATURE_BLACKLIST
class CapsCache(object): class CapsCache(object):

View file

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

View file

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