diff options
Diffstat (limited to 'client/dconf-client.c')
-rw-r--r-- | client/dconf-client.c | 697 |
1 files changed, 697 insertions, 0 deletions
diff --git a/client/dconf-client.c b/client/dconf-client.c new file mode 100644 index 0000000..c315693 --- /dev/null +++ b/client/dconf-client.c @@ -0,0 +1,697 @@ +/* + * Copyright © 2010 Codethink Limited + * Copyright © 2012 Canonical Limited + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the licence, 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + * + * Author: Ryan Lortie <desrt@desrt.ca> + */ + +#include "config.h" + +#include "dconf-client.h" + +#include "../engine/dconf-engine.h" +#include "../common/dconf-paths.h" +#include <glib-object.h> + +/** + * SECTION:client + * @title: DConfClient + * @short_description: Direct read and write access to dconf, based on GDBus + * + * This is the primary client interface to dconf. + * + * It allows applications to directly read from and write to the dconf + * database. Applications can subscribe to change notifications. + * + * Most applications probably don't want to access dconf directly and + * would be better off using something like #GSettings. + * + * Please note that the API of libdconf is not stable in any way. It + * has changed in incompatible ways in the past and there will be + * further changes in the future. + **/ + +/** + * DConfClient: + * + * The main object for interacting with dconf. This is a #GObject, so + * you should manage it with g_object_ref() and g_object_unref(). + **/ +struct _DConfClient +{ + GObject parent_instance; + + DConfEngine *engine; + GMainContext *context; +}; + +G_DEFINE_TYPE (DConfClient, dconf_client, G_TYPE_OBJECT) + +enum +{ + SIGNAL_CHANGED, + SIGNAL_WRITABILITY_CHANGED, + N_SIGNALS +}; +static guint dconf_client_signals[N_SIGNALS]; + +static void +dconf_client_finalize (GObject *object) +{ + DConfClient *client = DCONF_CLIENT (object); + + dconf_engine_unref (client->engine); + g_main_context_unref (client->context); + + G_OBJECT_CLASS (dconf_client_parent_class) + ->finalize (object); +} + +static void +dconf_client_init (DConfClient *client) +{ +} + +static void +dconf_client_class_init (DConfClientClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + + object_class->finalize = dconf_client_finalize; + + /** + * DConfClient::changed: + * @client: the #DConfClient reporting the change + * @prefix: the prefix under which the changes happened + * @changes: the list of paths that were changed, relative to @prefix + * @tag: the tag for the change, if it originated from the service + * + * This signal is emitted when the #DConfClient has a possible change + * to report. The signal is an indication that a change may have + * occurred; it's possible that the keys will still have the same value + * as before. + * + * To ensure that you receive notification about changes to paths that + * you are interested in you must call dconf_client_watch_fast() or + * dconf_client_watch_sync(). You may still receive notifications for + * paths that you did not explicitly watch. + * + * @prefix will be an absolute dconf path; see dconf_is_path(). + * @changes is a %NULL-terminated array of dconf rel paths; see + * dconf_is_rel_path(). + * + * @tag is an opaque tag string, or %NULL. The only thing you should + * do with @tag is to compare it to tag values returned by + * dconf_client_write_sync() or dconf_client_change_sync(). + * + * The number of changes being reported is equal to the length of + * @changes. Appending each item in @changes to @prefix will give the + * absolute path of each changed item. + * + * If a single key has changed then @prefix will be equal to the key + * and @changes will contain a single item: the empty string. + * + * If a single dir has changed (indicating that any key under the dir + * may have changed) then @prefix will be equal to the dir and + * @changes will contain a single empty string. + * + * If more than one change is being reported then @changes will have + * more than one item. + **/ + dconf_client_signals[SIGNAL_CHANGED] = g_signal_new ("changed", DCONF_TYPE_CLIENT, G_SIGNAL_RUN_LAST, + 0, NULL, NULL, NULL, G_TYPE_NONE, 3, + G_TYPE_STRING | G_SIGNAL_TYPE_STATIC_SCOPE, + G_TYPE_STRV | G_SIGNAL_TYPE_STATIC_SCOPE, + G_TYPE_STRING | G_SIGNAL_TYPE_STATIC_SCOPE); + + /** + * DConfClient::writability-changed: + * @client: the #DConfClient reporting the change + * @path: the dir or key that changed + * + * Signal emitted when writability for a key (or all keys in a dir) changes. + * It will be immediately followed by #DConfClient::changed signal for + * the path. + */ + dconf_client_signals[SIGNAL_WRITABILITY_CHANGED] = g_signal_new ("writability-changed", DCONF_TYPE_CLIENT, + G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, + G_TYPE_NONE, 1, + G_TYPE_STRING | G_SIGNAL_TYPE_STATIC_SCOPE); +} + +typedef struct +{ + DConfClient *client; + gchar *prefix; + gchar **changes; + gchar *tag; + gboolean is_writability; +} DConfClientChange; + +static gboolean +dconf_client_dispatch_change_signal (gpointer user_data) +{ + DConfClientChange *change = user_data; + + if (change->is_writability) + { + /* We know that the engine does it this way... */ + g_assert (change->changes[0][0] == '\0' && change->changes[1] == NULL); + + g_signal_emit (change->client, + dconf_client_signals[SIGNAL_WRITABILITY_CHANGED], 0, + change->prefix); + } + + g_signal_emit (change->client, dconf_client_signals[SIGNAL_CHANGED], 0, + change->prefix, change->changes, change->tag); + + g_object_unref (change->client); + g_free (change->prefix); + g_strfreev (change->changes); + g_free (change->tag); + g_slice_free (DConfClientChange, change); + + return G_SOURCE_REMOVE; +} + +void +dconf_engine_change_notify (DConfEngine *engine, + const gchar *prefix, + const gchar * const *changes, + const gchar * tag, + gboolean is_writability, + gpointer origin_tag, + gpointer user_data) +{ + GWeakRef *weak_ref = user_data; + DConfClientChange *change; + DConfClient *client; + + client = g_weak_ref_get (weak_ref); + + if (client == NULL) + return; + + g_return_if_fail (DCONF_IS_CLIENT (client)); + + change = g_slice_new (DConfClientChange); + change->client = client; + change->prefix = g_strdup (prefix); + change->changes = g_strdupv ((gchar **) changes); + change->tag = g_strdup (tag); + change->is_writability = is_writability; + + g_main_context_invoke (client->context, dconf_client_dispatch_change_signal, change); +} + +static void +dconf_client_free_weak_ref (gpointer data) +{ + GWeakRef *weak_ref = data; + + g_weak_ref_clear (weak_ref); + g_slice_free (GWeakRef, weak_ref); +} + +/** + * dconf_client_new: + * + * Creates a new #DConfClient. + * + * Returns: (transfer full): a new #DConfClient + **/ +DConfClient * +dconf_client_new (void) +{ + DConfClient *client; + GWeakRef *weak_ref; + + client = g_object_new (DCONF_TYPE_CLIENT, NULL); + weak_ref = g_slice_new (GWeakRef); + g_weak_ref_init (weak_ref, client); + client->engine = dconf_engine_new (NULL, weak_ref, dconf_client_free_weak_ref); + client->context = g_main_context_ref_thread_default (); + + return client; +} + +/** + * dconf_client_read: + * @client: a #DConfClient + * @key: the key to read the value of + * + * Reads the current value of @key. + * + * If @key exists, its value is returned. Otherwise, %NULL is returned. + * + * If there are outstanding "fast" changes in progress they may affect + * the result of this call. + * + * Returns: (transfer full) (nullable): a #GVariant, or %NULL + **/ +GVariant * +dconf_client_read (DConfClient *client, + const gchar *key) +{ + g_return_val_if_fail (DCONF_IS_CLIENT (client), NULL); + + return dconf_engine_read (client->engine, DCONF_READ_FLAGS_NONE, NULL, key); +} + +/** + * DConfReadFlags: + * @DCONF_READ_FLAGS_NONE: no flags + * @DCONF_READ_DEFAULT_VALUE: read the default value, ignoring any + * values in writable databases or any queued changes. This is + * effectively equivalent to asking what value would be read after a + * reset was written for the key in question. + * @DCONF_READ_USER_VALUE: read the user value, ignoring any system + * databases, including ignoring locks. It is even possible to read + * "invisible" values in the user database in this way, which would + * have normally been ignored because of locks. + * + * Since: 0.26 + */ + +/** + * dconf_client_read_full: + * @client: a #DConfClient + * @key: the key to read the default value of + * @flags: #DConfReadFlags + * @read_through: a #GQueue of #DConfChangeset + * + * Reads the current value of @key. + * + * If @flags contains %DCONF_READ_USER_VALUE then only the user value + * will be read. Locks are ignored, which means that it is possible to + * use this API to read "invisible" user values which are hidden by + * system locks. + * + * If @flags contains %DCONF_READ_DEFAULT_VALUE then only non-user + * values will be read. The result will be exactly equivalent to the + * value that would be read if the current value of the key were to be + * reset. + * + * Flags may not contain both %DCONF_READ_USER_VALUE and + * %DCONF_READ_DEFAULT_VALUE. + * + * If @read_through is non-%NULL, %DCONF_READ_DEFAULT_VALUE is not + * given then @read_through is checked for the key in question, subject + * to the restriction that the key in question is writable. This + * effectively answers the question of "what would happen if these + * changes were committed". + * + * If there are outstanding "fast" changes in progress they may affect + * the result of this call. + * + * If @flags is %DCONF_READ_FLAGS_NONE and @read_through is %NULL then + * this call is exactly equivalent to dconf_client_read(). + * + * Returns: (transfer full) (nullable): a #GVariant, or %NULL + * + * Since: 0.26 + */ +GVariant * +dconf_client_read_full (DConfClient *client, + const gchar *key, + DConfReadFlags flags, + const GQueue *read_through) +{ + g_return_val_if_fail (DCONF_IS_CLIENT (client), NULL); + + return dconf_engine_read (client->engine, flags, read_through, key); +} + +/** + * dconf_client_list: + * @client: a #DConfClient + * @dir: the dir to list the contents of + * @length: the length of the returned list + * + * Gets the list of all dirs and keys immediately under @dir. + * + * If @length is non-%NULL then it will be set to the length of the + * returned array. In any case, the array is %NULL-terminated. + * + * IF there are outstanding "fast" changes in progress then this call + * may return inaccurate results with respect to those outstanding + * changes. + * + * Returns: (transfer full) (not nullable): an array of strings, never %NULL. + **/ +gchar ** +dconf_client_list (DConfClient *client, + const gchar *dir, + gint *length) +{ + g_return_val_if_fail (DCONF_IS_CLIENT (client), NULL); + + return dconf_engine_list (client->engine, dir, length); +} + +/** + * dconf_client_list_locks: + * @client: a #DConfClient + * @dir: the dir to limit results to + * @length: the length of the returned list. + * + * Lists all locks under @dir in effect for @client. + * + * If no locks are in effect, an empty list is returned. If no keys are + * writable at all then a list containing @dir is returned. + * + * The returned list will be %NULL-terminated. + * + * Returns: (transfer full) (not nullable): an array of strings, never %NULL. + * + * Since: 0.26 + */ +gchar ** +dconf_client_list_locks (DConfClient *client, + const gchar *dir, + gint *length) +{ + g_return_val_if_fail (DCONF_IS_CLIENT (client), NULL); + g_return_val_if_fail (dconf_is_dir (dir, NULL), NULL); + + return dconf_engine_list_locks (client->engine, dir, length); +} + +/** + * dconf_client_is_writable: + * @client: a #DConfClient + * @key: the key to check for writability + * + * Checks if @key is writable (ie: the key has no locks). + * + * This call does not verify that writing to the key will actually be + * successful. It only checks that the database is writable and that + * there are no locks affecting @key. Other issues (such as a full disk + * or an inability to connect to the bus and start the service) may + * cause the write to fail. + * + * Returns: %TRUE if @key is writable + **/ +gboolean +dconf_client_is_writable (DConfClient *client, + const gchar *key) +{ + g_return_val_if_fail (DCONF_IS_CLIENT (client), FALSE); + + return dconf_engine_is_writable (client->engine, key); +} + +/** + * dconf_client_write_fast: + * @client: a #DConfClient + * @key: the key to write to + * @value: a #GVariant, the value to write. If it has a floating reference it's + * consumed. + * @error: a pointer to a %NULL #GError, or %NULL + * + * Writes @value to the given @key, or reset @key to its default value. + * + * If @value is %NULL then @key is reset to its default value (which may + * be completely unset), otherwise @value becomes the new value. + * + * This call merely queues up the write and returns immediately, without + * blocking. The only errors that can be detected or reported at this + * point are attempts to write to read-only keys. If the application + * exits immediately after this function returns then the queued call + * may never be sent; see dconf_client_sync(). + * + * A local copy of the written value is kept so that calls to + * dconf_client_read() that occur before the service actually makes the + * change will return the new value. + * + * If the write is queued then a change signal will be directly emitted. + * If this function is being called from the main context of @client + * then the signal is emitted before this function returns; otherwise it + * is scheduled on the main context. + * + * Returns: %TRUE if the write was queued + **/ +gboolean +dconf_client_write_fast (DConfClient *client, + const gchar *key, + GVariant *value, + GError **error) +{ + DConfChangeset *changeset; + gboolean success; + + g_return_val_if_fail (DCONF_IS_CLIENT (client), FALSE); + + changeset = dconf_changeset_new_write (key, value); + success = dconf_engine_change_fast (client->engine, changeset, NULL, error); + dconf_changeset_unref (changeset); + + return success; +} + +/** + * dconf_client_write_sync: + * @client: a #DConfClient + * @key: the key to write to + * @value: a #GVariant, the value to write. If it has a floating reference it's + * consumed. + * @tag: (out) (optional) (not nullable) (transfer full): the tag from this write + * @cancellable: a #GCancellable, or %NULL + * @error: a pointer to a %NULL #GError, or %NULL + * + * Write @value to the given @key, or reset @key to its default value. + * + * If @value is %NULL then @key is reset to its default value (which may + * be completely unset), otherwise @value becomes the new value. + * + * This call blocks until the write is complete. This call will + * therefore detect and report all cases of failure. If the modified + * key is currently being watched then a signal will be emitted from the + * main context of @client (once the signal arrives from the service). + * + * If @tag is non-%NULL then it is set to the unique tag associated with + * this write. This is the same tag that will appear in the following + * change signal. + * + * Returns: %TRUE on success, else %FALSE with @error set + **/ +gboolean +dconf_client_write_sync (DConfClient *client, + const gchar *key, + GVariant *value, + gchar **tag, + GCancellable *cancellable, + GError **error) +{ + DConfChangeset *changeset; + gboolean success; + + g_return_val_if_fail (DCONF_IS_CLIENT (client), FALSE); + + changeset = dconf_changeset_new_write (key, value); + success = dconf_engine_change_sync (client->engine, changeset, tag, error); + dconf_changeset_unref (changeset); + + return success; +} + +/** + * dconf_client_change_fast: + * @client: a #DConfClient + * @changeset: the changeset describing the requested change + * @error: a pointer to a %NULL #GError, or %NULL + * + * Performs the change operation described by @changeset. + * + * Once @changeset is passed to this call it can no longer be modified. + * + * This call merely queues up the write and returns immediately, without + * blocking. The only errors that can be detected or reported at this + * point are attempts to write to read-only keys. If the application + * exits immediately after this function returns then the queued call + * may never be sent; see dconf_client_sync(). + * + * A local copy of the written value is kept so that calls to + * dconf_client_read() that occur before the service actually makes the + * change will return the new value. + * + * If the write is queued then a change signal will be directly emitted. + * If this function is being called from the main context of @client + * then the signal is emitted before this function returns; otherwise it + * is scheduled on the main context. + * + * Returns: %TRUE if the requested changed was queued + **/ +gboolean +dconf_client_change_fast (DConfClient *client, + DConfChangeset *changeset, + GError **error) +{ + g_return_val_if_fail (DCONF_IS_CLIENT (client), FALSE); + + return dconf_engine_change_fast (client->engine, changeset, NULL, error); +} + +/** + * dconf_client_change_sync: + * @client: a #DConfClient + * @changeset: the changeset describing the requested change + * @tag: (out) (optional) (not nullable) (transfer full): the tag from this write + * @cancellable: a #GCancellable, or %NULL + * @error: a pointer to a %NULL #GError, or %NULL + * + * Performs the change operation described by @changeset. + * + * Once @changeset is passed to this call it can no longer be modified. + * + * This call blocks until the change is complete. This call will + * therefore detect and report all cases of failure. If any of the + * modified keys are currently being watched then a signal will be + * emitted from the main context of @client (once the signal arrives + * from the service). + * + * If @tag is non-%NULL then it is set to the unique tag associated with + * this change. This is the same tag that will appear in the following + * change signal. If @changeset makes no changes then @tag may be + * non-unique (eg: the empty string may be used for empty changesets). + * + * Returns: %TRUE on success, else %FALSE with @error set + **/ +gboolean +dconf_client_change_sync (DConfClient *client, + DConfChangeset *changeset, + gchar **tag, + GCancellable *cancellable, + GError **error) +{ + g_return_val_if_fail (DCONF_IS_CLIENT (client), FALSE); + + return dconf_engine_change_sync (client->engine, changeset, tag, error); +} + +/** + * dconf_client_watch_fast: + * @client: a #DConfClient + * @path: a path to watch + * + * Requests change notifications for @path. + * + * If @path is a key then the single key is monitored. If @path is a + * dir then all keys under the dir are monitored. + * + * This function queues the watch request with D-Bus and returns + * immediately. There is a very slim chance that the dconf database + * could change before the watch is actually established. If that is + * the case then a synthetic change signal will be emitted. + * + * Errors are silently ignored. + **/ +void +dconf_client_watch_fast (DConfClient *client, + const gchar *path) +{ + g_return_if_fail (DCONF_IS_CLIENT (client)); + + dconf_engine_watch_fast (client->engine, path); +} + +/** + * dconf_client_watch_sync: + * @client: a #DConfClient + * @path: a path to watch + * + * Requests change notifications for @path. + * + * If @path is a key then the single key is monitored. If @path is a + * dir then all keys under the dir are monitored. + * + * This function submits each of the various watch requests that are + * required to monitor a key and waits until each of them returns. By + * the time this function returns, the watch has been established. + * + * Errors are silently ignored. + **/ +void +dconf_client_watch_sync (DConfClient *client, + const gchar *path) +{ + g_return_if_fail (DCONF_IS_CLIENT (client)); + + dconf_engine_watch_sync (client->engine, path); +} + +/** + * dconf_client_unwatch_fast: + * @client: a #DConfClient + * @path: a path previously watched + * + * Cancels the effect of a previous call to dconf_client_watch_fast(). + * + * This call returns immediately. + * + * It is still possible that change signals are received after this call + * had returned (watching guarantees notification of changes, but + * unwatching does not guarantee no notifications). + **/ +void +dconf_client_unwatch_fast (DConfClient *client, + const gchar *path) +{ + g_return_if_fail (DCONF_IS_CLIENT (client)); + + dconf_engine_unwatch_fast (client->engine, path); +} + +/** + * dconf_client_unwatch_sync: + * @client: a #DConfClient + * @path: a path previously watched + * + * Cancels the effect of a previous call to dconf_client_watch_sync(). + * + * This function submits each of the various unwatch requests and waits + * until each of them returns. It is still possible that change signals + * are received after this call has returned (watching guarantees + * notification of changes, but unwatching does not guarantee no + * notifications). + **/ +void +dconf_client_unwatch_sync (DConfClient *client, + const gchar *path) +{ + g_return_if_fail (DCONF_IS_CLIENT (client)); + + dconf_engine_unwatch_sync (client->engine, path); +} + +/** + * dconf_client_sync: + * @client: a #DConfClient + * + * Blocks until all outstanding "fast" change or write operations have + * been submitted to the service. + * + * Applications should generally call this before exiting on any + * #DConfClient that they wrote to. + **/ +void +dconf_client_sync (DConfClient *client) +{ + g_return_if_fail (DCONF_IS_CLIENT (client)); + + dconf_engine_sync (client->engine); +} |