changeset 2444:30278ea1ca7c

plugin XEP-0060: added node watching methods to bridge: new methods psNodeWatchAdd and psNodeWatchRemove allows to set a watch for the time of the session on one node, to have a signal called when something change on this node. This signal (psEventRaw) send raw data (raw XML), in opposition to psEvent which is there to send high level data (e.g. parsed blog data). Those method are primarely there to let frontends manage local cache for pubsub nodes.
author Goffi <goffi@goffi.org>
date Sun, 19 Nov 2017 16:51:39 +0100
parents 81a45e7886c9
children 0199c0bd4c60
files src/plugins/plugin_exp_pubsub_hook.py src/plugins/plugin_xep_0060.py
diffstat 2 files changed, 35 insertions(+), 2 deletions(-) [+]
line wrap: on
line diff
--- a/src/plugins/plugin_exp_pubsub_hook.py	Sun Nov 19 16:46:07 2017 +0100
+++ b/src/plugins/plugin_exp_pubsub_hook.py	Sun Nov 19 16:51:39 2017 +0100
@@ -179,7 +179,7 @@
             - HOOK_TYPE_PYTHON_FILE: a python file
                 file must have a "hook" method which will be called
             - HOOK_TYPE_PYTHON_CODE: direct python code
-            /!\ Python hooks will be executed in SàT context,
+                /!\ Python hooks will be executed in SàT context,
                 with host, client and item as arguments, it means that:
                     - they can do whatever they wants, so don't run untrusted hooks
                     - they MUST NOT BLOCK, they are run in Twisted async environment and blocking would block whole SàT process
--- a/src/plugins/plugin_xep_0060.py	Sun Nov 19 16:46:07 2017 +0100
+++ b/src/plugins/plugin_xep_0060.py	Sun Nov 19 16:51:39 2017 +0100
@@ -92,8 +92,10 @@
         host.bridge.addMethod("psNodeAffiliationsSet", ".plugin", in_sign='ssa{ss}s', out_sign='', method=self._setNodeAffiliations, async=True)
         host.bridge.addMethod("psNodeSubscriptionsGet", ".plugin", in_sign='sss', out_sign='a{ss}', method=self._getNodeSubscriptions, async=True)
         host.bridge.addMethod("psNodeSubscriptionsSet", ".plugin", in_sign='ssa{ss}s', out_sign='', method=self._setNodeSubscriptions, async=True)
+        host.bridge.addMethod("psNodeDelete", ".plugin", in_sign='sss', out_sign='', method=self._deleteNode, async=True)
+        host.bridge.addMethod("psNodeWatchAdd", ".plugin", in_sign='sss', out_sign='', method=self._addWatch, async=False)
+        host.bridge.addMethod("psNodeWatchRemove", ".plugin", in_sign='sss', out_sign='', method=self._removeWatch, async=False)
         host.bridge.addMethod("psAffiliationsGet", ".plugin", in_sign='sss', out_sign='a{ss}', method=self._getAffiliations, async=True)
-        host.bridge.addMethod("psNodeDelete", ".plugin", in_sign='sss', out_sign='', method=self._deleteNode, async=True)
         host.bridge.addMethod("psItemsGet", ".plugin", in_sign='ssiassa{ss}s', out_sign='(asa{ss})', method=self._getItems, async=True)
         host.bridge.addMethod("psItemSend", ".plugin", in_sign='ssssa{ss}s', out_sign='s', method=self._sendItem, async=True)
         host.bridge.addMethod("psRetractItem", ".plugin", in_sign='sssbs', out_sign='', method=self._retractItem, async=True)
@@ -105,14 +107,20 @@
         host.bridge.addMethod("psGetSubscribeRTResult", ".plugin", in_sign='ss', out_sign='(ua(sss))', method=self._manySubscribeRTResult, async=True)
         host.bridge.addMethod("psGetFromMany", ".plugin", in_sign='a(ss)ia{ss}s', out_sign='s', method=self._getFromMany)
         host.bridge.addMethod("psGetFromManyRTResult", ".plugin", in_sign='ss', out_sign='(ua(sssasa{ss}))', method=self._getFromManyRTResult, async=True)
+
+        # high level observer method
         host.bridge.addSignal("psEvent", ".plugin", signature='ssssa{ss}s')  # args: category, service(jid), node, type (C.PS_ITEMS, C.PS_DELETE), data, profile
 
+        # low level observer method, used if service/node is in watching list (see psNodeWatch* methods)
+        host.bridge.addSignal("psEventRaw", ".plugin", signature='sssass')  # args: service(jid), node, type (C.PS_ITEMS, C.PS_DELETE), list of item_xml, profile
+
     def getHandler(self, client):
         client.pubsub_client = SatPubSubClient(self.host, self)
         return client.pubsub_client
 
     @defer.inlineCallbacks
     def profileConnected(self, client):
+        client.pubsub_watching = set()
         client.pubsub_service = yield self.host.findServiceEntity(client, "pubsub", "service")
 
     def getFeatures(self, profile):
@@ -577,6 +585,24 @@
     def deleteNode(self, client, service, nodeIdentifier):
         return client.pubsub_client.deleteNode(service, nodeIdentifier)
 
+    def _addWatch(self, service_s, node, profile_key):
+        """watch modifications on a node
+
+        This method should only be called from bridge
+        """
+        client = self.host.getClient(profile_key)
+        service = jid.JID(service_s) if service_s else client.jid.userhostJID()
+        client.pubsub_watching.add((service, node))
+
+    def _removeWatch(self, service_s, node, profile_key):
+        """remove a node watch
+
+        This method should only be called from bridge
+        """
+        client = self.host.getClient(profile_key)
+        service = jid.JID(service_s) if service_s else client.jid.userhostJID()
+        client.pubsub_watching.remove((service, node))
+
     def _retractItem(self, service_s, nodeIdentifier, itemIdentifier, notify, profile_key):
         return self._retractItems(service_s, nodeIdentifier, (itemIdentifier,), notify, profile_key)
 
@@ -897,11 +923,18 @@
         log.debug(u"Pubsub items received")
         for callback in self._getNodeCallbacks(event.nodeIdentifier, C.PS_ITEMS):
             callback(self.parent, event)
+        client = self.parent
+        if (event.sender, event.nodeIdentifier) in client.pubsub_watching:
+            raw_items = [i.toXml() for i in event.items]
+            self.host.bridge.psEventRaw(event.sender.full(), event.nodeIdentifier, C.PS_ITEMS, raw_items, client.profile)
 
     def deleteReceived(self, event):
         log.debug((u"Publish node deleted"))
         for callback in self._getNodeCallbacks(event.nodeIdentifier, C.PS_DELETE):
             callback(self.parent, event)
+        client = self.parent
+        if (event.sender, event.nodeIdentifier) in client.pubsub_watching:
+            self.host.bridge.psEventRaw(event.sender.full(), event.nodeIdentifier, C.PS_DELETE, [], client.profile)
 
     def subscriptions(self, service, nodeIdentifier, sender=None):
         """Return the list of subscriptions to the given service and node.