From bebdfb8e6264f61ffefce3ce297f860909ee2ea3 Mon Sep 17 00:00:00 2001
From: Ryan Lortie <desrt@desrt.ca>
Date: Sun, 27 Oct 2013 10:34:01 -0700
Subject: [PATCH] GSettings: add getters for user/default value

Add two new APIs: g_settings_get_user_value() and
g_settings_get_default_value().   Together, these should allow the
inspection of all interesting cases of "is this key set?" and "what
would happen if I reset this key?"

https://bugzilla.gnome.org/show_bug.cgi?id=668233
---
 docs/reference/gio/gio-sections.txt |   2 +
 gio/gsettings.c                     | 100 ++++++++++++++++++++++++++++++++++++
 gio/gsettings.h                     |   7 +++
 3 files changed, 109 insertions(+)

diff --git a/gio/gdelayedsettingsbackend.c b/gio/gdelayedsettingsbackend.c
index 6c1866b..20bcc37 100644
--- a/gio/gdelayedsettingsbackend.c
+++ b/gio/gdelayedsettingsbackend.c
@@ -104,6 +104,32 @@ g_delayed_settings_backend_read (GSettingsBackend   *backend,
   return result;
 }
 
+static GVariant *
+g_delayed_settings_backend_read_user_value (GSettingsBackend   *backend,
+                                            const gchar        *key,
+                                            const GVariantType *expected_type)
+{
+  GDelayedSettingsBackend *delayed = G_DELAYED_SETTINGS_BACKEND (backend);
+  gboolean value_found = FALSE;
+  gpointer result = NULL;
+
+  /* If we find an explicit NULL in our changeset then we want to return
+   * NULL (because the user value has been reset).
+   *
+   * Otherwise, chain up.
+   */
+  g_mutex_lock (&delayed->priv->lock);
+  value_found = g_tree_lookup_extended (delayed->priv->delayed, key, NULL, &result);
+  if (result)
+    g_variant_ref (result);
+  g_mutex_unlock (&delayed->priv->lock);
+
+  if (value_found)
+    return result;
+
+  return g_settings_backend_read_user_value (delayed->priv->backend, key, expected_type);
+}
+
 static gboolean
 g_delayed_settings_backend_write (GSettingsBackend *backend,
                                   const gchar      *key,
@@ -429,6 +455,7 @@ g_delayed_settings_backend_class_init (GDelayedSettingsBackendClass *class)
   GObjectClass *object_class = G_OBJECT_CLASS (class);
 
   backend_class->read = g_delayed_settings_backend_read;
+  backend_class->read_user_value = g_delayed_settings_backend_read_user_value;
   backend_class->write = g_delayed_settings_backend_write;
   backend_class->write_tree = g_delayed_settings_backend_write_tree;
   backend_class->reset = g_delayed_settings_backend_reset;
diff --git a/gio/gsettingsbackend.c b/gio/gsettingsbackend.c
index 608a452..be28dc1 100644
--- a/gio/gsettingsbackend.c
+++ b/gio/gsettingsbackend.c
@@ -746,6 +746,43 @@ g_settings_backend_read (GSettingsBackend   *backend,
 }
 
 /*< private >
+ * g_settings_backend_read_user_value:
+ * @backend: a #GSettingsBackend implementation
+ * @key: the key to read
+ * @expected_type: a #GVariantType
+ *
+ * Reads the 'user value' of a key.
+ *
+ * This is the value of the key that the user has control over and has
+ * set for themselves.  Put another way: if the user did not set the
+ * value for themselves, then this will return %NULL (even if the
+ * sysadmin has provided a default value).
+ *
+ * Returns: the value that was read, or %NULL
+ */
+GVariant *
+g_settings_backend_read_user_value (GSettingsBackend   *backend,
+                                    const gchar        *key,
+                                    const GVariantType *expected_type)
+{
+  GVariant *value;
+
+  value = G_SETTINGS_BACKEND_GET_CLASS (backend)
+    ->read_user_value (backend, key, expected_type);
+
+  if (value != NULL)
+    value = g_variant_take_ref (value);
+
+  if G_UNLIKELY (value && !g_variant_is_of_type (value, expected_type))
+    {
+      g_variant_unref (value);
+      value = NULL;
+    }
+
+  return value;
+}
+
+/*< private >
  * g_settings_backend_write:
  * @backend: a #GSettingsBackend implementation
  * @key: the name of the key
@@ -903,6 +940,14 @@ ignore_subscription (GSettingsBackend *backend,
 {
 }
 
+static GVariant *
+g_settings_backend_real_read_user_value (GSettingsBackend   *backend,
+                                         const gchar        *key,
+                                         const GVariantType *expected_type)
+{
+  return g_settings_backend_read (backend, key, expected_type, FALSE);
+}
+
 static void
 g_settings_backend_init (GSettingsBackend *backend)
 {
@@ -918,6 +963,8 @@ g_settings_backend_class_init (GSettingsBackendClass *class)
   class->subscribe = ignore_subscription;
   class->unsubscribe = ignore_subscription;
 
+  class->read_user_value = g_settings_backend_real_read_user_value;
+
   gobject_class->finalize = g_settings_backend_finalize;
 }
 
diff --git a/gio/gsettingsbackend.h b/gio/gsettingsbackend.h
index 293ca65..2c25355 100644
--- a/gio/gsettingsbackend.h
+++ b/gio/gsettingsbackend.h
@@ -93,7 +93,11 @@ struct _GSettingsBackendClass
   GPermission * (*get_permission)   (GSettingsBackend    *backend,
                                      const gchar         *path);
 
-  gpointer padding[24];
+  GVariant *    (*read_user_value)  (GSettingsBackend    *backend,
+                                     const gchar         *key,
+                                     const GVariantType  *expected_type);
+
+  gpointer padding[23];
 };
 
 struct _GSettingsBackend
diff --git a/gio/gsettingsbackendinternal.h b/gio/gsettingsbackendinternal.h
index f782c7c..0c22f90 100644
--- a/gio/gsettingsbackendinternal.h
+++ b/gio/gsettingsbackendinternal.h
@@ -62,6 +62,9 @@ GVariant *              g_settings_backend_read                         (GSettin
                                                                          const gchar                    *key,
                                                                          const GVariantType             *expected_type,
                                                                          gboolean                        default_value);
+GVariant *              g_settings_backend_read_user_value              (GSettingsBackend               *backend,
+                                                                         const gchar                    *key,
+                                                                         const GVariantType             *expected_type);
 gboolean                g_settings_backend_write                        (GSettingsBackend               *backend,
                                                                          const gchar                    *key,
                                                                          GVariant                       *value,
-- 
1.8.4

diff --git a/docs/reference/gio/gio-sections.txt b/docs/reference/gio/gio-sections.txt
index ee6c5de..5ed836b 100644
--- a/docs/reference/gio/gio-sections.txt
+++ b/docs/reference/gio/gio-sections.txt
@@ -2442,6 +2442,8 @@ g_settings_revert
 g_settings_get_has_unapplied
 g_settings_get_child
 g_settings_reset
+g_settings_get_user_value
+g_settings_get_default_value
 
 <SUBSECTION Introspection>
 g_settings_list_schemas
diff --git a/gio/gsettings.c b/gio/gsettings.c
index f31ad3f..7e16cde 100644
--- a/gio/gsettings.c
+++ b/gio/gsettings.c
@@ -1048,14 +1048,19 @@ g_settings_write_to_backend (GSettings          *settings,
 
 static GVariant *
 g_settings_read_from_backend (GSettings          *settings,
-                              GSettingsSchemaKey *key)
+                              GSettingsSchemaKey *key,
+                              gboolean            user_value_only,
+                              gboolean            default_value)
 {
   GVariant *value;
   GVariant *fixup;
   gchar *path;
 
   path = g_strconcat (settings->priv->path, key->name, NULL);
-  value = g_settings_backend_read (settings->priv->backend, path, key->type, FALSE);
+  if (user_value_only)
+    value = g_settings_backend_read_user_value (settings->priv->backend, path, key->type);
+  else
+    value = g_settings_backend_read (settings->priv->backend, path, key->type, default_value);
   g_free (path);
 
   if (value != NULL)
@@ -1095,7 +1100,7 @@ g_settings_get_value (GSettings   *settings,
   g_return_val_if_fail (key != NULL, NULL);
 
   g_settings_schema_key_init (&skey, settings->priv->schema, key);
-  value = g_settings_read_from_backend (settings, &skey);
+  value = g_settings_read_from_backend (settings, &skey, FALSE, FALSE);
 
   if (value == NULL)
     value = g_settings_schema_key_get_translated_default (&skey);
@@ -1151,7 +1156,7 @@ g_settings_get_enum (GSettings   *settings,
       return -1;
     }
 
-  value = g_settings_read_from_backend (settings, &skey);
+  value = g_settings_read_from_backend (settings, &skey, FALSE, FALSE);
 
   if (value == NULL)
     value = g_settings_schema_key_get_translated_default (&skey);
@@ -1264,7 +1269,7 @@ g_settings_get_flags (GSettings   *settings,
       return -1;
     }
 
-  value = g_settings_read_from_backend (settings, &skey);
+  value = g_settings_read_from_backend (settings, &skey, FALSE, FALSE);
 
   if (value == NULL)
     value = g_settings_schema_key_get_translated_default (&skey);
@@ -1522,7 +1527,7 @@ g_settings_get_mapped (GSettings           *settings,
 
   g_settings_schema_key_init (&skey, settings->priv->schema, key);
 
-  if ((value = g_settings_read_from_backend (settings, &skey)))
+  if ((value = g_settings_read_from_backend (settings, &skey, FALSE, FALSE)))
     {
       okay = mapping (value, &result, user_data);
       g_variant_unref (value);
@@ -2405,7 +2410,7 @@ g_settings_binding_key_changed (GSettings   *settings,
 
   g_value_init (&value, binding->property->value_type);
 
-  variant = g_settings_read_from_backend (binding->settings, &binding->key);
+  variant = g_settings_read_from_backend (binding->settings, &binding->key, FALSE, FALSE);
   if (variant && !binding->get_mapping (&value, variant, binding->user_data))
     {
       /* silently ignore errors in the user's config database */
@@ -2952,7 +2957,7 @@ g_settings_action_get_state (GAction *action)
   GSettingsAction *gsa = (GSettingsAction *) action;
   GVariant *value;
 
-  value = g_settings_read_from_backend (gsa->settings, &gsa->key);
+  value = g_settings_read_from_backend (gsa->settings, &gsa->key, FALSE, FALSE);
 
   if (value == NULL)
     value = g_settings_schema_key_get_translated_default (&gsa->key);
diff --git a/gio/gsettings.c b/gio/gsettings.c
index 7e16cde..dca65d9 100644
--- a/gio/gsettings.c
+++ b/gio/gsettings.c
@@ -1114,6 +1114,106 @@ g_settings_get_value (GSettings   *settings,
 }
 
 /**
+ * g_settings_get_user_value:
+ * @settings: a #GSettings object
+ * @key: the key to check for being set
+ *
+ * Checks the "user value" of a key, if there is one.
+ *
+ * The user value of a key is the last value that was set by the user.
+ *
+ * After calling g_settings_reset() this function should always return
+ * %NULL (assuming something is not wrong with the system
+ * configuration).
+ *
+ * It is possible that g_settings_get_value() will return a different
+ * value than this function.  This can happen in the case that the user
+ * set a value for a key that was subsequently locked down by the system
+ * administrator -- this function will return the user's old value.
+ *
+ * This function may be useful for adding a "reset" option to a UI or
+ * for providing indication that a particular value has been changed.
+ *
+ * It is a programmer error to give a @key that isn't contained in the
+ * schema for @settings.
+ *
+ * Returns: (allow none) (transfer full): the user's value, if set
+ *
+ * Since: 2.40
+ **/
+GVariant *
+g_settings_get_user_value (GSettings   *settings,
+                           const gchar *key)
+{
+  GSettingsSchemaKey skey;
+  GVariant *value;
+
+  g_return_val_if_fail (G_IS_SETTINGS (settings), NULL);
+  g_return_val_if_fail (key != NULL, NULL);
+
+  g_settings_schema_key_init (&skey, settings->priv->schema, key);
+  value = g_settings_read_from_backend (settings, &skey, TRUE, FALSE);
+  g_settings_schema_key_clear (&skey);
+
+  return value;
+}
+
+/**
+ * g_settings_get_default_value:
+ * @settings: a #GSettings object
+ * @key: the key to check for being set
+ *
+ * Gets the "default value" of a key.
+ *
+ * This is the value that would be read if g_settings_reset() were to be
+ * called on the key.
+ *
+ * Note that this may be a different value than returned by
+ * g_settings_schema_key_get_default_value() if the system administrator
+ * has provided a default value.
+ *
+ * Comparing the return values of g_settings_get_default_value() and
+ * g_settings_get_value() is not sufficient for determining if a value
+ * has been set because the user may have explicitly set the value to
+ * something that happens to be equal to the default.  The difference
+ * here is that if the default changes in the future, the user's key
+ * will still be set.
+ *
+ * This function may be useful for adding an indication to a UI of what
+ * the default value was before the user set it.
+ *
+ * It is a programmer error to give a @key that isn't contained in the
+ * schema for @settings.
+ *
+ * Returns: (allow none) (transfer full): the default value
+ *
+ * Since: 2.40
+ **/
+GVariant *
+g_settings_get_default_value (GSettings   *settings,
+                              const gchar *key)
+{
+  GSettingsSchemaKey skey;
+  GVariant *value;
+
+  g_return_val_if_fail (G_IS_SETTINGS (settings), NULL);
+  g_return_val_if_fail (key != NULL, NULL);
+
+  g_settings_schema_key_init (&skey, settings->priv->schema, key);
+  value = g_settings_read_from_backend (settings, &skey, FALSE, TRUE);
+
+  if (value == NULL)
+    value = g_settings_schema_key_get_translated_default (&skey);
+
+  if (value == NULL)
+    value = g_variant_ref (skey.default_value);
+
+  g_settings_schema_key_clear (&skey);
+
+  return value;
+}
+
+/**
  * g_settings_get_enum:
  * @settings: a #GSettings object
  * @key: the key to get the value for
diff --git a/gio/gsettings.h b/gio/gsettings.h
index 38c76f3..7cb26fd 100644
--- a/gio/gsettings.h
+++ b/gio/gsettings.h
@@ -112,6 +112,12 @@ GLIB_AVAILABLE_IN_ALL
 GVariant *              g_settings_get_value                            (GSettings          *settings,
                                                                          const gchar        *key);
 
+GVariant *              g_settings_get_user_value                       (GSettings          *settings,
+                                                                         const gchar        *key);
+GLIB_AVAILABLE_IN_2_38
+GVariant *              g_settings_get_default_value                    (GSettings          *settings,
+                                                                         const gchar        *key);
+
 GLIB_AVAILABLE_IN_ALL
 gboolean                g_settings_set                                  (GSettings          *settings,
                                                                          const gchar        *key,
-- 
1.8.4

