Changes in [132:1b19c49d30c2:133:2ea562934152]
- Files:
-
- 11 added
- 13 edited
Legend:
- Unmodified
- Added
- Removed
-
NEWS
r60 r73 1 0.6.3 (2009-08-20) 2 ================== 3 4 Features 5 -------- 6 7 - Add a jid attribute to XMPPClient (#18). 8 - Add a better presence protocol handler PresenceProtocol. This handler 9 is also useful for component or in-server use. 10 11 Fixes 12 ----- 13 14 - Use fallback port 5222 for failed SRV lookups for clients (#26). 15 16 17 0.6.2 (2009-07-08) 18 ================== 19 20 Features 21 -------- 22 23 - Add support for XMPP Ping (XEP-0199), doubling as example protocol 24 handler (#55). 25 - Provide examples for setting up clients, components and servers (#55). 26 - Make Service Discovery support accept non-deferred results from getDiscoInfo 27 and getDiscoItems (#55). 28 29 30 0.6.1 (2009-07-06) 31 ================== 32 33 Features 34 -------- 35 36 - Add an optional sender parameter for Service Discovery requests (#52). 37 38 Fixes: 39 ------ 40 41 - Fix regression in DeferredClientFactory (#51). 42 - Make IQ timeouts work with InternalComponent (#53). 43 44 1 45 0.6.0 (2009-04-22) 2 46 ================== -
README
r60 r73 1 Wokkel 0.6. 01 Wokkel 0.6.3 2 2 3 3 What is this? -
setup.py
r60 r73 7 7 8 8 setup(name='wokkel', 9 version='0.6. 0',9 version='0.6.3', 10 10 description='Twisted Jabber support library', 11 11 author='Ralph Meijer', -
wokkel/client.py
r55 r73 1 1 # -*- test-case-name: wokkel.test.test_client -*- 2 2 # 3 # Copyright (c) 2003-200 7Ralph Meijer3 # Copyright (c) 2003-2009 Ralph Meijer 4 4 # See LICENSE for details. 5 5 … … 80 80 81 81 82 82 83 class XMPPClient(StreamManager, service.Service): 83 84 """ … … 86 87 87 88 def __init__(self, jid, password, host=None, port=5222): 89 self.jid = jid 88 90 self.domain = jid.host 89 91 self.host = host … … 94 96 StreamManager.__init__(self, factory) 95 97 98 96 99 def startService(self): 97 100 service.Service.startService(self) 98 101 99 102 self._connection = self._getConnection() 103 100 104 101 105 def stopService(self): … … 104 108 self.factory.stopTrying() 105 109 self._connection.disconnect() 110 111 112 def _authd(self, xs): 113 """ 114 Called when the stream has been initialized. 115 116 Save the JID that we were assigned by the server, as the resource might 117 differ from the JID we asked for. This is stored on the authenticator 118 by its constituent initializers. 119 """ 120 self.jid = self.factory.authenticator.jid 121 StreamManager._authd(self, xs) 122 106 123 107 124 def initializationFailed(self, reason): … … 115 132 reason.raiseException() 116 133 134 117 135 def _getConnection(self): 118 136 if self.host: 119 137 return reactor.connectTCP(self.host, self.port, self.factory) 120 138 else: 121 c = SRVConnector(reactor, 'xmpp-client', self.domain, self.factory)139 c = XMPPClientConnector(reactor, self.domain, self.factory) 122 140 c.connect() 123 141 return c 142 124 143 125 144 … … 129 148 authenticator = client.XMPPAuthenticator(jid, password) 130 149 generic.DeferredXmlStreamFactory.__init__(self, authenticator) 150 self.streamManager = StreamManager(self) 131 151 132 152 … … 146 166 147 167 168 class XMPPClientConnector(SRVConnector): 169 def __init__(self, reactor, domain, factory): 170 SRVConnector.__init__(self, reactor, 'xmpp-client', domain, factory) 171 172 173 def pickServer(self): 174 host, port = SRVConnector.pickServer(self) 175 176 if not self.servers and not self.orderedServers: 177 # no SRV record, fall back.. 178 port = 5222 179 180 return host, port 181 182 183 148 184 def clientCreator(factory): 149 185 domain = factory.authenticator.jid.host 150 c = SRVConnector(reactor, 'xmpp-client', domain, factory)186 c = XMPPClientConnector(reactor, domain, factory) 151 187 c.connect() 152 188 return factory.deferred -
wokkel/compat.py
r53 r63 1 1 # -*- test-case-name: wokkel.test.test_compat -*- 2 2 # 3 # Copyright (c) 2001-200 8Twisted Matrix Laboratories.3 # Copyright (c) 2001-2009 Twisted Matrix Laboratories. 4 4 # See LICENSE for details. 5 5 6 6 from twisted.internet import protocol 7 7 from twisted.words.protocols.jabber import xmlstream 8 from twisted.words.xish import domish9 8 10 9 class BootstrapMixin(object): … … 95 94 self.installBootstraps(xs) 96 95 return xs 96 97 98 99 class IQ(xmlstream.IQ): 100 def __init__(self, *args, **kwargs): 101 # Make sure we have a reactor parameter 102 try: 103 reactor = kwargs['reactor'] 104 except KeyError: 105 from twisted.internet import reactor 106 kwargs['reactor'] = reactor 107 108 # Check if IQ's init accepts the reactor parameter 109 try: 110 xmlstream.IQ.__init__(self, *args, **kwargs) 111 except TypeError: 112 # Guess not. Remove the reactor parameter and try again. 113 del kwargs['reactor'] 114 xmlstream.IQ.__init__(self, *args, **kwargs) 115 116 # Patch the XmlStream instance so that it has a _callLater 117 self._xmlstream._callLater = reactor.callLater -
wokkel/disco.py
r130 r133 12 12 13 13 from twisted.internet import defer 14 from twisted.words.protocols.jabber import error, jid , xmlstream14 from twisted.words.protocols.jabber import error, jid 15 15 from twisted.words.xish import domish 16 16 17 17 from wokkel import data_form 18 from wokkel.compat import IQ 18 19 from wokkel.iwokkel import IDisco 19 20 from wokkel.subprotocols import IQHandlerMixin, XMPPHandler … … 346 347 347 348 348 class _DiscoRequest( xmlstream.IQ):349 class _DiscoRequest(IQ): 349 350 """ 350 351 Element representing an XMPP service discovery request. … … 362 363 @type nodeIdentifier: C{unicode} 363 364 """ 364 xmlstream.IQ.__init__(self, xs, "get")365 IQ.__init__(self, xs, "get") 365 366 query = self.addElement((namespace, 'query')) 366 367 if nodeIdentifier: … … 374 375 """ 375 376 376 def requestInfo(self, entity, nodeIdentifier='' ):377 def requestInfo(self, entity, nodeIdentifier='', sender=None): 377 378 """ 378 379 Request information discovery from a node. … … 380 381 @param entity: Entity to send the request to. 381 382 @type entity: L{jid.JID} 383 382 384 @param nodeIdentifier: Optional node to request info from. 383 385 @type nodeIdentifier: C{unicode} 386 387 @param sender: Optional sender address. 388 @type sender: L{jid.JID} 384 389 """ 385 390 386 391 request = _DiscoRequest(self.xmlstream, NS_DISCO_INFO, nodeIdentifier) 392 if sender is not None: 393 request['from'] = unicode(sender) 387 394 388 395 d = request.send(entity.full()) … … 391 398 392 399 393 def requestItems(self, entity, nodeIdentifier='' ):400 def requestItems(self, entity, nodeIdentifier='', sender=None): 394 401 """ 395 402 Request items discovery from a node. … … 397 404 @param entity: Entity to send the request to. 398 405 @type entity: L{jid.JID} 406 399 407 @param nodeIdentifier: Optional node to request info from. 400 408 @type nodeIdentifier: C{unicode} 409 410 @param sender: Optional sender address. 411 @type sender: L{jid.JID} 401 412 """ 402 413 403 414 request = _DiscoRequest(self.xmlstream, NS_DISCO_ITEMS, nodeIdentifier) 415 if sender is not None: 416 request['from'] = unicode(sender) 404 417 405 418 d = request.send(entity.full()) … … 526 539 @rtype: L{defer.Deferred} 527 540 """ 528 dl = [handler.getDiscoInfo(requestor, target, nodeIdentifier) 541 dl = [defer.maybeDeferred(handler.getDiscoInfo, requestor, target, 542 nodeIdentifier) 529 543 for handler in self.parent 530 544 if IDisco.providedBy(handler)] … … 548 562 @rtype: L{defer.Deferred} 549 563 """ 550 dl = [handler.getDiscoItems(requestor, target, nodeIdentifier) 564 dl = [defer.maybeDeferred(handler.getDiscoItems, requestor, target, 565 nodeIdentifier) 551 566 for handler in self.parent 552 567 if IDisco.providedBy(handler)] -
wokkel/generic.py
r57 r68 1 1 # -*- test-case-name: wokkel.test.test_generic -*- 2 2 # 3 # Copyright (c) 2003-200 8Ralph Meijer3 # Copyright (c) 2003-2009 Ralph Meijer 4 4 # See LICENSE for details. 5 5 … … 11 11 12 12 from twisted.internet import defer, protocol 13 from twisted.python import reflect 13 14 from twisted.words.protocols.jabber import error, jid, xmlstream 14 15 from twisted.words.protocols.jabber.xmlstream import toResponse … … 174 175 """ 175 176 176 s ender= None177 recipient= None177 stanzaKind = None 178 stanzaID = None 178 179 stanzaType = None 180 181 def __init__(self, recipient=None, sender=None): 182 self.recipient = recipient 183 self.sender = sender 184 179 185 180 186 @classmethod … … 186 192 187 193 def parseElement(self, element): 188 self.sender = jid.internJID(element['from'])189 194 if element.hasAttribute('from'): 190 195 self.sender = jid.internJID(element['from']) … … 192 197 self.recipient = jid.internJID(element['to']) 193 198 self.stanzaType = element.getAttribute('type') 199 self.stanzaID = element.getAttribute('id') 200 201 # Save element 202 stripNamespace(element) 203 self.element = element 204 205 # accumulate all childHandlers in the class hierarchy of Class 206 handlers = {} 207 reflect.accumulateClassDict(self.__class__, 'childParsers', handlers) 208 209 for child in element.elements(): 210 try: 211 handler = handlers[child.uri, child.name] 212 except KeyError: 213 pass 214 else: 215 getattr(self, handler)(child) 216 217 218 def toElement(self): 219 element = domish.Element((None, self.stanzaKind)) 220 if self.sender is not None: 221 element['from'] = self.sender.full() 222 if self.recipient is not None: 223 element['to'] = self.recipient.full() 224 if self.stanzaType: 225 element['type'] = self.stanzaType 226 if self.stanzaID: 227 element['id'] = self.stanzaID 228 return element 229 230 231 232 class ErrorStanza(Stanza): 233 234 def parseElement(self, element): 235 Stanza.parseElement(self, element) 236 self.exception = error.exceptionFromStanza(element) 194 237 195 238 -
wokkel/pubsub.py
r59 r63 1 1 # -*- test-case-name: wokkel.test.test_pubsub -*- 2 2 # 3 # Copyright (c) 2003-200 8Ralph Meijer3 # Copyright (c) 2003-2009 Ralph Meijer 4 4 # See LICENSE for details. 5 5 … … 15 15 from twisted.internet import defer 16 16 from twisted.python import log 17 from twisted.words.protocols.jabber import jid, error , xmlstream17 from twisted.words.protocols.jabber import jid, error 18 18 from twisted.words.xish import domish 19 19 20 20 from wokkel import disco, data_form, generic, shim 21 from wokkel.compat import IQ 21 22 from wokkel.subprotocols import IQHandlerMixin, XMPPHandler 22 23 from wokkel.iwokkel import IPubSubClient, IPubSubService, IPubSubResource … … 476 477 477 478 This renders all of the relevant parameters for this specific 478 requests into an L{ xmlstream.IQ}, and invoke its C{send} method.479 requests into an L{IQ}, and invoke its C{send} method. 479 480 This returns a deferred that fires upon reception of a response. See 480 L{ xmlstream.IQ} for details.481 L{IQ} for details. 481 482 482 483 @param xs: The XML stream to send the request on. … … 492 493 raise NotImplementedError() 493 494 494 iq = xmlstream.IQ(xs, self.stanzaType)495 iq = IQ(xs, self.stanzaType) 495 496 iq.addElement((childURI, 'pubsub')) 496 497 verbElement = iq.pubsub.addElement(childName) -
wokkel/test/test_client.py
r42 r73 1 # Copyright (c) 2003-200 7Ralph Meijer1 # Copyright (c) 2003-2009 Ralph Meijer 2 2 # See LICENSE for details. 3 3 … … 14 14 from twisted.words.protocols.jabber.xmlstream import INIT_FAILED_EVENT 15 15 16 try: 17 from twisted.words.protocols.jabber.xmlstream import XMPPHandler 18 except ImportError: 19 from wokkel.subprotocols import XMPPHandler 20 16 21 from wokkel import client 17 22 from wokkel.test.test_compat import BootstrapMixinTest 23 24 class XMPPClientTest(unittest.TestCase): 25 """ 26 Tests for L{client.XMPPClient}. 27 """ 28 29 def setUp(self): 30 self.client = client.XMPPClient(JID('user@example.org'), 'secret') 31 32 33 def test_jid(self): 34 """ 35 Make sure the JID we pass is stored on the client. 36 """ 37 self.assertEquals(JID('user@example.org'), self.client.jid) 38 39 40 def test_jidWhenInitialized(self): 41 """ 42 Make sure that upon login, the JID is updated from the authenticator. 43 """ 44 xs = self.client.factory.buildProtocol(None) 45 self.client.factory.authenticator.jid = JID('user@example.org/test') 46 xs.dispatch(xs, xmlstream.STREAM_AUTHD_EVENT) 47 self.assertEquals(JID('user@example.org/test'), self.client.jid) 48 49 18 50 19 51 class DeferredClientFactoryTest(BootstrapMixinTest): … … 78 110 79 111 112 def test_addHandler(self): 113 """ 114 Test the addition of a protocol handler. 115 """ 116 handler = XMPPHandler() 117 handler.setHandlerParent(self.factory.streamManager) 118 self.assertIn(handler, self.factory.streamManager) 119 self.assertIdentical(self.factory.streamManager, handler.parent) 120 121 122 def test_removeHandler(self): 123 """ 124 Test removal of a protocol handler. 125 """ 126 handler = XMPPHandler() 127 handler.setHandlerParent(self.factory.streamManager) 128 handler.disownHandlerParent(self.factory.streamManager) 129 self.assertNotIn(handler, self.factory.streamManager) 130 self.assertIdentical(None, handler.parent) 131 132 80 133 81 134 class ClientCreatorTest(unittest.TestCase): -
wokkel/test/test_compat.py
r53 r63 7 7 """ 8 8 9 from zope.interface import implements 9 10 from zope.interface.verify import verifyObject 10 from twisted.internet import defer, protocol11 from twisted.internet.interfaces import IProtocolFactory 11 from twisted.internet import protocol, task 12 from twisted.internet.interfaces import IProtocolFactory, IReactorTime 12 13 from twisted.trial import unittest 13 from twisted.words.xish import domish,utility14 from twisted.words.xish import utility 14 15 from twisted.words.protocols.jabber import xmlstream 15 from wokkel.compat import BootstrapMixin, XmlStreamServerFactory16 from wokkel.compat import BootstrapMixin, IQ, XmlStreamServerFactory 16 17 17 18 class DummyProtocol(protocol.Protocol, utility.EventDispatcher): … … 166 167 xs = self.factory.buildProtocol(None) 167 168 self.assertIdentical(self.factory, xs.factory) 169 170 171 172 class FakeReactor(object): 173 174 implements(IReactorTime) 175 def __init__(self): 176 self.clock = task.Clock() 177 self.callLater = self.clock.callLater 178 self.getDelayedCalls = self.clock.getDelayedCalls 179 180 181 182 class IQTest(unittest.TestCase): 183 """ 184 Tests for L{IQ}. 185 """ 186 187 def setUp(self): 188 self.reactor = FakeReactor() 189 self.clock = self.reactor.clock 190 191 192 def testRequestTimingOutEventDispatcher(self): 193 """ 194 Test that an iq request with a defined timeout times out. 195 """ 196 from twisted.words.xish import utility 197 output = [] 198 xs = utility.EventDispatcher() 199 xs.send = output.append 200 201 self.iq = IQ(xs, reactor=self.reactor) 202 self.iq.timeout = 60 203 d = self.iq.send() 204 self.assertFailure(d, xmlstream.TimeoutError) 205 206 self.clock.pump([1, 60]) 207 self.assertFalse(self.reactor.getDelayedCalls()) 208 self.assertFalse(xs.iqDeferreds) 209 return d -
wokkel/test/test_disco.py
r53 r67 515 515 516 516 517 def test_requestItemsFrom(self): 518 """ 519 A disco items request can be sent with an explicit sender address. 520 """ 521 d = self.protocol.requestItems(JID(u'example.org'), 522 sender=JID(u'test.example.org')) 523 524 iq = self.stub.output[-1] 525 self.assertEqual(u'test.example.org', iq.getAttribute(u'from')) 526 527 response = toResponse(iq, u'result') 528 response.addElement((NS_DISCO_ITEMS, u'query')) 529 self.stub.send(response) 530 531 return d 532 533 517 534 def test_requestInfo(self): 518 535 """ … … 550 567 551 568 self.stub.send(response) 569 return d 570 571 572 def test_requestInfoFrom(self): 573 """ 574 A disco info request can be sent with an explicit sender address. 575 """ 576 d = self.protocol.requestInfo(JID(u'example.org'), 577 sender=JID(u'test.example.org')) 578 579 iq = self.stub.output[-1] 580 self.assertEqual(u'test.example.org', iq.getAttribute(u'from')) 581 582 response = toResponse(iq, u'result') 583 response.addElement((NS_DISCO_INFO, u'query')) 584 self.stub.send(response) 585 552 586 return d 553 587 … … 717 751 718 752 753 def test_infoNotDeferred(self): 754 """ 755 C{info} should gather disco info from sibling handlers. 756 """ 757 discoItems = [disco.DiscoIdentity('dummy', 'generic', 758 'Generic Dummy Entity'), 759 disco.DiscoFeature('jabber:iq:version') 760 ] 761 762 class DiscoResponder(XMPPHandler): 763 implements(disco.IDisco) 764 765 def getDiscoInfo(self, requestor, target, nodeIdentifier): 766 if not nodeIdentifier: 767 return discoItems 768 else: 769 return [] 770 771 def cb(result): 772 self.assertEquals(discoItems, result) 773 774 self.service.parent = [self.service, DiscoResponder()] 775 d = self.service.info(JID('test@example.com'), JID('example.com'), '') 776 d.addCallback(cb) 777 return d 778 779 719 780 def test_items(self): 720 781 """ … … 739 800 d.addCallback(cb) 740 801 return d 802 803 804 def test_itemsNotDeferred(self): 805 """ 806 C{info} should also collect results not returned via a deferred. 807 """ 808 discoItems = [disco.DiscoItem(JID('example.com'), 'test', 'Test node')] 809 810 class DiscoResponder(XMPPHandler): 811 implements(disco.IDisco) 812 813 def getDiscoItems(self, requestor, target, nodeIdentifier): 814 if not nodeIdentifier: 815 return discoItems 816 else: 817 return [] 818 819 def cb(result): 820 self.assertEquals(discoItems, result) 821 822 self.service.parent = [self.service, DiscoResponder()] 823 d = self.service.items(JID('test@example.com'), JID('example.com'), '') 824 d.addCallback(cb) 825 return d -
wokkel/test/test_xmppim.py
r28 r68 1 # Copyright (c) 2003-200 8Ralph Meijer1 # Copyright (c) 2003-2009 Ralph Meijer 2 2 # See LICENSE for details 3 3 … … 6 6 """ 7 7 8 from twisted.internet import defer 8 9 from twisted.trial import unittest 9 10 from twisted.words.protocols.jabber.jid import JID 10 11 from twisted.words.protocols.jabber.xmlstream import toResponse 11 from twisted.words.xish import domish 12 from twisted.words.xish import domish, utility 12 13 13 14 from wokkel import xmppim 15 from wokkel.generic import ErrorStanza, parseXml 14 16 from wokkel.test.helpers import XmlStreamStub 15 17 18 NS_XML = 'http://www.w3.org/XML/1998/namespace' 16 19 NS_ROSTER = 'jabber:iq:roster' 17 20 … … 76 79 77 80 81 82 class AvailabilityPresenceTest(unittest.TestCase): 83 84 def test_fromElement(self): 85 xml = """<presence from='user@example.org' to='user@example.com'> 86 <show>chat</show> 87 <status>Let's chat!</status> 88 <priority>50</priority> 89 </presence> 90 """ 91 92 presence = xmppim.AvailabilityPresence.fromElement(parseXml(xml)) 93 self.assertEquals(JID('user@example.org'), presence.sender) 94 self.assertEquals(JID('user@example.com'), presence.recipient) 95 self.assertTrue(presence.available) 96 self.assertEquals('chat', presence.show) 97 self.assertEquals({None: "Let's chat!"}, presence.statuses) 98 self.assertEquals(50, presence.priority) 99 100 101 class PresenceProtocolTest(unittest.TestCase): 102 """ 103 Tests for L{xmppim.PresenceProtocol} 104 """ 105 106 def setUp(self): 107 self.output = [] 108 self.protocol = xmppim.PresenceProtocol() 109 self.protocol.parent = self 110 self.protocol.xmlstream = utility.EventDispatcher() 111 self.protocol.connectionInitialized() 112 113 114 def send(self, obj): 115 self.output.append(obj) 116 117 118 def test_errorReceived(self): 119 """ 120 Incoming presence stanzas are parsed and dispatched. 121 """ 122 xml = """<presence type="error"/>""" 123 124 def errorReceived(error): 125 xmppim.PresenceProtocol.errorReceived(self.protocol, error) 126 try: 127 self.assertIsInstance(error, ErrorStanza) 128 except: 129 d.errback() 130 else: 131 d.callback(None) 132 133 d = defer.Deferred() 134 self.protocol.errorReceived = errorReceived 135 self.protocol.xmlstream.dispatch(parseXml(xml)) 136 return d 137 138 139 def test_availableReceived(self): 140 """ 141 Incoming presence stanzas are parsed and dispatched. 142 """ 143 xml = """<presence/>""" 144 145 def availableReceived(presence): 146 xmppim.PresenceProtocol.availableReceived(self.protocol, presence) 147 try: 148 self.assertIsInstance(presence, xmppim.AvailabilityPresence) 149 except: 150 d.errback() 151 else: 152 d.callback(None) 153 154 d = defer.Deferred() 155 self.protocol.availableReceived = availableReceived 156 self.protocol.xmlstream.dispatch(parseXml(xml)) 157 return d 158 159 160 def test_unavailableReceived(self): 161 """ 162 Incoming presence stanzas are parsed and dispatched. 163 """ 164 xml = """<presence type='unavailable'/>""" 165 166 def unavailableReceived(presence): 167 xmppim.PresenceProtocol.unavailableReceived(self.protocol, presence) 168 try: 169 self.assertIsInstance(presence, xmppim.AvailabilityPresence) 170 except: 171 d.errback() 172 else: 173 d.callback(None) 174 175 d = defer.Deferred() 176 self.protocol.unavailableReceived = unavailableReceived 177 self.protocol.xmlstream.dispatch(parseXml(xml)) 178 return d 179 180 181 def test_subscribeReceived(self): 182 """ 183 Incoming presence stanzas are parsed and dispatched. 184 """ 185 xml = """<presence type='subscribe'/>""" 186 187 def subscribeReceived(presence): 188 xmppim.PresenceProtocol.subscribeReceived(self.protocol, presence) 189 try: 190 self.assertIsInstance(presence, xmppim.SubscriptionPresence) 191 except: 192 d.errback() 193 else: 194 d.callback(None) 195 196 d = defer.Deferred() 197 self.protocol.subscribeReceived = subscribeReceived 198 self.protocol.xmlstream.dispatch(parseXml(xml)) 199 return d 200 201 202 def test_unsubscribeReceived(self): 203 """ 204 Incoming presence stanzas are parsed and dispatched. 205 """ 206 xml = """<presence type='unsubscribe'/>""" 207 208 def unsubscribeReceived(presence): 209 xmppim.PresenceProtocol.unsubscribeReceived(self.protocol, presence) 210 try: 211 self.assertIsInstance(presence, xmppim.SubscriptionPresence) 212 except: 213 d.errback() 214 else: 215 d.callback(None) 216 217 d = defer.Deferred() 218 self.protocol.unsubscribeReceived = unsubscribeReceived 219 self.protocol.xmlstream.dispatch(parseXml(xml)) 220 return d 221 222 223 def test_subscribedReceived(self): 224 """ 225 Incoming presence stanzas are parsed and dispatched. 226 """ 227 xml = """<presence type='subscribed'/>""" 228 229 def subscribedReceived(presence): 230 xmppim.PresenceProtocol.subscribedReceived(self.protocol, presence) 231 try: 232 self.assertIsInstance(presence, xmppim.SubscriptionPresence) 233 except: 234 d.errback() 235 else: 236 d.callback(None) 237 238 d = defer.Deferred() 239 self.protocol.subscribedReceived = subscribedReceived 240 self.protocol.xmlstream.dispatch(parseXml(xml)) 241 return d 242 243 244 def test_unsubscribedReceived(self): 245 """ 246 Incoming presence stanzas are parsed and dispatched. 247 """ 248 xml = """<presence type='unsubscribed'/>""" 249 250 def unsubscribedReceived(presence): 251 xmppim.PresenceProtocol.unsubscribedReceived(self.protocol, 252 presence) 253 try: 254 self.assertIsInstance(presence, xmppim.SubscriptionPresence) 255 except: 256 d.errback() 257 else: 258 d.callback(None) 259 260 d = defer.Deferred() 261 self.protocol.unsubscribedReceived = unsubscribedReceived 262 self.protocol.xmlstream.dispatch(parseXml(xml)) 263 return d 264 265 266 def test_probeReceived(self): 267 """ 268 Incoming presence stanzas are parsed and dispatched. 269 """ 270 xml = """<presence type='probe'/>""" 271 272 def probeReceived(presence): 273 xmppim.PresenceProtocol.probeReceived(self.protocol, presence) 274 try: 275 self.assertIsInstance(presence, xmppim.ProbePresence) 276 except: 277 d.errback() 278 else: 279 d.callback(None) 280 281 d = defer.Deferred() 282 self.protocol.probeReceived = probeReceived 283 self.protocol.xmlstream.dispatch(parseXml(xml)) 284 return d 285 286 def test_available(self): 287 """ 288 It should be possible to pass a sender address. 289 """ 290 self.protocol.available(JID('user@example.com'), 291 show=u'chat', 292 status=u'Talk to me!', 293 priority=50) 294 element = self.output[-1] 295 self.assertEquals("user@example.com", element.getAttribute('to')) 296 self.assertIdentical(None, element.getAttribute('type')) 297 self.assertEquals(u'chat', unicode(element.show)) 298 self.assertEquals(u'Talk to me!', unicode(element.status)) 299 self.assertEquals(u'50', unicode(element.priority)) 300 301 def test_availableLanguages(self): 302 """ 303 It should be possible to pass a sender address. 304 """ 305 self.protocol.available(JID('user@example.com'), 306 show=u'chat', 307 statuses={None: u'Talk to me!', 308 'nl': u'Praat met me!'}, 309 priority=50) 310 element = self.output[-1] 311 self.assertEquals("user@example.com", element.getAttribute('to')) 312 self.assertIdentical(None, element.getAttribute('type')) 313 self.assertEquals(u'chat', unicode(element.show)) 314 315 statuses = {} 316 for status in element.elements(): 317 if status.name == 'status': 318 lang = status.getAttribute((NS_XML, 'lang')) 319 statuses[lang] = unicode(status) 320 321 self.assertIn(None, statuses) 322 self.assertEquals(u'Talk to me!', statuses[None]) 323 self.assertIn('nl', statuses) 324 self.assertEquals(u'Praat met me!', statuses['nl']) 325 self.assertEquals(u'50', unicode(element.priority)) 326 327 328 def test_availableSender(self): 329 """ 330 It should be possible to pass a sender address. 331 """ 332 self.protocol.available(JID('user@example.com'), 333 sender=JID('user@example.org')) 334 element = self.output[-1] 335 self.assertEquals("user@example.org", element.getAttribute('from')) 336 337 338 def test_unavailableDirected(self): 339 """ 340 Test sending of directed unavailable presence broadcast. 341 """ 342 343 self.protocol.unavailable(JID('user@example.com')) 344 element = self.output[-1] 345 self.assertEquals("presence", element.name) 346 self.assertEquals(None, element.uri) 347 self.assertEquals("user@example.com", element.getAttribute('to')) 348 self.assertEquals("unavailable", element.getAttribute('type')) 349 350 def test_unavailableWithStatus(self): 351 """ 352 Test sending of directed unavailable presence broadcast with status. 353 """ 354 355 self.protocol.unavailable(JID('user@example.com'), 356 {None: 'Disconnected'}) 357 element = self.output[-1] 358 self.assertEquals("presence", element.name) 359 self.assertEquals(None, element.uri) 360 self.assertEquals("user@example.com", element.getAttribute('to')) 361 self.assertEquals("unavailable", element.getAttribute('type')) 362 self.assertEquals("Disconnected", unicode(element.status)) 363 364 365 def test_unavailableBroadcast(self): 366 """ 367 Test sending of unavailable presence broadcast. 368 """ 369 370 self.protocol.unavailable(None) 371 element = self.output[-1] 372 self.assertEquals("presence", element.name) 373 self.assertEquals(None, element.uri) 374 self.assertEquals(None, element.getAttribute('to')) 375 self.assertEquals("unavailable", element.getAttribute('type')) 376 377 378 def test_unavailableBroadcastNoRecipientParameter(self): 379 """ 380 Test sending of unavailable presence broadcast by not passing entity. 381 """ 382 383 self.protocol.unavailable() 384 element = self.output[-1] 385 self.assertEquals("presence", element.name) 386 self.assertEquals(None, element.uri) 387 self.assertEquals(None, element.getAttribute('to')) 388 self.assertEquals("unavailable", element.getAttribute('type')) 389 390 391 def test_unavailableSender(self): 392 """ 393 It should be possible to pass a sender address. 394 """ 395 self.protocol.unavailable(JID('user@example.com'), 396 sender=JID('user@example.org')) 397 element = self.output[-1] 398 self.assertEquals("user@example.org", element.getAttribute('from')) 399 400 401 def test_subscribeSender(self): 402 """ 403 It should be possible to pass a sender address. 404 """ 405 self.protocol.subscribe(JID('user@example.com'), 406 sender=JID('user@example.org')) 407 element = self.output[-1] 408 self.assertEquals("user@example.org", element.getAttribute('from')) 409 410 411 def test_unsubscribeSender(self): 412 """ 413 It should be possible to pass a sender address. 414 """ 415 self.protocol.unsubscribe(JID('user@example.com'), 416 sender=JID('user@example.org')) 417 element = self.output[-1] 418 self.assertEquals("user@example.org", element.getAttribute('from')) 419 420 421 def test_subscribedSender(self): 422 """ 423 It should be possible to pass a sender address. 424 """ 425 self.protocol.subscribed(JID('user@example.com'), 426 sender=JID('user@example.org')) 427 element = self.output[-1] 428 self.assertEquals("user@example.org", element.getAttribute('from')) 429 430 431 def test_unsubscribedSender(self): 432 """ 433 It should be possible to pass a sender address. 434 """ 435 self.protocol.unsubscribed(JID('user@example.com'), 436 sender=JID('user@example.org')) 437 element = self.output[-1] 438 self.assertEquals("user@example.org", element.getAttribute('from')) 439 440 441 def test_probeSender(self): 442 """ 443 It should be possible to pass a sender address. 444 """ 445 self.protocol.probe(JID('user@example.com'), 446 sender=JID('user@example.org')) 447 element = self.output[-1] 448 self.assertEquals("user@example.org", element.getAttribute('from')) 449 450 451 78 452 class RosterClientProtocolTest(unittest.TestCase): 79 453 """ -
wokkel/xmppim.py
r28 r68 1 1 # -*- test-case-name: wokkel.test.test_xmppim -*- 2 2 # 3 # Copyright (c) 2003-200 8Ralph Meijer3 # Copyright (c) 2003-2009 Ralph Meijer 4 4 # See LICENSE for details. 5 5 … … 14 14 15 15 from twisted.words.protocols.jabber.jid import JID 16 from twisted.words.protocols.jabber.xmlstream import IQ17 16 from twisted.words.xish import domish 18 17 18 from wokkel.compat import IQ 19 from wokkel.generic import ErrorStanza, Stanza 19 20 from wokkel.subprotocols import XMPPHandler 20 21 … … 114 115 def _onPresenceUnsubscribe(self, presence): 115 116 self.unsubscribeReceived(JID(presence["from"])) 117 116 118 117 119 def availableReceived(self, entity, show=None, statuses=None, priority=0): … … 246 248 """ 247 249 self.send(Presence(to=entity, type='unsubscribed')) 250 251 252 253 class BasePresence(Stanza): 254 """ 255 Stanza of kind presence. 256 """ 257 stanzaKind = 'presence' 258 259 260 261 class AvailabilityPresence(BasePresence): 262 """ 263 Presence. 264 265 This represents availability presence (as opposed to 266 L{SubscriptionPresence}). 267 268 @ivar available: The availability being communicated. 269 @type available: C{bool} 270 @ivar show: More specific availability. Can be one of C{'chat'}, C{'away'}, 271 C{'xa'}, C{'dnd'} or C{None}. 272 @type show: C{str} or C{NoneType} 273 @ivar statuses: Natural language texts to detail the (un)availability. 274 These are represented as a mapping from language code 275 (C{str} or C{None}) to the corresponding text (C{unicode}). 276 If the key is C{None}, the associated text is in the 277 default language. 278 @type statuses: C{dict} 279 @ivar priority: Priority level for this resource. Must be between -128 and 280 127. Defaults to 0. 281 @type priority: C{int} 282 """ 283 284 childParsers = {(None, 'show'): '_childParser_show', 285 (None, 'status'): '_childParser_status', 286 (None, 'priority'): '_childParser_priority'} 287 288 def __init__(self, recipient=None, sender=None, available=True, 289 show=None, status=None, statuses=None, priority=0): 290 BasePresence.__init__(self, recipient=recipient, sender=sender) 291 self.available = available 292 self.show = show 293 self.statuses = statuses or {} 294 if status: 295 self.statuses[None] = status 296 self.priority = priority 297 298 299 def _childParser_show(self, element): 300 show = unicode(element) 301 if show in ('chat', 'away', 'xa', 'dnd'): 302 self.show = show 303 304 305 def _childParser_status(self, element): 306 lang = element.getAttribute((NS_XML, 'lang'), None) 307 text = unicode(element) 308 self.statuses[lang] = text 309 310 311 def _childParser_priority(self, element): 312 try: 313 self.priority = int(unicode(element)) 314 except ValueError: 315 pass 316 317 318 def parseElement(self, element): 319 BasePresence.parseElement(self, element) 320 321 if self.stanzaType == 'unavailable': 322 self.available = False 323 324 325 def toElement(self): 326 if not self.available: 327 self.stanzaType = 'unavailable' 328 329 presence = BasePresence.toElement(self) 330 331 if self.available: 332 if self.show in ('chat', 'away', 'xa', 'dnd'): 333 presence.addElement('show', content=self.show) 334 if self.priority != 0: 335 presence.addElement('priority', content=unicode(self.priority)) 336 337 for lang, text in self.statuses.iteritems(): 338 status = presence.addElement('status', content=text) 339 if lang: 340 status[(NS_XML, 'lang')] = lang 341 342 return presence 343 344 345 346 class SubscriptionPresence(BasePresence): 347 """ 348 Presence subscription request or response. 349 350 This kind of presence is used to represent requests for presence 351 subscription and their replies. 352 353 Based on L{BasePresence} and {Stanza}, it just uses the L{stanzaType} 354 attribute to represent the type of subscription presence. This can be 355 one of C{'subscribe'}, C{'unsubscribe'}, C{'subscribed'} and 356 C{'unsubscribed'}. 357 """ 358 359 360 361 class ProbePresence(BasePresence): 362 """ 363 Presence probe request. 364 """ 365 366 stanzaType = 'probe' 367 368 369 370 class PresenceProtocol(XMPPHandler): 371 """ 372 XMPP Presence protocol. 373 374 @cvar presenceTypeParserMap: Maps presence stanza types to their respective 375 stanza parser classes (derived from L{Stanza}). 376 @type presenceTypeParserMap: C{dict} 377 """ 378 379 presenceTypeParserMap = { 380 'error': ErrorStanza, 381 'available': AvailabilityPresence, 382 'unavailable': AvailabilityPresence, 383 'subscribe': SubscriptionPresence, 384 'unsubscribe': SubscriptionPresence, 385 'subscribed': SubscriptionPresence, 386 'unsubscribed': SubscriptionPresence, 387 'probe': ProbePresence, 388 } 389 390 def connectionInitialized(self): 391 self.xmlstream.addObserver("/presence", self._onPresence) 392 393 394 def _onPresence(self, element): 395 stanza = Stanza.fromElement(element) 396 397 presenceType = stanza.stanzaType or 'available' 398 399 try: 400 parser = self.presenceTypeParserMap[presenceType] 401 except KeyError: 402 return 403 404 presence = parser.fromElement(element) 405 406 try: 407 handler = getattr(self, '%sReceived' % presenceType) 408 except AttributeError: 409 return 410 else: 411 handler(presence) 412 413 414 def errorReceived(self, presence): 415 """ 416 Error presence was received. 417 """ 418 pass 419 420 421 def availableReceived(self, presence): 422 """ 423 Available presence was received. 424 """ 425 pass 426 427 428 def unavailableReceived(self, presence): 429 """ 430 Unavailable presence was received. 431 """ 432 pass 433 434 435 def subscribedReceived(self, presence): 436 """ 437 Subscription approval confirmation was received. 438 """ 439 pass 440 441 442 def unsubscribedReceived(self, presence): 443 """ 444 Unsubscription confirmation was received. 445 """ 446 pass 447 448 449 def subscribeReceived(self, presence): 450 """ 451 Subscription request was received. 452 """ 453 pass 454 455 456 def unsubscribeReceived(self, presence): 457 """ 458 Unsubscription request was received. 459 """ 460 pass 461 462 463 def probeReceived(self, presence): 464 """ 465 Probe presence was received. 466 """ 467 pass 468 469 470 def available(self, recipient=None, show=None, statuses=None, priority=0, 471 status=None, sender=None): 472 """ 473 Send available presence. 474 475 @param recipient: Optional Recipient to which the presence should be 476 sent. 477 @type recipient: {JID} 478 479 @param show: Optional detailed presence information. One of C{'away'}, 480 C{'xa'}, C{'chat'}, C{'dnd'}. 481 @type show: C{str} 482 483 @param statuses: Mapping of natural language descriptions of the 484 availability status, keyed by the language descriptor. A status 485 without a language specified, is keyed with C{None}. 486 @type statuses: C{dict} 487 488 @param priority: priority level of the resource. 489 @type priority: C{int} 490 """ 491 presence = AvailabilityPresence(recipient=recipient, sender=sender, 492 show=show, statuses=statuses, 493 status=status, priority=priority) 494 self.send(presence.toElement()) 495 496 497 def unavailable(self, recipient=None, statuses=None, sender=None): 498 """ 499 Send unavailable presence. 500 501 @param recipient: Optional entity to which the presence should be sent. 502 @type recipient: {JID} 503 504 @param statuses: dictionary of natural language descriptions of the 505 availability status, keyed by the language descriptor. A status 506 without a language specified, is keyed with C{None}. 507 @type statuses: C{dict} 508 """ 509 presence = AvailabilityPresence(recipient=recipient, sender=sender, 510 available=False, statuses=statuses) 511 self.send(presence.toElement()) 512 513 514 def subscribe(self, recipient, sender=None): 515 """ 516 Send subscription request 517 518 @param recipient: Entity to subscribe to. 519 @type recipient: {JID} 520 """ 521 presence = SubscriptionPresence(recipient=recipient, sender=sender) 522 presence.stanzaType = 'subscribe' 523 self.send(presence.toElement()) 524 525 526 def unsubscribe(self, recipient, sender=None): 527 """ 528 Send unsubscription request 529 530 @param recipient: Entity to unsubscribe from. 531 @type recipient: {JID} 532 """ 533 presence = SubscriptionPresence(recipient=recipient, sender=sender) 534 presence.stanzaType = 'unsubscribe' 535 self.send(presence.toElement()) 536 537 538 def subscribed(self, recipient, sender=None): 539 """ 540 Send subscription confirmation. 541 542 @param recipient: Entity that subscribed. 543 @type recipient: {JID} 544 """ 545 presence = SubscriptionPresence(recipient=recipient, sender=sender) 546 presence.stanzaType = 'subscribed' 547 self.send(presence.toElement()) 548 549 550 def unsubscribed(self, recipient, sender=None): 551 """ 552 Send unsubscription confirmation. 553 554 @param recipient: Entity that unsubscribed. 555 @type recipient: {JID} 556 """ 557 presence = SubscriptionPresence(recipient=recipient, sender=sender) 558 presence.stanzaType = 'unsubscribed' 559 self.send(presence.toElement()) 560 561 562 def probe(self, recipient, sender=None): 563 """ 564 Send presence probe. 565 566 @param recipient: Entity to be probed. 567 @type recipient: {JID} 568 """ 569 presence = ProbePresence(recipient=recipient, sender=sender) 570 self.send(presence.toElement()) 571 248 572 249 573
Note: See TracChangeset
for help on using the changeset viewer.