Changeset 2:47f1cb624f14


Ignore:
Timestamp:
Aug 22, 2007, 3:43:43 PM (13 years ago)
Author:
Ralph Meijer <ralphm@…>
Branch:
default
Convert:
svn:b33ecbfc-034c-dc11-8662-000475d9059e/trunk@3
Message:

Add in pubsub client support, client helpers and generic XMPP subprotocol handlers.

Location:
wokkel
Files:
2 added
3 edited

Legend:

Unmodified
Added
Removed
  • wokkel/generic.py

    r1 r2  
    99
    1010from twisted.internet import defer
    11 from twisted.words.protocols.jabber import error, xmlstream
     11from twisted.words.protocols.jabber import error
    1212
    1313from wokkel import disco
  • wokkel/iwokkel.py

    r1 r2  
    8989        @type nodeIdentifier: C{unicode}
    9090        """
     91
     92
     93class IPubSubClient(Interface):
     94
     95    def itemsReceived(notifier, node, items):
     96        """
     97        Called when items have been received from a node.
     98
     99        @param notifier: the entity from which the notification was received.
     100        @type notifier: L{jid.JID}
     101        @param node: identifier of the node the items belong to.
     102        @type node: C{unicode}
     103        @param items: list of received items as domish elements.
     104        @type items: C{list} of L{domish.Element}
     105        """
     106
     107    def createNode(node=None):
     108        """
     109        Create a new publish subscribe node.
     110
     111        @param node: optional suggestion for the new node's identifier. If
     112                     omitted, the creation of an instant node will be
     113                     attempted.
     114        @type node: L{unicode}
     115        @return: a deferred that fires with the identifier of the newly created
     116                 node. Note that this can differ from the suggested identifier
     117                 if the publish subscribe service chooses to modify or ignore
     118                 the suggested identifier.
     119        @rtype: L{defer.Deferred}
     120        """
     121
     122    def deleteNode(node):
     123        """
     124        Delete a node.
     125
     126        @param node: identifier of the node to be deleted.
     127        @type node: L{unicode}
     128        @rtype: L{defer.Deferred}
     129        """
     130
     131    def subscribe(node, subscriber):
     132        """
     133        Subscribe to a node with a given JID.
     134
     135        @param node: identifier of the node to subscribe to.
     136        @type node: L{unicode}
     137        @param subscriber: JID to subscribe to the node.
     138        @type subscriber: L{jid.JID}
     139        @rtype: L{defer.Deferred}
     140        """
     141
     142    def publish(requestor, node, items=[]):
     143        """
     144        Publish to a node.
     145
     146        Node that the C{items} parameter is optional, because so-called
     147        transient, notification-only nodes do not use items and publish
     148        actions only signify a change in some resource.
     149
     150        @param node: identifier of the node to publish to.
     151        @type node: L{unicode}
     152        @param items: list of item elements.
     153        @type items: L{list} of L{Item}
     154        @rtype: L{defer.Deferred}
     155        """
     156
    91157
    92158class IPubSubService(Interface):
  • wokkel/pubsub.py

    r1 r2  
    1414
    1515from twisted.internet import defer
    16 from twisted.words.protocols.jabber import jid, error
     16from twisted.words.protocols.jabber import jid, error, xmlstream
    1717from twisted.words.xish import domish
    1818
    1919from wokkel import disco, data_form
    2020from wokkel.subprotocols import IQHandlerMixin, XMPPHandler
    21 from wokkel.iwokkel import IPubSubService
     21from wokkel.iwokkel import IPubSubClient, IPubSubService
    2222
    2323# Iq get and set XPath queries
     
    7171
    7272class BadRequest(error.StanzaError):
     73    """
     74    Bad request stanza error.
     75    """
    7376    def __init__(self):
    7477        error.StanzaError.__init__(self, 'bad-request')
    7578
     79
     80
     81class SubscriptionPending(Exception):
     82    """
     83    Raised when the requested subscription is pending acceptance.
     84    """
     85
     86
     87class SubscriptionUnconfigured(Exception):
     88    """
     89    Raised when the requested subscription needs to be configured before
     90    becoming active.
     91    """
     92
     93
    7694class PubSubError(error.StanzaError):
     95    """
     96    Exception with publish-subscribe specific condition.
     97    """
    7798    def __init__(self, condition, pubsubCondition, feature=None, text=None):
    7899        appCondition = domish.Element((NS_PUBSUB_ERRORS, pubsubCondition))
     
    83104                                         appCondition=appCondition)
    84105
     106
    85107class Unsupported(PubSubError):
    86108    def __init__(self, feature, text=None):
     
    90112                                   text)
    91113
     114
    92115class OptionsUnavailable(Unsupported):
    93116    def __init__(self):
    94117        Unsupported.__init__(self, 'subscription-options-unavailable')
     118
     119
     120class Item(domish.Element):
     121    """
     122    Publish subscribe item.
     123
     124    This behaves like an object providing L{domish.IElement}.
     125
     126    Item payload can be added using C{addChild} or C{addRawXml}, or using the
     127    C{payload} keyword argument to C{__init__}.
     128    """
     129
     130    def __init__(self, id=None, payload=None):
     131        """
     132        @param id: optional item identifier
     133        @type id: L{unicode}
     134        @param payload: optional item payload. Either as a domish element, or
     135                        as serialized XML.
     136        @type payload: object providing L{domish.IElement} or L{unicode}.
     137        """
     138
     139        domish.Element.__init__(self, (None, 'item'))
     140        if id is not None:
     141            self['id'] = id
     142        if payload is not None:
     143            if isinstance(payload, basestring):
     144                self.addRawXml(payload)
     145            else:
     146                self.addChild(payload)
     147
     148class PubSubRequest(xmlstream.IQ):
     149    """
     150    Base class for publish subscribe user requests.
     151
     152    @cvar namespace: request namespace
     153    @cvar verb: request verb
     154    @cvar method: type attribute of the IQ request. Either C{'set'} or C{'get'}
     155    @ivar command: command element of the request. This is the direct child of
     156                   the C{pubsub} element in the C{namespace} with the name
     157                   C{verb}.
     158    """
     159
     160    namespace = NS_PUBSUB
     161    method = 'set'
     162
     163    def __init__(self, xs):
     164        xmlstream.IQ.__init__(self, xs, self.method)
     165        self.addElement((self.namespace, 'pubsub'))
     166
     167        self.command = self.pubsub.addElement(self.verb)
     168
     169    def send(self, to):
     170        destination = unicode(to)
     171        return xmlstream.IQ.send(self, destination)
     172
     173class CreateNode(PubSubRequest):
     174    verb = 'create'
     175
     176    def __init__(self, xs, node=None):
     177        PubSubRequest.__init__(self, xs)
     178        if node:
     179            self.command["node"] = node
     180
     181class DeleteNode(PubSubRequest):
     182    verb = 'delete'
     183    def __init__(self, xs, node):
     184        PubSubRequest.__init__(self, xs)
     185        self.command["node"] = node
     186
     187class Subscribe(PubSubRequest):
     188    verb = 'subscribe'
     189
     190    def __init__(self, xs, node, subscriber):
     191        PubSubRequest.__init__(self, xs)
     192        self.command["node"] = node
     193        self.command["jid"] = subscriber.full()
     194
     195class Publish(PubSubRequest):
     196    verb = 'publish'
     197
     198    def __init__(self, xs, node):
     199        PubSubRequest.__init__(self, xs)
     200        self.command["node"] = node
     201
     202    def addItem(self, id=None, payload=None):
     203        item = self.command.addElement("item")
     204        item.addChild(payload)
     205
     206        if id is not None:
     207            item["id"] = id
     208
     209        return item
     210
     211class PubSubClient(XMPPHandler):
     212    """
     213    Publish subscribe client protocol.
     214    """
     215
     216    implements(IPubSubClient)
     217
     218    def connectionInitialized(self):
     219        self.xmlstream.addObserver('/message/event[@xmlns="%s"]/items' %
     220                                   NS_PUBSUB_EVENT, self._onItems)
     221
     222    def _onItems(self, message):
     223        try:
     224            notifier = jid.JID(message["from"])
     225            node = message.event.items["node"]
     226        except KeyError:
     227            return
     228
     229        items = [element for element in message.event.items.elements()
     230                         if element.name == 'item']
     231
     232        self.itemsReceived(notifier, node, items)
     233
     234    def itemsReceived(self, notifier, node, items):
     235        pass
     236
     237    def createNode(self, service, node=None):
     238        request = CreateNode(self.xmlstream, node)
     239
     240        def cb(iq):
     241            try:
     242                new_node = iq.pubsub.create["node"]
     243            except AttributeError:
     244                # the suggested node identifier was accepted
     245                new_node = node
     246            return new_node
     247
     248        return request.send(service).addCallback(cb)
     249
     250    def deleteNode(self, service, node):
     251        return DeleteNode(self.xmlstream, node).send(service)
     252
     253    def subscribe(self, service, node, subscriber):
     254        request = Subscribe(self.xmlstream, node, subscriber)
     255
     256        def cb(iq):
     257            subscription = iq.pubsub.subscription["subscription"]
     258
     259            if subscription == 'pending':
     260                raise SubscriptionPending
     261            elif subscription == 'unconfigured':
     262                raise SubscriptionUnconfigured
     263            else:
     264                # we assume subscription == 'subscribed'
     265                # any other value would be invalid, but that should have
     266                # yielded a stanza error.
     267                return None
     268
     269        return request.send(service).addCallback(cb)
     270
     271    def publish(self, service, node, items=[]):
     272        request = Publish(self.xmlstream, node)
     273        for item in items:
     274            request.command.addChild(item)
     275
     276        return request.send(service)
    95277
    96278class PubSubService(XMPPHandler, IQHandlerMixin):
     
    215397
    216398        items = []
    217         for child in iq.pubsub.publish.children:
    218             if child.__class__ == domish.Element and child.name == 'item':
    219                 items.append(child)
     399        for element in iq.pubsub.publish.elements():
     400            if element.uri == NS_PUBSUB and element.name == 'item':
     401                items.append(element)
    220402
    221403        return self.publish(requestor, nodeIdentifier, items)
Note: See TracChangeset for help on using the changeset viewer.