# HG changeset patch # User Goffi # Date 1452032422 -3600 # Node ID 0640d72d68412e7b1cb8b6fead31309854a727b0 # Parent 777b4e63fc8adde4a7df452ada47c960344027b6 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 parent instead of a directly diff -r 777b4e63fc8a -r 0640d72d6841 wokkel/mam.py --- a/wokkel/mam.py Tue Jan 05 23:20:22 2016 +0100 +++ b/wokkel/mam.py Tue Jan 05 23:20:22 2016 +0100 @@ -25,18 +25,21 @@ U{XEP-0313}. """ -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 @@ # 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 @@ return message -class MAMQueryRequest(): +class MAMRequest(object): """ A Message Archive Management request. @@ -98,25 +99,27 @@ self.query_id = query_id @classmethod - def parse(cls, element): + def fromElement(cls, element): """Parse the DOM representation of a MAM request. - @param element: MAM request element. + @param element: element containing a MAM . @type element: L{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 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 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 @@ return mam_elt -class MAMPrefs(): +class MAMPrefs(object): """ A Message Archive Management request. @@ -180,7 +183,7 @@ self.never = never @classmethod - def parse(cls, prefs_elt): + def fromElement(cls, prefs_elt): """Parse the DOM representation of a MAM request. @param prefs_elt: MAM request element. @@ -241,45 +244,36 @@ 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). - @type service: L{JID} - - @param form: Data Form to filter the request. - @type form: L{Form} + @param mam_query: query to use + @type form: L{MAMRequest} - @param rsm: RSM request instance. - @type rsm: L{RSMRequest} - - @param node: Pubsub node to query, or None if inappropriate. - @type node: C{unicode} + @param service: Entity offering the MAM service (None for user server). + @type service: L{JID} @param sender: Optional sender address. @type sender: L{JID} - @param query_id: Optional query id - @type query_id: C{unicode} - @return: A deferred that fires upon receiving a response. @rtype: L{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} @@ -291,8 +285,8 @@ @rtype: L{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 @@ @rtype: L{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 @@ """ # 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 @@ """ @param mam: The MAM request. - @type mam: L{MAMQueryReques} + @type mam: L{MAMQueryReques} @param requestor: JID of the requestor. @type requestor: L{JID} @@ -387,15 +381,29 @@ @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} + """ + + +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 @@ 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 @@ @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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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} + @param datetime_obj: Offset-aware timestamp to convert. + @type datetime_obj: L{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} @@ -587,6 +586,9 @@ 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} """