diff sat/plugins/plugin_exp_invitation.py @ 3462:12dc234f698c

plugin invitation: pubsub invitations: - new Pubsub invitation plugin, to have a generic way to manage invitation on Pubsub based features - `invitePreflight` and `onInvitationPreflight` method can be implemented to customise invitation for a namespace - refactored events invitations to use the new plugin - a Pubsub invitation can now be for a whole node instead of a specific item - if invitation is for a node, a namespace can be specified to indicate what this node is about. It is then added in `extra` data - an element (domish.Element) can be added in `extra` data, it will then be added in the invitation - some code modernisation
author Goffi <goffi@goffi.org>
date Fri, 19 Feb 2021 15:50:22 +0100
parents 000b6722bd35
children be6d91572633
line wrap: on
line diff
--- a/sat/plugins/plugin_exp_invitation.py	Fri Feb 19 15:49:59 2021 +0100
+++ b/sat/plugins/plugin_exp_invitation.py	Fri Feb 19 15:50:22 2021 +0100
@@ -16,15 +16,18 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+from typing import Optional
+from zope.interface import implementer
+from twisted.internet import defer
+from twisted.words.protocols.jabber import jid
+from twisted.words.protocols.jabber.xmlstream import XMPPHandler
+from wokkel import disco, iwokkel
 from sat.core.i18n import _
 from sat.core import exceptions
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
-from twisted.internet import defer
-from twisted.words.protocols.jabber import jid
-from wokkel import disco, iwokkel
-from zope.interface import implementer
-from twisted.words.protocols.jabber.xmlstream import XMPPHandler
+from sat.core.xmpp import SatXMPPEntity
+from sat.tools import utils
 
 log = getLogger(__name__)
 
@@ -123,16 +126,25 @@
                 invitation_elt['thumb_url'] = thumb_url
         return mess_data, invitation_elt
 
-    def sendPubsubInvitation(self, client, invitee_jid, service, node,
-                             item_id, name, extra):
+    def sendPubsubInvitation(
+        self,
+        client: SatXMPPEntity,
+        invitee_jid: jid.JID,
+        service: jid.JID,
+        node: str,
+        item_id: Optional[str],
+        name: Optional[str],
+        extra: Optional[dict]
+    ) -> None:
         """Send an pubsub invitation in a <message> stanza
 
-        @param invitee_jid(jid.JID): entitee to send invitation to
-        @param service(jid.JID): pubsub service
-        @param node(unicode): pubsub node
-        @param item_id(unicode): pubsub id
-        @param name(unicode, None): see [_generateBaseInvitation]
-        @param extra(dict, None): see [_generateBaseInvitation]
+        @param invitee_jid: entitee to send invitation to
+        @param service: pubsub service
+        @param node: pubsub node
+        @param item_id: pubsub id
+            None when the invitation is for a whole node
+        @param name: see [_generateBaseInvitation]
+        @param extra: see [_generateBaseInvitation]
         """
         if extra is None:
             extra = {}
@@ -141,8 +153,22 @@
         pubsub_elt = invitation_elt.addElement("pubsub")
         pubsub_elt["service"] = service.full()
         pubsub_elt["node"] = node
-        pubsub_elt["item"] = item_id
-        return client.send(mess_data["xml"])
+        if item_id is None:
+            try:
+                namespace = extra.pop("namespace")
+            except KeyError:
+                raise exceptions.DataError('"namespace" key is missing in "extra" data')
+            node_data_elt = pubsub_elt.addElement("node_data")
+            node_data_elt["namespace"] = namespace
+            try:
+                node_data_elt.addChild(extra["element"])
+            except KeyError:
+                pass
+        else:
+            pubsub_elt["item"] = item_id
+        if "element" in extra:
+            invitation_elt.addChild(extra.pop("element"))
+        client.send(mess_data["xml"])
 
     async def sendFileSharingInvitation(
         self, client, invitee_jid, service, repos_type=None, namespace=None, path=None,
@@ -207,53 +233,61 @@
             file_sharing_elt["path"] = path
         client.send(mess_data["xml"])
 
-    @defer.inlineCallbacks
-    def _parsePubsubElt(self, client, pubsub_elt):
+    async def _parsePubsubElt(self, client, pubsub_elt):
         try:
             service = jid.JID(pubsub_elt["service"])
             node = pubsub_elt["node"]
-            item_id = pubsub_elt.getAttribute("item")
         except (RuntimeError, KeyError):
-            log.warning(_("Bad invitation, ignoring"))
-            raise exceptions.DataError
+            raise exceptions.DataError("Bad invitation, ignoring")
+
+        item_id = pubsub_elt.getAttribute("item")
+
+        if item_id is not None:
+            try:
+                items, metadata = await self._p.getItems(
+                    client, service, node, item_ids=[item_id]
+                )
+            except Exception as e:
+                log.warning(_("Can't get item linked with invitation: {reason}").format(
+                            reason=e))
+            try:
+                item_elt = items[0]
+            except IndexError:
+                log.warning(_("Invitation was linking to a non existing item"))
+                raise exceptions.DataError
 
-        try:
-            items, metadata = yield self._p.getItems(client, service, node,
-                                                     item_ids=[item_id])
-        except Exception as e:
-            log.warning(_("Can't get item linked with invitation: {reason}").format(
-                        reason=e))
-        try:
-            item_elt = items[0]
-        except IndexError:
-            log.warning(_("Invitation was linking to a non existing item"))
-            raise exceptions.DataError
+            try:
+                namespace = item_elt.firstChildElement().uri
+            except Exception as e:
+                log.warning(_("Can't retrieve namespace of invitation: {reason}").format(
+                    reason = e))
+                raise exceptions.DataError
 
-        try:
-            namespace = item_elt.firstChildElement().uri
-        except Exception as e:
-            log.warning(_("Can't retrieve namespace of invitation: {reason}").format(
-                reason = e))
-            raise exceptions.DataError
+            args = [service, node, item_id, item_elt]
+        else:
+            try:
+                node_data_elt = next(pubsub_elt.elements((NS_INVITATION, "node_data")))
+            except StopIteration:
+                raise exceptions.DataError("Bad invitation, ignoring")
+            namespace = node_data_elt['namespace']
+            args = [service, node, None, node_data_elt]
 
-        args = [service, node, item_id, item_elt]
-        defer.returnValue((namespace, args))
+        return namespace, args
 
-    def _parseFileSharingElt(self, client, file_sharing_elt):
+    async def _parseFileSharingElt(self, client, file_sharing_elt):
         try:
             service = jid.JID(file_sharing_elt["service"])
         except (RuntimeError, KeyError):
             log.warning(_("Bad invitation, ignoring"))
             raise exceptions.DataError
         repos_type = file_sharing_elt.getAttribute("type", "files")
-        namespace = file_sharing_elt.getAttribute("namespace")
+        sharing_ns = file_sharing_elt.getAttribute("namespace")
         path = file_sharing_elt.getAttribute("path")
-        args = [service, repos_type, namespace, path]
+        args = [service, repos_type, sharing_ns, path]
         ns_fis = self.host.getNamespace("fis")
         return ns_fis, args
 
-    @defer.inlineCallbacks
-    def onInvitation(self, message_elt, client):
+    async def onInvitation(self, message_elt, client):
         log.debug("invitation received [{profile}]".format(profile=client.profile))
         invitation_elt = message_elt.invitation
 
@@ -275,7 +309,7 @@
                     xml = elt.toXml()))
                 continue
             try:
-                namespace, args = yield method(client, elt)
+                namespace, args = await method(client, elt)
             except exceptions.DataError:
                 log.warning("Can't parse invitation element: {xml}".format(
                             xml = elt.toXml()))
@@ -288,7 +322,7 @@
                     'No handler for namespace "{namespace}", invitation ignored')
                     .format(namespace=namespace))
             else:
-                cb(client, name, extra, *args)
+                await utils.asDeferred(cb, client, namespace, name, extra, *args)
 
 
 @implementer(iwokkel.IDisco)
@@ -299,7 +333,10 @@
 
     def connectionInitialized(self):
         self.xmlstream.addObserver(
-            INVITATION, self.plugin_parent.onInvitation, client=self.parent
+            INVITATION,
+            lambda message_elt: defer.ensureDeferred(
+                self.plugin_parent.onInvitation(message_elt, client=self.parent)
+            ),
         )
 
     def getDiscoInfo(self, requestor, target, nodeIdentifier=""):