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
This commit is contained in:
Ryan Lortie 2013-10-27 17:18:10 -07:00
parent 3041d0a8db
commit cbf8cf8598

View File

@ -146,6 +146,8 @@ struct _GSettingsSchema
GvdbTable *table; GvdbTable *table;
gchar *id; gchar *id;
GSettingsSchema *extends;
gint ref_count; gint ref_count;
}; };
@ -407,6 +409,7 @@ g_settings_schema_source_lookup (GSettingsSchemaSource *source,
{ {
GSettingsSchema *schema; GSettingsSchema *schema;
GvdbTable *table; GvdbTable *table;
const gchar *extends;
g_return_val_if_fail (source != NULL, NULL); g_return_val_if_fail (source != NULL, NULL);
g_return_val_if_fail (schema_id != 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) if (schema->gettext_domain)
bind_textdomain_codeset (schema->gettext_domain, "UTF-8"); 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; return schema;
} }
@ -892,6 +903,9 @@ g_settings_schema_unref (GSettingsSchema *schema)
{ {
if (g_atomic_int_dec_and_test (&schema->ref_count)) 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); g_settings_schema_source_unref (schema->source);
gvdb_table_unref (schema->table); gvdb_table_unref (schema->table);
g_free (schema->items); g_free (schema->items);
@ -921,10 +935,15 @@ GVariantIter *
g_settings_schema_get_value (GSettingsSchema *schema, g_settings_schema_get_value (GSettingsSchema *schema,
const gchar *key) const gchar *key)
{ {
GSettingsSchema *s = schema;
GVariantIter *iter; GVariantIter *iter;
GVariant *value; 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)) 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); g_error ("Settings schema '%s' does not contain a key named '%s'", schema->id, key);
@ -976,33 +995,42 @@ const GQuark *
g_settings_schema_list (GSettingsSchema *schema, g_settings_schema_list (GSettingsSchema *schema,
gint *n_items) gint *n_items)
{ {
gint i, j;
if (schema->items == NULL) if (schema->items == NULL)
{ {
gchar **list; GSettingsSchema *s;
GHashTableIter iter;
GHashTable *items;
gpointer name;
gint len; gint len;
gint i;
list = gvdb_table_list (schema->table, ""); items = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
len = list ? g_strv_length (list) : 0;
schema->items = g_new (GQuark, len); for (s = schema; s; s = s->extends)
j = 0;
for (i = 0; i < len; i++)
if (list[i][0] != '.')
{ {
if (g_str_has_suffix (list[i], "/")) gchar **list;
{
/* This is a child. Check to make sure that list = gvdb_table_list (s->table, "");
* instantiating the child would actually work before we
* return it from list() and cause a crash. for (i = 0; list[i]; i++)
g_hash_table_add (items, list[i]); /* transfer ownership */
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, "/"))
{
GSettingsSchemaSource *source; GSettingsSchemaSource *source;
GVariant *child_schema; GVariant *child_schema;
GvdbTable *child_table; GvdbTable *child_table;
child_schema = gvdb_table_get_raw_value (schema->table, list[i]); child_schema = gvdb_table_get_raw_value (schema->table, name);
if (!child_schema) if (!child_schema)
continue; continue;
@ -1014,9 +1042,12 @@ g_settings_schema_list (GSettingsSchema *schema,
g_variant_unref (child_schema); g_variant_unref (child_schema);
/* Schema is not found -> don't add it to the list */ /* Schema is not found -> remove it from the list */
if (child_table == NULL) if (child_table == NULL)
{
g_hash_table_iter_remove (&iter);
continue; continue;
}
/* Make sure the schema is relocatable or at the /* Make sure the schema is relocatable or at the
* expected path * expected path
@ -1028,27 +1059,36 @@ g_settings_schema_list (GSettingsSchema *schema,
gboolean same; gboolean same;
path = gvdb_table_get_raw_value (child_table, ".path"); path = gvdb_table_get_raw_value (child_table, ".path");
expected = g_strconcat (schema->path, list[i], NULL); expected = g_strconcat (schema->path, name, NULL);
same = g_str_equal (expected, g_variant_get_string (path, NULL)); same = g_str_equal (expected, g_variant_get_string (path, NULL));
g_variant_unref (path); g_variant_unref (path);
g_free (expected); g_free (expected);
/* Schema is non-relocatable and did not have the
* expected path -> remove it from the list
*/
if (!same) if (!same)
g_hash_table_iter_remove (&iter);
}
gvdb_table_unref (child_table);
}
/* 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))
{ {
gvdb_table_unref (child_table); schema->items[i++] = g_quark_from_string (name);
continue; g_hash_table_iter_steal (&iter);
}
} }
schema->n_items = i;
g_assert (i == len);
gvdb_table_unref (child_table); g_hash_table_unref (items);
/* Else, it's good... */
}
schema->items[j++] = g_quark_from_string (list[i]);
}
schema->n_items = j;
g_strfreev (list);
} }
*n_items = schema->n_items; *n_items = schema->n_items;