[glib] GSettings: properly support 'extends'



commit cbf8cf8598e527a0d3b895cbfedef6b728ab8b82
Author: Ryan Lortie <desrt desrt ca>
Date:   Sun Oct 27 17:18:10 2013 -0700

    GSettings: properly support 'extends'
    
    Support the 'extends' attribute that has been supported by the compiler
    for a long time by doing three things:
    
     - when creating a schema that extends another schema, lookup that other
       schema
    
     - when looking up keys and we can't find them in the schema, check
       (recursively) in the 'extends' schema
    
     - when listing all keys in a schema, also visit the extends schemas,
       but take care to avoid duplicates caused by overrides
    
    Extend the testsuite to verify that it works.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=645453

 gio/gsettingsschema.c |  152 +++++++++++++++++++++++++++++++------------------
 1 files changed, 96 insertions(+), 56 deletions(-)
---
diff --git a/gio/gsettingsschema.c b/gio/gsettingsschema.c
index 80b0bb6..b79d91a 100644
--- a/gio/gsettingsschema.c
+++ b/gio/gsettingsschema.c
@@ -146,6 +146,8 @@ struct _GSettingsSchema
   GvdbTable *table;
   gchar *id;
 
+  GSettingsSchema *extends;
+
   gint ref_count;
 };
 
@@ -407,6 +409,7 @@ g_settings_schema_source_lookup (GSettingsSchemaSource *source,
 {
   GSettingsSchema *schema;
   GvdbTable *table;
+  const gchar *extends;
 
   g_return_val_if_fail (source != NULL, NULL);
   g_return_val_if_fail (schema_id != NULL, NULL);
@@ -432,6 +435,14 @@ g_settings_schema_source_lookup (GSettingsSchemaSource *source,
   if (schema->gettext_domain)
     bind_textdomain_codeset (schema->gettext_domain, "UTF-8");
 
+  extends = g_settings_schema_get_string (schema, ".extends");
+  if (extends)
+    {
+      schema->extends = g_settings_schema_source_lookup (source, extends, TRUE);
+      if (schema->extends == NULL)
+        g_warning ("Schema '%s' extends schema '%s' but we could not find it", schema_id, extends);
+    }
+
   return schema;
 }
 
@@ -892,6 +903,9 @@ g_settings_schema_unref (GSettingsSchema *schema)
 {
   if (g_atomic_int_dec_and_test (&schema->ref_count))
     {
+      if (schema->extends)
+        g_settings_schema_unref (schema->extends);
+
       g_settings_schema_source_unref (schema->source);
       gvdb_table_unref (schema->table);
       g_free (schema->items);
@@ -921,10 +935,15 @@ GVariantIter *
 g_settings_schema_get_value (GSettingsSchema *schema,
                              const gchar     *key)
 {
+  GSettingsSchema *s = schema;
   GVariantIter *iter;
   GVariant *value;
 
-  value = gvdb_table_get_raw_value (schema->table, key);
+  g_return_val_if_fail (schema != NULL, NULL);
+
+  for (s = schema; s; s = schema->extends)
+    if ((value = gvdb_table_get_raw_value (schema->table, key)))
+      break;
 
   if G_UNLIKELY (value == NULL || !g_variant_is_of_type (value, G_VARIANT_TYPE_TUPLE))
     g_error ("Settings schema '%s' does not contain a key named '%s'", schema->id, key);
@@ -976,79 +995,100 @@ const GQuark *
 g_settings_schema_list (GSettingsSchema *schema,
                         gint            *n_items)
 {
-  gint i, j;
-
   if (schema->items == NULL)
     {
-      gchar **list;
+      GSettingsSchema *s;
+      GHashTableIter iter;
+      GHashTable *items;
+      gpointer name;
       gint len;
+      gint i;
+
+      items = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+
+      for (s = schema; s; s = s->extends)
+        {
+          gchar **list;
 
-      list = gvdb_table_list (schema->table, "");
-      len = list ? g_strv_length (list) : 0;
+          list = gvdb_table_list (s->table, "");
 
-      schema->items = g_new (GQuark, len);
-      j = 0;
+          for (i = 0; list[i]; i++)
+            g_hash_table_add (items, list[i]); /* transfer ownership */
 
-      for (i = 0; i < len; i++)
-        if (list[i][0] != '.')
+          g_free (list); /* free container only */
+        }
+
+      /* Do a first pass to eliminate child items that do not map to
+       * valid schemas (ie: ones that would crash us if we actually
+       * tried to create them).
+       */
+      g_hash_table_iter_init (&iter, items);
+      while (g_hash_table_iter_next (&iter, &name, NULL))
+        if (g_str_has_suffix (name, "/"))
           {
-            if (g_str_has_suffix (list[i], "/"))
-              {
-                /* This is a child.  Check to make sure that
-                 * instantiating the child would actually work before we
-                 * return it from list() and cause a crash.
-                 */
-                GSettingsSchemaSource *source;
-                GVariant *child_schema;
-                GvdbTable *child_table;
+            GSettingsSchemaSource *source;
+            GVariant *child_schema;
+            GvdbTable *child_table;
 
-                child_schema = gvdb_table_get_raw_value (schema->table, list[i]);
-                if (!child_schema)
-                  continue;
+            child_schema = gvdb_table_get_raw_value (schema->table, name);
+            if (!child_schema)
+              continue;
 
-                child_table = NULL;
+            child_table = NULL;
 
-                for (source = schema_sources; source; source = source->parent)
-                  if ((child_table = gvdb_table_get_table (source->table, g_variant_get_string 
(child_schema, NULL))))
-                    break;
+            for (source = schema_sources; source; source = source->parent)
+              if ((child_table = gvdb_table_get_table (source->table, g_variant_get_string (child_schema, 
NULL))))
+                break;
 
-                g_variant_unref (child_schema);
+            g_variant_unref (child_schema);
 
-                /* Schema is not found -> don't add it to the list */
-                if (child_table == NULL)
-                  continue;
+            /* Schema is not found -> remove it from the list */
+            if (child_table == NULL)
+              {
+                g_hash_table_iter_remove (&iter);
+                continue;
+              }
 
-                /* Make sure the schema is relocatable or at the
-                 * expected path
+            /* Make sure the schema is relocatable or at the
+             * expected path
+             */
+            if (gvdb_table_has_value (child_table, ".path"))
+              {
+                GVariant *path;
+                gchar *expected;
+                gboolean same;
+
+                path = gvdb_table_get_raw_value (child_table, ".path");
+                expected = g_strconcat (schema->path, name, NULL);
+                same = g_str_equal (expected, g_variant_get_string (path, NULL));
+                g_variant_unref (path);
+                g_free (expected);
+
+                /* Schema is non-relocatable and did not have the
+                 * expected path -> remove it from the list
                  */
-                if (gvdb_table_has_value (child_table, ".path"))
-                  {
-                    GVariant *path;
-                    gchar *expected;
-                    gboolean same;
-
-                    path = gvdb_table_get_raw_value (child_table, ".path");
-                    expected = g_strconcat (schema->path, list[i], NULL);
-                    same = g_str_equal (expected, g_variant_get_string (path, NULL));
-                    g_variant_unref (path);
-                    g_free (expected);
-
-                    if (!same)
-                      {
-                        gvdb_table_unref (child_table);
-                        continue;
-                      }
-                  }
-
-                gvdb_table_unref (child_table);
-                /* Else, it's good... */
+                if (!same)
+                  g_hash_table_iter_remove (&iter);
               }
 
-            schema->items[j++] = g_quark_from_string (list[i]);
+            gvdb_table_unref (child_table);
           }
-      schema->n_items = j;
 
-      g_strfreev (list);
+      /* Now create the list */
+      len = g_hash_table_size (items);
+      schema->items = g_new (GQuark, len + 1);
+      i = 0;
+      g_hash_table_iter_init (&iter, items);
+
+      while (g_hash_table_iter_next (&iter, &name, NULL))
+        {
+          schema->items[i++] = g_quark_from_string (name);
+          g_hash_table_iter_steal (&iter);
+        }
+      schema->n_items = i;
+      g_assert (i == len);
+
+      g_hash_table_unref (items);
     }
 
   *n_items = schema->n_items;


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