tmp (wokkel.mam): cleaning and bug fix:

- renamed MAMQueryRequest to MAMQuery
    - use of MAMQuery in queryArchive instead of separate arguments
    - fixed bad use of subprotocols.IQHandlerMixing.handleRequest
    - changed order of arguments in BuildForm to have more often used first
    - renamed parse to fromElement for coherency
    - added IMAMService interface
    - MAMService requestClass can be changed (it is specified in _request_class class attribute
    - use new-style classes where it make sense
    - MAMRequest.fromElement parse a <query/> parent instead of a <query/> directly
This commit is contained in:
Goffi 2016-01-05 23:20:22 +01:00
commit 1f10ddb274

View file

@ -25,18 +25,21 @@ This protocol is specified in
U{XEP-0313<http://xmpp.org/extensions/xep-0313.html>}.
"""
from dateutil.tz import tzutc
from dateutil import tz
from zope.interface import Interface, implements
from zope.interface import implements
from zope.interface import Interface
from twisted.words.protocols.jabber.xmlstream import IQ, toResponse
from twisted.words.protocols.jabber import xmlstream
from twisted.words.xish import domish
from twisted.words.protocols.jabber import jid
from twisted.internet import defer
from twisted.python import log
from wokkel.subprotocols import IQHandlerMixin, XMPPHandler
from wokkel import disco, data_form, delay
from wokkel import subprotocols
from wokkel import disco
from wokkel import data_form
from wokkel import delay
import rsm
@ -50,13 +53,11 @@ PREFS_SET_REQUEST = "/iq[@type='set']/prefs[@xmlns='%s']" % NS_MAM
# TODO: add the tests!
class MAMError(Exception):
"""
MAM error.
"""
class Unsupported(MAMError):
def __init__(self, feature, text=None):
self.feature = feature
@ -71,7 +72,7 @@ class Unsupported(MAMError):
return message
class MAMQueryRequest():
class MAMRequest(object):
"""
A Message Archive Management <query/> request.
@ -98,25 +99,27 @@ class MAMQueryRequest():
self.query_id = query_id
@classmethod
def parse(cls, element):
def fromElement(cls, element):
"""Parse the DOM representation of a MAM <query/> request.
@param element: MAM <query/> request element.
@param element: element containing a MAM <query/>.
@type element: L{Element<twisted.words.xish.domish.Element>}
@return: MAMQueryRequest instance.
@rtype: L{MAMQueryRequest}
@return: MAMRequest instance.
@rtype: L{MAMRequest}
"""
if element.uri != NS_MAM or element.name != 'query':
raise MAMError('Element provided is not a MAM <query/> request')
form = data_form.findForm(element, NS_MAM)
try:
rsm_request = rsm.RSMRequest.parse(element)
query = element.elements(NS_MAM, 'query').next()
except StopIteration:
raise MAMError("Can't find MAM <query/> in element")
form = data_form.findForm(query, NS_MAM)
try:
rsm_request = rsm.RSMRequest.fromElement(query)
except rsm.RSMNotFoundError:
rsm_request = None
node = element.getAttribute('node')
query_id = element.getAttribute('queryid')
return MAMQueryRequest(form, rsm_request, node, query_id)
node = query.getAttribute('node')
query_id = query.getAttribute('queryid')
return MAMRequest(form, rsm_request, node, query_id)
def toElement(self):
"""
@ -151,7 +154,7 @@ class MAMQueryRequest():
return mam_elt
class MAMPrefs():
class MAMPrefs(object):
"""
A Message Archive Management <prefs/> request.
@ -180,7 +183,7 @@ class MAMPrefs():
self.never = never
@classmethod
def parse(cls, prefs_elt):
def fromElement(cls, prefs_elt):
"""Parse the DOM representation of a MAM <prefs/> request.
@param prefs_elt: MAM <prefs/> request element.
@ -241,45 +244,36 @@ class MAMPrefs():
return mam_elt
class MAMClient(XMPPHandler):
class MAMClient(subprotocols.XMPPHandler):
"""
MAM client.
This handler implements the protocol for sending out MAM requests.
"""
def queryArchive(self, service=None, form=None, rsm=None, node=None, sender=None, query_id=None):
def queryArchive(self, mam_query, service=None, sender=None):
"""Query a user, MUC or pubsub archive.
@param service: Entity offering the MAM service (None for user archives).
@param mam_query: query to use
@type form: L{MAMRequest}
@param service: Entity offering the MAM service (None for user server).
@type service: L{JID<twisted.words.protocols.jabber.jid.JID>}
@param form: Data Form to filter the request.
@type form: L{Form<wokkel.data_form.Form>}
@param rsm: RSM request instance.
@type rsm: L{RSMRequest<wokkel.rsm.RSMRequest>}
@param node: Pubsub node to query, or None if inappropriate.
@type node: C{unicode}
@param sender: Optional sender address.
@type sender: L{JID<twisted.words.protocols.jabber.jid.JID>}
@param query_id: Optional query id
@type query_id: C{unicode}
@return: A deferred that fires upon receiving a response.
@rtype: L{Deferred<twisted.internet.defer.Deferred>}
"""
iq = IQ(self.xmlstream, 'set')
MAMQueryRequest(form, rsm, node, query_id).render(iq)
iq = xmlstream.IQ(self.xmlstream, 'set')
mam_query.render(iq)
if sender is not None:
iq['from'] = unicode(sender)
return iq.send(to=service.full() if service else None)
def queryFields(self, service=None, sender=None):
"""Ask the server about additional supported fields.
"""Ask the server about supported fields.
@param service: Entity offering the MAM service (None for user archives).
@type service: L{JID<twisted.words.protocols.jabber.jid.JID>}
@ -291,8 +285,8 @@ class MAMClient(XMPPHandler):
@rtype: L{Deferred<twisted.internet.defer.Deferred>}
"""
# http://xmpp.org/extensions/xep-0313.html#query-form
iq = IQ(self.xmlstream, 'get')
MAMQueryRequest().render(iq)
iq = xmlstream.IQ(self.xmlstream, 'get')
MAMRequest().render(iq)
if sender is not None:
iq['from'] = unicode(sender)
d = iq.send(to=service.full() if service else None)
@ -313,7 +307,7 @@ class MAMClient(XMPPHandler):
@rtype: L{Deferred<twisted.internet.defer.Deferred>}
"""
# http://xmpp.org/extensions/xep-0313.html#prefs
iq = IQ(self.xmlstream, 'get')
iq = xmlstream.IQ(self.xmlstream, 'get')
MAMPrefs().render(iq)
if sender is not None:
iq['from'] = unicode(sender)
@ -342,7 +336,7 @@ class MAMClient(XMPPHandler):
"""
# http://xmpp.org/extensions/xep-0313.html#prefs
assert default is not None
iq = IQ(self.xmlstream, 'set')
iq = xmlstream.IQ(self.xmlstream, 'set')
MAMPrefs(default, always, never).render(iq)
if sender is not None:
iq['from'] = unicode(sender)
@ -355,7 +349,7 @@ class IMAMResource(Interface):
"""
@param mam: The MAM <query/> request.
@type mam: L{MAMQueryReques<wokkel.mam.MAMQueryRequest>}
@type mam: L{MAMQueryReques<wokkel.mam.MAMRequest>}
@param requestor: JID of the requestor.
@type requestor: L{JID<twisted.words.protocols.jabber.jid.JID>}
@ -387,15 +381,29 @@ class IMAMResource(Interface):
@rtype: L{wokkel.mam.MAMPrefs}
"""
class IMAMService(Interface):
"""
Interface for XMPP MAM service.
"""
class MAMService(XMPPHandler, IQHandlerMixin):
def addFilter(self, field):
"""
Add a new filter for querying MAM archive.
@param field: data form field of the filter
@type field: L{Form<wokkel.data_form.Field>}
"""
class MAMService(subprotocols.XMPPHandler, subprotocols.IQHandlerMixin):
"""
Protocol implementation for a MAM service.
This handler waits for XMPP Ping requests and sends a response.
"""
implements(IMAMService, disco.IDisco)
implements(disco.IDisco)
_request_class = MAMRequest
iqHandlers = {FIELDS_REQUEST: '_onFieldsRequest',
ARCHIVE_REQUEST: '_onArchiveRequest',
@ -454,11 +462,10 @@ class MAMService(XMPPHandler, IQHandlerMixin):
This immediately replies with a result response.
"""
response = toResponse(iq, 'result')
query = response.addElement((NS_MAM, 'query'))
query.addChild(buildForm('form', extra=self.extra_fields).toElement())
self.xmlstream.send(response)
iq.handled = True
query = domish.Element((NS_MAM, 'query'))
query.addChild(buildForm(extra_fields=self.extra_fields).toElement(), formType='form')
return query
def _onArchiveRequest(self, iq):
"""
@ -468,7 +475,8 @@ class MAMService(XMPPHandler, IQHandlerMixin):
@return: A tuple with list of message data (id, element, data) and RSM element
@rtype: C{tuple}
"""
mam_ = MAMQueryRequest.parse(iq.query)
iq.handled = True
mam_ = self._request_class.fromElement(iq)
requestor = jid.JID(iq['from'])
# remove unsupported filters
@ -481,14 +489,12 @@ class MAMService(XMPPHandler, IQHandlerMixin):
for key in unsupported_fields:
del mam_.form.fields[key]
def forward_message(id_, elt, date):
def forwardMessage(id_, elt, date):
msg = domish.Element((None, 'message'))
msg['to'] = iq['from']
result = msg.addElement((NS_MAM, 'result'))
try:
result['queryid'] = iq.query['queryid']
except KeyError:
pass
if mam_.query_id is not None:
result['queryid'] = mam_.query_id
result['id'] = id_
forward = result.addElement((NS_FORWARD, 'forwarded'))
forward.addChild(delay.Delay(date).toElement())
@ -498,18 +504,17 @@ class MAMService(XMPPHandler, IQHandlerMixin):
def cb(result):
msg_data, rsm_elt = result
for data in msg_data:
forward_message(*data)
forwardMessage(*data)
response = toResponse(iq, 'result')
fin = response.addElement((NS_MAM, 'fin'))
fin_elt = domish.Element((NS_MAM, 'fin'))
if rsm_elt is not None:
fin.addChild(rsm_elt)
self.xmlstream.send(response)
fin_elt.addChild(rsm_elt)
return fin_elt
d = defer.maybeDeferred(self.resource.onArchiveRequest, mam_, requestor)
d.addCallback(cb)
iq.handled = True
return d
def _onPrefsGetRequest(self, iq):
"""
@ -517,16 +522,14 @@ class MAMService(XMPPHandler, IQHandlerMixin):
This immediately replies with a result response.
"""
response = toResponse(iq, 'result')
iq.handled = True
requestor = jid.JID(iq['from'])
def cb(prefs):
response.addChild(prefs.toElement())
self.xmlstream.send(response)
return prefs.toElement()
self.resource.onPrefsGetRequest(requestor).addCallback(cb)
iq.handled = True
d = self.resource.onPrefsGetRequest(requestor).addCallback(cb)
return d
def _onPrefsSetRequest(self, iq):
"""
@ -534,17 +537,16 @@ class MAMService(XMPPHandler, IQHandlerMixin):
This immediately replies with a result response.
"""
response = toResponse(iq, 'result')
iq.handled = True
prefs = MAMPrefs.parse(iq.prefs)
prefs = MAMPrefs.fromElement(iq.prefs)
requestor = jid.JID(iq['from'])
def cb(prefs):
response.addChild(prefs.toElement())
self.xmlstream.send(response)
return prefs.toElement()
self.resource.onPrefsSetRequest(prefs, requestor).addCallback(cb)
iq.handled = True
d = self.resource.onPrefsSetRequest(prefs, requestor).addCallback(cb)
return d
def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
if nodeIdentifier:
@ -555,25 +557,22 @@ class MAMService(XMPPHandler, IQHandlerMixin):
return []
def datetime2utc(datetime):
def datetime2utc(datetime_obj):
"""Convert a datetime to a XEP-0082 compliant UTC datetime.
@param datetime: Offset-aware timestamp to convert.
@type datetime: L{datetime<datetime.datetime>}
@param datetime_obj: Offset-aware timestamp to convert.
@type datetime_obj: L{datetime<datetime.datetime>}
@return: The datetime converted to UTC.
@rtype: C{unicode}
"""
stampFormat = '%Y-%m-%dT%H:%M:%SZ'
return datetime.astimezone(tzutc()).strftime(stampFormat)
return datetime_obj.astimezone(tz.tzutc()).strftime(stampFormat)
def buildForm(formType='submit', start=None, end=None, with_jid=None, extra_fields=None):
def buildForm(start=None, end=None, with_jid=None, extra_fields=None, formType='submit'):
"""Prepare a Data Form for MAM.
@param formType: The type of the Data Form ('submit' or 'form').
@type formType: C{unicode}
@param start: Offset-aware timestamp to filter out older messages.
@type start: L{datetime<datetime.datetime>}
@ -587,6 +586,9 @@ def buildForm(formType='submit', start=None, end=None, with_jid=None, extra_fiel
specification.
@type: C{list}
@param formType: The type of the Data Form ('submit' or 'form').
@type formType: C{unicode}
@return: XEP-0004 Data Form object.
@rtype: L{Form<wokkel.data_form.Form>}
"""