changeset 24:0640d72d6841

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
author Goffi <goffi@goffi.org>
date Tue, 05 Jan 2016 23:20:22 +0100
parents 777b4e63fc8a
children 4816f7d55367
files wokkel/mam.py
diffstat 1 files changed, 84 insertions(+), 82 deletions(-) [+]
line wrap: on
line diff
--- 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<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 @@
 
 # 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 <query/> 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 <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 @@
         return mam_elt
 
 
-class MAMPrefs():
+class MAMPrefs(object):
     """
     A Message Archive Management <prefs/> 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 <prefs/> request.
 
         @param prefs_elt: MAM <prefs/> 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<twisted.words.protocols.jabber.jid.JID>}
-
-        @param form: Data Form to filter the request.
-        @type form: L{Form<wokkel.data_form.Form>}
+        @param mam_query: query to use
+        @type form: L{MAMRequest}
 
-        @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 service: Entity offering the MAM service (None for user server).
+        @type service: L{JID<twisted.words.protocols.jabber.jid.JID>}
 
         @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 @@
         @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 @@
         @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 @@
         """
         # 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 <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 @@
         @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 @@
 
         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<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 @@
         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>}
     """