use sat.tmp.wokkel as a buffer module until the changes are integrated to wokkel
This commit is contained in:
parent
eaa0f83237
commit
642e8adbf4
7 changed files with 2046 additions and 0 deletions
|
|
@ -0,0 +1,6 @@
|
|||
import wokkel
|
||||
from sat.tmp.wokkel import delay, pubsub, rsm
|
||||
|
||||
wokkel.delay = delay
|
||||
wokkel.pubsub = pubsub
|
||||
wokkel.rsm = rsm
|
||||
6
src/tmp/README
Normal file
6
src/tmp/README
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
Use this module to temporary store files that need to be integrated to other
|
||||
projects such as Wokkel.
|
||||
|
||||
For example, the changeset that introduced this folder adds RSM (XEP-0059)
|
||||
support to Wokkel. The files in sat.tmp.wokkel are imported over the initial
|
||||
wokkel files from sat.plugins.__init__.py
|
||||
0
src/tmp/__init__.py
Normal file
0
src/tmp/__init__.py
Normal file
0
src/tmp/wokkel/__init__.py
Normal file
0
src/tmp/wokkel/__init__.py
Normal file
117
src/tmp/wokkel/delay.py
Normal file
117
src/tmp/wokkel/delay.py
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
# -*- test-case-name: wokkel.test.test_delay -*-
|
||||
#
|
||||
# Copyright (c) Ralph Meijer.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Delayed Delivery.
|
||||
|
||||
Support for comunicating Delayed Delivery information as specified by
|
||||
U{XEP-0203<http://xmpp.org/extensions/xep-0203.html>} and its predecessor
|
||||
U{XEP-0091<http://xmpp.org/extensions/xep-0091.html>}.
|
||||
"""
|
||||
|
||||
from dateutil.parser import parse
|
||||
from dateutil.tz import tzutc
|
||||
|
||||
from twisted.words.protocols.jabber.jid import InvalidFormat, JID
|
||||
from twisted.words.xish import domish
|
||||
|
||||
NS_DELAY = 'urn:xmpp:delay'
|
||||
NS_JABBER_DELAY = 'jabber:x:delay'
|
||||
|
||||
class Delay(object):
|
||||
"""
|
||||
Delayed Delivery information.
|
||||
|
||||
Instances of this class represent delayed delivery information that can be
|
||||
parsed from and rendered into both XEP-0203 and legacy XEP-0091 formats.
|
||||
|
||||
@ivar stamp: The timestamp the stanza was originally sent.
|
||||
@type stamp: L{datetime.datetime}
|
||||
@ivar sender: The optional entity that originally sent the stanza or
|
||||
delayed its delivery.
|
||||
@type sender: L{JID}
|
||||
"""
|
||||
|
||||
def __init__(self, stamp, sender=None):
|
||||
self.stamp = stamp
|
||||
self.sender = sender
|
||||
|
||||
|
||||
def toElement(self, legacy=False):
|
||||
"""
|
||||
Render this instance into a domish Element.
|
||||
|
||||
@param legacy: If C{True}, use the legacy XEP-0091 format.
|
||||
@type legacy: C{bool}
|
||||
"""
|
||||
if not self.stamp:
|
||||
raise ValueError("stamp is required")
|
||||
if self.stamp.tzinfo is None:
|
||||
raise ValueError("stamp is not offset-aware")
|
||||
|
||||
if legacy:
|
||||
element = domish.Element((NS_JABBER_DELAY, 'x'))
|
||||
stampFormat = '%Y%m%dT%H:%M:%S'
|
||||
else:
|
||||
element = domish.Element((NS_DELAY, 'delay'))
|
||||
stampFormat = '%Y-%m-%dT%H:%M:%SZ'
|
||||
|
||||
stamp = self.stamp.astimezone(tzutc())
|
||||
element['stamp'] = stamp.strftime(stampFormat)
|
||||
|
||||
if self.sender:
|
||||
element['from'] = self.sender.full()
|
||||
|
||||
return element
|
||||
|
||||
|
||||
@staticmethod
|
||||
def fromElement(element):
|
||||
"""
|
||||
Create an instance from a domish Element.
|
||||
"""
|
||||
try:
|
||||
stamp = parse(element[u'stamp'])
|
||||
|
||||
# Assume UTC if no timezone was given
|
||||
if stamp.tzinfo is None:
|
||||
stamp = stamp.replace(tzinfo=tzutc())
|
||||
except (KeyError, ValueError, TypeError):
|
||||
stamp = None
|
||||
|
||||
try:
|
||||
sender = JID(element[u'from'])
|
||||
except (KeyError, InvalidFormat):
|
||||
sender = None
|
||||
|
||||
delay = Delay(stamp, sender)
|
||||
return delay
|
||||
|
||||
|
||||
|
||||
class DelayMixin(object):
|
||||
"""
|
||||
Mixin for parsing delayed delivery information from stanzas.
|
||||
|
||||
This can be used as a mixin for subclasses of L{wokkel.generic.Stanza}
|
||||
for parsing delayed delivery information. If both XEP-0203 and XEP-0091
|
||||
formats are present, the former takes precedence.
|
||||
"""
|
||||
|
||||
delay = None
|
||||
|
||||
childParsers = {
|
||||
(NS_DELAY, 'delay'): '_childParser_delay',
|
||||
(NS_JABBER_DELAY, 'x'): '_childParser_legacyDelay',
|
||||
}
|
||||
|
||||
|
||||
def _childParser_delay(self, element):
|
||||
self.delay = Delay.fromElement(element)
|
||||
|
||||
|
||||
def _childParser_legacyDelay(self, element):
|
||||
if not self.delay:
|
||||
self.delay = Delay.fromElement(element)
|
||||
1568
src/tmp/wokkel/pubsub.py
Executable file
1568
src/tmp/wokkel/pubsub.py
Executable file
File diff suppressed because it is too large
Load diff
349
src/tmp/wokkel/rsm.py
Normal file
349
src/tmp/wokkel/rsm.py
Normal file
|
|
@ -0,0 +1,349 @@
|
|||
# -*- test-case-name: wokkel.test.test_rsm -*-
|
||||
#
|
||||
# Copyright (c) Adrien Cossa.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
XMPP Result Set Management protocol.
|
||||
|
||||
This protocol is specified in
|
||||
U{XEP-0059<http://xmpp.org/extensions/xep-0059.html>}.
|
||||
"""
|
||||
|
||||
from twisted.python import log
|
||||
from twisted.words.xish import domish
|
||||
|
||||
import pubsub
|
||||
import copy
|
||||
|
||||
|
||||
# RSM namespace
|
||||
NS_RSM = 'http://jabber.org/protocol/rsm'
|
||||
|
||||
|
||||
class RSMRequest():
|
||||
"""
|
||||
A Result Set Management request.
|
||||
|
||||
@ivar max: limit on the number of retrieved items.
|
||||
@itype max: C{int} or C{unicode}
|
||||
|
||||
@ivar index: starting index of the requested page.
|
||||
@itype index: C{int} or C{unicode}
|
||||
|
||||
@ivar after: ID of the element immediately preceding the page.
|
||||
@itype after: C{unicode}
|
||||
|
||||
@ivar before: ID of the element immediately following the page.
|
||||
@itype before: C{unicode}
|
||||
"""
|
||||
|
||||
max = 10
|
||||
index = None
|
||||
after = None
|
||||
before = None
|
||||
|
||||
def __init__(self, max=None, index=None, after=None, before=None):
|
||||
if max is not None:
|
||||
max = int(max)
|
||||
assert(max >= 0)
|
||||
self.max = max
|
||||
|
||||
if index is not None:
|
||||
assert(after is None and before is None)
|
||||
index = int(index)
|
||||
assert(index >= 0)
|
||||
self.index = index
|
||||
|
||||
if after is not None:
|
||||
assert(before is None)
|
||||
assert(isinstance(after, unicode))
|
||||
self.after = after
|
||||
|
||||
if before is not None:
|
||||
assert(isinstance(before, unicode))
|
||||
self.before = before
|
||||
|
||||
@classmethod
|
||||
def parse(cls, element):
|
||||
"""Parse the given request element.
|
||||
|
||||
@param element: request containing a set element.
|
||||
@type element: L{domish.Element}
|
||||
|
||||
@return: RSMRequest instance.
|
||||
@rtype: L{RSMRequest}
|
||||
"""
|
||||
try:
|
||||
set_elt = domish.generateElementsQNamed(element.elements(),
|
||||
name="set",
|
||||
uri=NS_RSM).next()
|
||||
except StopIteration:
|
||||
return None
|
||||
|
||||
request = RSMRequest()
|
||||
for elt in list(set_elt.elements()):
|
||||
if elt.name in ('before', 'after'):
|
||||
setattr(request, elt.name, ''.join(elt.children))
|
||||
elif elt.name in ('max', 'index'):
|
||||
setattr(request, elt.name, int(''.join(elt.children)))
|
||||
|
||||
if request.max is None:
|
||||
log.err("RSM request is missing its 'max' element!")
|
||||
|
||||
return request
|
||||
|
||||
def render(self, element=None):
|
||||
"""Render a RSM page request, eventually embed it in the given element.
|
||||
|
||||
@param element: request element.
|
||||
@type element: L{domish.Element}
|
||||
|
||||
@return: RSM request element.
|
||||
@rtype: L{domish.Element}
|
||||
"""
|
||||
if element and element.name == 'pubsub' and hasattr(element, 'items'):
|
||||
element.items.attributes['max_items'] = unicode(self.max)
|
||||
|
||||
set_elt = domish.Element((NS_RSM, 'set'))
|
||||
set_elt.addElement('max').addContent(unicode(self.max))
|
||||
|
||||
if self.index is not None:
|
||||
set_elt.addElement('index').addContent(unicode(self.index))
|
||||
|
||||
if self.before is not None:
|
||||
if self.before == '': # request the last page
|
||||
set_elt.addElement('before')
|
||||
else:
|
||||
set_elt.addElement('before').addContent(self.before)
|
||||
|
||||
if self.after is not None:
|
||||
set_elt.addElement('after').addContent(self.after)
|
||||
|
||||
if element:
|
||||
element.addChild(set_elt)
|
||||
|
||||
return set_elt
|
||||
|
||||
|
||||
class RSMResponse():
|
||||
"""
|
||||
A Result Set Management response.
|
||||
|
||||
@ivar count: total number of items.
|
||||
@itype count: C{int}
|
||||
|
||||
@ivar index: starting index of the returned page.
|
||||
@itype index: C{int}
|
||||
|
||||
@ivar first: ID of the first element of the returned page.
|
||||
@itype first: C{unicode}
|
||||
|
||||
@ivar last: ID of the last element of the returned page.
|
||||
@itype last: C{unicode}
|
||||
"""
|
||||
|
||||
count = 0
|
||||
index = None
|
||||
first = None
|
||||
last = None
|
||||
|
||||
def __init__(self, count=None, index=None, first=None, last=None):
|
||||
if count is not None:
|
||||
assert(isinstance(count, int) and count >= 0)
|
||||
self.count = count
|
||||
|
||||
if index is not None:
|
||||
assert(isinstance(index, int) and index >= 0)
|
||||
self.index = index
|
||||
assert(isinstance(first, unicode))
|
||||
self.first = first
|
||||
assert(isinstance(last, unicode))
|
||||
self.last = last
|
||||
else:
|
||||
assert(first is None and last is None)
|
||||
|
||||
@classmethod
|
||||
def parse(cls, element):
|
||||
"""Parse the given response element.
|
||||
|
||||
@param element: response element.
|
||||
@type element: L{domish.Element}
|
||||
|
||||
@return: RSMResponse instance.
|
||||
@rtype: L{RSMResponse}
|
||||
"""
|
||||
try:
|
||||
set_elt = domish.generateElementsQNamed(element.elements(),
|
||||
name="set",
|
||||
uri=NS_RSM).next()
|
||||
except StopIteration:
|
||||
return None
|
||||
|
||||
response = RSMResponse()
|
||||
for elt in list(set_elt.elements()):
|
||||
if elt.name in ('first', 'last'):
|
||||
setattr(response, elt.name, ''.join(elt.children))
|
||||
if elt.name == 'first':
|
||||
response.index = int(elt.getAttribute("index"))
|
||||
elif elt.name == 'count':
|
||||
response.count = int(''.join(elt.children))
|
||||
|
||||
if response.count is None:
|
||||
log.err("RSM response is missing its 'count' element!")
|
||||
|
||||
return response
|
||||
|
||||
def render(self, parent=None):
|
||||
"""Render a RSM page response, eventually embed it in the given element.
|
||||
|
||||
@param element: response element.
|
||||
@type element: L{domish.Element}
|
||||
|
||||
@return: RSM request element.
|
||||
@rtype: L{domish.Element}
|
||||
"""
|
||||
set_elt = domish.Element((NS_RSM, 'set'))
|
||||
set_elt.addElement('count').addContent(unicode(self.count))
|
||||
|
||||
if self.index is not None:
|
||||
first_elt = set_elt.addElement('first')
|
||||
first_elt.addContent(self.first)
|
||||
first_elt['index'] = unicode(self.index)
|
||||
|
||||
set_elt.addElement('last').addContent(self.last)
|
||||
|
||||
if parent:
|
||||
parent.addChild(set_elt)
|
||||
|
||||
return set_elt
|
||||
|
||||
def toDict(self):
|
||||
"""Return a dict representation of the object.
|
||||
|
||||
@return: a dict of strings.
|
||||
@rtype: C{dict} binding C{unicode} to C{unicode}
|
||||
"""
|
||||
result = {}
|
||||
for attr in ('count', 'index', 'first', 'last'):
|
||||
value = getattr(self, attr)
|
||||
if value is not None:
|
||||
result[attr] = unicode(value)
|
||||
return result
|
||||
|
||||
|
||||
class PubSubRequest(pubsub.PubSubRequest):
|
||||
"""PubSubRequest extension to handle RSM.
|
||||
|
||||
@ivar rsm: RSM request instance.
|
||||
@type rsm: L{RSMRequest}
|
||||
"""
|
||||
|
||||
rsm = None
|
||||
|
||||
def __init__(self, verb=None):
|
||||
pubsub.PubSubRequest.__init__(self, verb)
|
||||
self._parameters = copy.deepcopy(pubsub.PubSubRequest._parameters)
|
||||
self._parameters['items'].append('rsm')
|
||||
|
||||
def _parse_rsm(self, verbElement):
|
||||
self.rsm = RSMRequest.parse(verbElement.parent)
|
||||
|
||||
def _render_rsm(self, verbElement):
|
||||
if self.rsm:
|
||||
self.rsm.render(verbElement.parent)
|
||||
|
||||
|
||||
class PubSubClient(pubsub.PubSubClient):
|
||||
"""PubSubClient extension to handle RSM."""
|
||||
|
||||
_rsm_responses = {}
|
||||
|
||||
def items(self, service, nodeIdentifier, maxItems=None, itemIdentifiers=None,
|
||||
subscriptionIdentifier=None, sender=None, ext_data=None):
|
||||
"""
|
||||
Retrieve previously published items from a publish subscribe node.
|
||||
|
||||
@param service: The publish subscribe service that keeps the node.
|
||||
@type service: L{JID<twisted.words.protocols.jabber.jid.JID>}
|
||||
|
||||
@param nodeIdentifier: The identifier of the node.
|
||||
@type nodeIdentifier: C{unicode}
|
||||
|
||||
@param maxItems: Optional limit on the number of retrieved items.
|
||||
@type maxItems: C{int}
|
||||
|
||||
@param itemIdentifiers: Identifiers of the items to be retrieved.
|
||||
@type itemIdentifiers: C{set}
|
||||
|
||||
@param subscriptionIdentifier: Optional subscription identifier. In
|
||||
case the node has been subscribed to multiple times, this narrows
|
||||
the results to the specific subscription.
|
||||
@type subscriptionIdentifier: C{unicode}
|
||||
|
||||
@param ext_data: extension data.
|
||||
@type ext_data: L{dict}
|
||||
|
||||
@return: a Deferred that fires a C{list} of L{domish.Element}.
|
||||
@rtype: L{defer.Deferred}
|
||||
"""
|
||||
request = PubSubRequest('items') # that's a rsm.PubSubRequest instance
|
||||
request.recipient = service
|
||||
request.nodeIdentifier = nodeIdentifier
|
||||
if maxItems:
|
||||
request.maxItems = str(int(maxItems))
|
||||
request.subscriptionIdentifier = subscriptionIdentifier
|
||||
request.sender = sender
|
||||
request.itemIdentifiers = itemIdentifiers
|
||||
if ext_data and 'rsm' in ext_data:
|
||||
request.rsm = ext_data['rsm']
|
||||
|
||||
def cb(iq):
|
||||
items = []
|
||||
if iq.pubsub.items:
|
||||
for element in iq.pubsub.items.elements():
|
||||
if element.uri == pubsub.NS_PUBSUB and element.name == 'item':
|
||||
items.append(element)
|
||||
|
||||
if request.rsm:
|
||||
response = RSMResponse.parse(iq.pubsub)
|
||||
if response is not None:
|
||||
self._rsm_responses[ext_data['id']] = response
|
||||
return items
|
||||
|
||||
d = request.send(self.xmlstream)
|
||||
d.addCallback(cb)
|
||||
return d
|
||||
|
||||
def getRSMResponse(self, id):
|
||||
"""
|
||||
Post-retrieve the RSM response data after items retrieval is done.
|
||||
|
||||
@param id: extension data ID
|
||||
@type id: C{unicode}
|
||||
|
||||
@return: dict representation of the RSM response.
|
||||
@rtype: C{dict} of C{unicode}
|
||||
"""
|
||||
# This method exists to not modify the return value of self.items.
|
||||
if id not in self._rsm_responses:
|
||||
return {}
|
||||
result = self._rsm_responses[id].toDict()
|
||||
del self._rsm_responses[id]
|
||||
return result
|
||||
|
||||
|
||||
class PubSubService(pubsub.PubSubService):
|
||||
"""PubSubService extension to handle RSM."""
|
||||
|
||||
_request_class = PubSubRequest
|
||||
|
||||
def _toResponse_items(self, result, resource, request):
|
||||
response = pubsub.PubSubService._toResponse_items(self, result,
|
||||
resource, request)
|
||||
set_elts = [elt for elt in result if elt.name == 'set']
|
||||
if set_elts:
|
||||
assert(len(set_elts) == 1)
|
||||
response.addChild(set_elts[0])
|
||||
|
||||
return response
|
||||
Loading…
Reference in a new issue