use sat.tmp.wokkel as a buffer module until the changes are integrated to wokkel

This commit is contained in:
souliane 2014-12-15 12:46:58 +01:00
commit 642e8adbf4
7 changed files with 2046 additions and 0 deletions

View file

@ -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
View 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
View file

View file

117
src/tmp/wokkel/delay.py Normal file
View 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

File diff suppressed because it is too large Load diff

349
src/tmp/wokkel/rsm.py Normal file
View 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