comparison src/plugins/plugin_xep_0050.py @ 2406:1e5b2c35964e

plugin XEP-0050: renamed "requestCommandsList" method to "list" + added a run command + fixes: - XMLUI cancellation is fixed - when called from bridge, "list" (adHocList) command doesn't prepend instructions
author Goffi <goffi@goffi.org>
date Tue, 31 Oct 2017 23:11:13 +0100
parents 918e38622a48
children 8b37a62336c3
comparison
equal deleted inserted replaced
2405:f4b6176eb65f 2406:1e5b2c35964e
213 def __init__(self, host): 213 def __init__(self, host):
214 log.info(_("plugin XEP-0050 initialization")) 214 log.info(_("plugin XEP-0050 initialization"))
215 self.host = host 215 self.host = host
216 self.requesting = Sessions() 216 self.requesting = Sessions()
217 self.answering = {} 217 self.answering = {}
218 host.bridge.addMethod("requestCommand", ".plugin", in_sign='ss', out_sign='s', 218 host.bridge.addMethod("adHocRun", ".plugin", in_sign='sss', out_sign='s',
219 method=self._requestCommandsList, 219 method=self._run,
220 async=True)
221 host.bridge.addMethod("adHocList", ".plugin", in_sign='ss', out_sign='s',
222 method=self._list,
220 async=True) 223 async=True)
221 self.__requesting_id = host.registerCallback(self._requestingEntity, with_data=True) 224 self.__requesting_id = host.registerCallback(self._requestingEntity, with_data=True)
222 host.importMenu((D_("Service"), D_("Commands")), self._commandsMenu, security_limit=2, help_string=D_("Execute ad-hoc commands")) 225 host.importMenu((D_("Service"), D_("Commands")), self._commandsMenu, security_limit=2, help_string=D_("Execute ad-hoc commands"))
223 226
224 def getHandler(self, client): 227 def getHandler(self, client):
231 try: 234 try:
232 del self.answering[client.profile] 235 del self.answering[client.profile]
233 except KeyError: 236 except KeyError:
234 pass 237 pass
235 238
236 def _items2XMLUI(self, items): 239 def _items2XMLUI(self, items, no_instructions):
237 """ Convert discovery items to XMLUI dialog """ 240 """ Convert discovery items to XMLUI dialog """
238 # TODO: manage items on different jids 241 # TODO: manage items on different jids
239 form_ui = xml_tools.XMLUI("form", submit_id=self.__requesting_id) 242 form_ui = xml_tools.XMLUI("form", submit_id=self.__requesting_id)
240 243
241 form_ui.addText(_("Please select a command"), 'instructions') 244 if not no_instructions:
245 form_ui.addText(_("Please select a command"), 'instructions')
242 246
243 options = [(item.nodeIdentifier, item.name) for item in items] 247 options = [(item.nodeIdentifier, item.name) for item in items]
244 form_ui.addList("node", options) 248 form_ui.addList("node", options)
245 return form_ui 249 return form_ui
246 250
329 # we add any present note to the instructions 333 # we add any present note to the instructions
330 form.instructions.extend(self._mergeNotes(notes)) 334 form.instructions.extend(self._mergeNotes(notes))
331 return xml_tools.dataForm2XMLUI(form, self.__requesting_id, session_id=session_id) 335 return xml_tools.dataForm2XMLUI(form, self.__requesting_id, session_id=session_id)
332 336
333 def _requestingEntity(self, data, profile): 337 def _requestingEntity(self, data, profile):
338 def serialise(ret_data):
339 if 'xmlui' in ret_data:
340 ret_data['xmlui'] = ret_data['xmlui'].toXml()
341 return ret_data
342
343 d = self.requestingEntity(data, profile)
344 d.addCallback(serialise)
345 return d
346
347 def requestingEntity(self, data, profile):
334 """ 348 """
335 request and entity and create XMLUI accordingly 349 request and entity and create XMLUI accordingly
336 @param data: data returned by previous XMLUI (first one must come from self._commandsMenu) 350 @param data: data returned by previous XMLUI (first one must come from self._commandsMenu)
337 @param profile: %(doc_profile)s 351 @param profile: %(doc_profile)s
338 @return: callback dict result (with "xmlui" corresponding to the answering dialog, or empty if it's finished without error) 352 @return: callback dict result (with "xmlui" corresponding to the answering dialog, or empty if it's finished without error)
339 353
340 """ 354 """
355 if C.bool(data.get('cancelled', C.BOOL_FALSE)):
356 return defer.succeed({})
357 client = self.host.getClient(profile)
341 # TODO: cancel, prev and next are not managed 358 # TODO: cancel, prev and next are not managed
342 # TODO: managed answerer errors 359 # TODO: managed answerer errors
343 # TODO: manage nodes with a non data form payload 360 # TODO: manage nodes with a non data form payload
344 if "session_id" not in data: 361 if "session_id" not in data:
345 # we just had the jid, we now request it for the available commands 362 # we just had the jid, we now request it for the available commands
346 session_id, session_data = self.requesting.newSession(profile=profile) 363 session_id, session_data = self.requesting.newSession(profile=client.profile)
347 entity = jid.JID(data[xml_tools.SAT_FORM_PREFIX+'jid']) 364 entity = jid.JID(data[xml_tools.SAT_FORM_PREFIX+'jid'])
348 session_data['jid'] = entity 365 session_data['jid'] = entity
349 d = self.requestCommandsList(entity, profile) 366 d = self.list(client, entity)
350 367
351 def sendItems(xmlui): 368 def sendItems(xmlui):
352 xmlui.session_id = session_id # we need to keep track of the session 369 xmlui.session_id = session_id # we need to keep track of the session
353 return {'xmlui': xmlui.toXml()} 370 return {'xmlui': xmlui}
354 371
355 d.addCallback(sendItems) 372 d.addCallback(sendItems)
356 else: 373 else:
357 # we have started a several forms sessions 374 # we have started a several forms sessions
358 try: 375 try:
359 session_data = self.requesting.profileGet(data["session_id"], profile) 376 session_data = self.requesting.profileGet(data["session_id"], client.profile)
360 except KeyError: 377 except KeyError:
361 log.warning ("session id doesn't exist, session has probably expired") 378 log.warning ("session id doesn't exist, session has probably expired")
362 # TODO: send error dialog 379 # TODO: send error dialog
363 return defer.succeed({}) 380 return defer.succeed({})
364 session_id = data["session_id"] 381 session_id = data["session_id"]
368 # node has already been received 385 # node has already been received
369 except KeyError: 386 except KeyError:
370 # it's the first time we know the node, we save it in session data 387 # it's the first time we know the node, we save it in session data
371 session_data['node'] = data[xml_tools.SAT_FORM_PREFIX+'node'] 388 session_data['node'] = data[xml_tools.SAT_FORM_PREFIX+'node']
372 389
373 client = self.host.getClient(profile)
374
375 # we request execute node's command 390 # we request execute node's command
376 iq_elt = compat.IQ(client.xmlstream, 'set') 391 iq_elt = compat.IQ(client.xmlstream, 'set')
377 iq_elt['to'] = entity.full() 392 iq_elt['to'] = entity.full()
378 command_elt = iq_elt.addElement("command", NS_COMMANDS) 393 command_elt = iq_elt.addElement("command", NS_COMMANDS)
379 command_elt['node'] = session_data['node'] 394 command_elt['node'] = session_data['node']
386 pass 401 pass
387 402
388 command_elt.addChild(xml_tools.XMLUIResultToElt(data)) # We add the XMLUI result to the command payload 403 command_elt.addChild(xml_tools.XMLUIResultToElt(data)) # We add the XMLUI result to the command payload
389 d = iq_elt.send() 404 d = iq_elt.send()
390 d.addCallback(self._commandsAnswer2XMLUI, session_id, session_data) 405 d.addCallback(self._commandsAnswer2XMLUI, session_id, session_data)
391 d.addCallback(lambda xmlui: {'xmlui': xmlui.toXml()} if xmlui is not None else {}) 406 d.addCallback(lambda xmlui: {'xmlui': xmlui} if xmlui is not None else {})
392 407
393 return d 408 return d
394 409
395 def _commandsMenu(self, menu_data, profile): 410 def _commandsMenu(self, menu_data, profile):
396 """ First XMLUI activated by menu: ask for target jid 411 """ First XMLUI activated by menu: ask for target jid
444 else: 459 else:
445 raise AdHocError(XEP_0050.ERROR.INTERNAL) 460 raise AdHocError(XEP_0050.ERROR.INTERNAL)
446 461
447 return (payload, status, None, note) 462 return (payload, status, None, note)
448 463
449 def _requestCommandsList(self, to_jid_s, profile_key): 464 def _run(self, service_jid_s='', node='', profile_key=C.PROF_KEY_NONE):
450 d = self.requestCommandsList(jid.JID(to_jid_s), profile_key) 465 client = self.host.getClient(profile_key)
466 service_jid = jid.JID(service_jid_s) if service_jid_s else None
467 d = self.run(client, service_jid, node or None)
451 d.addCallback(lambda xmlui: xmlui.toXml()) 468 d.addCallback(lambda xmlui: xmlui.toXml())
452 return d 469 return d
453 470
454 def requestCommandsList(self, to_jid, profile_key): 471 @defer.inlineCallbacks
455 """ Request available commands 472 def run(self, client, service_jid=None, node=None):
456 @param to_jid: the entity answering the commands 473 """run an ad-hoc command
457 @param profile_key: %(doc_profile)s 474
458 475 @param service_jid(jid.JID, None): jid of the ad-hoc service
459 """ 476 None to use profile's server
477 @param node(unicode, None): node of the ad-hoc commnad
478 None to get initial list
479 @return(unicode): command page XMLUI
480 """
481 if service_jid is None:
482 service_jid = jid.JID(client.jid.host)
483 session_id, session_data = self.requesting.newSession(profile=client.profile)
484 session_data['jid'] = service_jid
485 if node is None:
486 xmlui = yield self.list(client, service_jid)
487 else:
488 session_data['node'] = node
489 cb_data = yield self.requestingEntity({'session_id': session_id}, client.profile)
490 xmlui = cb_data['xmlui']
491
492 xmlui.session_id = session_id
493 defer.returnValue(xmlui)
494
495 def _list(self, to_jid_s, profile_key):
460 client = self.host.getClient(profile_key) 496 client = self.host.getClient(profile_key)
461 d = client.disco.requestItems(to_jid, NS_COMMANDS) 497 to_jid = jid.JID(to_jid_s) if to_jid_s else None
462 d.addCallback(self._items2XMLUI) 498 d = self.list(client, to_jid, no_instructions=True)
499 d.addCallback(lambda xmlui: xmlui.toXml())
463 return d 500 return d
464 501
465 def addAdHocCommand(self, callback, label, node=None, features = None, timeout = 600, allowed_jids = None, allowed_groups = None, 502 def list(self, client, to_jid, no_instructions=False):
466 allowed_magics = None, forbidden_jids = None, forbidden_groups = None, profile_key=C.PROF_KEY_NONE): 503 """Request available commands
504
505 @param to_jid(jid.JID, None): the entity answering the commands
506 None to use profile's server
507 @param no_instructions(bool): if True, don't add instructions widget
508 """
509 d = self.host.getDiscoItems(client, to_jid, NS_COMMANDS)
510 d.addCallback(self._items2XMLUI, no_instructions)
511 return d
512
513 def addAdHocCommand(self, callback, label, node=None, features=None, timeout=600, allowed_jids=None, allowed_groups=None,
514 allowed_magics=None, forbidden_jids=None, forbidden_groups=None, profile_key=C.PROF_KEY_NONE):
467 """Add an ad-hoc command for the current profile 515 """Add an ad-hoc command for the current profile
468 516
469 @param callback: method associated with this ad-hoc command which return the payload data (see AdHocCommand._sendAnswer), can return a deferred 517 @param callback: method associated with this ad-hoc command which return the payload data (see AdHocCommand._sendAnswer), can return a deferred
470 @param label: label associated with this command on the main menu 518 @param label: label associated with this command on the main menu
471 @param node: disco item node associated with this command. None to use autogenerated node 519 @param node: disco item node associated with this command. None to use autogenerated node