Mercurial > libervia-backend
comparison src/plugins/plugin_xep_0050.py @ 763:ab851b46009c
plugin xep-0050 (ad-hoc commands): requesting part. first draft
author | Goffi <goffi@goffi.org> |
---|---|
date | Tue, 24 Dec 2013 15:43:52 +0100 |
parents | 86224a13cc1d |
children | bfabeedbf32e |
comparison
equal
deleted
inserted
replaced
762:aed7d99276b8 | 763:ab851b46009c |
---|---|
20 from logging import debug, info, warning, error | 20 from logging import debug, info, warning, error |
21 from twisted.words.protocols.jabber import jid | 21 from twisted.words.protocols.jabber import jid |
22 from twisted.words.protocols.jabber import error as xmpp_error | 22 from twisted.words.protocols.jabber import error as xmpp_error |
23 from twisted.words.xish import domish | 23 from twisted.words.xish import domish |
24 from twisted.internet import defer, reactor | 24 from twisted.internet import defer, reactor |
25 from wokkel import disco, iwokkel, data_form | 25 from wokkel import disco, iwokkel, data_form, compat |
26 from sat.core import exceptions | 26 from sat.core import exceptions |
27 from sat.memory.memory import Sessions | 27 from sat.memory.memory import Sessions |
28 from uuid import uuid4 | 28 from uuid import uuid4 |
29 from sat.tools import xml_tools | |
29 | 30 |
30 from zope.interface import implements | 31 from zope.interface import implements |
31 | 32 |
32 try: | 33 try: |
33 from twisted.words.protocols.xmlstream import XMPPHandler | 34 from twisted.words.protocols.xmlstream import XMPPHandler |
202 ('item-not-found', None), ('feature-not-implemented', None), ('internal-server-error', None)) # XEP-0050 §4.6 Table 5 | 203 ('item-not-found', None), ('feature-not-implemented', None), ('internal-server-error', None)) # XEP-0050 §4.6 Table 5 |
203 | 204 |
204 def __init__(self, host): | 205 def __init__(self, host): |
205 info(_("plugin XEP-0050 initialization")) | 206 info(_("plugin XEP-0050 initialization")) |
206 self.host = host | 207 self.host = host |
207 self.requesting = {} | 208 self.requesting = Sessions() |
208 self.answering = {} | 209 self.answering = {} |
210 host.bridge.addMethod("requestCommand", ".plugin", in_sign='ss', out_sign='s', | |
211 method=self._requestCommandsList, | |
212 async=True) | |
213 self.__requesting_id = host.registerCallback(self._requestingEntity, with_data=True) | |
214 host.importMenu("Service", "commands", self._commandsMenu, help_string=_("Execute ad-hoc commands")) | |
209 | 215 |
210 def getHandler(self, profile): | 216 def getHandler(self, profile): |
211 return XEP_0050_handler(self) | 217 return XEP_0050_handler(self) |
212 | 218 |
213 def profileConnected(self, profile): | 219 def profileConnected(self, profile): |
217 try: | 223 try: |
218 del self.answering[profile] | 224 del self.answering[profile] |
219 except KeyError: | 225 except KeyError: |
220 pass | 226 pass |
221 | 227 |
228 def _items2XMLUI(self, items): | |
229 """ Convert discovery items to XMLUI dialog """ | |
230 # TODO: manage items on different jids | |
231 form_ui = xml_tools.XMLUI("form", submit_id=self.__requesting_id) | |
232 | |
233 form_ui.addText(_("Please select a command"), 'instructions') | |
234 | |
235 options = [(item.nodeIdentifier, item.name) for item in items] | |
236 form_ui.addList(options, "node") | |
237 return form_ui | |
238 | |
239 def _commandsAnswer2XMLUI(self, iq_elt, session_id, session_data): | |
240 """ | |
241 Convert command answer to an ui for frontend | |
242 @param iq_elt: command result | |
243 @param session_id: id of the session used with the frontend | |
244 @param profile_key: %(doc_profile_key)s | |
245 | |
246 """ | |
247 command_elt = iq_elt.elements(NS_COMMANDS, "command").next() | |
248 status = command_elt.getAttribute('status', XEP_0050.STATUS.EXECUTING) | |
249 if status in [XEP_0050.STATUS.COMPLETED, XEP_0050.STATUS.CANCELED]: | |
250 # the command session is finished, we purge our session | |
251 del self.requesting[session_id] | |
252 return None | |
253 remote_session_id = command_elt.getAttribute('sessionid') | |
254 if remote_session_id: | |
255 session_data['remote_id'] = remote_session_id | |
256 data_elt = command_elt.elements(data_form.NS_X_DATA, 'x').next() | |
257 form = data_form.Form.fromElement(data_elt) | |
258 return xml_tools.dataForm2XMLUI(form, self.__requesting_id, session_id=session_id) | |
259 | |
260 def _requestingEntity(self, data, profile): | |
261 """ | |
262 request and entity and create XMLUI accordingly | |
263 @param data: data returned by previous XMLUI (first one must come from self._commandsMenu) | |
264 @param profile: %(doc_profile)s | |
265 @return: callback dict result (with "xmlui" corresponding to the answering dialog, or empty if it's finished without error) | |
266 | |
267 """ | |
268 # TODO: cancel, prev and next are not managed | |
269 # TODO: managed answerer errors | |
270 # TODO: manage nodes with a non data form payload | |
271 if "session_id" not in data: | |
272 # we just had the jid, we now request it for the available commands | |
273 session_id, session_data = self.requesting.newSession(profile=profile) | |
274 entity = jid.JID(data[xml_tools.SAT_FORM_PREFIX+'jid']) | |
275 session_data['jid'] = entity | |
276 d = self.requestCommandsList(entity, profile) | |
277 | |
278 def sendItems(xmlui): | |
279 xmlui.setSessionId(session_id) # we need to keep track of the session | |
280 return {'xmlui': xmlui.toXml()} | |
281 | |
282 d.addCallback(sendItems) | |
283 else: | |
284 # we have started a several forms sessions | |
285 try: | |
286 session_data = self.requesting.profileGet(data["session_id"], profile) | |
287 except KeyError: | |
288 warning ("session id doesn't exist, session has probably expired") | |
289 # TODO: send error dialog | |
290 return defer.succeed({}) | |
291 session_id = data["session_id"] | |
292 entity = session_data['jid'] | |
293 try: | |
294 node = session_data['node'] | |
295 # node has already been received | |
296 except KeyError: | |
297 # it's the first time we know the node, we save it in session data | |
298 node = session_data['node'] = data[xml_tools.SAT_FORM_PREFIX+'node'] | |
299 | |
300 client = self.host.getClient(profile) | |
301 if not client: | |
302 raise exceptions.ProfileUnknownError | |
303 | |
304 # we request execute node's command | |
305 iq_elt = compat.IQ(client.xmlstream, 'set') | |
306 iq_elt['to'] = entity.full() | |
307 command_elt = iq_elt.addElement("command", NS_COMMANDS) | |
308 command_elt['node'] = session_data['node'] | |
309 command_elt['action'] = XEP_0050.ACTION.EXECUTE | |
310 try: | |
311 # remote_id is the XEP_0050 sessionid used by answering command | |
312 # while session_id is our own session id used with the frontend | |
313 command_elt['sessionid'] = session_data['remote_id'] | |
314 except KeyError: | |
315 pass | |
316 | |
317 command_elt.addChild(xml_tools.XMLUIResultToElt(data)) # We add the XMLUI result to the command payload | |
318 d = iq_elt.send() | |
319 d.addCallback(self._commandsAnswer2XMLUI, session_id, session_data) | |
320 d.addCallback(lambda xmlui: {'xmlui': xmlui.toXml()} if xmlui is not None else {}) | |
321 | |
322 return d | |
323 | |
324 def _commandsMenu(self, profile): | |
325 """ First XMLUI activated by menu: ask for target jid | |
326 @param profile: %(doc_profile)s | |
327 | |
328 """ | |
329 form_ui = xml_tools.XMLUI("form", submit_id=self.__requesting_id) | |
330 form_ui.addText(_("Please enter target jid"), 'instructions') | |
331 form_ui.changeLayout("pairs") | |
332 form_ui.addLabel("jid") | |
333 form_ui.addString("jid") | |
334 return form_ui.toXml() | |
335 | |
222 def _statusCallback(self, command_elt, session_data, action, node, profile): | 336 def _statusCallback(self, command_elt, session_data, action, node, profile): |
223 """ Ad-hoc command used to change the "show" part of status """ | 337 """ Ad-hoc command used to change the "show" part of status """ |
224 actions = session_data.setdefault('actions',[]) | 338 actions = session_data.setdefault('actions',[]) |
225 actions.append(action) | 339 actions.append(action) |
226 | 340 |
227 if len(actions) == 1: | 341 if len(actions) == 1: |
342 # it's our first request, we ask the desired new status | |
228 status = XEP_0050.STATUS.EXECUTING | 343 status = XEP_0050.STATUS.EXECUTING |
229 form = data_form.Form('form', title=_('status selection')) | 344 form = data_form.Form('form', title=_('status selection')) |
230 show_options = [data_form.Option(name, label) for name, label in SHOWS.items()] | 345 show_options = [data_form.Option(name, label) for name, label in SHOWS.items()] |
231 field = data_form.Field('list-single', 'show', options=show_options, required=True) | 346 field = data_form.Field('list-single', 'show', options=show_options, required=True) |
232 form.addField(field) | 347 form.addField(field) |
233 | 348 |
234 payload = form.toElement() | 349 payload = form.toElement() |
235 note = None | 350 note = None |
236 | 351 |
237 elif len(actions) == 2: # we should have the answer here | 352 elif len(actions) == 2: |
353 # we should have the answer here | |
238 try: | 354 try: |
239 x_elt = command_elt.elements(data_form.NS_X_DATA,'x').next() | 355 x_elt = command_elt.elements(data_form.NS_X_DATA,'x').next() |
240 answer_form = data_form.Form.fromElement(x_elt) | 356 answer_form = data_form.Form.fromElement(x_elt) |
241 show = answer_form['show'] | 357 show = answer_form['show'] |
242 except KeyError, StopIteration: | 358 except KeyError, StopIteration: |
256 note = (self.NOTE.INFO, _(u"Status updated")) | 372 note = (self.NOTE.INFO, _(u"Status updated")) |
257 else: | 373 else: |
258 raise AdHocError(XEP_0050.ERROR.INTERNAL) | 374 raise AdHocError(XEP_0050.ERROR.INTERNAL) |
259 | 375 |
260 return (payload, status, None, note) | 376 return (payload, status, None, note) |
377 | |
378 def _requestCommandsList(self, to_jid_s, profile_key): | |
379 d = self.requestCommandsList(jid.JID(to_jid_s), profile_key) | |
380 d.addCallback(lambda xmlui: xmlui.toXml()) | |
381 return d | |
382 | |
383 def requestCommandsList(self, to_jid, profile_key): | |
384 """ Request available commands | |
385 @param to_jid: the entity answering the commands | |
386 @param profile_key: %(doc_profile)s | |
387 | |
388 """ | |
389 client = self.host.getClient(profile_key) | |
390 d = client.disco.requestItems(to_jid, NS_COMMANDS) | |
391 d.addCallback(self._items2XMLUI) | |
392 return d | |
261 | 393 |
262 def addAdHocCommand(self, callback, label, node="", features = None, timeout = 600, allowed_jids = None, allowed_groups = None, | 394 def addAdHocCommand(self, callback, label, node="", features = None, timeout = 600, allowed_jids = None, allowed_groups = None, |
263 allowed_magics = None, forbidden_jids = None, forbidden_groups = None, profile_key="@NONE@"): | 395 allowed_magics = None, forbidden_jids = None, forbidden_groups = None, profile_key="@NONE@"): |
264 """ | 396 """ |
265 | 397 |