[conduit/new-settings] Initial work to improve settings serialization



commit 424cbf4c704d30eb23ce099220f6d7b4098f7b77
Author: John Stowers <john stowers gmail com>
Date:   Fri Apr 17 21:27:34 2009 +1200

    Initial work to improve settings serialization
    
    Improvements:
    * Modularises code
    * Supports nested settings, i.e. lists of strings, etc
---
 conduit/ModuleWrapper.py              |    7 +-
 conduit/SyncSet.py                    |   24 +++--
 conduit/XMLSerialization.py           |  176 +++++++++++++++++++++++++++++++++
 conduit/dataproviders/DataProvider.py |   60 +++---------
 4 files changed, 212 insertions(+), 55 deletions(-)

diff --git a/conduit/ModuleWrapper.py b/conduit/ModuleWrapper.py
index 2ba1c36..a93ac43 100644
--- a/conduit/ModuleWrapper.py
+++ b/conduit/ModuleWrapper.py
@@ -245,8 +245,8 @@ class ModuleWrapper:
     
         return self.descriptiveIcon
         
-    def set_configuration_xml(self, xmltext):
-        self.module.set_configuration_xml(xmltext)
+    def set_configuration_xml(self, xmltext, xmlversion):
+        self.module.set_configuration_xml(xmltext, xmlversion)
 
     def get_configuration_xml(self):
         return self.module.get_configuration_xml()
@@ -282,8 +282,9 @@ class PendingDataproviderWrapper(ModuleWrapper):
     def get_key(self):
         return self.key
 
-    def set_configuration_xml(self, xmltext):
+    def set_configuration_xml(self, xmltext, xmlversion):
         self.xmltext = xmltext
+        self.xmlversion = xmlversion
 
     def get_configuration_xml(self):
         return self.xmltext
diff --git a/conduit/SyncSet.py b/conduit/SyncSet.py
index 857ae5d..84fef59 100644
--- a/conduit/SyncSet.py
+++ b/conduit/SyncSet.py
@@ -17,7 +17,7 @@ import conduit.Settings as Settings
 
 #Increment this number when the xml settings file
 #changes format
-SETTINGS_VERSION = "1"
+SETTINGS_VERSION = "2"
 
 class SyncSet(gobject.GObject):
     """
@@ -55,7 +55,7 @@ class SyncSet(gobject.GObject):
                 except Exception:
                     log.warn("Could not uninitialize %s" % dp, exc_info=True)
                 
-    def _restore_dataprovider(self, cond, wrapperKey, dpName="", dpxml="", trySourceFirst=True):
+    def _restore_dataprovider(self, cond, wrapperKey, dpName="", dpxml="", xml_version=SETTINGS_VERSION, trySourceFirst=True):
         """
         Adds the dataprovider back onto the canvas at the specifed
         location and configures it with the given settings
@@ -68,7 +68,7 @@ class SyncSet(gobject.GObject):
             if dpxml:
                 for i in dpxml.childNodes:
                     if i.nodeType == i.ELEMENT_NODE and i.localName == "configuration":
-                        wrapper.set_configuration_xml(xmltext=i.toxml())
+                        wrapper.set_configuration_xml(xmltext=i.toxml(), xmlversion=xml_version)
         cond.add_dataprovider(wrapper, trySourceFirst)
 
     def on_dataprovider_available_unavailable(self, loader, dpw):
@@ -208,10 +208,20 @@ class SyncSet(gobject.GObject):
             
             #check the xml file is in a version we can read.
             if doc.documentElement.hasAttribute("settings-version"):
-                if SETTINGS_VERSION != doc.documentElement.getAttribute("settings-version"):
-                    log.info("%s xml file is incorrect version" % xmlSettingFilePath)
+                xml_version = doc.documentElement.getAttribute("settings-version")
+                try:
+                    xml_version = int(xml_version)
+                except ValueError, TypeError:
+                    log.error("%s xml file version is not valid" % xmlSettingFilePath)
+                    os.remove(xmlSettingFilePath)
+                    return
+                if int(SETTINGS_VERSION) < xml_version:
+                    log.warning("%s xml file is incorrect version" % xmlSettingFilePath)
                     os.remove(xmlSettingFilePath)
                     return
+            else:
+                log.info("%s xml file version not found, assuming version 1" % xmlSettingFilePath)
+                xml_version = 1
             
             #Parse...    
             for conds in doc.getElementsByTagName("conduit"):
@@ -242,7 +252,7 @@ class SyncSet(gobject.GObject):
                         name = i.getAttribute("name")
                         #add to canvas
                         if len(key) > 0:
-                            self._restore_dataprovider(cond, key, name, i, True)
+                            self._restore_dataprovider(cond, key, name, i, xml_version, True)
                     #many datasinks
                     elif i.nodeType == i.ELEMENT_NODE and i.localName == "datasinks":
                         #each datasink
@@ -252,7 +262,7 @@ class SyncSet(gobject.GObject):
                                 name = sink.getAttribute("name")
                                 #add to canvas
                                 if len(key) > 0:
-                                    self._restore_dataprovider(cond, key, name, sink, False)
+                                    self._restore_dataprovider(cond, key, name, sink, xml_version, False)
 
         except:
             log.warn("Error parsing %s. Exception:\n%s" % (xmlSettingFilePath, traceback.format_exc()))
diff --git a/conduit/XMLSerialization.py b/conduit/XMLSerialization.py
new file mode 100644
index 0000000..f4db41a
--- /dev/null
+++ b/conduit/XMLSerialization.py
@@ -0,0 +1,176 @@
+# Copyright 2009 - Andrew Stomont <andyjstormont googlemail com>
+#
+# This file is part of GPE-Mail.
+#
+# GPE-Mail is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# GPE-Mail is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GPE-Mail.  If not, see <http://www.gnu.org/licenses/>.
+
+from os.path import exists
+from xml.dom.minidom import parseString
+
+class Settings( object ):
+    """
+    A class to store/retrieve data to/from an XML file
+    """
+
+    def __init__(self, xml_text="<configuration/>", xml_version = 2):
+        """
+        Initializes Settings class
+        """
+        self.xml_document = parseString(xml_text)
+        self.xml_version = xml_version
+
+    def __string_to_type__(self, string, desired_type):
+        """
+        Converts a string into the desired scalar type
+        """
+        if desired_type == "bool":
+            return self.__bool_from_string__(string)
+        elif desired_type == "float":
+            return float(string)
+        elif desired_type == "int":
+            return int(string)
+        elif desired_type == "str":
+            return str(string) # 'just in case'
+
+    def __type_as_string__(self, data_type):
+        """
+        Returns string name of given data type
+        """
+        if type(data_type) == list:
+            return "list"
+        elif type(data_type) == tuple:
+            return "tuple"
+        elif type(data_type) == dict:
+            return "dict"
+        elif type(data_type) == int:
+            return "int"
+        elif type(data_type) == float:
+            return "float"
+        elif type(data_type) == str:
+            return "str"
+        elif type(data_type) == bool:
+            return "bool"
+
+    def __bool_from_string__(self, string):
+        """
+        Returns a bool from a string representation
+        """
+        if string == "True":
+            return True
+        else:
+            return False
+
+    def __getitem__(self, name):
+        """
+        Called when variable get via subscript interface
+        """
+        node = self.__get_data_node__(name)
+        if node:
+            return self.__node_to_data__(node)
+        else:
+            raise KeyError(name)
+
+    def __setitem__(self, name, value):
+        """
+        Called when variable set via subscript interface
+        """
+        newNode = self.__data_to_node__(name, value)
+        oldNode = self.__get_data_node__(name)
+        if oldNode:
+            self.xml_document.documentElement.replaceChild(newNode, oldNode)
+        else:
+            self.xml_document.documentElement.appendChild(newNode)
+
+    def __delitem__(self, name):
+        """
+        Deletes item from saved file
+        """
+        node = self.__get_data_node__(name)
+        if node:
+            self.xml_document.documentElement.removeChild(node)
+        else:
+            raise KeyError(name)
+
+    def __contains__(self, name):
+        """
+        This gets called by the 'in' construct
+        """
+        node = self.__get_data_node__(name)
+        if node:
+            return True
+        return False
+
+    def __get_data_node__(self, name):
+        """
+        Returns data node with given name
+        """
+        for node in self.xml_document.documentElement.childNodes:
+            if node.nodeType == node.ELEMENT_NODE and node.nodeName == name:
+                return node
+
+    def __iter__(self):
+        return self.iteritems()
+
+    def iteritems(self):
+        for node in self.xml_document.documentElement.childNodes:
+            if node.nodeType == node.ELEMENT_NODE:
+                yield node.nodeName, self.__node_to_data__(node)
+
+    def __data_to_node__(self, name, data):
+        """
+        Converts a python data type into an xml node
+        """
+        node = self.xml_document.createElement(str(name))
+        node.setAttribute("type", self.__type_as_string__(data))
+        #node.setAttribute("name", str(name))
+        if type(data) == dict:
+            for (index, value) in data.iteritems():
+                node.appendChild( self.__data_to_node__(index, value ))
+        elif type(data) == list:
+            for (index, value) in enumerate(data):
+                node.appendChild(self.__data_to_node__("item", value))
+        else:
+            node.appendChild(self.xml_document.createTextNode(str(data)))
+        return node
+
+    def __node_to_data__(self, node):
+        """
+        Returns python data from data node
+        """
+        node_type = node.getAttribute("type")
+        if node_type == "dict":
+            retval = {}
+            for childNode in node.childNodes:
+                if childNode.nodeType == node.ELEMENT_NODE:
+                    retval[childNode.nodeName] = self.__node_to_data__(childNode)
+            return retval
+        elif node_type in ("list", "tuple"):
+            if self.xml_version == 1:
+                retval = node.firstChild.data.split(',')
+                return retval
+            else:
+                retval = []
+                for childNode in node.childNodes:
+                    if childNode.nodeType == node.ELEMENT_NODE:
+                        #retval.insert(int(childNode.nodeName), self.__node_to_data__(childNode))
+                        retval.append(self.__node_to_data__(childNode))
+                if node_type == "tuple":
+                    retval = tuple(retval)
+                return retval
+        else:
+            if len(node.childNodes) > 0:
+                return self.__string_to_type__(node.firstChild.data, node_type)
+            else:
+                return ""
+
diff --git a/conduit/dataproviders/DataProvider.py b/conduit/dataproviders/DataProvider.py
index 11d839f..6b2614e 100644
--- a/conduit/dataproviders/DataProvider.py
+++ b/conduit/dataproviders/DataProvider.py
@@ -15,6 +15,7 @@ import conduit
 import conduit.ModuleWrapper as ModuleWrapper
 import conduit.utils as Utils
 import conduit.Settings as Settings
+import conduit.XMLSerialization as XMLSerialization
 
 STATUS_NONE = _("Ready")
 STATUS_CHANGE_DETECTED = _("New data to sync")
@@ -269,25 +270,11 @@ class DataProviderBase(gobject.GObject):
         Returns the dataprovider configuration as xml
         @rtype: C{string}
         """
-        doc = xml.dom.minidom.Element("configuration")
+        xml_configuration = XMLSerialization.Settings()
         configDict = self.get_configuration()
-        for config in configDict:
-                configxml = xml.dom.minidom.Element(str(config))
-                #store the value and value type
-                try:
-                    vtype = Settings.TYPE_TO_TYPE_NAME[ type(configDict[config]) ]
-                    value = Settings.TYPE_TO_STRING[  type(configDict[config]) ](configDict[config])
-                except KeyError:
-                    log.warn("Cannot convert %s to string. Value of %s not saved" % (type(configDict[config]), config))
-                    vtype = Settings.TYPE_TO_TYPE_NAME[str]
-                    value = Settings.TYPE_TO_STRING[str](configDict[config])
-                configxml.setAttribute("type", vtype)
-                valueNode = xml.dom.minidom.Text()
-                valueNode.data = value
-                configxml.appendChild(valueNode)
-                doc.appendChild(configxml)
-
-        return doc.toxml()
+        for name, value in configDict.iteritems():
+            xml_configuration[name] = value
+        return xml_configuration.xml_document.toxml()
 
     def _get_configuration_parameters(self, configuration):
         '''
@@ -378,39 +365,22 @@ class DataProviderBase(gobject.GObject):
                         callable(getattr(self, c, None)))
                         )
 
-    def set_configuration_xml(self, xmltext):
+    def set_configuration_xml(self, xmltext, xmlversion):
         """
         Restores applications settings from XML
 
         @param xmltext: xml representation of settings
         @type xmltext: C{string}
         """
-        doc = xml.dom.minidom.parseString(xmltext)
-        configxml = doc.documentElement
-
-        if configxml.nodeType == configxml.ELEMENT_NODE and configxml.localName == "configuration":
-            settings = {}
-            for s in configxml.childNodes:
-                if s.nodeType == s.ELEMENT_NODE:
-                    #now convert the setting to the correct type (if filled out)
-                    if s.hasChildNodes():
-                        raw = s.firstChild.data
-                        vtype = s.getAttribute("type")
-                        try:
-                            data = Settings.STRING_TO_TYPE[vtype](raw)
-                        except KeyError:
-                            #fallback to string type
-                            log.warn("Cannot convert string (%s) to native type %s\n" % (raw, vtype, traceback.format_exc()))
-                            data = str(raw)
-                        settings[s.localName] = data
-
-            try:
-                self.set_configuration(settings)
-            except Exception, err: 
-                log.warn("Error restoring %s configuration\n%s" % 
-                        (self._name_, traceback.format_exc()))
-        else:
-            log.debug("Could not find <configuration> xml fragment")
+        xml_configuration = XMLSerialization.Settings(xmltext, xmlversion)
+        settings = {}
+        for name, value in xml_configuration:
+            settings[name] = value
+        try:
+            self.set_configuration(settings)
+        except Exception, err: 
+            log.warn("Error restoring %s configuration\n%s" % 
+                    (self._name_, traceback.format_exc()))
 
     def get_UID(self):
         """



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]