Mercurial > libervia-backend
annotate src/plugins/plugin_exp_pubsub_hook.py @ 2498:d6de69da3dd4
core (client): component improvments:
- renamed component boolean to is_component for more clarity
- profileConnected/profileDisconnected don't use a suffix anymore, it's called for both client and component.
To check for there, the is_component boolean is enough
- fixed dependencies handling
- componentStart is not mandatory anymore, as a component can just be used with its handler
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 28 Feb 2018 18:28:39 +0100 |
parents | 0046283a285d |
children |
rev | line source |
---|---|
2307 | 1 #!/usr/bin/env python2 |
2 # -*- coding: utf-8 -*- | |
3 | |
4 # SAT plugin for Pubsub Hooks | |
2483 | 5 # Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org) |
2307 | 6 |
7 # This program is free software: you can redistribute it and/or modify | |
8 # it under the terms of the GNU Affero General Public License as published by | |
9 # the Free Software Foundation, either version 3 of the License, or | |
10 # (at your option) any later version. | |
11 | |
12 # This program is distributed in the hope that it will be useful, | |
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 # GNU Affero General Public License for more details. | |
16 | |
17 # You should have received a copy of the GNU Affero General Public License | |
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
19 | |
20 from sat.core.i18n import _ | |
21 from sat.core.constants import Const as C | |
22 from sat.core import exceptions | |
23 from sat.core.log import getLogger | |
24 from sat.memory import persistent | |
25 from twisted.words.protocols.jabber import jid | |
26 from twisted.internet import defer | |
27 log = getLogger(__name__) | |
28 | |
29 NS_PUBSUB_HOOK = 'PUBSUB_HOOK' | |
30 | |
31 PLUGIN_INFO = { | |
32 C.PI_NAME: "PubSub Hook", | |
33 C.PI_IMPORT_NAME: NS_PUBSUB_HOOK, | |
34 C.PI_TYPE: "EXP", | |
35 C.PI_PROTOCOLS: [], | |
36 C.PI_DEPENDENCIES: ["XEP-0060"], | |
37 C.PI_MAIN: "PubsubHook", | |
38 C.PI_HANDLER: "no", | |
39 C.PI_DESCRIPTION: _("""Experimental plugin to launch on action on Pubsub notifications""") | |
40 } | |
41 | |
42 # python module | |
43 HOOK_TYPE_PYTHON = u'python' | |
44 # python file path | |
45 HOOK_TYPE_PYTHON_FILE = u'python_file' | |
46 # python code directly | |
47 HOOK_TYPE_PYTHON_CODE = u'python_code' | |
48 HOOK_TYPES = (HOOK_TYPE_PYTHON, HOOK_TYPE_PYTHON_FILE, HOOK_TYPE_PYTHON_CODE) | |
49 | |
50 | |
51 class PubsubHook(object): | |
52 | |
53 def __init__(self, host): | |
54 log.info(_(u"PubSub Hook initialization")) | |
55 self.host = host | |
56 self.node_hooks = {} # keep track of the number of hooks per node (for all profiles) | |
57 host.bridge.addMethod("psHookAdd", ".plugin", | |
58 in_sign='ssssbs', out_sign='', | |
59 method=self._addHook | |
60 ) | |
61 host.bridge.addMethod("psHookRemove", ".plugin", | |
62 in_sign='sssss', out_sign='i', | |
63 method=self._removeHook | |
64 ) | |
65 host.bridge.addMethod("psHookList", ".plugin", | |
66 in_sign='s', out_sign='aa{ss}', | |
67 method=self._listHooks | |
68 ) | |
69 | |
70 @defer.inlineCallbacks | |
71 def profileConnected(self, client): | |
72 hooks = client._hooks = persistent.PersistentBinaryDict(NS_PUBSUB_HOOK, client.profile) | |
73 client._hooks_temporary = {} | |
74 yield hooks.load() | |
75 for node in hooks: | |
76 self._installNodeManager(client, node) | |
77 | |
78 def profileDisconnected(self, client): | |
79 for node in client._hooks: | |
80 self._removeNodeManager(client, node) | |
81 | |
82 def _installNodeManager(self, client, node): | |
83 if node in self.node_hooks: | |
84 log.debug(_(u"node manager already set for {node}").format(node=node)) | |
85 self.node_hooks[node] += 1 | |
86 else: | |
87 # first hook on this node | |
88 self.host.plugins['XEP-0060'].addManagedNode(node, items_cb=self._itemsReceived) | |
89 self.node_hooks[node] = 0 | |
90 log.info(_(u"node manager installed on {node}").format( | |
91 node = node)) | |
92 | |
93 def _removeNodeManager(self, client, node): | |
94 try: | |
95 self.node_hooks[node] -= 1 | |
96 except KeyError: | |
97 log.error(_(u"trying to remove a {node} without hook").format(node=node)) | |
98 else: | |
99 if self.node_hooks[node] == 0: | |
100 del self.node_hooks[node] | |
101 self.host.plugins['XEP-0060'].removeManagedNode(node, self._itemsReceived) | |
102 log.debug(_(u"hook removed")) | |
103 else: | |
104 log.debug(_(u"node still needed for an other hook")) | |
105 | |
106 def installHook(self, client, service, node, hook_type, hook_arg, persistent): | |
107 if hook_type not in HOOK_TYPES: | |
108 raise exceptions.DataError(_(u'{hook_type} is not handled').format(hook_type=hook_type)) | |
109 if hook_type != HOOK_TYPE_PYTHON_FILE: | |
110 raise NotImplementedError(_(u'{hook_type} hook type not implemented yet').format(hook_type=hook_type)) | |
111 self._installNodeManager(client, node) | |
112 hook_data = {'service': service, | |
113 'type': hook_type, | |
114 'arg': hook_arg | |
115 } | |
116 | |
117 if persistent: | |
118 hooks_list = client._hooks.setdefault(node,[]) | |
119 hooks_list.append(hook_data) | |
120 client._hooks.force(node) | |
121 else: | |
122 hooks_list = client._hooks_temporary.setdefault(node,[]) | |
123 hooks_list.append(hook_data) | |
124 | |
125 log.info(_(u"{persistent} hook installed on {node} for {profile}").format( | |
126 persistent = _(u'persistent') if persistent else _(u'temporary'), | |
127 node = node, | |
128 profile = client.profile)) | |
129 | |
130 def _itemsReceived(self, client, itemsEvent): | |
131 node = itemsEvent.nodeIdentifier | |
132 for hooks in (client._hooks, client._hooks_temporary): | |
133 if node not in hooks: | |
134 continue | |
135 hooks_list = hooks[node] | |
136 for hook_data in hooks_list[:]: | |
137 if hook_data['service'] != itemsEvent.sender.userhostJID(): | |
138 continue | |
139 try: | |
140 callback = hook_data['callback'] | |
141 except KeyError: | |
142 # first time we get this hook, we create the callback | |
143 hook_type = hook_data['type'] | |
144 try: | |
145 if hook_type == HOOK_TYPE_PYTHON_FILE: | |
146 hook_globals = {} | |
147 execfile(hook_data['arg'], hook_globals) | |
148 callback = hook_globals['hook'] | |
149 else: | |
150 raise NotImplementedError(_(u'{hook_type} hook type not implemented yet').format( | |
151 hook_type=hook_type)) | |
152 except Exception as e: | |
153 log.warning(_(u"Can't load Pubsub hook at node {node}, it will be removed: {reason}").format( | |
154 node=node, reason=e)) | |
155 hooks_list.remove(hook_data) | |
156 continue | |
157 | |
158 for item in itemsEvent.items: | |
159 try: | |
160 callback(self.host, client, item) | |
161 except Exception as e: | |
162 log.warning(_(u"Error while running Pubsub hook for node {node}: {msg}").format( | |
163 node = node, | |
164 msg = e)) | |
165 | |
166 def _addHook(self, service, node, hook_type, hook_arg, persistent, profile): | |
167 client = self.host.getClient(profile) | |
168 service = jid.JID(service) if service else client.jid.userhostJID() | |
169 return self.addHook(client, service, unicode(node), unicode(hook_type), unicode(hook_arg), persistent) | |
170 | |
171 def addHook(self, client, service, node, hook_type, hook_arg, persistent): | |
172 r"""Add a hook which will be triggered on a pubsub notification | |
173 | |
174 @param service(jid.JID): service of the node | |
175 @param node(unicode): Pubsub node | |
176 @param hook_type(unicode): type of the hook, one of: | |
177 - HOOK_TYPE_PYTHON: a python module (must be in path) | |
178 module must have a "hook" method which will be called | |
179 - HOOK_TYPE_PYTHON_FILE: a python file | |
180 file must have a "hook" method which will be called | |
181 - HOOK_TYPE_PYTHON_CODE: direct python code | |
2444
30278ea1ca7c
plugin XEP-0060: added node watching methods to bridge:
Goffi <goffi@goffi.org>
parents:
2307
diff
changeset
|
182 /!\ Python hooks will be executed in SàT context, |
2307 | 183 with host, client and item as arguments, it means that: |
184 - they can do whatever they wants, so don't run untrusted hooks | |
185 - they MUST NOT BLOCK, they are run in Twisted async environment and blocking would block whole SàT process | |
186 - item are domish.Element | |
187 @param hook_arg(unicode): argument of the hook, depending on the hook_type | |
188 can be a module path, file path, python code | |
189 """ | |
190 assert service is not None | |
191 return self.installHook(client, service, node, hook_type, hook_arg, persistent) | |
192 | |
193 def _removeHook(self, service, node, hook_type, hook_arg, profile): | |
194 client = self.host.getClient(profile) | |
195 service = jid.JID(service) if service else client.jid.userhostJID() | |
196 return self.removeHook(client, service, node, hook_type or None, hook_arg or None) | |
197 | |
198 def removeHook(self, client, service, node, hook_type=None, hook_arg=None): | |
199 """Remove a persistent or temporaty root | |
200 | |
201 @param service(jid.JID): service of the node | |
202 @param node(unicode): Pubsub node | |
203 @param hook_type(unicode, None): same as for [addHook] | |
204 match all if None | |
205 @param hook_arg(unicode, None): same as for [addHook] | |
206 match all if None | |
207 @return(int): number of hooks removed | |
208 """ | |
209 removed = 0 | |
210 for hooks in (client._hooks, client._hooks_temporary): | |
211 if node in hooks: | |
212 for hook_data in hooks[node]: | |
213 if (service != hook_data[u'service'] | |
214 or hook_type is not None and hook_type != hook_data[u'type'] | |
215 or hook_arg is not None and hook_arg != hook_data[u'arg']): | |
216 continue | |
217 hooks[node].remove(hook_data) | |
218 removed += 1 | |
219 if not hooks[node]: | |
220 # no more hooks, we can remove the node | |
221 del hooks[node] | |
222 self._removeNodeManager(client, node) | |
223 else: | |
224 if hooks == client._hooks: | |
225 hooks.force(node) | |
226 return removed | |
227 | |
228 def _listHooks(self, profile): | |
229 hooks_list = self.listHooks(self.host.getClient(profile)) | |
230 for hook in hooks_list: | |
231 hook[u'service'] = hook[u'service'].full() | |
232 hook[u'persistent'] = C.boolConst(hook[u'persistent']) | |
233 return hooks_list | |
234 | |
235 def listHooks(self, client): | |
236 """return list of registered hooks""" | |
237 hooks_list = [] | |
238 for hooks in (client._hooks, client._hooks_temporary): | |
239 persistent = hooks is client._hooks | |
240 for node, hooks_data in hooks.iteritems(): | |
241 for hook_data in hooks_data: | |
242 hooks_list.append({u'service': hook_data[u'service'], | |
243 u'node': node, | |
244 u'type': hook_data[u'type'], | |
245 u'arg': hook_data[u'arg'], | |
246 u'persistent': persistent | |
247 }) | |
248 return hooks_list | |
249 |