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:
parent
346953fd93
commit
3295b08b26
|
@ -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):
|
||||
|
|
|
@ -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 '''
|
||||
|
|
|
@ -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()
|
Loading…
Reference in New Issue