Mercurial > libervia-backend
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'] |