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:
parent
cdac9644db
commit
1f10ddb274
1 changed files with 84 additions and 82 deletions
|
|
@ -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>}
|
||||
"""
|
||||
|
|
|
|||
Loading…
Reference in a new issue