/*
* 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 .
*
* Author: Ryan Lortie
*/
#include "config.h"
#include "dconf-client.h"
#include "../engine/dconf-engine.h"
#include "../common/dconf-paths.h"
#include
/**
* 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: 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: 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: 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: 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: 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 is @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
* @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
* @tag: (out) (allow-none): 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) (allow-none): 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);
}