comparison src/core/sat_main.py @ 759:93bd868b8fb6

backend, frontends: callbacks refactoring: - launchAction is now async, and return a dictionary for its result - no more action_id, actionResult* are deprecated - callback system is about to be unified
author Goffi <goffi@goffi.org>
date Tue, 24 Dec 2013 15:19:08 +0100
parents e3ad48a2aab2
children e1c64a5b4588
comparison
equal deleted inserted replaced
758:86224a13cc1d 759:93bd868b8fb6
40 from sat.core import exceptions 40 from sat.core import exceptions
41 from sat.memory.memory import Memory 41 from sat.memory.memory import Memory
42 from sat.tools.xml_tools import tupleList2dataForm 42 from sat.tools.xml_tools import tupleList2dataForm
43 from sat.tools.misc import TriggerManager 43 from sat.tools.misc import TriggerManager
44 from glob import glob 44 from glob import glob
45 from uuid import uuid4
45 46
46 try: 47 try:
47 from twisted.words.protocols.xmlstream import XMPPHandler 48 from twisted.words.protocols.xmlstream import XMPPHandler
48 except ImportError: 49 except ImportError:
49 from wokkel.subprotocols import XMPPHandler 50 from wokkel.subprotocols import XMPPHandler
101 error(_('Trying to redefine a constant')) 102 error(_('Trying to redefine a constant'))
102 raise Exception 103 raise Exception
103 CONST[name] = value 104 CONST[name] = value
104 105
105 def __init__(self): 106 def __init__(self):
106 #TODO: standardize callback system 107 self._cb_map = {} # map from callback_id to callbacks
107 108 self.__private_data = {} # used for internal callbacks (key = id) FIXME: to be removed
108 self.__general_cb_map = {} # callback called for general reasons (key = name)
109 self.__private_data = {} # used for internal callbacks (key = id)
110 self.profiles = {} 109 self.profiles = {}
111 self.plugins = {} 110 self.plugins = {}
112 self.menus = {} # dynamic menus. key: (type, category, name), value: menu data (dictionnary) 111 self.menus = {} # dynamic menus. key: (type, category, name), value: menu data (dictionnary)
113 112
114 self.memory = Memory(self) 113 self.memory = Memory(self)
155 self.bridge.register("subscription", self.subscription) 154 self.bridge.register("subscription", self.subscription)
156 self.bridge.register("addContact", self.addContact) 155 self.bridge.register("addContact", self.addContact)
157 self.bridge.register("updateContact", self.updateContact) 156 self.bridge.register("updateContact", self.updateContact)
158 self.bridge.register("delContact", self.delContact) 157 self.bridge.register("delContact", self.delContact)
159 self.bridge.register("isConnected", self.isConnected) 158 self.bridge.register("isConnected", self.isConnected)
160 self.bridge.register("launchAction", self.launchAction) 159 self.bridge.register("launchAction", self.launchCallback)
161 self.bridge.register("confirmationAnswer", self.confirmationAnswer) 160 self.bridge.register("confirmationAnswer", self.confirmationAnswer)
162 self.bridge.register("getProgress", self.getProgress) 161 self.bridge.register("getProgress", self.getProgress)
163 self.bridge.register("getMenus", self.getMenus) 162 self.bridge.register("getMenus", self.getMenus)
164 self.bridge.register("getMenuHelp", self.getMenuHelp) 163 self.bridge.register("getMenuHelp", self.getMenuHelp)
165 self.bridge.register("asyncCallMenu", self.callMenu) 164 self.bridge.register("asyncCallMenu", self.callMenu)
386 connector = reactor.connectTCP(server, port, serverRegistrer) 385 connector = reactor.connectTCP(server, port, serverRegistrer)
387 serverRegistrer.clientConnectionLost = lambda conn, reason: connector.disconnect() 386 serverRegistrer.clientConnectionLost = lambda conn, reason: connector.disconnect()
388 387
389 return next_id 388 return next_id
390 389
391 def registerNewAccountCB(self, id, data, profile): 390 def registerNewAccountCB(self, data, profile):
391 # FIXME: to be removed/redone elsewhere
392 user = jid.parse(self.memory.getParamA("JabberID", "Connection", profile_key=profile))[0] 392 user = jid.parse(self.memory.getParamA("JabberID", "Connection", profile_key=profile))[0]
393 password = self.memory.getParamA("Password", "Connection", profile_key=profile) 393 password = self.memory.getParamA("Password", "Connection", profile_key=profile)
394 server = self.memory.getParamA("Server", "Connection", profile_key=profile) 394 server = self.memory.getParamA("Server", "Connection", profile_key=profile)
395 395
396 if not user or not password or not server: 396 if not user or not password or not server:
405 self.askConfirmation( 405 self.askConfirmation(
406 confirm_id, "YES/NO", 406 confirm_id, "YES/NO",
407 {"message": _("Are you sure to register new account [%(user)s] to server %(server)s ?") % {'user': user, 'server': server, 'profile': profile}}, 407 {"message": _("Are you sure to register new account [%(user)s] to server %(server)s ?") % {'user': user, 'server': server, 'profile': profile}},
408 self.regisConfirmCB, profile) 408 self.regisConfirmCB, profile)
409 print "===============+++++++++++ REGISTER NEW ACCOUNT++++++++++++++============" 409 print "===============+++++++++++ REGISTER NEW ACCOUNT++++++++++++++============"
410 print "id=", id
411 print "data=", data
412 410
413 def regisConfirmCB(self, id, accepted, data, profile): 411 def regisConfirmCB(self, id, accepted, data, profile):
414 print _("register Confirmation CB ! (%s)") % str(accepted) 412 print _("register Confirmation CB ! (%s)") % str(accepted)
415 action_id, profile = self.__private_data[id] 413 action_id, profile = self.__private_data[id]
416 del self.__private_data[id] 414 del self.__private_data[id]
426 """submit a form 424 """submit a form
427 @param target: target jid where we are submitting 425 @param target: target jid where we are submitting
428 @param fields: list of tuples (name, value) 426 @param fields: list of tuples (name, value)
429 @return: tuple: (id, deferred) 427 @return: tuple: (id, deferred)
430 """ 428 """
429 # FIXME: to be removed
431 430
432 profile = self.memory.getProfileName(profile_key) 431 profile = self.memory.getProfileName(profile_key)
433 assert(profile) 432 assert(profile)
434 to_jid = jid.JID(target) 433 to_jid = jid.JID(target)
435 434
467 return 466 return
468 if profile not in self.profiles: 467 if profile not in self.profiles:
469 return False 468 return False
470 return self.profiles[profile].isConnected() 469 return self.profiles[profile].isConnected()
471 470
472 def launchAction(self, type, data, profile_key):
473 """Launch a specific action asked by client
474 @param type: action type (button)
475 @param data: needed data to launch the action
476
477 @return: action id for result, or empty string in case or error
478 """
479 profile = self.memory.getProfileName(profile_key)
480 if not profile:
481 error(_('trying to launch action with a non-existant profile'))
482 raise Exception # TODO: raise a proper exception
483 if type == "button":
484 try:
485 cb_name = data['callback_id']
486 except KeyError:
487 error(_("Incomplete data"))
488 return ""
489 id = sat_next_id()
490 self.callGeneralCB(cb_name, id, data, profile=profile)
491 return id
492 else:
493 error(_("Unknown action type"))
494 return ""
495 471
496 ## jabber methods ## 472 ## jabber methods ##
497 473
498 def getWaitingConf(self, profile_key=None): 474 def getWaitingConf(self, profile_key=None):
499 assert(profile_key) 475 assert(profile_key)
806 @param conf_id: conf_id used to get answer 782 @param conf_id: conf_id used to get answer
807 @param conf_type: confirmation conf_type ("YES/NO", "FILE_TRANSFER") 783 @param conf_type: confirmation conf_type ("YES/NO", "FILE_TRANSFER")
808 @param data: data (depend of confirmation conf_type) 784 @param data: data (depend of confirmation conf_type)
809 @param cb: callback called with the answer 785 @param cb: callback called with the answer
810 """ 786 """
787 # FIXME: use XMLUI and *callback methods for dialog
811 client = self.getClient(profile) 788 client = self.getClient(profile)
812 if not client: 789 if not client:
813 raise exceptions.ProfileUnknownError(_("Asking confirmation a non-existant profile")) 790 raise exceptions.ProfileUnknownError(_("Asking confirmation a non-existant profile"))
814 if conf_id in client._waiting_conf: 791 if conf_id in client._waiting_conf:
815 error(_("Attempt to register two callbacks for the same confirmation")) 792 error(_("Attempt to register two callbacks for the same confirmation"))
861 except KeyError: 838 except KeyError:
862 pass 839 pass
863 #debug("Requested progress for unknown progress_id") 840 #debug("Requested progress for unknown progress_id")
864 return data 841 return data
865 842
866 def registerGeneralCB(self, name, CB): 843 def registerCallback(self, callback, *args, **kwargs):
867 """Register a callback called for general reason""" 844 """ Register a callback.
868 self.__general_cb_map[name] = CB 845 Use with_data=True in kwargs if the callback use the optional data dict
869 846 use force_id=id to avoid generated id. Can lead to name conflict, avoid if possible
870 def removeGeneralCB(self, name): 847 @param callback: any callable
871 """Remove a general callback""" 848 @return: id of the registered callback
872 if name not in self.__general_cb_map: 849 """
873 error(_("Trying to remove an unknow general callback")) 850 callback_id = kwargs.pop('force_id', None)
851 if callback_id is None:
852 callback_id = str(uuid4())
874 else: 853 else:
875 del self.__general_cb_map[name] 854 if callback_id in self._cb_map:
876 855 raise exceptions.ConflictError(_(u"id already registered"))
877 def callGeneralCB(self, name, *args, **kwargs): 856 self._cb_map[callback_id] = (callback, args, kwargs)
878 """Call general function back""" 857 return callback_id
858
859 def removeCallback(self, callback_id):
860 """ Remove a previously registered callback
861 @param callback_id: id returned by [registerCallback] """
862 del self._cb_map[callback_id]
863
864 def launchCallback(self, callback_id, data=None, profile_key="@NONE@"):
865 """Launch a specific callback
866 @param callback_id: id of the action (callback) to launch
867 @param data: optional data
868 @profile_key: %(doc_profile_key)s
869 @return: a deferred which fire a dict where key can be:
870 - xmlui: a XMLUI need to be displayed
871 """
872 profile = self.memory.getProfileName(profile_key)
873 if not profile:
874 raise exceptions.ProfileUnknownError(_('trying to launch action with a non-existant profile'))
875
879 try: 876 try:
880 return self.__general_cb_map[name](*args, **kwargs) 877 callback, args, kwargs = self._cb_map[callback_id]
881 except KeyError: 878 except KeyError:
882 error(_("Trying to call unknown function (%s)") % name) 879 raise exceptions.DataError("Unknown callback id")
883 return None 880
881 if kwargs.get("with_data", False):
882 if data is None:
883 raise exceptions.DataError("Required data for this callback is missing")
884 args,kwargs=list(args)[:],kwargs.copy() # we don't want to modify the original (kw)args
885 args.insert(0, data)
886 kwargs["profile"] = profile
887 del kwargs["with_data"]
888
889 return defer.maybeDeferred(callback, *args, **kwargs)
884 890
885 #Menus management 891 #Menus management
886 892
887 def importMenu(self, category, name, callback, callback_args=None, callback_kwargs=None, help_string="", type_="NORMAL"): 893 def importMenu(self, category, name, callback, callback_args=None, callback_kwargs=None, help_string="", type_="NORMAL"):
888 """register a new menu for frontends 894 """register a new menu for frontends
923 @param name: name of the menu to call 929 @param name: name of the menu to call
924 @param type_: type of the menu to call 930 @param type_: type of the menu to call
925 @param profile_key: %(doc_profile_key)s 931 @param profile_key: %(doc_profile_key)s
926 @return: XMLUI or empty string if it's a one shot menu 932 @return: XMLUI or empty string if it's a one shot menu
927 """ 933 """
934 # TODO: menus should use launchCallback
928 profile = self.memory.getProfileName(profile_key) 935 profile = self.memory.getProfileName(profile_key)
929 if not profile: 936 if not profile:
930 raise exceptions.ProfileUnknownError 937 raise exceptions.ProfileUnknownError
931 menu_data = self.menus[(type_, category, name)] 938 menu_data = self.menus[(type_, category, name)]
932 callback = menu_data['callback'] 939 callback = menu_data['callback']