diff 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
line wrap: on
line diff
--- a/src/core/sat_main.py	Tue Dec 24 15:19:08 2013 +0100
+++ b/src/core/sat_main.py	Tue Dec 24 15:19:08 2013 +0100
@@ -42,6 +42,7 @@
 from sat.tools.xml_tools import tupleList2dataForm
 from sat.tools.misc import TriggerManager
 from glob import glob
+from uuid import uuid4
 
 try:
     from twisted.words.protocols.xmlstream import XMPPHandler
@@ -103,10 +104,8 @@
         CONST[name] = value
 
     def __init__(self):
-        #TODO: standardize callback system
-
-        self.__general_cb_map = {}  # callback called for general reasons (key = name)
-        self.__private_data = {}  # used for internal callbacks (key = id)
+        self._cb_map = {}  # map from callback_id to callbacks
+        self.__private_data = {}  # used for internal callbacks (key = id) FIXME: to be removed
         self.profiles = {}
         self.plugins = {}
         self.menus = {}  # dynamic menus. key: (type, category, name), value: menu data (dictionnary)
@@ -157,7 +156,7 @@
         self.bridge.register("updateContact", self.updateContact)
         self.bridge.register("delContact", self.delContact)
         self.bridge.register("isConnected", self.isConnected)
-        self.bridge.register("launchAction", self.launchAction)
+        self.bridge.register("launchAction", self.launchCallback)
         self.bridge.register("confirmationAnswer", self.confirmationAnswer)
         self.bridge.register("getProgress", self.getProgress)
         self.bridge.register("getMenus", self.getMenus)
@@ -388,7 +387,8 @@
 
         return next_id
 
-    def registerNewAccountCB(self, id, data, profile):
+    def registerNewAccountCB(self, data, profile):
+        # FIXME: to be removed/redone elsewhere
         user = jid.parse(self.memory.getParamA("JabberID", "Connection", profile_key=profile))[0]
         password = self.memory.getParamA("Password", "Connection", profile_key=profile)
         server = self.memory.getParamA("Server", "Connection", profile_key=profile)
@@ -407,8 +407,6 @@
             {"message": _("Are you sure to register new account [%(user)s] to server %(server)s ?") % {'user': user, 'server': server, 'profile': profile}},
             self.regisConfirmCB, profile)
         print "===============+++++++++++ REGISTER NEW ACCOUNT++++++++++++++============"
-        print "id=", id
-        print "data=", data
 
     def regisConfirmCB(self, id, accepted, data, profile):
         print _("register Confirmation CB ! (%s)") % str(accepted)
@@ -428,6 +426,7 @@
         @param fields: list of tuples (name, value)
         @return: tuple: (id, deferred)
         """
+        # FIXME: to be removed
 
         profile = self.memory.getProfileName(profile_key)
         assert(profile)
@@ -469,29 +468,6 @@
             return False
         return self.profiles[profile].isConnected()
 
-    def launchAction(self, type, data, profile_key):
-        """Launch a specific action asked by client
-        @param type: action type (button)
-        @param data: needed data to launch the action
-
-        @return: action id for result, or empty string in case or error
-        """
-        profile = self.memory.getProfileName(profile_key)
-        if not profile:
-            error(_('trying to launch action with a non-existant profile'))
-            raise Exception  # TODO: raise a proper exception
-        if type == "button":
-            try:
-                cb_name = data['callback_id']
-            except KeyError:
-                error(_("Incomplete data"))
-                return ""
-            id = sat_next_id()
-            self.callGeneralCB(cb_name, id, data, profile=profile)
-            return id
-        else:
-            error(_("Unknown action type"))
-            return ""
 
     ## jabber methods ##
 
@@ -808,6 +784,7 @@
         @param data: data (depend of confirmation conf_type)
         @param cb: callback called with the answer
         """
+        # FIXME: use XMLUI and *callback methods for dialog
         client = self.getClient(profile)
         if not client:
             raise exceptions.ProfileUnknownError(_("Asking confirmation a non-existant profile"))
@@ -863,24 +840,53 @@
             #debug("Requested progress for unknown progress_id")
         return data
 
-    def registerGeneralCB(self, name, CB):
-        """Register a callback called for general reason"""
-        self.__general_cb_map[name] = CB
-
-    def removeGeneralCB(self, name):
-        """Remove a general callback"""
-        if name not in self.__general_cb_map:
-            error(_("Trying to remove an unknow general callback"))
+    def registerCallback(self, callback, *args, **kwargs):
+        """ Register a callback.
+        Use with_data=True in kwargs if the callback use the optional data dict
+        use force_id=id to avoid generated id. Can lead to name conflict, avoid if possible
+        @param callback: any callable
+        @return: id of the registered callback
+        """
+        callback_id = kwargs.pop('force_id', None)
+        if callback_id is None:
+            callback_id = str(uuid4())
         else:
-            del self.__general_cb_map[name]
+            if callback_id in self._cb_map:
+                raise exceptions.ConflictError(_(u"id already registered"))
+        self._cb_map[callback_id] = (callback, args, kwargs)
+        return callback_id
+
+    def removeCallback(self, callback_id):
+        """ Remove a previously registered callback
+        @param callback_id: id returned by [registerCallback] """
+        del self._cb_map[callback_id]
 
-    def callGeneralCB(self, name, *args, **kwargs):
-        """Call general function back"""
+    def launchCallback(self, callback_id, data=None, profile_key="@NONE@"):
+        """Launch a specific callback
+        @param callback_id: id of the action (callback) to launch
+        @param data: optional data
+        @profile_key: %(doc_profile_key)s
+        @return: a deferred which fire a dict where key can be:
+            - xmlui: a XMLUI need to be displayed
+        """
+        profile = self.memory.getProfileName(profile_key)
+        if not profile:
+            raise exceptions.ProfileUnknownError(_('trying to launch action with a non-existant profile'))
+
         try:
-            return self.__general_cb_map[name](*args, **kwargs)
+            callback, args, kwargs = self._cb_map[callback_id]
         except KeyError:
-            error(_("Trying to call unknown function (%s)") % name)
-            return None
+            raise exceptions.DataError("Unknown callback id")
+
+        if kwargs.get("with_data", False):
+            if data is None:
+                raise exceptions.DataError("Required data for this callback is missing")
+            args,kwargs=list(args)[:],kwargs.copy() # we don't want to modify the original (kw)args
+            args.insert(0, data)
+            kwargs["profile"] = profile
+            del kwargs["with_data"]
+
+        return defer.maybeDeferred(callback, *args, **kwargs)
 
     #Menus management
 
@@ -925,6 +931,7 @@
         @param profile_key: %(doc_profile_key)s
         @return: XMLUI or empty string if it's a one shot menu
         """
+        # TODO: menus should use launchCallback
         profile = self.memory.getProfileName(profile_key)
         if not profile:
             raise exceptions.ProfileUnknownError