diff src/tools/memory.py @ 423:6c20c76abdcc

backend: - bridge async D-Bus method now automatically manage callback and errback, we just have to return a deferred - getParams, getParamsForCategory and getParamsUI are now asynchronous primitivus: management of asynchronous getParamsUI
author Goffi <goffi@goffi.org>
date Mon, 07 Nov 2011 00:09:22 +0100
parents acd908528ef7
children e4e9187e3b5b
line wrap: on
line diff
--- a/src/tools/memory.py	Sun Nov 06 15:19:51 2011 +0100
+++ b/src/tools/memory.py	Mon Nov 07 00:09:22 2011 +0100
@@ -86,13 +86,15 @@
         @return: deferred triggered once params are loaded"""
         return self.storage.loadGenParams(self.params_gen)
         
-    def loadIndParams(self, profile):
+    def loadIndParams(self, profile, cache=None):
         """Load individual parameters
-        set self.params cache
+        set self.params cache or a temporary cache
         @param profile: profile to load (*must exist*)
+        @param cache: if not None, will be used to store the value, as a short time cache
         @return: deferred triggered once params are loaded"""
-        self.params[profile] = {}
-        return self.storage.loadIndParams(self.params, profile)
+        if cache == None:
+            self.params[profile] = {}
+        return self.storage.loadIndParams(self.params[profile] if cache==None else cache, profile)
    
     def purgeProfile(self, profile):
         """Remove cache data of a profile
@@ -129,25 +131,20 @@
         self.storage.createProfile(profile)
         return False
 
-    def asyncCreateProfile(self, profile, callback, errback):
+    def asyncCreateProfile(self, profile):
         """Create a new profile
         @param profile: name of the profile
         @param callback: called when the profile actually exists in database and memory
         @param errback: called with a string constant as parameter:
                         - CONFLICT: the profile already exists
                         - CANCELED: profile creation canceled
-                        - DATABASE: profile creation in database failed"""
-        #FIXME: must be asynchronous and call the callback once the profile actually exists
+        """
         if self.storage.hasProfile(profile):
             info (_('The profile name already exists'))
-            errback("CONFLICT")
-            return
+            return defer.fail("CONFLICT")
         if not self.host.trigger.point("ProfileCreation", profile):
-            errback("CANCELED")
-            return
-        d = self.storage.createProfile(profile)
-        d.addCallback(lambda ignore: callback())
-        d.addErrback(lambda ignore: errback("DATABASE"))
+            return defer.fail("CANCEL")
+        return self.storage.createProfile(profile)
 
 
     def deleteProfile(self, profile):
@@ -175,7 +172,7 @@
             default = self.host.memory.getPrivate('Profile_default')
             if not default or not default in self.params:
                 info(_('No default profile, returning first one')) #TODO: manage real default profile
-                default = self.getProfilesList()[0]
+                default = self.storage.getProfilesList()[0]
                 self.host.memory.setPrivate('Profile_default', default)
             return default #FIXME: temporary, must use real default value, and fallback to first one if it doesn't exists
         if not self.storage.hasProfile(profile_key):
@@ -274,14 +271,12 @@
         else:
             return node[1].getAttribute(attr)
 
-    def asyncGetParamA(self, name, category, attr="value", profile_key="@DEFAULT@", callback=None, errback=None):
+    def asyncGetParamA(self, name, category, attr="value", profile_key="@DEFAULT@"):
         """Helper method to get a specific attribute
            @param name: name of the parameter
            @param category: category of the parameter
            @param attr: name of the attribute (default: "value")
-           @param profile: owner of the param (@ALL@ for everyone)
-           @param callback: called when the profile is connected
-           @param errback: called is the connection fail"""
+           @param profile: owner of the param (@ALL@ for everyone)"""
         node = self.__getParamNode(name, category)
         if not node:
             error(_("Requested param [%(name)s] in category [%(category)s] doesn't exist !") % {'name':name, 'category':category})
@@ -289,132 +284,146 @@
 
         if node[0] == 'general':
             value = self.__getParam(None, category, name, 'general')
-            callback(value if value!=None else node[1].getAttribute(attr))
-            return
+            return defer.succeed(value if value!=None else node[1].getAttribute(attr))
         
         assert(node[0] == 'individual')
 
         profile = self.getProfileName(profile_key)
         if not profile:
             error(_('Requesting a param for a non-existant profile'))
-            errback()
-            return
+            return defer.fail()
         
         if attr != "value": 
-            callback(node[1].getAttribute(attr))
-            return
+            return defer.succeed(node[1].getAttribute(attr))
         default = node[1].getAttribute(attr)
         try:
             value = self.__getParam(profile, category, name)
-            callback(value if value!=None else default)
+            return defer.succeed(value if value!=None else default)
         except ProfileNotInCacheError:
             #We have to ask data to the storage manager
             d = self.storage.getIndParam(category, name, profile)
-            d.addCallback(lambda value: callback(value if value!=None else default))
-            d.addErrback(lambda x:errback())
+            return d.addCallback(lambda value: value if value!=None else default)
 
-    def __getParam(self, profile, category, name, type='individual'):
+    def __getParam(self, profile, category, name, type='individual', cache=None):
         """Return the param, or None if it doesn't exist
         @param profile: the profile name (not profile key, i.e. name and not something like @DEFAULT@)
         @param category: param category
         @param name: param name
+        @param type: "general" or "individual"
+        @param cache: temporary cache, to use when profile is not logged
+        @return: param value or None if it doesn't exist
         """
         if type == 'general':
             if self.params_gen.has_key((category, name)):
                 return self.params_gen[(category, name)]
             return None  #This general param has the default value
         assert (type == 'individual')
-        if not self.params.has_key(profile):
+        if self.params.has_key(profile):
+            cache = self.params[profile] # if profile is in main cache, we use it,
+                                         # ignoring the temporary cache
+        elif cache == None: #else we use the temporary cache if it exists, or raise an exception
             raise ProfileNotInCacheError
-        if not self.params[profile].has_key((category, name)):
+        if not cache.has_key((category, name)):
             return None
-        return self.params[profile][(category, name)]
+        return cache[(category, name)]
 
     def __constructProfileXml(self, profile):
         """Construct xml for asked profile, filling values when needed
         /!\ as noticed in doc, don't forget to unlink the minidom.Document
         @param profile: profile name (not key !)
-        @return: minidom.Document of the profile xml (cf warning above)
+        @return: a deferred that fire a minidom.Document of the profile xml (cf warning above)
         """
-        #TODO: asynchronous equivalent
-        prof_xml = minidom.parseString('<params/>')
-        cache = {}
+        def constructProfile(ignore,profile_cache):
+            prof_xml = minidom.parseString('<params/>')
+            cache = {}
+
+            for type_node in self.dom.documentElement.childNodes:
+                if type_node.nodeName == 'general' or type_node.nodeName == 'individual':  #we use all params, general and individual
+                    for cat_node in type_node.childNodes:
+                        if cat_node.nodeName == 'category':
+                            category = cat_node.getAttribute('name')
+                            if not cache.has_key(category):
+                                cache[category] = dest_cat = cat_node.cloneNode(True) #we make a copy for the new xml
+                                new_node = True
+                            else:
+                                dest_cat = cache[category]
+                                new_node = False #It's not a new node, we will merge information
+                            params = cat_node.getElementsByTagName("param")
+                            dest_params = {}
+                            for node in dest_cat.childNodes:
+                                if node.nodeName != "param":
+                                    continue
+                                dest_params[node.getAttribute('name')] = node
 
-        for type_node in self.dom.documentElement.childNodes:
-            if type_node.nodeName == 'general' or type_node.nodeName == 'individual':  #we use all params, general and individual
-                for cat_node in type_node.childNodes:
-                    if cat_node.nodeName == 'category':
-                        category = cat_node.getAttribute('name')
-                        if not cache.has_key(category):
-                            cache[category] = dest_cat = cat_node.cloneNode(True) #we make a copy for the new xml
-                            new_node = True
-                        else:
-                            dest_cat = cache[category]
-                            new_node = False #It's not a new node, we will merge information
-                        params = cat_node.getElementsByTagName("param")
-                        dest_params = {}
-                        for node in dest_cat.childNodes:
-                            if node.nodeName != "param":
-                                continue
-                            dest_params[node.getAttribute('name')] = node
+                            for param_node in params:
+                                name = param_node.getAttribute('name')
+                                
+                                if name not in dest_params:
+                                    dest_params[name] = param_node.cloneNode(True)
+                                    dest_cat.appendChild(dest_params[name])
 
-                        for param_node in params:
-                            name = param_node.getAttribute('name')
-                            
-                            if name not in dest_params:
-                                dest_params[name] = param_node.cloneNode(True)
-                                dest_cat.appendChild(dest_params[name])
+                                profile_value = self.__getParam(profile, category, name, type_node.nodeName, cache=profile_cache)
+                                if profile_value!=None:  #there is a value for this profile, we must change the default
+                                    dest_params[name].setAttribute('value', profile_value)
+                            if new_node:
+                                prof_xml.documentElement.appendChild(dest_cat)
+            return prof_xml
+        
+        
+        if self.params.has_key(profile):
+            d = defer.succeed(None)
+            profile_cache = self.params[profile]
+        else:
+            #profile is not in cache, we load values in a short time cache
+            profile_cache = {}
+            d = self.loadIndParams(profile, profile_cache)
 
-                            profile_value = self.__getParam(profile, category, name, type_node.nodeName)
-                            if profile_value!=None:  #there is a value for this profile, we must change the default
-                                dest_params[name].setAttribute('value', profile_value)
-                        if new_node:
-                            prof_xml.documentElement.appendChild(dest_cat)
-        return prof_xml
-
+        return d.addCallback(constructProfile, profile_cache)
 
     def getParamsUI(self, profile_key):
         """Return a SàT XMLUI for parameters, with given profile"""
-        #TODO: asynchronous equivalent
         profile = self.getProfileName(profile_key)
         if not profile:
             error(_("Asking params for inexistant profile"))
             return ""
-        param_xml = self.getParams(profile)
-        return paramsXml2xmlUI(param_xml)
+        d = self.getParams(profile)
+        return d.addCallback(lambda param_xml:paramsXml2xmlUI(param_xml))
 
     def getParams(self, profile_key):
         """Construct xml for asked profile
         Take params xml as skeleton"""
-        #TODO: asynchronous equivalent
         profile = self.getProfileName(profile_key)
         if not profile:
             error(_("Asking params for inexistant profile"))
             return ""
-        prof_xml = self.__constructProfileXml(profile) 
-        return_xml = prof_xml.toxml()
-        prof_xml.unlink()
+        
+        def returnXML(prof_xml):
+            return_xml = prof_xml.toxml()
+            prof_xml.unlink()
+            return return_xml
 
-        return return_xml
+        return self.__constructProfileXml(profile).addCallback(returnXML) 
 
     def getParamsForCategory(self, category, profile_key):
         """Return node's xml for selected category"""
         #TODO: manage category of general type (without existant profile)
-        #TODO: asynchronous equivalent
         profile = self.getProfileName(profile_key)
         if not profile:
             error(_("Asking params for inexistant profile"))
             return ""
-        prof_xml = self.__constructProfileXml(profile) 
+
+        def returnCategoryXml(prof_xml):
+            for node in prof_xml.getElementsByTagName("category"):
+                if node.nodeName == "category" and node.getAttribute("name") == category:
+                    result = node.toxml()
+                    prof_xml.unlink()
+                    return result
+
+            prof_xml.unlink()
+            return "<category />"
         
-        for node in prof_xml.getElementsByTagName("category"):
-            if node.nodeName == "category" and node.getAttribute("name") == category:
-                result = node.toxml()
-                prof_xml.unlink()
-                return result
-
-        prof_xml.unlink()
-        return "<category />"
+        d = self.__constructProfileXml(profile)
+        return d.addCallback(returnCategoryXml)
 
     def __getParamNode(self, name, category, type="@ALL@"): #FIXME: is type useful ?
         """Return a node from the param_xml
@@ -463,7 +472,7 @@
         if node[0] == 'general':
             self.params_gen[(category, name)] = value
             self.storage.setGenParam(category, name, value)
-            for profile in self.getProfilesList():
+            for profile in self.storage.getProfilesList():
                 if self.host.isConnected(profile):
                     self.host.bridge.paramUpdate(name, value, category, profile)
             return
@@ -625,11 +634,11 @@
         """
         return self.params.createProfile(name)
     
-    def asyncCreateProfile(self, name, callback, errback):
+    def asyncCreateProfile(self, name):
         """Create a new profile
         @param name: Profile name
         """
-        return self.params.asyncCreateProfile(name, callback, errback)
+        return self.params.asyncCreateProfile(name)
     
     def deleteProfile(self, name):
         """Delete an existing profile
@@ -839,8 +848,8 @@
     def getParamA(self, name, category, attr="value", profile_key='@DEFAULT@'):
         return self.params.getParamA(name, category, attr, profile_key)
     
-    def asyncGetParamA(self, name, category, attr="value", profile_key='@DEFAULT@', callback=None, errback=None):
-        return self.params.asyncGetParamA(name, category, attr, profile_key, callback, errback)
+    def asyncGetParamA(self, name, category, attr="value", profile_key='@DEFAULT@'):
+        return self.params.asyncGetParamA(name, category, attr, profile_key)
     
     def getParamsUI(self, profile_key):
         return self.params.getParamsUI(profile_key)