/*
* Copyright © 2010 Codethink Limited
+ * Copyright © 2011 Canonical Limited
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
#include "config.h"
-#include "gsettingsschema.h"
+#include "gsettingsschema-internal.h"
+#include "gsettings.h"
+
#include "gvdb/gvdb-reader.h"
+#include "strinfo.c"
#include <glibintl.h>
+#include <string.h>
-G_DEFINE_TYPE (GSettingsSchema, g_settings_schema, G_TYPE_OBJECT)
-
-struct _GSettingsSchemaPrivate
+struct _GSettingsSchema
{
const gchar *gettext_domain;
const gchar *path;
GQuark *items;
gint n_items;
GvdbTable *table;
- gchar *name;
+ gchar *id;
+
+ gint ref_count;
};
-static GSList *schema_sources;
+typedef struct _GSettingsSchemaSource GSettingsSchemaSource;
+
+G_DEFINE_BOXED_TYPE (GSettingsSchemaSource, g_settings_schema_source, g_settings_schema_source_ref, g_settings_schema_source_unref)
+G_DEFINE_BOXED_TYPE (GSettingsSchema, g_settings_schema, g_settings_schema_ref, g_settings_schema_unref)
+
+struct _GSettingsSchemaSource
+{
+ GSettingsSchemaSource *parent;
+ GvdbTable *table;
+
+ gint ref_count;
+};
+
+static GSettingsSchemaSource *schema_sources;
+
+static void
+prepend_schema_table (GvdbTable *table)
+{
+ GSettingsSchemaSource *source;
+
+ /* we steal the reference from 'schema_sources' for our ->parent */
+ source = g_slice_new (GSettingsSchemaSource);
+ source->parent = schema_sources;
+ source->table = table;
+ source->ref_count = 1;
+
+ schema_sources = source;
+}
+
+GSettingsSchemaSource *
+g_settings_schema_source_ref (GSettingsSchemaSource *source)
+{
+ g_atomic_int_inc (&source->ref_count);
+
+ return source;
+}
+
+void
+g_settings_schema_source_unref (GSettingsSchemaSource *source)
+{
+ if (g_atomic_int_dec_and_test (&source->ref_count))
+ {
+ if (source == schema_sources)
+ g_error ("g_settings_schema_source_unref() called too many times on the default schema source");
+
+ if (source->parent)
+ g_settings_schema_source_unref (source->parent);
+ gvdb_table_unref (source->table);
+
+ g_slice_free (GSettingsSchemaSource, source);
+ }
+}
+
+GSettingsSchemaSource *
+g_settings_schema_source_new_from_directory (GSettingsSchemaSource *parent,
+ const gchar *directory,
+ gboolean trusted,
+ GError **error)
+{
+ GSettingsSchemaSource *source;
+ GvdbTable *table;
+ gchar *filename;
+
+ filename = g_build_filename (directory, "gschemas.compiled", NULL);
+ table = gvdb_table_new (filename, trusted, error);
+ g_free (filename);
+
+ if (table == NULL)
+ return NULL;
+
+ source = g_slice_new (GSettingsSchemaSource);
+ source->parent = parent ? g_settings_schema_source_ref (parent) : NULL;
+ source->table = table;
+ source->ref_count = 1;
+
+ return source;
+}
static void
initialise_schema_sources (void)
{
static gsize initialised;
+ /* need a separate variable because 'schema_sources' may legitimately
+ * be null if we have zero valid schema sources
+ */
if G_UNLIKELY (g_once_init_enter (&initialised))
{
- const gchar * const *dir;
+ const gchar * const *dirs;
const gchar *path;
+ gint i;
+
+ /* iterate in reverse: count up, then count down */
+ dirs = g_get_system_data_dirs ();
+ for (i = 0; dirs[i]; i++);
- for (dir = g_get_system_data_dirs (); *dir; dir++)
+ while (i--)
{
gchar *filename;
GvdbTable *table;
- filename = g_build_filename (*dir, "glib-2.0", "schemas",
- "gschemas.compiled", NULL);
+ filename = g_build_filename (dirs[i], "glib-2.0", "schemas", "gschemas.compiled", NULL);
table = gvdb_table_new (filename, TRUE, NULL);
if (table != NULL)
- schema_sources = g_slist_prepend (schema_sources, table);
+ prepend_schema_table (table);
g_free (filename);
}
- schema_sources = g_slist_reverse (schema_sources);
-
if ((path = g_getenv ("GSETTINGS_SCHEMA_DIR")) != NULL)
{
gchar *filename;
table = gvdb_table_new (filename, TRUE, NULL);
if (table != NULL)
- schema_sources = g_slist_prepend (schema_sources, table);
+ prepend_schema_table (table);
g_free (filename);
}
}
}
+GSettingsSchemaSource *
+g_settings_schema_source_get_default (void)
+{
+ initialise_schema_sources ();
+
+ return schema_sources;
+}
+
+GSettingsSchema *
+g_settings_schema_source_lookup (GSettingsSchemaSource *source,
+ const gchar *schema_id,
+ gboolean recursive)
+{
+ GSettingsSchema *schema;
+ GvdbTable *table;
+
+ g_return_val_if_fail (source != NULL, NULL);
+ g_return_val_if_fail (schema_id != NULL, NULL);
+
+ table = gvdb_table_get_table (source->table, schema_id);
+
+ if (table == NULL && recursive)
+ for (source = source->parent; source; source = source->parent)
+ if ((table = gvdb_table_get_table (source->table, schema_id)))
+ break;
+
+ if (table == NULL)
+ return NULL;
+
+ schema = g_slice_new0 (GSettingsSchema);
+ schema->ref_count = 1;
+ schema->id = g_strdup (schema_id);
+ schema->table = table;
+ schema->path = g_settings_schema_get_string (schema, ".path");
+ schema->gettext_domain = g_settings_schema_get_string (schema, ".gettext-domain");
+
+ if (schema->gettext_domain)
+ bind_textdomain_codeset (schema->gettext_domain, "UTF-8");
+
+ return schema;
+}
+
static gboolean
steal_item (gpointer key,
gpointer value,
{
if (g_once_init_enter (&schema_lists_initialised))
{
+ GSettingsSchemaSource *source;
GHashTable *single, *reloc;
const gchar **ptr;
- GSList *source;
gchar **list;
gint i;
single = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
reloc = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
- for (source = schema_sources; source; source = source->next)
+ for (source = schema_sources; source; source = source->parent)
{
- list = gvdb_table_list (source->data, "");
+ list = gvdb_table_list (source->table, "");
g_assert (list != NULL);
{
GvdbTable *table;
- table = gvdb_table_get_table (source->data, list[i]);
+ table = gvdb_table_get_table (source->table, list[i]);
g_assert (table != NULL);
if (gvdb_table_has_value (table, ".path"))
g_hash_table_insert (single, g_strdup (list[i]), NULL);
else
g_hash_table_insert (reloc, g_strdup (list[i]), NULL);
+
+ gvdb_table_unref (table);
}
}
return relocatable_schema_list;
}
-static void
-g_settings_schema_finalize (GObject *object)
+GSettingsSchema *
+g_settings_schema_ref (GSettingsSchema *schema)
{
- GSettingsSchema *schema = G_SETTINGS_SCHEMA (object);
-
- gvdb_table_unref (schema->priv->table);
- g_free (schema->priv->items);
- g_free (schema->priv->name);
-
- G_OBJECT_CLASS (g_settings_schema_parent_class)
- ->finalize (object);
-}
+ g_atomic_int_inc (&schema->ref_count);
-static void
-g_settings_schema_init (GSettingsSchema *schema)
-{
- schema->priv = G_TYPE_INSTANCE_GET_PRIVATE (schema, G_TYPE_SETTINGS_SCHEMA,
- GSettingsSchemaPrivate);
+ return schema;
}
-static void
-g_settings_schema_class_init (GSettingsSchemaClass *class)
+void
+g_settings_schema_unref (GSettingsSchema *schema)
{
- GObjectClass *object_class = G_OBJECT_CLASS (class);
-
- object_class->finalize = g_settings_schema_finalize;
+ if (g_atomic_int_dec_and_test (&schema->ref_count))
+ {
+ gvdb_table_unref (schema->table);
+ g_free (schema->items);
+ g_free (schema->id);
- g_type_class_add_private (class, sizeof (GSettingsSchemaPrivate));
+ g_slice_free (GSettingsSchema, schema);
+ }
}
const gchar *
const gchar *result = NULL;
GVariant *value;
- if ((value = gvdb_table_get_raw_value (schema->priv->table, key)))
+ if ((value = gvdb_table_get_raw_value (schema->table, key)))
{
result = g_variant_get_string (value, NULL);
g_variant_unref (value);
return result;
}
-GSettingsSchema *
-g_settings_schema_new (const gchar *name)
-{
- GSettingsSchema *schema;
- GvdbTable *table = NULL;
- GSList *source;
-
- initialise_schema_sources ();
-
- for (source = schema_sources; source; source = source->next)
- {
- GvdbTable *file = source->data;
-
- if ((table = gvdb_table_get_table (file, name)))
- break;
- }
-
- if (table == NULL)
- g_error ("Settings schema '%s' is not installed\n", name);
-
- schema = g_object_new (G_TYPE_SETTINGS_SCHEMA, NULL);
- schema->priv->name = g_strdup (name);
- schema->priv->table = table;
- schema->priv->path =
- g_settings_schema_get_string (schema, ".path");
- schema->priv->gettext_domain =
- g_settings_schema_get_string (schema, ".gettext-domain");
-
- if (schema->priv->gettext_domain)
- bind_textdomain_codeset (schema->priv->gettext_domain, "UTF-8");
-
- return schema;
-}
-
GVariantIter *
g_settings_schema_get_value (GSettingsSchema *schema,
const gchar *key)
GVariantIter *iter;
GVariant *value;
- value = gvdb_table_get_raw_value (schema->priv->table, key);
+ value = gvdb_table_get_raw_value (schema->table, key);
if G_UNLIKELY (value == NULL)
- g_error ("schema does not contain a key named '%s'", key);
+ g_error ("Settings schema '%s' does not contain a key named '%s'", schema->id, key);
iter = g_variant_iter_new (value);
g_variant_unref (value);
const gchar *
g_settings_schema_get_path (GSettingsSchema *schema)
{
- return schema->priv->path;
+ return schema->path;
}
const gchar *
g_settings_schema_get_gettext_domain (GSettingsSchema *schema)
{
- return schema->priv->gettext_domain;
+ return schema->gettext_domain;
}
gboolean
g_settings_schema_has_key (GSettingsSchema *schema,
const gchar *key)
{
- return gvdb_table_has_value (schema->priv->table, key);
+ return gvdb_table_has_value (schema->table, key);
}
const GQuark *
{
gint i, j;
- if (schema->priv->items == NULL)
+ if (schema->items == NULL)
{
gchar **list;
gint len;
- list = gvdb_table_list (schema->priv->table, "");
- len = g_strv_length (list);
+ list = gvdb_table_list (schema->table, "");
+ len = list ? g_strv_length (list) : 0;
- schema->priv->items = g_new (GQuark, len);
+ schema->items = g_new (GQuark, len);
j = 0;
for (i = 0; i < len; i++)
if (list[i][0] != '.')
- schema->priv->items[j++] = g_quark_from_string (list[i]);
- schema->priv->n_items = j;
+ schema->items[j++] = g_quark_from_string (list[i]);
+ schema->n_items = j;
g_strfreev (list);
}
- *n_items = schema->priv->n_items;
- return schema->priv->items;
+ *n_items = schema->n_items;
+ return schema->items;
+}
+
+const gchar *
+g_settings_schema_get_id (GSettingsSchema *schema)
+{
+ return schema->id;
+}
+
+static inline void
+endian_fixup (GVariant **value)
+{
+#if G_BYTE_ORDER == G_BIG_ENDIAN
+ GVariant *tmp;
+
+ tmp = g_variant_byteswap (*value);
+ g_variant_unref (*value);
+ *value = tmp;
+#endif
+}
+
+void
+g_settings_schema_key_init (GSettingsSchemaKey *key,
+ GSettingsSchema *schema,
+ const gchar *name)
+{
+ GVariantIter *iter;
+ GVariant *data;
+ guchar code;
+
+ memset (key, 0, sizeof *key);
+
+ iter = g_settings_schema_get_value (schema, name);
+
+ key->schema = g_settings_schema_ref (schema);
+ key->default_value = g_variant_iter_next_value (iter);
+ endian_fixup (&key->default_value);
+ key->type = g_variant_get_type (key->default_value);
+ key->name = g_intern_string (name);
+
+ while (g_variant_iter_next (iter, "(y*)", &code, &data))
+ {
+ switch (code)
+ {
+ case 'l':
+ /* translation requested */
+ g_variant_get (data, "(y&s)", &key->lc_char, &key->unparsed);
+ break;
+
+ case 'e':
+ /* enumerated types... */
+ key->is_enum = TRUE;
+ goto choice;
+
+ case 'f':
+ /* flags... */
+ key->is_flags = TRUE;
+ goto choice;
+
+ choice: case 'c':
+ /* ..., choices, aliases */
+ key->strinfo = g_variant_get_fixed_array (data, &key->strinfo_length, sizeof (guint32));
+ break;
+
+ case 'r':
+ g_variant_get (data, "(**)", &key->minimum, &key->maximum);
+ endian_fixup (&key->minimum);
+ endian_fixup (&key->maximum);
+ break;
+
+ default:
+ g_warning ("unknown schema extension '%c'", code);
+ break;
+ }
+
+ g_variant_unref (data);
+ }
+
+ g_variant_iter_free (iter);
+}
+
+void
+g_settings_schema_key_clear (GSettingsSchemaKey *key)
+{
+ if (key->minimum)
+ g_variant_unref (key->minimum);
+
+ if (key->maximum)
+ g_variant_unref (key->maximum);
+
+ g_variant_unref (key->default_value);
+
+ g_settings_schema_unref (key->schema);
+}
+
+gboolean
+g_settings_schema_key_type_check (GSettingsSchemaKey *key,
+ GVariant *value)
+{
+ g_return_val_if_fail (value != NULL, FALSE);
+
+ return g_variant_is_of_type (value, key->type);
+}
+
+gboolean
+g_settings_schema_key_range_check (GSettingsSchemaKey *key,
+ GVariant *value)
+{
+ if (key->minimum == NULL && key->strinfo == NULL)
+ return TRUE;
+
+ if (g_variant_is_container (value))
+ {
+ gboolean ok = TRUE;
+ GVariantIter iter;
+ GVariant *child;
+
+ g_variant_iter_init (&iter, value);
+ while (ok && (child = g_variant_iter_next_value (&iter)))
+ {
+ ok = g_settings_schema_key_range_check (key, child);
+ g_variant_unref (child);
+ }
+
+ return ok;
+ }
+
+ if (key->minimum)
+ {
+ return g_variant_compare (key->minimum, value) <= 0 &&
+ g_variant_compare (value, key->maximum) <= 0;
+ }
+
+ return strinfo_is_string_valid (key->strinfo, key->strinfo_length,
+ g_variant_get_string (value, NULL));
+}
+
+GVariant *
+g_settings_schema_key_range_fixup (GSettingsSchemaKey *key,
+ GVariant *value)
+{
+ const gchar *target;
+
+ if (g_settings_schema_key_range_check (key, value))
+ return g_variant_ref (value);
+
+ if (key->strinfo == NULL)
+ return NULL;
+
+ if (g_variant_is_container (value))
+ {
+ GVariantBuilder builder;
+ GVariantIter iter;
+ GVariant *child;
+
+ g_variant_iter_init (&iter, value);
+ g_variant_builder_init (&builder, g_variant_get_type (value));
+
+ while ((child = g_variant_iter_next_value (&iter)))
+ {
+ GVariant *fixed;
+
+ fixed = g_settings_schema_key_range_fixup (key, child);
+ g_variant_unref (child);
+
+ if (fixed == NULL)
+ {
+ g_variant_builder_clear (&builder);
+ return NULL;
+ }
+
+ g_variant_builder_add_value (&builder, fixed);
+ g_variant_unref (fixed);
+ }
+
+ return g_variant_ref_sink (g_variant_builder_end (&builder));
+ }
+
+ target = strinfo_string_from_alias (key->strinfo, key->strinfo_length,
+ g_variant_get_string (value, NULL));
+ return target ? g_variant_ref_sink (g_variant_new_string (target)) : NULL;
+}
+
+
+GVariant *
+g_settings_schema_key_get_translated_default (GSettingsSchemaKey *key)
+{
+ const gchar *translated;
+ GError *error = NULL;
+ const gchar *domain;
+ GVariant *value;
+
+ domain = g_settings_schema_get_gettext_domain (key->schema);
+
+ if (key->lc_char == '\0')
+ /* translation not requested for this key */
+ return NULL;
+
+ if (key->lc_char == 't')
+ translated = g_dcgettext (domain, key->unparsed, LC_TIME);
+ else
+ translated = g_dgettext (domain, key->unparsed);
+
+ if (translated == key->unparsed)
+ /* the default value was not translated */
+ return NULL;
+
+ /* try to parse the translation of the unparsed default */
+ value = g_variant_parse (key->type, translated, NULL, NULL, &error);
+
+ if (value == NULL)
+ {
+ g_warning ("Failed to parse translated string `%s' for "
+ "key `%s' in schema `%s': %s", key->unparsed, key->name,
+ g_settings_schema_get_id (key->schema), error->message);
+ g_warning ("Using untranslated default instead.");
+ g_error_free (error);
+ }
+
+ else if (!g_settings_schema_key_range_check (key, value))
+ {
+ g_warning ("Translated default `%s' for key `%s' in schema `%s' "
+ "is outside of valid range", key->unparsed, key->name,
+ g_settings_schema_get_id (key->schema));
+ g_variant_unref (value);
+ value = NULL;
+ }
+
+ return value;
+}
+
+gint
+g_settings_schema_key_to_enum (GSettingsSchemaKey *key,
+ GVariant *value)
+{
+ gboolean it_worked;
+ guint result;
+
+ it_worked = strinfo_enum_from_string (key->strinfo, key->strinfo_length,
+ g_variant_get_string (value, NULL),
+ &result);
+
+ /* 'value' can only come from the backend after being filtered for validity,
+ * from the translation after being filtered for validity, or from the schema
+ * itself (which the schema compiler checks for validity). If this assertion
+ * fails then it's really a bug in GSettings or the schema compiler...
+ */
+ g_assert (it_worked);
+
+ return result;
+}
+
+GVariant *
+g_settings_schema_key_from_enum (GSettingsSchemaKey *key,
+ gint value)
+{
+ const gchar *string;
+
+ string = strinfo_string_from_enum (key->strinfo, key->strinfo_length, value);
+
+ if (string == NULL)
+ return NULL;
+
+ return g_variant_new_string (string);
+}
+
+guint
+g_settings_schema_key_to_flags (GSettingsSchemaKey *key,
+ GVariant *value)
+{
+ GVariantIter iter;
+ const gchar *flag;
+ guint result;
+
+ result = 0;
+ g_variant_iter_init (&iter, value);
+ while (g_variant_iter_next (&iter, "&s", &flag))
+ {
+ gboolean it_worked;
+ guint flag_value;
+
+ it_worked = strinfo_enum_from_string (key->strinfo, key->strinfo_length, flag, &flag_value);
+ /* as in g_settings_to_enum() */
+ g_assert (it_worked);
+
+ result |= flag_value;
+ }
+
+ return result;
+}
+
+GVariant *
+g_settings_schema_key_from_flags (GSettingsSchemaKey *key,
+ guint value)
+{
+ GVariantBuilder builder;
+ gint i;
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("as"));
+
+ for (i = 0; i < 32; i++)
+ if (value & (1u << i))
+ {
+ const gchar *string;
+
+ string = strinfo_string_from_enum (key->strinfo, key->strinfo_length, 1u << i);
+
+ if (string == NULL)
+ {
+ g_variant_builder_clear (&builder);
+ return NULL;
+ }
+
+ g_variant_builder_add (&builder, "s", string);
+ }
+
+ return g_variant_builder_end (&builder);
}