"single file" backend



Hey,
	So, at GUADEC Michael brought up the issue again of GConf having so
many little XML files scattered across the disk and there seemed to be
some agreement that it would be worth at least giving a backend which
read/writes to a single file a try.

	I implemented this in the markup backend, but with a little twist.
Instead of a single file, you can just reduce the number of files used
by the backend - e.g. each app's configuration might be saved to a
single file ...

	The way it works is that if a dir contains %gconf-tree.xml, then it is
assumed to contain the entire tree under that directory and, therefore,
%gconf.xml and any sub-directories on the filesystem is ingored. I've
also included  a little utility to merge a part of the tree into a
single file ...

	After all that, I benchmarked some of the possibilities with the simple
test case of dumping the entire GConf tree. Not necessarily the case we
are trying to speed up, but the results were quite interesting.

	So, the four cases timed below are:

	1) Using the XML backend
	2) Using the markup backend, with a file for each dir
	3) Using the markup backend with a single file for the 
	   entire tree 
	4) Using the markup backend with a single file for each
	   subdir of /apps and /schemas/apps

	Its pretty clear from the timings that (4) is marginally better than
(3) which is better than (2), but by far the biggest difference is
between the markup backend and the XML backend :-)

	Anyway, it would be good to some better benchmarking of things like
application startup[1] with each case, especially on some slower
machines. You're pretty good a doing these kind of comparisons Michael
... :-)

	Attached is the patch to the markup backend (which also fixes quite a
few things in the backend), the script I used for the benchmarks and the
results.

Good Luck,
Mark.

[1] - Amazingly, with case (4) - that's all I've tried so far - the
panel starts up as fast as the terminal ... and the panel easily took 3
to 4 times as long before that. That suggests to me that the panel is
doing something silly with GConf, though ...


Index: Makefile.am
===================================================================
RCS file: /cvs/gnome/gconf/backends/Makefile.am,v
retrieving revision 1.28
diff -u -p -r1.28 Makefile.am
--- Makefile.am	26 Apr 2003 07:21:09 -0000	1.28
+++ Makefile.am	6 Jul 2003 15:56:50 -0000
@@ -4,8 +4,7 @@ INCLUDES= -I$(top_srcdir) -I$(top_buildd
 
 backenddir = $(pkglibdir)/$(MAJOR_VERSION)
 
-backend_LTLIBRARIES = libgconfbackend-xml.la
-noinst_LTLIBRARIES = libgconfbackend-markup.la
+backend_LTLIBRARIES = libgconfbackend-xml.la libgconfbackend-markup.la
 
 libgconfbackend_xml_la_SOURCES = \
 	xml-cache.h		\
@@ -31,3 +30,7 @@ noinst_PROGRAMS = xml-test
 
 xml_test_SOURCES= $(libgconfbackend_xml_la_SOURCES) xml-test.c
 xml_test_LDADD = $(DEPENDENT_WITH_XML_LIBS) $(top_builddir)/gconf/libgconf-$(MAJOR_VERSION).la
+
+bin_PROGRAMS = gconf-merge-tree
+gconf_merge_tree_SOURCES = gconf-merge-tree.c
+gconf_merge_tree_LDADD = $(DEPENDENT_LIBS) $(top_builddir)/gconf/libgconf-$(MAJOR_VERSION).la
Index: gconf-merge-tree.c
===================================================================
RCS file: gconf-merge-tree.c
diff -N gconf-merge-tree.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ gconf-merge-tree.c	6 Jul 2003 15:56:50 -0000
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2001 Sun Microsystems, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors:
+ *     Mark McLoughlin <mark skynet ie>
+ */
+
+#include "markup-tree.c"
+
+guint
+_gconf_mode_t_to_mode (mode_t orig)
+{
+  /* I don't think this is portable. */
+  guint mode = 0;
+  guint fullmask = S_IRWXG | S_IRWXU | S_IRWXO;
+  
+  mode = orig & fullmask;
+  
+  g_return_val_if_fail (mode <= 0777, 0700);
+  
+  return mode;
+}
+
+static void
+recursively_load_entire_tree (MarkupDir *dir)
+{
+  GSList *tmp;
+
+  load_entries (dir);
+  load_subdirs (dir);
+
+  tmp = dir->subdirs;
+  while (tmp != NULL)
+    {
+      MarkupDir *subdir = tmp->data;
+
+      recursively_load_entire_tree (subdir);
+
+      tmp = tmp->next;
+    }
+}
+
+static gboolean
+merge_tree (const char *root_dir)
+{
+  struct stat statbuf;
+  guint dir_mode;
+  guint file_mode;
+  MarkupTree *tree;
+  GError *error;
+
+  if (stat (root_dir, &statbuf) == 0)
+    {
+      dir_mode = _gconf_mode_t_to_mode (statbuf.st_mode);
+      /* dir_mode without search bits */
+      file_mode = dir_mode & (~0111);
+    }
+  else
+    {
+      fprintf (stderr, _("Cannot find directory %s\n"), root_dir);
+      return FALSE;
+
+    }
+
+  tree = markup_tree_get (root_dir, dir_mode, file_mode);
+
+  recursively_load_entire_tree (tree->root);
+
+  error = NULL;
+  save_tree (tree->root, TRUE, file_mode, &error);
+  if (error)
+    {
+      char *markup_file;
+
+      markup_file = markup_dir_build_path (tree->root, TRUE, TRUE);
+      fprintf (stderr, _("Error saving GConf tree to '%s': %s\n"),
+	       markup_file,
+	       error->message);
+      g_error_free (error);
+      g_free (markup_file);
+      markup_tree_unref (tree);
+      return FALSE;
+    }
+
+  tree->root->entries_need_save = FALSE;
+  tree->root->some_subdir_needs_sync = FALSE;
+
+  markup_tree_unref (tree);
+
+  return TRUE;
+}
+
+int
+main (int argc, char **argv)
+{
+  setlocale (LC_ALL, "");
+  _gconf_init_i18n ();
+  textdomain (GETTEXT_PACKAGE);
+
+  if (argc != 2)
+    {
+      fprintf (stderr, _("Usage: %s <dir>\n"), argv [0]);
+      return 1;
+    }
+
+  if (!strcmp (argv [1], "--help"))
+    {
+      printf (_("Usage: %s <dir>\n"
+		"  Merges a markup backend filesystem hierarchy like:\n"
+		"    dir/%%gconf.xml\n"
+		"        subdir1/%%gconf.xml\n"
+		"        subdir2/%%gconf.xml\n"
+		"  to:\n"
+		"    dir/%%gconf-tree.xml\n"), argv [0]);
+      return 0;
+    }
+
+  return !merge_tree (argv [1]);
+}
+
Index: markup-tree.c
===================================================================
RCS file: /cvs/gnome/gconf/backends/markup-tree.c,v
retrieving revision 1.6
diff -u -p -r1.6 markup-tree.c
--- markup-tree.c	31 May 2003 15:31:36 -0000	1.6
+++ markup-tree.c	6 Jul 2003 15:56:51 -0000
@@ -57,14 +57,16 @@ static LocalSchemaInfo* local_schema_inf
 static void             local_schema_info_free (LocalSchemaInfo *info);
 
 
-static MarkupDir* markup_dir_new        (MarkupTree *tree,
-                                         MarkupDir  *parent,
-                                         const char *name);
-static void       markup_dir_free       (MarkupDir  *dir);
-static gboolean   markup_dir_needs_sync (MarkupDir  *dir);
-static gboolean   markup_dir_sync       (MarkupDir  *dir);
-static char*      markup_dir_build_path (MarkupDir  *dir,
-                                         gboolean    with_data_file);
+static MarkupDir* markup_dir_new                    (MarkupTree *tree,
+						     MarkupDir  *parent,
+						     const char *name);
+static void       markup_dir_free                   (MarkupDir  *dir);
+static gboolean   markup_dir_needs_sync             (MarkupDir  *dir);
+static gboolean   markup_dir_sync                   (MarkupDir  *dir);
+static char*      markup_dir_build_path             (MarkupDir  *dir,
+						     gboolean    with_data_file,
+						     gboolean    subtree_data_file);
+static void       markup_dir_flag_entries_need_save (MarkupDir  *dir);
 
 static MarkupEntry* markup_entry_new             (MarkupDir   *dir,
                                                   const char  *name);
@@ -74,12 +76,13 @@ static void         markup_entry_set_mod
 static void         markup_entry_set_mod_time    (MarkupEntry *entry,
                                                   GTime        mtime);
 
-static GSList*  parse_entries (const char  *filename,
-                               GError     **err);
-static gboolean save_entries  (const char  *filename,
-                               guint        file_mode,
-                               GSList      *entries,
-                               GError     **err);
+static void parse_tree (MarkupDir  *root,
+			gboolean    parse_subtree,
+			GError    **err);
+static void save_tree  (MarkupDir  *root,
+			gboolean    save_as_subtree,
+			guint       file_mode,
+			GError    **err);
 
 
 struct _MarkupTree
@@ -158,7 +161,7 @@ markup_tree_unref (MarkupTree *tree)
 void
 markup_tree_rebuild (MarkupTree *tree)
 {
-  g_return_if_fail (markup_dir_needs_sync (tree->root));
+  g_return_if_fail (!markup_dir_needs_sync (tree->root));
 
   markup_dir_free (tree->root);
   tree->root = markup_dir_new (tree, NULL, "/");  
@@ -188,6 +191,12 @@ struct _MarkupDir
 
   /* We are pretty sure the filesystem dir exists */
   guint filesystem_dir_probably_exists : 1;
+
+  /* Not represented by an actual filesystem dir */
+  guint not_in_filesystem : 1;
+
+  /* Save to %gconf-tree.xml when syncing */
+  guint save_as_subtree : 1;
 };
 
 static MarkupDir*
@@ -209,8 +218,28 @@ markup_dir_new (MarkupTree *tree,
 static void
 markup_dir_free (MarkupDir *dir)
 {
-  /* FIXME free the entries */
-  
+  GSList *tmp;
+
+  tmp = dir->entries;
+  while (tmp)
+    {
+      MarkupEntry *entry = tmp->data;
+
+      markup_entry_free (entry);
+
+      tmp = tmp->next;
+    }
+
+  tmp = dir->subdirs;
+  while (tmp)
+    {
+      MarkupDir *subdir = tmp->data;
+
+      markup_dir_free (subdir);
+
+      tmp = tmp->next;
+    }
+
   g_free (dir->name);
 
   g_free (dir);
@@ -325,34 +354,27 @@ markup_tree_sync (MarkupTree *tree,
 }
 
 static gboolean
-load_entries (MarkupDir *dir)
+load_subtree (MarkupDir *dir)
 {
-  /* Load the entries in this directory */
+  /* Load subtree from %gconf-tree.xml if exists */
+
+  GError *tmp_err = NULL;
   char *markup_file;
-  GSList *entries;
-  GError *tmp_err;
-  GSList *tmp;
-  
-  if (dir->entries_loaded)
-    return TRUE;
 
-  /* We mark it loaded even if the next stuff
-   * fails, because we don't want to keep trying and
-   * failing, plus we have invariants
-   * that assume entries_loaded is TRUE once we've
-   * called load_entries()
-   */
-  dir->entries_loaded = TRUE;
-  
-  markup_file = markup_dir_build_path (dir, TRUE);
+  markup_file = markup_dir_build_path (dir, TRUE, TRUE);
+  if (!gconf_file_exists (markup_file))
+    {
+      g_free (markup_file);
+      return FALSE;
+    }
 
-  tmp_err = NULL;
-  entries = parse_entries (markup_file, &tmp_err);
-  
+  dir->subdirs_loaded  = TRUE;
+  dir->entries_loaded  = TRUE;
+  dir->save_as_subtree = TRUE;
+
+  parse_tree (dir, TRUE, &tmp_err);
   if (tmp_err)
     {
-      g_assert (entries == NULL);
-
       /* note that tmp_err may be a G_MARKUP_ERROR while only
        * GCONF_ERROR could be returned, so if we decide to return this
        * error someday we need to fix it up first
@@ -362,29 +384,58 @@ load_entries (MarkupDir *dir)
        * when creating a new directory
        */
       gconf_log (GCL_DEBUG,
-                 "Failed to load file \"%s\": %s",
-                 markup_file, tmp_err->message);
+		 "Failed to load file \"%s\": %s",
+		 markup_file, tmp_err->message);
       g_error_free (tmp_err);
-      g_free (markup_file);
-      return FALSE;
     }
 
   g_free (markup_file);
 
-  g_assert (dir->entries == NULL);
-  dir->entries = entries;
+  return TRUE;
+}
 
-  /* Fill in entry->dir */
-  tmp = dir->entries;
-  while (tmp != NULL)
+static gboolean
+load_entries (MarkupDir *dir)
+{
+  /* Load the entries in this directory */
+  
+  if (dir->entries_loaded)
+    return TRUE;
+
+  /* We mark it loaded even if the next stuff
+   * fails, because we don't want to keep trying and
+   * failing, plus we have invariants
+   * that assume entries_loaded is TRUE once we've
+   * called load_entries()
+   */
+  dir->entries_loaded = TRUE;
+
+  if (!load_subtree (dir))
     {
-      MarkupEntry *entry = tmp->data;
+      GError *tmp_err = NULL;
 
-      entry->dir = dir;
-      
-      tmp = tmp->next;
+      parse_tree (dir, FALSE, &tmp_err);
+      if (tmp_err)
+	{
+	  char *markup_file;
+
+	  /* note that tmp_err may be a G_MARKUP_ERROR while only
+	   * GCONF_ERROR could be returned, so if we decide to return this
+	   * error someday we need to fix it up first
+	   */
+
+	  /* this message is debug-only because it usually happens
+	   * when creating a new directory
+	   */
+	  markup_file = markup_dir_build_path (dir, TRUE, FALSE);
+	  gconf_log (GCL_DEBUG,
+		     "Failed to load file \"%s\": %s",
+		     markup_file, tmp_err->message);
+	  g_error_free (tmp_err);
+	  g_free (markup_file);
+	}
     }
-  
+
   return TRUE;
 }
 
@@ -414,7 +465,10 @@ load_subdirs (MarkupDir *dir)
 
   g_assert (dir->subdirs == NULL);
 
-  markup_dir = markup_dir_build_path (dir, FALSE);
+  if (load_subtree (dir))
+    return TRUE;
+
+  markup_dir = markup_dir_build_path (dir, FALSE, FALSE);
   
   dp = opendir (markup_dir);
   
@@ -536,10 +590,9 @@ markup_dir_ensure_entry (MarkupDir   *di
   
   /* Create a new entry */
   entry = markup_entry_new (dir, relative_key);
-  dir->entries = g_slist_prepend (dir->entries, entry);
 
   /* Need to save this */
-  dir->entries_need_save = TRUE;
+  markup_dir_flag_entries_need_save (dir);
   markup_dir_queue_sync (dir);
   
   return entry;
@@ -589,7 +642,7 @@ markup_dir_ensure_subdir (MarkupDir   *d
       g_return_val_if_fail (dir->subdirs_loaded, NULL);
       
       subdir = markup_dir_new (dir->tree, dir, relative_key);
-      subdir->entries_need_save = TRUE; /* so we save empty %gconf.xml */
+      markup_dir_flag_entries_need_save (subdir); /* so we save empty %gconf.xml */
       
       /* we don't need to load stuff, since we know the dir didn't exist */
       subdir->entries_loaded = TRUE;
@@ -633,6 +686,22 @@ markup_dir_needs_sync (MarkupDir *dir)
 }
 
 static void
+markup_dir_flag_entries_need_save (MarkupDir *dir)
+{
+
+  if (!dir->not_in_filesystem)
+    dir->entries_need_save = TRUE;
+
+  else
+    {
+      /* root must be a filesystem dir */
+      g_assert (dir->parent);
+
+      markup_dir_flag_entries_need_save (dir->parent);
+    }
+}
+
+static void
 markup_entry_clean_old_local_schemas (MarkupEntry *entry)
 {
 
@@ -720,28 +789,32 @@ delete_useless_subdirs (MarkupDir *dir)
           subdir->entries == NULL &&
           subdir->subdirs == NULL)
         {
-          char *fs_dirname;
-          char *fs_filename;
-          
-          fs_dirname = markup_dir_build_path (subdir, FALSE);
-          fs_filename = markup_dir_build_path (subdir, TRUE);
+
+	  if (!dir->not_in_filesystem)
+	    {
+	      char *fs_dirname;
+	      char *fs_filename;
           
-          if (unlink (fs_filename) < 0)
-            {
-              gconf_log (GCL_WARNING,
-                         _("Could not remove \"%s\": %s\n"),
-                         fs_filename, strerror (errno));
-            }
+	      fs_dirname = markup_dir_build_path (subdir, FALSE, FALSE);
+	      fs_filename = markup_dir_build_path (subdir, TRUE, dir->save_as_subtree);
 
-          if (rmdir (fs_dirname) < 0)
-            {
-              gconf_log (GCL_WARNING,
-                         _("Could not remove \"%s\": %s\n"),
-                         fs_dirname, strerror (errno));
-            }
+	      if (unlink (fs_filename) < 0)
+		{
+		  gconf_log (GCL_WARNING,
+			     _("Could not remove \"%s\": %s\n"),
+			     fs_filename, strerror (errno));
+		}
+
+	      if (rmdir (fs_dirname) < 0)
+		{
+		  gconf_log (GCL_WARNING,
+			     _("Could not remove \"%s\": %s\n"),
+			     fs_dirname, strerror (errno));
+		}
           
-          g_free (fs_dirname);
-          g_free (fs_filename);
+	      g_free (fs_dirname);
+	      g_free (fs_filename);
+	    }
 
           markup_dir_free (subdir);
 
@@ -759,7 +832,30 @@ delete_useless_subdirs (MarkupDir *dir)
   dir->subdirs = g_slist_reverse (kept_subdirs);
 
   return some_deleted;
-} 
+}
+
+static gboolean
+delete_useless_subdirs_recurse (MarkupDir *dir)
+{
+  GSList *tmp;
+  gboolean retval = FALSE;
+
+  tmp = dir->subdirs;
+  while (tmp)
+    {
+      MarkupDir *subdir = tmp->data;
+
+      if (delete_useless_subdirs (subdir))
+	retval = TRUE;
+
+      tmp = tmp->next;
+    }
+
+  if (delete_useless_subdirs (dir))
+    retval = TRUE;
+
+  return retval;
+}
 
 static gboolean
 delete_useless_entries (MarkupDir *dir)
@@ -805,6 +901,7 @@ markup_dir_sync (MarkupDir *dir)
 {
   char *fs_dirname;
   char *fs_filename;
+  char *fs_subtree;
   GSList *tmp;
   gboolean some_useless_entries;
   gboolean some_useless_subdirs;
@@ -812,25 +909,14 @@ markup_dir_sync (MarkupDir *dir)
   some_useless_entries = FALSE;
   some_useless_subdirs = FALSE;
 
-#if 0
-  {
-    MarkupDir *parent;
-
-    parent = dir->parent;
-    while (parent)
-      {
-        fputs ("  ", stdout);
-        parent = parent->parent;
-      }
-
-    g_print ("%s\n", dir->name);
-  }
-#endif
-    
   /* We assume our parent directories have all been synced, before
    * we are synced. So we don't need to mkdir() parent directories.
    */
 
+  /* We must have been synced already */
+  if (dir->not_in_filesystem)
+    return TRUE;
+
   /* Sanitize the entries */
   tmp = dir->entries;
   while (tmp != NULL)
@@ -842,8 +928,9 @@ markup_dir_sync (MarkupDir *dir)
       tmp = tmp->next;
     }
   
-  fs_dirname = markup_dir_build_path (dir, FALSE);
-  fs_filename = markup_dir_build_path (dir, TRUE);
+  fs_dirname = markup_dir_build_path (dir, FALSE, FALSE);
+  fs_filename = markup_dir_build_path (dir, TRUE, FALSE);
+  fs_subtree = markup_dir_build_path (dir, TRUE, TRUE);
 
   /* For a dir to be loaded as a subdir, it must have a
    * %gconf.xml file, even if it has no entries in that
@@ -851,7 +938,8 @@ markup_dir_sync (MarkupDir *dir)
    * even though the entry list is initially empty.
    */
   
-  if (dir->entries_need_save)
+  if (dir->entries_need_save ||
+      (dir->some_subdir_needs_sync && dir->save_as_subtree))
     {
       GError *err;
       
@@ -869,22 +957,24 @@ markup_dir_sync (MarkupDir *dir)
       
       /* Now write the file */
       err = NULL;
-      save_entries (fs_filename, dir->tree->file_mode, dir->entries, &err);
+      save_tree (dir, dir->save_as_subtree, dir->tree->file_mode, &err);
       if (err != NULL)
         {
           gconf_log (GCL_WARNING,
                      _("Failed to write \"%s\": %s\n"),
-                     fs_filename, err->message);
+                     !dir->save_as_subtree ? fs_filename : fs_subtree, err->message);
           
           g_error_free (err);
         }
       else
         {
           dir->entries_need_save = FALSE;
+	  if (dir->save_as_subtree)
+	    dir->some_subdir_needs_sync = FALSE;
         }
     }
 
-  if (dir->some_subdir_needs_sync)
+  if (dir->some_subdir_needs_sync && !dir->save_as_subtree)
     {
       GSList *tmp;
       gboolean one_failed;
@@ -929,12 +1019,24 @@ markup_dir_sync (MarkupDir *dir)
    * we're deleting our subdirs, the root dir (tree->root)
    * never gets deleted - this is intentional.
    */
-  if (delete_useless_subdirs (dir))
-    some_useless_subdirs = TRUE;
+
+  if (!dir->save_as_subtree)
+    {
+      if (delete_useless_subdirs (dir))
+	some_useless_subdirs = TRUE;
+    }
+  else
+    {
+      /* We haven't recursively synced subdirs so we need
+       * to now recursively delete useless subdirs.
+       */
+      if (delete_useless_subdirs_recurse (dir))
+	some_useless_subdirs = TRUE;
+    }
   
   g_free (fs_dirname);
   g_free (fs_filename);
-
+  g_free (fs_subtree);
 
   /* If we deleted an entry or subdir from this directory, and hadn't
    * fully loaded this directory, we now don't know whether the entry
@@ -958,7 +1060,8 @@ markup_dir_sync (MarkupDir *dir)
 
 static char*
 markup_dir_build_path (MarkupDir  *dir,
-                       gboolean    with_data_file)
+                       gboolean    with_data_file,
+		       gboolean    subtree_data_file)
 {
   GString *name;
   GSList *components;
@@ -988,7 +1091,7 @@ markup_dir_build_path (MarkupDir  *dir,
   g_slist_free (components);
 
   if (with_data_file)
-    g_string_append (name, "/%gconf.xml");
+    g_string_append (name, !subtree_data_file ? "/%gconf.xml" : "/%gconf-tree.xml");
 
   return g_string_free (name, FALSE);
 }
@@ -1005,10 +1108,10 @@ markup_entry_new (MarkupDir  *dir,
 
   entry = g_new0 (MarkupEntry, 1);
 
-  /* "dir" may be NULL during %gconf.xml parsing */
+  entry->name = g_strdup (name);
 
   entry->dir = dir;
-  entry->name = g_strdup (name);
+  dir->entries = g_slist_prepend (dir->entries, entry);
 
   return entry;
 }
@@ -1169,7 +1272,7 @@ markup_entry_set_value (MarkupEntry     
   entry->mod_time = time (NULL);
 
   /* Need to save to disk */
-  entry->dir->entries_need_save = TRUE;
+  markup_dir_flag_entries_need_save (entry->dir);
   markup_dir_queue_sync (entry->dir);
 }
 
@@ -1239,7 +1342,7 @@ markup_entry_unset_value (MarkupEntry *e
   entry->mod_time = time (NULL);
 
   /* Need to save to disk */
-  entry->dir->entries_need_save = TRUE;
+  markup_dir_flag_entries_need_save (entry->dir);
   markup_dir_queue_sync (entry->dir);
 }
 
@@ -1263,7 +1366,7 @@ markup_entry_set_schema_name (MarkupEntr
   entry->mod_time = time (NULL);
 
   /* Need to save to disk */
-  entry->dir->entries_need_save = TRUE;
+  markup_dir_flag_entries_need_save (entry->dir);
   markup_dir_queue_sync (entry->dir);
 }
 
@@ -1443,6 +1546,7 @@ typedef enum
 {
   STATE_START,
   STATE_GCONF,
+  STATE_DIR,
   STATE_ENTRY,
   STATE_STRINGVALUE,
   STATE_LONGDESC,
@@ -1462,16 +1566,19 @@ typedef enum
 
 typedef struct
 {
-  GSList *states;
+  GSList      *states;
+
+  MarkupDir   *root;
+  GSList      *dir_stack;
+ 
   MarkupEntry *current_entry;  
-  GSList *value_stack;
-  GSList *value_freelist;
+  GSList      *value_stack;
+  GSList      *value_freelist;
 
   /* Collected while parsing a schema entry */
-  GSList *local_schemas;
+  GSList      *local_schemas;
 
-  GSList *complete_entries;
-  
+  guint        allow_subdirs : 1;
 } ParseInfo;
 
 static void set_error (GError             **err,
@@ -1480,7 +1587,9 @@ static void set_error (GError           
                        const char          *format,
                        ...) G_GNUC_PRINTF (4, 5);
 
-static void          parse_info_init        (ParseInfo    *info);
+static void          parse_info_init        (ParseInfo    *info,
+					     MarkupDir    *root,
+					     gboolean      allow_subdirs);
 static void          parse_info_free        (ParseInfo    *info);
 
 static void       push_state (ParseInfo  *info,
@@ -1492,7 +1601,11 @@ static void        value_stack_push (Par
                                      GConfValue *value,
                                      gboolean    add_to_freelist);
 static GConfValue* value_stack_peek (ParseInfo  *info);
-static void        value_stack_pop  (ParseInfo  *info);
+static GConfValue* value_stack_pop  (ParseInfo  *info);
+static void        dir_stack_push   (ParseInfo  *info,
+				     MarkupDir  *dir);
+static MarkupDir*  dir_stack_peek   (ParseInfo  *info);
+static MarkupDir*  dir_stack_pop    (ParseInfo  *info);
 
 
 static void start_element_handler (GMarkupParseContext  *context,
@@ -1544,32 +1657,38 @@ set_error (GError             **err,
 }
 
 static void
-parse_info_init (ParseInfo *info)
+parse_info_init (ParseInfo *info,
+		 MarkupDir *root,
+		 gboolean   allow_subdirs)
 {
   info->states = g_slist_prepend (NULL, GINT_TO_POINTER (STATE_START));
+
+  info->root = root;
+  info->dir_stack = NULL;
+
   info->current_entry = NULL;
   info->value_stack = NULL;
   info->value_freelist = NULL;
+
   info->local_schemas = NULL;
-  info->complete_entries = NULL;
+
+  info->allow_subdirs = allow_subdirs != FALSE;
+
+  dir_stack_push (info, root);
 }
 
 static void
 parse_info_free (ParseInfo *info)
 {
-  if (info->current_entry)
-    markup_entry_free (info->current_entry);
+  g_slist_free (info->dir_stack);
+  
+  /* Don't free current_entry - always owned by tree */
 
   g_slist_foreach (info->local_schemas,
                    (GFunc) local_schema_info_free,
                    NULL);
   g_slist_free (info->local_schemas);
 
-  g_slist_foreach (info->complete_entries,
-                   (GFunc) markup_entry_free,
-                   NULL);
-  g_slist_free (info->complete_entries);
-
   /* only free values on the freelist, not those on the stack,
    * but all values in the freelist are also in the stack.
    */
@@ -1623,13 +1742,45 @@ value_stack_peek (ParseInfo *info)
   return info->value_stack ? info->value_stack->data : NULL;
 }
 
-static void
+static GConfValue*
 value_stack_pop (ParseInfo *info)
 {
-  info->value_freelist = g_slist_remove (info->value_freelist,
-                                         info->value_stack->data);
-  info->value_stack = g_slist_remove (info->value_stack,
-                                      info->value_stack->data);
+  GConfValue *retval;
+
+  retval = info->value_stack ? info->value_stack->data : NULL;
+
+  info->value_freelist = g_slist_remove (info->value_freelist, retval);
+  info->value_stack    = g_slist_remove (info->value_stack, retval);
+
+  return retval;
+}
+
+static void
+dir_stack_push (ParseInfo *info,
+		MarkupDir *dir)
+{
+  info->dir_stack = g_slist_prepend (info->dir_stack, dir);
+}
+
+static MarkupDir*
+dir_stack_peek (ParseInfo *info)
+{
+  g_return_val_if_fail (info->dir_stack != NULL, NULL);
+
+  return info->dir_stack->data;
+}
+
+static MarkupDir*
+dir_stack_pop (ParseInfo *info)
+{
+  MarkupDir *retval;
+
+  g_return_val_if_fail (info->dir_stack != NULL, NULL);
+
+  retval = info->dir_stack->data;
+  info->dir_stack = g_slist_remove (info->dir_stack, retval);
+
+  return retval;
 }
 
 #define ELEMENT_IS(name) (strcmp (element_name, (name)) == 0)
@@ -1691,9 +1842,6 @@ locate_attributes (GMarkupParseContext *
 
   va_end (args);
 
-  if (!retval)
-    return retval;
-
   i = 0;
   while (attribute_names[i])
     {
@@ -2204,7 +2352,7 @@ parse_entry_element (GMarkupParseContext
   GConfValue *value;
   GError *tmp_err;
   
-  g_return_if_fail (peek_state (info) == STATE_GCONF);
+  g_return_if_fail (peek_state (info) == STATE_GCONF || peek_state (info) == STATE_DIR);
   g_return_if_fail (ELEMENT_IS ("entry"));
   g_return_if_fail (info->current_entry == NULL);
 
@@ -2266,7 +2414,7 @@ parse_entry_element (GMarkupParseContext
         g_error_free (tmp_err);
     }
   
-  info->current_entry = markup_entry_new (NULL, name);
+  info->current_entry = markup_entry_new (dir_stack_peek (info), name);
   if (value != NULL)
     {
       info->current_entry->value = value;
@@ -2274,8 +2422,8 @@ parse_entry_element (GMarkupParseContext
     }
       
   if (muser)
-    markup_entry_set_mod_user (info->current_entry,
-                               muser);
+    markup_entry_set_mod_user (info->current_entry, muser);
+
   if (mtime)
     {
       GTime vmtime;
@@ -2283,7 +2431,7 @@ parse_entry_element (GMarkupParseContext
       vmtime = gconf_string_to_gulong (mtime);
       
       markup_entry_set_mod_time (info->current_entry,
-                                 vmtime);
+				 vmtime);
     }
 
   /* don't use markup_entry_set_schema_name because it would
@@ -2294,6 +2442,51 @@ parse_entry_element (GMarkupParseContext
 }
 
 static void
+parse_dir_element (GMarkupParseContext  *context,
+		   const gchar          *element_name,
+		   const gchar         **attribute_names,
+		   const gchar         **attribute_values,
+		   ParseInfo            *info,
+		   GError              **error)
+{
+  MarkupDir *dir;
+  MarkupDir *subdir;
+  const char *name;
+  
+  g_return_if_fail (peek_state (info) == STATE_GCONF || peek_state (info) == STATE_DIR);
+  g_return_if_fail (ELEMENT_IS ("dir"));
+
+  push_state (info, STATE_DIR);
+
+  name = NULL;
+  
+  if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+                          error,
+                          "name", &name,
+                          NULL))
+    return;
+
+  if (name == NULL)
+    {
+      set_error (error, context, GCONF_ERROR_PARSE_ERROR,
+                 _("No \"%s\" attribute on element <%s>"),
+                 "name", element_name);
+      return;
+    }
+
+  dir = dir_stack_peek (info);
+
+  subdir = markup_dir_new (info->root->tree, dir, name);
+  dir->subdirs = g_slist_prepend (dir->subdirs, subdir);
+
+  subdir->not_in_filesystem = TRUE;
+  subdir->entries_loaded    = TRUE;
+  subdir->subdirs_loaded    = TRUE;
+
+  dir_stack_push (info, subdir);
+}
+
+static void
 parse_local_schema_child_element (GMarkupParseContext  *context,
                                   const gchar          *element_name,
                                   const gchar         **attribute_names,
@@ -2675,6 +2868,7 @@ start_element_handler (GMarkupParseConte
                    element_name);
       break;
 
+    case STATE_DIR:      
     case STATE_GCONF:
       if (ELEMENT_IS ("entry"))
         {
@@ -2682,10 +2876,16 @@ start_element_handler (GMarkupParseConte
                                attribute_names, attribute_values,
                                info, error);
         }
+      else if (ELEMENT_IS ("dir") && info->allow_subdirs)
+	{
+	  parse_dir_element (context, element_name,
+			     attribute_names, attribute_values,
+			     info, error);
+	}
       else
         set_error (error, context, GCONF_ERROR_PARSE_ERROR,
                    _("Element <%s> is not allowed inside a <%s> element"),
-                   element_name, "gconf");
+                   element_name, current_state == STATE_GCONF ? "gconf" : "dir");
       break;
 
     case STATE_ENTRY:
@@ -2734,14 +2934,12 @@ end_element_handler (GMarkupParseContext
       g_assert (info->current_entry);
       g_assert (info->current_entry->local_schemas == NULL);
 
-      info->current_entry->local_schemas = info->local_schemas;
+      info->current_entry->local_schemas = g_slist_reverse (info->local_schemas);
       info->local_schemas = NULL;
 
       if (info->current_entry->value != NULL)
         value_stack_pop (info);
       
-      info->complete_entries = g_slist_prepend (info->complete_entries,
-                                                info->current_entry);
       info->current_entry = NULL;
 
       pop_state (info);
@@ -2772,7 +2970,19 @@ end_element_handler (GMarkupParseContext
       pop_state (info);
       break;
 
+    case STATE_DIR:
     case STATE_GCONF:
+      {
+	MarkupDir *dir;
+      
+	dir = dir_stack_pop (info);
+	dir->entries = g_slist_reverse (dir->entries);
+	dir->subdirs = g_slist_reverse (dir->subdirs);
+
+	pop_state (info);
+      }
+      break;
+
     case STATE_LOCAL_SCHEMA:
     case STATE_LONGDESC:
     case STATE_STRINGVALUE:
@@ -2848,6 +3058,9 @@ text_handler (GMarkupParseContext *conte
     case STATE_GCONF:
       NO_TEXT ("gconf");
       break;
+    case STATE_DIR:
+      NO_TEXT ("dir");
+      break;
     case STATE_ENTRY:
       NO_TEXT ("entry");
       break;
@@ -2869,13 +3082,15 @@ text_handler (GMarkupParseContext *conte
     }
 }
 
-static GSList*
-parse_entries (const char *filename,
-               GError    **err)
+static void
+parse_tree (MarkupDir  *root,
+	    gboolean    parse_subtree,
+	    GError    **err)
 {
   GMarkupParseContext *context;
   GError *error;
   ParseInfo info;
+  char *filename;
   char *text;
   int length;
   GSList *retval;
@@ -2884,30 +3099,34 @@ parse_entries (const char *filename,
   length = 0;
   retval = NULL;
 
-  error = NULL;
-  if (!g_file_get_contents (filename,
-                            &text,
-                            &length,
-                            &error))
-    {
-      g_propagate_error (err, error);
-      return NULL;
-    }
+  filename = markup_dir_build_path (root, TRUE, parse_subtree);
+  
+  parse_info_init (&info, root, parse_subtree);
 
-  g_assert (text);
+  text = NULL;
+  length = 0;
+  error = NULL;
+  if (!g_file_get_contents (filename, &text, &length, &error))
+    goto out;
 
   /* Empty documents are OK */
   if (length == 0)
-    {
-      g_free (text);
-      return NULL;
-    }
-  
-  parse_info_init (&info);
+    goto out;
+
+  g_assert (text);
 
   context = g_markup_parse_context_new (&gconf_parser,
                                         0, &info, NULL);
 
+  /* FIXME: if we encounter some error while parsing we
+   *        soldier on with and return whatever we did
+   *        manage to parse. Is this behavious okay, or
+   *        do we want to go back to the previous behaviour
+   *        of ignoring what we managed to parse ?
+   *        Hmm, did the previous behaviour cause the
+   *        problem file to be deleted. Maybe that *is*
+   *        what we want.
+   */
   error = NULL;
   if (!g_markup_parse_context_parse (context,
                                      text,
@@ -2921,53 +3140,38 @@ parse_entries (const char *filename,
 
   g_markup_parse_context_free (context);
 
-  goto out;
-
  out:
 
+  g_free (filename);
   g_free (text);
-
-  if (error)
-    {
-      g_propagate_error (err, error);
-    }
-  else if (info.complete_entries)
-    {
-      retval = info.complete_entries;
-      info.complete_entries = NULL;
-    }
-  else
-    {
-      /* No entries in here, but that's not an error,
-       * empty files are allowed.
-       */
-    }
-
   parse_info_free (&info);
 
-  return retval;
+  if (error)
+    g_propagate_error (err, error);
 }
 
 /*
  * Save
  */
 
+#define INDENT_SPACES 8
+
 static gboolean write_list_children   (GConfValue  *value,
                                        FILE        *f,
-                                       const char  *indent);
+                                       int          indent);
 static gboolean write_pair_children   (GConfValue  *value,
                                        FILE        *f,
-                                       const char  *indent);
+                                       int          indent);
 static gboolean write_schema_children (GConfValue  *value,
                                        GSList      *local_schemas,
                                        FILE        *f,
-                                       const char  *indent);
+                                       int          indent);
 
 static gboolean
 write_value_element (GConfValue  *value,
                      GSList      *local_schemas,
                      FILE        *f,
-                     const char  *indent) 
+                     int          indent) 
 {
   /* We are at the "<foo bar="whatever"" stage here,
    * <foo> still missing the closing >
@@ -2983,6 +3187,9 @@ write_value_element (GConfValue  *value,
       if (fprintf (f, " ltype=\"%s\"",
                    gconf_value_type_to_string (gconf_value_get_list_type (value))) < 0)
         return FALSE;
+
+      if (fputs (">\n", f) < 0)
+	return FALSE;
       break;
       
     case GCONF_VALUE_SCHEMA:
@@ -3050,17 +3257,20 @@ write_value_element (GConfValue  *value,
                   return FALSE;
               }
           }
+
+	if (fputs (">\n", f) < 0)
+	  return FALSE;
       }
       break;
 
     case GCONF_VALUE_INT:
-      if (fprintf (f, "value=\"%d\"",
+      if (fprintf (f, " value=\"%d\"",
                    gconf_value_get_int (value)) < 0)
         return FALSE;
       break;
 
     case GCONF_VALUE_BOOL:
-      if (fprintf (f, "value=\"%s\"",
+      if (fprintf (f, " value=\"%s\"",
                    gconf_value_get_bool (value) ? "true" : "false") < 0)
         return FALSE;
       break;
@@ -3070,7 +3280,7 @@ write_value_element (GConfValue  *value,
         char *s;
 
         s = gconf_double_to_string (gconf_value_get_float (value));
-        if (fprintf (f, "value=\"%s\"", s) < 0)
+        if (fprintf (f, " value=\"%s\"", s) < 0)
           {
             g_free (s);
             return FALSE;
@@ -3082,28 +3292,31 @@ write_value_element (GConfValue  *value,
     case GCONF_VALUE_INVALID:
     case GCONF_VALUE_STRING:
     case GCONF_VALUE_PAIR:
+      if (fputs (">\n", f) < 0)
+	return FALSE;
       break;
     }
   
-  if (fputs (">\n", f) < 0)
-    return FALSE;
-  
   switch (value->type)
     {
     case GCONF_VALUE_STRING:
       {
         char *s;
+	char *whitespace;
         
         s = g_markup_escape_text (gconf_value_get_string (value),
                                   -1);
+	whitespace = g_strnfill (indent, ' ');
         
         if (fprintf (f, "%s<stringvalue>%s</stringvalue>\n",
-                     indent, s) < 0)
+                     whitespace, s) < 0)
           {
+	    g_free (whitespace);
             g_free (s);
             return FALSE;
           }
         
+	g_free (whitespace);
         g_free (s);
       }
       break;
@@ -3136,102 +3349,157 @@ write_value_element (GConfValue  *value,
 static gboolean
 write_list_children (GConfValue  *value,
                      FILE        *f,
-                     const char  *indent)
+                     int          indent)
 {
   GSList *tmp;
+  gboolean retval = FALSE;
+  char *whitespace;
+
+  whitespace = g_strnfill (indent, ' ');
 
   tmp = gconf_value_get_list (value);
   while (tmp != NULL)
     {
       GConfValue *li = tmp->data;
 
-      if (fputs (indent, f) < 0)
-        return FALSE;
+      if (fputs (whitespace, f) < 0)
+	goto out;
       
       if (fputs ("<li", f) < 0)
-        return FALSE;
+	goto out;
 
-      if (!write_value_element (li, NULL, f, indent))
-        return FALSE;      
+      if (!write_value_element (li, NULL, f, indent + INDENT_SPACES))
+	goto out;
 
-      if (fprintf (f, "%s</li>", indent) < 0)
-        return FALSE;
+      switch (li->type)
+	{
+	case GCONF_VALUE_BOOL:
+	case GCONF_VALUE_INT:
+	case GCONF_VALUE_FLOAT:
+	  if (fputs ("/>", f) < 0)
+	    goto out;
+	  break;
+	default:
+	  if (fprintf (f, "%s</li>\n", whitespace) < 0)
+	    goto out;
+	  break;
+	}
       
       tmp = tmp->next;
     }
 
-  return TRUE;
+  retval = TRUE;
+
+ out:
+
+  g_free (whitespace);
+
+  return retval;
 }
 
 static gboolean
 write_pair_children (GConfValue  *value,
                      FILE        *f,
-                     const char  *indent)
+                     int          indent)
 {
   GConfValue *child;
+  gboolean retval = FALSE;
+  char *whitespace;
+
+  whitespace = g_strnfill (indent, ' ');
   
   child = gconf_value_get_car (value);
 
   if (child != NULL)
     {
-      if (fputs (indent, f) < 0)
-        return FALSE;
-      
+      if (fputs (whitespace, f) < 0)
+	goto out;
+
       if (fputs ("<car", f) < 0)
-        return FALSE;
+	goto out;
 
-      if (!write_value_element (child, NULL, f, indent))
-        return FALSE;      
-      
-      if (fprintf (f, "%s</car>", indent) < 0)
-        return FALSE;
+      if (!write_value_element (child, NULL, f, indent + INDENT_SPACES))
+	goto out;
+
+      switch (child->type)
+	{
+	case GCONF_VALUE_BOOL:
+	case GCONF_VALUE_INT:
+	case GCONF_VALUE_FLOAT:
+	  if (fputs ("/>", f) < 0)
+	    goto out;
+	  break;
+	default:
+	  if (fprintf (f, "%s</car>\n", whitespace) < 0)
+	    goto out;
+	  break;
+	}
     }
 
   child = gconf_value_get_cdr (value);
 
   if (child != NULL)
     {
-      if (fputs (indent, f) < 0)
-        return FALSE;
+      if (fputs (whitespace, f) < 0)
+	goto out;
       
       if (fputs ("<cdr", f) < 0)
-        return FALSE;
+	goto out;
 
-      if (!write_value_element (child, NULL, f, indent))
-        return FALSE;      
+      if (!write_value_element (child, NULL, f, indent + INDENT_SPACES))
+	goto out;
       
-      if (fprintf (f, "%s</cdr>", indent) < 0)
-        return FALSE;
+      switch (child->type)
+	{
+	case GCONF_VALUE_BOOL:
+	case GCONF_VALUE_INT:
+	case GCONF_VALUE_FLOAT:
+	  if (fputs ("/>", f) < 0)
+	    goto out;
+	  break;
+	default:
+	  if (fprintf (f, "%s</cdr>\n", whitespace) < 0)
+	    goto out;
+	  break;
+	}
     }
+
+  retval = TRUE;
+ 
+ out:
+
+  g_free (whitespace);
   
-  return TRUE;
+  return retval;
 }
 
 static gboolean
 write_schema_children (GConfValue  *value,
                        GSList      *local_schemas,
                        FILE        *f,
-                       const char  *indent)
+                       int          indent)
 {
   /* Here we write each local_schema, in turn a local_schema can
    * contain <default> and <longdesc> and have locale and short_desc
    * attributes
    */
   GSList *tmp;
-  
-  if (fputs (indent, f) < 0)
-    return FALSE;
-
-  if (fputs ("<local_schema", f) < 0)
-    return FALSE;
+  gboolean retval;
+  char *whitespace;
 
+  whitespace = g_strnfill (indent, ' ');
+  
   tmp = local_schemas;
   while (tmp != NULL)
     {
-      LocalSchemaInfo *local_schema;
+      LocalSchemaInfo *local_schema = tmp->data;
       char *s;
 
-      local_schema = tmp->data;
+      if (fputs (whitespace, f) < 0)
+	goto out;
+
+      if (fputs ("<local_schema", f) < 0)
+	goto out;
       
       g_assert (local_schema->locale);
       
@@ -3240,7 +3508,7 @@ write_schema_children (GConfValue  *valu
       if (fprintf (f, " locale=\"%s\"", s) < 0)
         {
           g_free (s);
-          return FALSE;
+	  goto out;
         }
       
       g_free (s);
@@ -3252,115 +3520,212 @@ write_schema_children (GConfValue  *valu
           if (fprintf (f, " short_desc=\"%s\"", s) < 0)
             {
               g_free (s);
-              return FALSE;
+	      goto out;
             }
           
           g_free (s);
         }
 
       if (fputs (">\n", f) < 0)
-        return FALSE;
+	goto out;
 
       if (local_schema->default_value)
         {
-          if (fputs (indent, f) < 0)
-            return FALSE;
+          if (fputs (whitespace, f) < 0)
+	    goto out;
 
-          if (fputs ("<default", f) < 0)
-            return FALSE;
+          if (fputs ("  <default", f) < 0)
+	    goto out;
 
-          if (!write_value_element (local_schema->default_value, NULL, f, indent))
-            return FALSE;      
-          
-          if (fprintf (f, "%s</default>", indent) < 0)
-            return FALSE;
+          if (!write_value_element (local_schema->default_value, NULL, f, indent + INDENT_SPACES))
+	    goto out;
+
+	  switch (local_schema->default_value->type)
+	    {
+	    case GCONF_VALUE_BOOL:
+	    case GCONF_VALUE_INT:
+	    case GCONF_VALUE_FLOAT:
+	      if (fputs ("/>\n", f) < 0)
+		goto out;
+	      break;
+	    default:
+	      if (fprintf (f, "%s  </default>\n", whitespace) < 0)
+		goto out;
+	      break;
+	    }
         }
 
       if (local_schema->long_desc)
         {
-          if (fputs (indent, f) < 0)
-            return FALSE;
-
-          if (fputs ("<longdesc>\n", f) < 0)
-            return FALSE;
+          if (fprintf (f, "%s  <longdesc>", whitespace) < 0)
+	    goto out;
 
           s = g_markup_escape_text (local_schema->long_desc, -1);
           
           if (fputs (s, f) < 0)
             {
               g_free (s);
-              return FALSE;
+	      goto out;
             }
           
           g_free (s);
+
+          if (fputs ("</longdesc>\n", f) < 0)
+	    goto out;
         }
-      
+
+      if (fputs (whitespace, f) < 0)
+	goto out;
+
+      if (fputs ("</local_schema>\n", f) < 0)
+	goto out;
+
       tmp = tmp->next;
     }
         
-  if (fputs (indent, f) < 0)
-    return FALSE;
+  retval = TRUE;
 
-  if (fputs ("</local_schema>\n", f) < 0)
-    return FALSE;
+ out:
+
+  g_free (whitespace);
 
   return TRUE;
 }
 
 static gboolean
 write_entry (MarkupEntry *entry,
-             FILE        *f)
+             FILE        *f,
+	     int          indent)
 {
-  if (fputs ("  <entry", f) < 0)
-    return FALSE;
+  gboolean  retval = FALSE;
+  char     *whitespace;
+
+  whitespace = g_strnfill (indent, ' ');
+
+  if (fprintf (f, "%s<entry", whitespace) < 0)
+    goto out;
 
   g_assert (entry->name != NULL);
   
   if (fprintf (f, " name=\"%s\" mtime=\"%lu\"",
                entry->name,
                (unsigned long) entry->mod_time) < 0)
-    return FALSE;
+    goto out;
   
   if (entry->schema_name)
     {
       if (fprintf (f, " schema=\"%s\"", entry->schema_name) < 0)
-        return FALSE;
+	goto out;
     }
 
   if (entry->mod_user)
     {
       if (fprintf (f, " muser=\"%s\"", entry->mod_user) < 0)
-        return FALSE;
+	goto out;
     }
 
   if (entry->value != NULL)
     {
-      if (!write_value_element (entry->value, entry->local_schemas, f, "    "))
-        return FALSE;
-    }
+      if (!write_value_element (entry->value, entry->local_schemas, f, indent + INDENT_SPACES))
+	goto out;
+
+      switch (entry->value->type)
+	{
+	case GCONF_VALUE_BOOL:
+	case GCONF_VALUE_INT:
+	case GCONF_VALUE_FLOAT:
+	  if (fputs ("/>\n", f) < 0)
+	    goto out;
+	  break;
+	default:
+	  if (fprintf (f, "%s</entry>\n", whitespace) < 0)
+	    goto out;
+	  break;
+	}
+      }
   else
     {
-      if (fputs (">", f) < 0)
-        return FALSE;
+      if (fputs ("/>\n", f) < 0)
+	goto out;
     }
 
-  if (fputs ("  </entry>\n", f) < 0)
-    return FALSE;
+  retval = TRUE;
+
+ out:
+
+  g_free (whitespace);
   
-  return TRUE;
+  return retval;
 }
 
 static gboolean
-save_entries (const char  *filename,
-              guint        file_mode,
-              GSList      *entries,
-              GError     **err)
+write_dir (MarkupDir *dir,
+	   FILE      *f,
+	   int        indent)
+{
+  GSList *tmp;
+  gboolean retval = FALSE;
+  char *whitespace;
+
+  /* This dir will be deleted from the 
+   * MarkupTree after syncing anyway ...
+   */
+  if (!dir->entries && !dir->subdirs)
+    return TRUE;
+
+  whitespace = g_strnfill (indent, ' ');
+
+  g_assert (dir->name != NULL);
+  
+  if (fprintf (f, "%s<dir name=\"%s\">\n", whitespace, dir->name) < 0)
+    goto out;
+
+  tmp = dir->entries;
+  while (tmp != NULL)
+    {
+      MarkupEntry *entry = tmp->data;
+      
+      if (!write_entry (entry, f, indent + INDENT_SPACES))
+	goto out;
+        
+      tmp = tmp->next;
+    }
+
+  tmp = dir->subdirs;
+  while (tmp != NULL)
+    {
+      MarkupDir *subdir = tmp->data;
+      
+      if (!write_dir (subdir, f, indent + INDENT_SPACES))
+	goto out;
+        
+      tmp = tmp->next;
+    }
+
+  if (fprintf (f, "%s</dir>\n", whitespace) < 0)
+    return FALSE;
+
+  retval = TRUE;
+
+ out:
+
+  g_free (whitespace);
+
+  return retval;
+}
+
+static void
+save_tree (MarkupDir  *dir,
+	   gboolean    save_as_subtree,
+	   guint       file_mode,
+	   GError    **err)
 {
   /* We save to a secondary file then copy over, to handle
    * out-of-disk-space robustly
    */
   FILE *f;
   int new_fd;
+  char *filename;
   char *new_filename;
   char *err_str;
   gboolean write_failed;
@@ -3369,6 +3734,8 @@ save_entries (const char  *filename,
   err_str = NULL;
   new_fd = -1;
   f = NULL;
+
+  filename = markup_dir_build_path (dir, TRUE, save_as_subtree);
   
   new_filename = g_strconcat (filename, ".new", NULL);
   new_fd = open (new_filename, O_WRONLY | O_CREAT, file_mode);
@@ -3382,7 +3749,7 @@ save_entries (const char  *filename,
   /* Leave the file empty to avoid parsing it later
    * if there are no entries in it.
    */
-  if (entries == NULL)
+  if (dir->entries == NULL && (!save_as_subtree || dir->subdirs == NULL))
     goto done_writing;
   
   f = fdopen (new_fd, "w");
@@ -3412,12 +3779,12 @@ save_entries (const char  *filename,
   {
     GSList *tmp;
     
-    tmp = entries;
+    tmp = dir->entries;
     while (tmp != NULL)
       {
         MarkupEntry *entry = tmp->data;
         
-        if (!write_entry (entry, f))
+        if (!write_entry (entry, f, INDENT_SPACES))
           {
             write_failed = TRUE;
             goto done_writing;
@@ -3425,6 +3792,20 @@ save_entries (const char  *filename,
         
         tmp = tmp->next;
       }
+
+    tmp = dir->subdirs;
+    while (tmp != NULL)
+      {
+	MarkupDir *dir = tmp->data;
+
+	if (!write_dir (dir, f, INDENT_SPACES))
+	  {
+	    write_failed = TRUE;
+	    goto done_writing;
+	  }
+
+	tmp = tmp->next;
+      }
   }
 
   if (fputs ("</gconf>\n", f) < 0)
@@ -3462,6 +3843,7 @@ save_entries (const char  *filename,
   
  out:
   g_free (new_filename);
+  g_free (filename);
   
   if (err_str)
     {
@@ -3478,8 +3860,6 @@ save_entries (const char  *filename,
 
   if (f != NULL)
     fclose (f);
-
-  return err_str == NULL;
 }
 
 /*
#!/bin/sh

killall gconfd-2

cp /gnome/head/INSTALL/etc/gconf/2/path-xml /gnome/head/INSTALL/etc/gconf/2/path

echo "First XML dump:"
time gconftool-2 --dump / > /dev/null
echo "Second XML dump:"
time gconftool-2 --dump / > /dev/null
echo "Third XML dump:"
time gconftool-2 --dump / > /dev/null

killall gconfd-2

cp /gnome/head/INSTALL/etc/gconf/2/path-markup /gnome/head/INSTALL/etc/gconf/2/path

find ~/.gconf-markup -name %gconf-tree.xml -exec rm -f {} \;
find /gnome/head/INSTALL/etc/gconf/gconf.markup.defaults -name %gconf-tree.xml -exec rm -f {} \;

sleep 5

echo; echo;
echo "First Markup dump:"
time gconftool-2 --dump /  > /dev/null
echo "Second Markup dump:"
time gconftool-2 --dump /  > /dev/null
echo "Third Markup dump:"
time gconftool-2 --dump /  > /dev/null

killall gconfd-2

cp /gnome/head/INSTALL/etc/gconf/2/path-markup /gnome/head/INSTALL/etc/gconf/2/path

gconf-merge-tree ~/.gconf-markup
gconf-merge-tree /gnome/head/INSTALL/etc/gconf/gconf.markup.defaults

sleep 5

echo; echo;
echo "First Markup dump (single file):"
time gconftool-2 --dump /  > /dev/null
echo "Second Markup dump (single file):"
time gconftool-2 --dump /  > /dev/null
echo "Third Markup dump (single file):"
time gconftool-2 --dump /  > /dev/null

killall gconfd-2

cp /gnome/head/INSTALL/etc/gconf/2/path-markup /gnome/head/INSTALL/etc/gconf/2/path

find ~/.gconf-markup -name %gconf-tree.xml -exec rm -f {} \;
find /gnome/head/INSTALL/etc/gconf/gconf.markup.defaults -name %gconf-tree.xml -exec rm -f {} \;

for iii in ~/.gconf-markup/apps/* ~/.gconf-markup/schemas/apps/* /gnome/head/INSTALL/etc/gconf/gconf.markup.defaults/apps/* /gnome/head/INSTALL/etc/gconf/gconf.markup.defaults/schemas/apps/*; do
  if [ -d $iii ]; then
    gconf-merge-tree $iii;
  fi
done

sleep 5

echo; echo;
echo "First Markup dump (files in /apps and /schemas/apps):"
time gconftool-2 --dump /  > /dev/null
echo "Second Markup dump (files in /apps and /schemas/apps):"
time gconftool-2 --dump /  > /dev/null
echo "Third Markup dump (files in /apps and /schemas/apps):"
time gconftool-2 --dump /  > /dev/null
First XML dump:

real	0m4.827s
user	0m0.130s
sys	0m0.020s
Second XML dump:

real	0m1.553s
user	0m0.080s
sys	0m0.010s
Third XML dump:

real	0m1.419s
user	0m0.100s
sys	0m0.010s


First Markup dump:

real	0m2.374s
user	0m0.100s
sys	0m0.000s
Second Markup dump:

real	0m0.246s
user	0m0.090s
sys	0m0.000s
Third Markup dump:

real	0m0.251s
user	0m0.060s
sys	0m0.010s


First Markup dump (single file):

real	0m1.952s
user	0m0.110s
sys	0m0.020s
Second Markup dump (single file):

real	0m0.252s
user	0m0.120s
sys	0m0.010s
Third Markup dump (single file):

real	0m0.245s
user	0m0.080s
sys	0m0.000s


First Markup dump (files in /apps and /schemas/apps):

real	0m1.872s
user	0m0.110s
sys	0m0.010s
Second Markup dump (files in /apps and /schemas/apps):

real	0m0.247s
user	0m0.110s
sys	0m0.000s
Third Markup dump (files in /apps and /schemas/apps):

real	0m0.249s
user	0m0.070s
sys	0m0.020s


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