source: wokkel/disco.py @ 47:895c67e2ed9f

Last change on this file since 47:895c67e2ed9f was 46:4ee1f9c08b22, checked in by Ralph Meijer <ralphm@…>, 14 years ago

If provided, return NodeID in the response for Service Discovery requests.

This also renames the namespace constants to include DISCO in their name
and provides better tests and docstrings.

Author: ralphm.
Reviewer: tofu.
Fixes #7.

File size: 6.9 KB
Line 
1# -*- test-case-name: wokkel.test.test_disco -*-
2#
3# Copyright (c) 2003-2008 Ralph Meijer
4# See LICENSE for details.
5
6"""
7XMPP Service Discovery.
8
9The XMPP service discovery protocol is documented in
10U{XEP-0030<http://www.xmpp.org/extensions/xep-0030.html>}.
11"""
12
13from twisted.internet import defer
14from twisted.words.protocols.jabber import error, jid
15from twisted.words.xish import domish
16
17from wokkel.iwokkel import IDisco
18from wokkel.subprotocols import IQHandlerMixin, XMPPHandler
19
20NS_DISCO = 'http://jabber.org/protocol/disco'
21NS_DISCO_INFO = NS_DISCO + '#info'
22NS_DISCO_ITEMS = NS_DISCO + '#items'
23
24IQ_GET = '/iq[@type="get"]'
25DISCO_INFO = IQ_GET + '/query[@xmlns="' + NS_DISCO_INFO + '"]'
26DISCO_ITEMS = IQ_GET + '/query[@xmlns="' + NS_DISCO_ITEMS + '"]'
27
28class DiscoFeature(domish.Element):
29    """
30    Element representing an XMPP service discovery feature.
31    """
32
33    def __init__(self, feature):
34        domish.Element.__init__(self, (NS_DISCO_INFO, 'feature'),
35                                attribs={'var': feature})
36
37
38class DiscoIdentity(domish.Element):
39    """
40    Element representing an XMPP service discovery identity.
41    """
42
43    def __init__(self, category, type, name = None):
44        domish.Element.__init__(self, (NS_DISCO_INFO, 'identity'),
45                                attribs={'category': category,
46                                         'type': type})
47        if name:
48            self['name'] = name
49
50
51class DiscoItem(domish.Element):
52    """
53    Element representing an XMPP service discovery item.
54    """
55
56    def __init__(self, jid, node='', name=None):
57        domish.Element.__init__(self, (NS_DISCO_ITEMS, 'item'),
58                                attribs={'jid': jid.full()})
59        if node:
60            self['node'] = node
61
62        if name:
63            self['name'] = name
64
65
66
67class DiscoHandler(XMPPHandler, IQHandlerMixin):
68    """
69    Protocol implementation for XMPP Service Discovery.
70
71    This handler will listen to XMPP service discovery requests and
72    query the other handlers in L{parent} (see L{XMPPHandlerContainer}) for
73    their identities, features and items according to L{IDisco}.
74    """
75
76    iqHandlers = {DISCO_INFO: '_onDiscoInfo',
77                  DISCO_ITEMS: '_onDiscoItems'}
78
79    def connectionInitialized(self):
80        self.xmlstream.addObserver(DISCO_INFO, self.handleRequest)
81        self.xmlstream.addObserver(DISCO_ITEMS, self.handleRequest)
82
83
84    def _onDiscoInfo(self, iq):
85        """
86        Called for incoming disco info requests.
87
88        @param iq: The request iq element.
89        @type iq: L{Element<twisted.words.xish.domish.Element>}
90        """
91        requestor = jid.internJID(iq["from"])
92        target = jid.internJID(iq["to"])
93        nodeIdentifier = iq.query.getAttribute("node", '')
94
95        def toResponse(info):
96            if nodeIdentifier and not info:
97                raise error.StanzaError('item-not-found')
98            else:
99                response = domish.Element((NS_DISCO_INFO, 'query'))
100                if nodeIdentifier:
101                    response['node'] = nodeIdentifier
102
103                for item in info:
104                    response.addChild(item)
105
106            return response
107
108        d = self.info(requestor, target, nodeIdentifier)
109        d.addCallback(toResponse)
110        return d
111
112
113    def _onDiscoItems(self, iq):
114        """
115        Called for incoming disco items requests.
116
117        @param iq: The request iq element.
118        @type iq: L{Element<twisted.words.xish.domish.Element>}
119        """
120        requestor = jid.internJID(iq["from"])
121        target = jid.internJID(iq["to"])
122        nodeIdentifier = iq.query.getAttribute("node", '')
123
124        def toResponse(items):
125            response = domish.Element((NS_DISCO_ITEMS, 'query'))
126            if nodeIdentifier:
127                response['node'] = nodeIdentifier
128
129            for item in items:
130                response.addChild(item)
131
132            return response
133
134        d = self.items(requestor, target, nodeIdentifier)
135        d.addCallback(toResponse)
136        return d
137
138
139    def _gatherResults(self, deferredList):
140        """
141        Gather results from a list of deferreds.
142
143        Similar to L{defer.gatherResults}, but flattens the returned results,
144        consumes errors after the first one and fires the errback of the
145        returned deferred with the failure of the first deferred that fires its
146        errback.
147
148        @param deferredList: List of deferreds for which the results should be
149                             gathered.
150        @type deferredList: C{list}
151        @return: Deferred that fires with a list of gathered results.
152        @rtype: L{defer.Deferred}
153        """
154        def cb(resultList):
155            results = []
156            for success, value in resultList:
157                results.extend(value)
158            return results
159
160        def eb(failure):
161            failure.trap(defer.FirstError)
162            return failure.value.subFailure
163
164        d = defer.DeferredList(deferredList, fireOnOneErrback=1,
165                                             consumeErrors=1)
166        d.addCallbacks(cb, eb)
167        return d
168
169
170    def info(self, requestor, target, nodeIdentifier):
171        """
172        Inspect all sibling protocol handlers for disco info.
173
174        Calls the L{getDiscoInfo<IDisco.getDiscoInfo>} method on all child
175        handlers of the parent, that provide L{IDisco}.
176
177        @param requestor: The entity that sent the request.
178        @type requestor: L{JID<twisted.words.protocols.jabber.jid.JID>}
179        @param target: The entity the request was sent to.
180        @type target: L{JID<twisted.words.protocols.jabber.jid.JID>}
181        @param nodeIdentifier: The optional node being queried, or C{''}.
182        @type nodeIdentifier: C{unicode}
183        @return: Deferred with the gathered results from sibling handlers.
184        @rtype: L{defer.Deferred}
185        """
186        dl = [handler.getDiscoInfo(requestor, target, nodeIdentifier)
187              for handler in self.parent
188              if IDisco.providedBy(handler)]
189        return self._gatherResults(dl)
190
191
192    def items(self, requestor, target, nodeIdentifier):
193        """
194        Inspect all sibling protocol handlers for disco items.
195
196        Calls the L{getDiscoItems<IDisco.getDiscoItems>} method on all child
197        handlers of the parent, that provide L{IDisco}.
198
199        @param requestor: The entity that sent the request.
200        @type requestor: L{JID<twisted.words.protocols.jabber.jid.JID>}
201        @param target: The entity the request was sent to.
202        @type target: L{JID<twisted.words.protocols.jabber.jid.JID>}
203        @param nodeIdentifier: The optional node being queried, or C{''}.
204        @type nodeIdentifier: C{unicode}
205        @return: Deferred with the gathered results from sibling handlers.
206        @rtype: L{defer.Deferred}
207        """
208        dl = [handler.getDiscoItems(requestor, target, nodeIdentifier)
209              for handler in self.parent
210              if IDisco.providedBy(handler)]
211        return self._gatherResults(dl)
Note: See TracBrowser for help on using the repository browser.