diff options
author | Ryan Lortie <desrt@desrt.ca> | 2010-04-28 13:23:32 -0500 |
---|---|---|
committer | Ryan Lortie <desrt@desrt.ca> | 2010-04-28 13:23:32 -0500 |
commit | 00a39d453558d82a5d8845e552e2b2648f71ae37 (patch) | |
tree | 72e9f362763da62f44f7588b1c1ea0c6ee8fc525 | |
download | dconf-00a39d453558d82a5d8845e552e2b2648f71ae37.tar.gz |
Initial commit
-rw-r--r-- | .gitignore | 21 | ||||
-rw-r--r-- | Makefile.am | 1 | ||||
-rwxr-xr-x | autogen.sh | 21 | ||||
-rw-r--r-- | configure.ac | 22 | ||||
-rw-r--r-- | dconf.doap | 19 | ||||
-rw-r--r-- | gsettings/Makefile.am | 11 | ||||
-rw-r--r-- | gsettings/dconfdatabase.c | 456 | ||||
-rw-r--r-- | gsettings/dconfdatabase.h | 34 | ||||
-rw-r--r-- | gsettings/dconfsettingsbackend.c | 141 | ||||
-rw-r--r-- | gvdb/Makefile.am | 6 | ||||
-rw-r--r-- | service/.gitignore | 2 | ||||
-rw-r--r-- | service/Makefile.am | 20 | ||||
-rw-r--r-- | service/dconf-rebuilder.c | 183 | ||||
-rw-r--r-- | service/dconf-rebuilder.h | 8 | ||||
-rw-r--r-- | service/service.c | 332 |
15 files changed, 1277 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..edd1617 --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +# for all subdirectories +Makefile.in +Makefile +.libs +.deps +*.o +*.lo +*.la +*.pc + +# autofoo stuff here +compile +config.* +configure +depcomp +aclocal.m4 +autom4te.cache +libtool +ltmain.sh +missing +install-sh diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..d6cb639 --- /dev/null +++ b/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = gvdb service gsettings diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..857505a --- /dev/null +++ b/autogen.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +set -e + +if [ "$1" = "clean" ]; then + rm -f aclocal.m4 configure missing install-sh depcomp ltmain.sh \ + config.* `find . -name Makefile.in` compile libtool + rm -rf autom4te.cache + exit +fi + +libtoolize --automake +aclocal +automake --add-missing --foreign +autoconf + +CFLAGS=${CFLAGS=-ggdb -Werror} +LDFLAGS=${LDFLAGS=-Wl,-O1} +export CFLAGS LDFLAGS + +./configure --enable-silent-rules "$@" diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..a9f1cfb --- /dev/null +++ b/configure.ac @@ -0,0 +1,22 @@ +AC_INIT(dconf, 0.3) +AM_INIT_AUTOMAKE +AM_SILENT_RULES +AC_PROG_LIBTOOL +AC_PROG_CC + +PKG_CHECK_MODULES(gio, gdbus-standalone) +giomodulesdir=`pkg-config --variable=giomoduledir gio-2.0` +AC_SUBST(giomodulesdir) + +AC_ARG_WITH(dbus_service_dir, [ --with-dbus-service-dir=PATH choose directory for dbus service files, [default=PREFIX/share/dbus-1/services]], dbusservicedir="$withval", dbusservicedir=${datadir}/dbus-1/services) +AC_SUBST(dbusservicedir) + +AC_ARG_WITH(dbus_system_service_dir, [ --with-dbus-system-service-dir=PATH choose directory for dbus system service files, [default=PREFIX/share/dbus-1/system-services]], dbussystemservicedir="$withval", dbussystemservicedir=${datadir}/dbus-1/system-services) +AC_SUBST(dbussystemservicedir) + +AC_OUTPUT([ + gsettings/Makefile + service/Makefile + gvdb/Makefile + Makefile +]) diff --git a/dconf.doap b/dconf.doap new file mode 100644 index 0000000..69a2178 --- /dev/null +++ b/dconf.doap @@ -0,0 +1,19 @@ +<?xml version='1.0' encoding='utf-8'?> + +<Project xmlns='http://usefulinc.com/ns/doap#' + xmlns:foaf='http://xmlns.com/foaf/0.1/' + xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' + xmlns:gnome='http://api.gnome.org/doap-extensions#'> + + <name xml:lang='en'>dconf</name> + <shortdesc xml:lang='en'>configuation database system</shortdesc> + + <maintainer> + <foaf:Person> + <foaf:name>Ryan Lortie</foaf:name> + <foaf:mbox rdf:resource='mailto:desrt@desrt.ca'/> + <gnome:userid>ryanl</gnome:userid> + </foaf:Person> + </maintainer> + +</Project> diff --git a/gsettings/Makefile.am b/gsettings/Makefile.am new file mode 100644 index 0000000..41b7cc2 --- /dev/null +++ b/gsettings/Makefile.am @@ -0,0 +1,11 @@ +AM_CFLAGS = $(gio_CFLAGS) -I$(top_srcdir)/gvdb + +giomodules_LTLIBRARIES = libdconfsettings.la + +libdconfsettings_la_LIBADD = $(gio_LIBS) +libdconfsettings_la_SOURCES = \ + ../gvdb/gvdb-reader.c \ + dconfdatabase.h \ + dconfdatabase.c \ + dconfsettingsbackend.c + diff --git a/gsettings/dconfdatabase.c b/gsettings/dconfdatabase.c new file mode 100644 index 0000000..2f34d9d --- /dev/null +++ b/gsettings/dconfdatabase.c @@ -0,0 +1,456 @@ +#define G_SETTINGS_ENABLE_BACKEND +#include <gio/gsettingsbackend.h> +#include "dconfdatabase.h" + +#include <string.h> +#include <gdbus/gdbus.h> +#include "gvdb-reader.h" + +typedef struct _Outstanding Outstanding; + +struct _DConfDatabase +{ + GStaticMutex lock; + + GDBusConnection *bus; + GvdbTable *value_table; + GSList *backends; + const gchar *context; + + Outstanding *outstanding; + guint64 anti_expose; +}; + +struct _Outstanding +{ + Outstanding *next; + + volatile guint32 serial; + + gchar *reset_path, *set_key; + GVariant *set_value; + + GTree *tree; +}; + +static volatile guint32 * +dconf_database_new_outstanding (DConfDatabase *database, + const gchar *reset_path, + const gchar *set_key, + GVariant *set_value, + GTree *tree) +{ + Outstanding *outstanding; + + outstanding = g_slice_new (Outstanding); + outstanding->serial = 0; + outstanding->reset_path = g_strdup (reset_path); + outstanding->set_key = g_strdup (set_key); + + if (set_value) + outstanding->set_value = g_variant_ref (set_value); + else + outstanding->set_value = NULL; + + if (tree) + outstanding->tree = g_tree_ref (tree); + else + outstanding->tree = NULL; + + g_static_mutex_lock (&database->lock); + outstanding->next = database->outstanding; + database->outstanding = outstanding; + g_static_mutex_unlock (&database->lock); + + return &outstanding->serial; +} + +static gboolean +dconf_database_remove_outstanding (DConfDatabase *database, + GDBusMessage *message, + guint64 *anti_expose) +{ + Outstanding **node; + guint32 serial; + + if G_LIKELY (database->outstanding == NULL) + return FALSE; + + serial = g_dbus_message_get_reply_serial (message); + + if (serial == 0) + return FALSE; + + g_static_mutex_lock (&database->lock); + + /* this could be made more asymptotically efficient by using a queue + * or a double-linked list with a 'tail' pointer but the usual case + * here will be one outstanding item and very rarely more than a few. + * + * so we scan... + */ + for (node = &database->outstanding; *node; node = &(*node)->next) + if ((*node)->serial == serial) + { + Outstanding *tmp; + + tmp = *node; + *node = tmp->next; + + g_static_mutex_unlock (&database->lock); + + g_variant_get (g_dbus_message_get_body (message), "(t)", anti_expose); + + g_free (tmp->reset_path); + g_free (tmp->set_key); + + if (tmp->set_value) + g_variant_unref (tmp->set_value); + + if (tmp->tree) + g_tree_unref (tmp->tree); + + return TRUE; + } + + g_static_mutex_unlock (&database->lock); + + return FALSE; +} + +static gboolean +dconf_database_scan_outstanding_tree (GTree *tree, + const gchar *key, + gsize key_length, + gpointer *value) +{ + gchar *mykey; + + mykey = g_alloca (key_length + 1); + memcpy (mykey, key, key_length + 1); + + while (!g_tree_lookup_extended (tree, mykey, NULL, value) && + --key_length) + { + while (mykey[key_length - 1] != '/') + key_length--; + + mykey[key_length] = '\0'; + } + + return key_length != 0; +} + +static gboolean +dconf_database_scan_outstanding (DConfDatabase *database, + const gchar *key, + GVariant **value) +{ + Outstanding *node; + gsize length; + + length = strlen (key); + + if G_LIKELY (database->outstanding == NULL) + return FALSE; + + g_static_mutex_lock (&database->lock); + + for (node = database->outstanding; node; node = node->next) + { + if (node->reset_path) + { + if (g_str_has_prefix (key, node->reset_path)) + { + *value = NULL; + return TRUE; + } + } + + else if (node->set_key) + { + if (strcmp (key, node->set_key) == 0) + { + if (node->set_value != NULL) + *value = g_variant_ref (*value); + else + *value = NULL; + + return TRUE; + } + } + + else + { + gpointer result; + + if (dconf_database_scan_outstanding_tree (node->tree, key, + length, &result)) + { + if (result) + *value = g_variant_ref (result); + else + *value = NULL; + + return TRUE; + } + } + } + + g_static_mutex_unlock (&database->lock); + + return FALSE; +} + +static void +dconf_database_reopen_file (DConfDatabase *database) +{ + gchar *filename; + + if (database->value_table != NULL) + gvdb_table_unref (database->value_table); + + filename = g_build_filename (g_get_user_config_dir (), "dconf", NULL); + database->value_table = gvdb_table_new (filename, FALSE, NULL); + g_free (filename); +} + +GVariant * +dconf_database_read (DConfDatabase *database, + const gchar *key) +{ + GVariant *value; + + if (dconf_database_scan_outstanding (database, key, &value)) + return value; + + if (database->value_table == NULL) + return NULL; + + dconf_database_reopen_file (database); + + return gvdb_table_get_value (database->value_table, key, NULL); +} + +static void +dconf_database_incoming_signal (DConfDatabase *database, + GDBusMessage *message) +{ + const gchar **keys; + const gchar *name; + guint64 serial; + + if (strcmp (g_dbus_message_get_interface (message), + "ca.desrt.dconf.Writer") != 0 || + strcmp (g_dbus_message_get_member (message), "Notify") != 0) + return; + + g_variant_get (g_dbus_message_get_body (message), + "(t&s^a&s)", &serial, &name, &keys); + + if (serial != database->anti_expose) + { + GSList *node; + + if (keys[0] == NULL) + { + for (node = database->backends; node; node = node->next) + g_settings_backend_changed (node->data, name, NULL); + } + else + { + for (node = database->backends; node; node = node->next) + g_settings_backend_keys_changed (node->data, name, keys, NULL); + } + } + + g_free (keys); +} + +static gboolean +dconf_database_filter_function (GDBusConnection *connection, + GDBusMessage *message, + gpointer user_data) +{ + DConfDatabase *database = user_data; + + switch (g_dbus_message_get_type (message)) + { + case G_DBUS_MESSAGE_TYPE_METHOD_RETURN: + return dconf_database_remove_outstanding (database, message, + &database->anti_expose); + + case G_DBUS_MESSAGE_TYPE_SIGNAL: + dconf_database_incoming_signal (database, message); + return FALSE; + + default: + return FALSE; + } +} + +void +dconf_database_write_tree (DConfDatabase *database, + GTree *tree, + gpointer origin_tag) +{ + volatile guint32 *serial; + const gchar **keys; + GVariant **values; + gchar *path; + + serial = dconf_database_new_outstanding (database, NULL, NULL, NULL, tree); + g_settings_backend_flatten_tree (tree, &path, &keys, &values); + + { + GSList *node; + + for (node = database->backends; node; node = node->next) + g_settings_backend_keys_changed (node->data, path, + keys, origin_tag); + } + + { + GDBusMessage *message; + GVariantBuilder args; + gsize i; + + message = + g_dbus_message_new_method_call ("ca.desrt.dconf", "/", + "ca.desrt.dconf.Writer", "Write"); + + g_variant_builder_init (&args, G_VARIANT_TYPE ("(ssa{smv})")); + g_variant_builder_add (&args, "s", database->context); + g_variant_builder_add (&args, "s", path); + + g_variant_builder_open (&args, G_VARIANT_TYPE ("a{smv}")); + for (i = 0; keys[i]; i++) + g_variant_builder_add (&args, "{smv}", keys[i], values[i]); + + g_variant_builder_close (&args); + + g_dbus_message_set_body (message, + g_variant_builder_end (&args)); + g_dbus_connection_send_message (database->bus, + message, serial, NULL); + g_object_unref (message); + } + + g_free (path); + g_free (keys); + g_free (values); +} + +gchar ** +dconf_database_list (DConfDatabase *database, + const gchar *path, + gsize *length) +{ + gchar **result; + + result = gvdb_table_list (database->value_table, path); + + if (result) + *length = g_strv_length (result); + + return result; +} + +static void +dconf__message_set_body (GDBusMessage *message, + GVariant *body) +{ + gchar *printed; + + g_variant_ref_sink (body); + printed = g_variant_print (body, FALSE); + g_variant_unref (body); + + g_dbus_message_set_body (message, g_variant_new ("(s)", printed)); + g_free (printed); +} + +void +dconf_database_write (DConfDatabase *database, + const gchar *path_or_key, + GVariant *value, + gpointer origin_tag) +{ + volatile guint32 *serial; + GDBusMessage *message; + + serial = dconf_database_new_outstanding (database, NULL, + path_or_key, value, + NULL); + + message = g_dbus_message_new_method_call ("ca.desrt.dconf", "/", + "ca.desrt.dconf.Writer", "Write"); + dconf__message_set_body (message, g_variant_new ("(smv)", path_or_key, value)); + g_dbus_connection_send_message (database->bus, message, serial, NULL); + g_object_unref (message); + + { + GSList *node; + + for (node = database->backends; node; node = node->next) + g_settings_backend_changed (node->data, path_or_key, origin_tag); + } +} + +static void +send_match_rule (DConfDatabase *database, + const gchar *method, + const gchar *name) +{ + GDBusMessage *message; + gchar *rule; + + rule = g_strdup_printf ("interface='ca.desrt.dconf.Writer'," + "arg1path='%s'", name); + message = g_dbus_message_new_method_call ("org.freedesktop.DBus", "/", + "org.freedesktop.DBus", method); + g_dbus_message_set_body (message, g_variant_new ("(s)", (rule))); + g_dbus_message_set_flags (message, G_DBUS_MESSAGE_FLAGS_NO_REPLY_EXPECTED); + g_dbus_connection_send_message (database->bus, message, NULL, NULL); + g_object_unref (message); + g_free (rule); +} + +void +dconf_database_subscribe (DConfDatabase *database, + const gchar *name) +{ + send_match_rule (database, "AddMatch", name); +} + +void +dconf_database_unsubscribe (DConfDatabase *database, + const gchar *name) +{ + send_match_rule (database, "RemoveMatch", name); +} + +DConfDatabase * +dconf_database_get_for_backend (gpointer backend) +{ + static gsize instance; + + if (g_once_init_enter (&instance)) + { + DConfDatabase *database; + + database = g_slice_new0 (DConfDatabase); + g_static_mutex_init (&database->lock); + database->outstanding = NULL; + database->backends = g_slist_prepend (database->backends, backend); + database->bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL); + g_dbus_connection_add_filter (database->bus, + dconf_database_filter_function, + database, NULL); + dconf_database_reopen_file (database); + + g_once_init_leave (&instance, (gsize) database); + } + + return (DConfDatabase *) instance; +} diff --git a/gsettings/dconfdatabase.h b/gsettings/dconfdatabase.h new file mode 100644 index 0000000..99d1f6e --- /dev/null +++ b/gsettings/dconfdatabase.h @@ -0,0 +1,34 @@ +#ifndef _dconfdatabase_h_ +#define _dconfdatabase_h_ + +#include <glib.h> + +typedef struct _DConfDatabase DConfDatabase; + +G_GNUC_INTERNAL +DConfDatabase * dconf_database_get_for_backend (gpointer backend); + +G_GNUC_INTERNAL +GVariant * dconf_database_read (DConfDatabase *database, + const gchar *key); +G_GNUC_INTERNAL +gchar ** dconf_database_list (DConfDatabase *database, + const gchar *path, + gsize *length); +G_GNUC_INTERNAL +void dconf_database_write (DConfDatabase *database, + const gchar *path_or_key, + GVariant *value, + gpointer origin_tag); +G_GNUC_INTERNAL +void dconf_database_write_tree (DConfDatabase *database, + GTree *tree, + gpointer origin_tag); +G_GNUC_INTERNAL +void dconf_database_subscribe (DConfDatabase *database, + const gchar *name); +G_GNUC_INTERNAL +void dconf_database_unsubscribe (DConfDatabase *database, + const gchar *name); + +#endif diff --git a/gsettings/dconfsettingsbackend.c b/gsettings/dconfsettingsbackend.c new file mode 100644 index 0000000..b8e6411 --- /dev/null +++ b/gsettings/dconfsettingsbackend.c @@ -0,0 +1,141 @@ +#define G_SETTINGS_ENABLE_BACKEND +#include <gio/gsettingsbackend.h> +#include <gio/gio.h> + +#include "dconfdatabase.h" + +typedef GSettingsBackendClass DConfSettingsBackendClass; + +typedef struct +{ + GSettingsBackend backend; + + DConfDatabase *database; +} DConfSettingsBackend; + +G_DEFINE_TYPE (DConfSettingsBackend, + dconf_settings_backend, + G_TYPE_SETTINGS_BACKEND) + +static GVariant * +dconf_settings_backend_read (GSettingsBackend *backend, + const gchar *key, + const GVariantType *expected_type, + gboolean default_value) +{ + DConfSettingsBackend *dcsb = (DConfSettingsBackend *) backend; + + if (default_value) + return NULL; + + return dconf_database_read (dcsb->database, key); +} + +static gchar ** +dconf_settings_backend_list (GSettingsBackend *backend, + const gchar *path, + gsize *length) +{ + DConfSettingsBackend *dcsb = (DConfSettingsBackend *) backend; + + return dconf_database_list (dcsb->database, path, length); +} + +static gboolean +dconf_settings_backend_write (GSettingsBackend *backend, + const gchar *path_or_key, + GVariant *value, + gpointer origin_tag) +{ + DConfSettingsBackend *dcsb = (DConfSettingsBackend *) backend; + + dconf_database_write (dcsb->database, path_or_key, value, origin_tag); + + return TRUE; +} + +static gboolean +dconf_settings_backend_write_tree (GSettingsBackend *backend, + GTree *tree, + gpointer origin_tag) +{ + DConfSettingsBackend *dcsb = (DConfSettingsBackend *) backend; + + dconf_database_write_tree (dcsb->database, tree, origin_tag); + + return TRUE; +} + +static void +dconf_settings_backend_reset (GSettingsBackend *backend, + const gchar *path_or_key, + gpointer origin_tag) +{ + dconf_settings_backend_write (backend, path_or_key, NULL, origin_tag); +} + +static gboolean +dconf_settings_backend_get_writable (GSettingsBackend *backend, + const gchar *key) +{ + return TRUE; +} + +static void +dconf_settings_backend_subscribe (GSettingsBackend *backend, + const gchar *name) +{ + DConfSettingsBackend *dcsb = (DConfSettingsBackend *) backend; + + dconf_database_subscribe (dcsb->database, name); +} + +static void +dconf_settings_backend_unsubscribe (GSettingsBackend *backend, + const gchar *name) +{ + DConfSettingsBackend *dcsb = (DConfSettingsBackend *) backend; + + dconf_database_unsubscribe (dcsb->database, name); +} + +static void +dconf_settings_backend_init (DConfSettingsBackend *dcsb) +{ + dcsb->database = dconf_database_get_for_backend (dcsb); +} + +static void +dconf_settings_backend_class_init (GSettingsBackendClass *class) +{ + class->read = dconf_settings_backend_read; + class->list = dconf_settings_backend_list; + class->write = dconf_settings_backend_write; + class->write_keys = dconf_settings_backend_write_tree; + class->reset = dconf_settings_backend_reset; + class->reset_path = dconf_settings_backend_reset; + class->get_writable = dconf_settings_backend_get_writable; + class->subscribe = dconf_settings_backend_subscribe; + class->unsubscribe = dconf_settings_backend_unsubscribe; +} + +void +g_io_module_load (GIOModule *module) +{ + g_type_module_use (G_TYPE_MODULE (module)); + g_io_extension_point_implement (G_SETTINGS_BACKEND_EXTENSION_POINT_NAME, + dconf_settings_backend_get_type (), + "dconf", 100); +} + +void +g_io_module_unload (GIOModule *module) +{ + g_assert_not_reached (); +} + +gchar ** +g_io_module_query (void) +{ + return g_strsplit (",", G_SETTINGS_BACKEND_EXTENSION_POINT_NAME, 0); +} diff --git a/gvdb/Makefile.am b/gvdb/Makefile.am new file mode 100644 index 0000000..47e77c8 --- /dev/null +++ b/gvdb/Makefile.am @@ -0,0 +1,6 @@ +EXTRA_DIST = \ + gvdb-format.h \ + gvdb-reader.h \ + gvdb-reader.c \ + gvdb-builder.h \ + gvdb-builder.c diff --git a/service/.gitignore b/service/.gitignore new file mode 100644 index 0000000..aefa930 --- /dev/null +++ b/service/.gitignore @@ -0,0 +1,2 @@ +dconf-service +ca.desrt.dconf.service diff --git a/service/Makefile.am b/service/Makefile.am new file mode 100644 index 0000000..dbd509e --- /dev/null +++ b/service/Makefile.am @@ -0,0 +1,20 @@ +AM_CFLAGS = $(gio_CFLAGS) -I$(top_srcdir)/gvdb + +libexec_PROGRAMS = dconf-service + +dbussystemservice_DATA = ca.desrt.dconf.service +dbusservice_DATA = ca.desrt.dconf.service + +dconf_service_LDADD = $(gio_LIBS) +dconf_service_SOURCES = \ + ../gvdb/gvdb-builder.c \ + ../gvdb/gvdb-reader.c \ + dconf-rebuilder.h \ + dconf-rebuilder.h \ + dconf-rebuilder.c \ + service.c + +DISTCLEANFILES = ca.desrt.dconf.service + +ca.desrt.dconf.service: Makefile + $(AM_V_GEN) echo -e '[D-BUS Service]\nName=ca.desrt.dconf\nExec=${libexecdir}/dconf-service' > $@ diff --git a/service/dconf-rebuilder.c b/service/dconf-rebuilder.c new file mode 100644 index 0000000..5856942 --- /dev/null +++ b/service/dconf-rebuilder.c @@ -0,0 +1,183 @@ +#include "dconf-rebuilder.h" + +#include <string.h> + +#include "gvdb-reader.h" +#include "gvdb-builder.h" + +typedef struct +{ + const gchar *prefix; + gint prefix_len; + + GHashTable *table; + const gchar **keys; + GVariant **values; + gint n_items; + gint index; + + gchar name[4096]; + gint name_len; +} DConfRebuilderState; + +static GvdbItem * +dconf_rebuilder_get_parent (GHashTable *table, + gchar *key, + gint length) +{ + GvdbItem *grandparent, *parent; + + if (length == 1) + return NULL; + + while (key[--length - 1] != '/'); + key[length] = '\0'; + + parent = g_hash_table_lookup (table, key); + + if (parent == NULL) + { + parent = gvdb_hash_table_insert (table, key); + + grandparent = dconf_rebuilder_get_parent (table, key, length); + + if (grandparent != NULL) + gvdb_item_set_parent (parent, grandparent); + } + + return parent; +} + +static void +dconf_rebuilder_insert (GHashTable *table, + const gchar *key, + GVariant *value) +{ + GvdbItem *item; + gchar *mykey; + gint length; + + length = strlen (key); + mykey = g_alloca (length); + memcpy (mykey, key, length); + + g_assert (g_hash_table_lookup (table, key) == NULL); + item = gvdb_hash_table_insert (table, key); + + gvdb_item_set_parent (item, + dconf_rebuilder_get_parent (table, mykey, length)); + + gvdb_item_set_value (item, value); +} + +static void +dconf_rebuilder_put_item (DConfRebuilderState *state) +{ + if (state->values[state->index] != NULL) + { + gchar *fullname; + + fullname = g_strconcat (state->prefix, state->keys[state->index], NULL); + dconf_rebuilder_insert (state->table, fullname, + state->values[state->index]); + g_free (fullname); + } + + state->index++; +} + +static gboolean +dconf_rebuilder_walk_name (DConfRebuilderState *state, + const gchar *name, + gsize name_len) +{ + gint cmp; + + g_assert (state->name_len + name_len < sizeof state->name - 1); + memcpy (state->name + state->name_len, name, name_len); + state->name[state->name_len + name_len] = '\0'; + + if (state->index == state->n_items) + return TRUE; + + if (state->name_len + name_len < state->prefix_len || + memcmp (state->name, state->prefix, state->prefix_len) != 0) + return TRUE; + + while ((cmp = strcmp (state->name + state->prefix_len, + state->keys[state->index])) > 0) + { + dconf_rebuilder_put_item (state); + + if (state->index == state->n_items) + return TRUE; + } + + return cmp != 0; +} + +static void +dconf_rebuilder_walk_value (const gchar *name, + gsize name_len, + GVariant *value, + gpointer user_data) +{ + DConfRebuilderState *state = user_data; + + if (dconf_rebuilder_walk_name (state, name, name_len)) + dconf_rebuilder_insert (state->table, state->name, value); + + else + dconf_rebuilder_put_item (state); +} + +static gboolean +dconf_rebuilder_walk_open (const gchar *name, + gsize name_len, + gpointer user_data) +{ + DConfRebuilderState *state = user_data; + + if (dconf_rebuilder_walk_name (state, name, name_len)) + { + state->name_len += name_len; + return TRUE; + } + + return FALSE; +} + +static void +dconf_rebuilder_walk_close (gpointer user_data) +{ + DConfRebuilderState *state = user_data; + + while (--state->name_len && state->name[state->name_len - 1] != '/'); +} + +gboolean +dconf_rebuilder_rebuild (const gchar *filename, + const gchar *prefix, + const gchar **keys, + GVariant **values, + int n_items, + GError **error) +{ + DConfRebuilderState state = { prefix, strlen (prefix), + 0, keys, values, n_items }; + GvdbTable *old; + + state.table = gvdb_hash_table_new (NULL, NULL); + + if ((old = gvdb_table_new (filename, FALSE, NULL))) + gvdb_table_walk (old, "/", + dconf_rebuilder_walk_open, + dconf_rebuilder_walk_value, + dconf_rebuilder_walk_close, + &state); + + while (state.index != state.n_items) + dconf_rebuilder_put_item (&state); + + return gvdb_table_write_contents (state.table, filename, FALSE, error); +} diff --git a/service/dconf-rebuilder.h b/service/dconf-rebuilder.h new file mode 100644 index 0000000..fd4c212 --- /dev/null +++ b/service/dconf-rebuilder.h @@ -0,0 +1,8 @@ +#include <glib.h> + +gboolean dconf_rebuilder_rebuild (const gchar *filename, + const gchar *prefix, + const gchar **keys, + GVariant **values, + gint n_items, + GError **error); diff --git a/service/service.c b/service/service.c new file mode 100644 index 0000000..5cbace1 --- /dev/null +++ b/service/service.c @@ -0,0 +1,332 @@ +/* + * Copyright © 2010 Codethink 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, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Ryan Lortie <desrt@desrt.ca> + */ + +#include <gdbus/gdbus.h> +#include <string.h> +#include <stdio.h> + +#include "dconf-rebuilder.h" + +static const GDBusArgInfo lock_in_args[] = { + { "name", "s" }, { "locked", "b" } +}; +static const GDBusArgInfo in_args[] = { { "inargs", "s" } }; +static const GDBusArgInfo out_args[] = { { "serial", "t" } }; +static const GDBusMethodInfo writer_methods[] = { + { "Write", "s", 1, in_args, "t", 1, out_args }, + { "Merge", "s", 1, in_args, "t", 1, out_args }, +/* { "Lock", "sb", 1, lock_in_args, "" } */ +}; +static const GDBusInterfaceInfo writer_interface = { + "ca.desrt.dconf.Writer", 1, writer_methods +}; + +typedef struct +{ + GMainLoop *loop; + guint64 serial; + gchar *path; +} DConfWriter; + +static void +emit_notify_signal (GDBusConnection *connection, + guint64 serial, + const gchar *prefix, + const gchar **keys, + guint n_keys) +{ + GVariantBuilder builder; + GVariant *items; + gchar *path; + + g_variant_builder_init (&builder, G_VARIANT_TYPE ("as")); + + if (n_keys > 1) + { + const gchar *last_reset = NULL; + gint last_reset_len; + gint i; + + for (i = 0; i < n_keys; i++) + { + gint length = strlen (keys[i]); + + if (last_reset && length > last_reset_len && + memcmp (last_reset, keys[i], last_reset_len) == 0) + continue; + + if (length == 0 || keys[i][length - 1] == '/') + { + last_reset_len = length; + last_reset = keys[i]; + } + else + { + if (last_reset != NULL) + { + g_variant_builder_add (&builder, "s", last_reset); + last_reset = NULL; + } + + g_variant_builder_add (&builder, "s", keys[i]); + } + } + } + + items = g_variant_builder_end (&builder); + + if (g_variant_n_children (items) == 0) + path = g_strconcat (prefix, keys[0], NULL); + else + path = g_strdup (prefix); + + g_dbus_connection_emit_signal (connection, NULL, "/", + "ca.desrt.dconf.Writer", "Notify", + g_variant_new ("(ts@as)", + serial, path, items), + NULL); + g_free (path); +} + +static void +method_call (GDBusConnection *connection, + gpointer user_data, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation) +{ + DConfWriter *writer = user_data; + + if (strcmp (method_name, "Write") == 0) + { + GError *error = NULL; + GVariant *keyvalue; + const gchar *key; + gsize key_length; + GVariant *value; + guint64 serial; + GVariant *none; + + g_variant_get (parameters, "(@smv)", &keyvalue, &value); + key = g_variant_get_string (keyvalue, &key_length); + g_variant_unref (keyvalue); + + if (key[0] != '/' || strstr (key, "//")) + { + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, + G_DBUS_ERROR_INVALID_ARGS, + "invalid key: %s", key); + if (value != NULL) + g_variant_unref (value); + + return; + } + + if (key[key_length - 1] == '/' && value != NULL) + { + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, + G_DBUS_ERROR_INVALID_ARGS, + "can not set value to path"); + g_variant_unref (value); + return; + } + + if (!dconf_rebuilder_rebuild (writer->path, "", &key, + &value, 1, &error)) + { + g_dbus_method_invocation_return_gerror (invocation, error); + g_error_free (error); + return; + } + + serial = writer->serial++; + g_dbus_method_invocation_return_value (invocation, + g_variant_new ("(t)", serial)); + none = g_variant_new_array (G_VARIANT_TYPE_STRING, NULL, 0); + g_dbus_connection_emit_signal (connection, NULL, "/", + "ca.desrt.dconf.Writer", "Notify", + g_variant_new ("(ts@as)", + serial, key, none), + NULL); + } + else if (strcmp (method_name, "Merge")) + { + GError *error = NULL; + const gchar *prefix; + GVariantIter *iter; + const gchar **keys; + GVariant **values; + guint64 serial; + gsize length; + gint i = 0; + gint j; + + g_variant_get (parameters, "(&sa{smv})", &prefix, &iter); + length = g_variant_iter_n_children (iter); + + keys = g_new (const gchar *, length); + values = g_new (GVariant *, length); + while (g_variant_iter_next (iter, "{&smv}", &keys[i], &values[i])) + { + if (keys[i][0] == '/' || strstr (keys[i], "//") || + (i > 0 && !(strcmp (keys[i - 1], keys[i]) < 0))) + { + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, + G_DBUS_ERROR_INVALID_ARGS, + "invalid key list"); + + for (j = 0; j <= i; j++) + if (values[j] != NULL) + g_variant_unref (values[j]); + + g_free (values); + g_free (keys); + + return; + } + + i++; + } + g_variant_iter_free (iter); + keys[i] = NULL; + + if (!dconf_rebuilder_rebuild (writer->path, prefix, keys, + values, i, &error)) + { + g_dbus_method_invocation_return_gerror (invocation, error); + g_error_free (error); + return; + } + + serial = writer->serial++; + + g_dbus_method_invocation_return_value (invocation, + g_variant_new ("(t)", serial)); + emit_notify_signal (connection, serial, prefix, keys, i); + + for (j = 0; j < i; j++) + if (values[j] != NULL) + g_variant_unref (values[j]); + + g_free (values); + g_free (keys); + } + else + g_assert_not_reached (); +} + +static void +fake_method_call (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + GVariant *real_parameters; + const GVariantType *type; + const gchar *printed; + GError *error = NULL; + + if (strcmp (method_name, "Merge") == 0) + type = G_VARIANT_TYPE ("(sa{smv})"); + else + type = G_VARIANT_TYPE ("(smv)"); + + g_variant_get (parameters, "(&s)", &printed); + real_parameters = g_variant_parse (type, printed, NULL, NULL, &error); + + if (real_parameters == NULL) + { + g_dbus_method_invocation_return_gerror (invocation, error); + g_error_free (error); + return; + } + + method_call (connection, user_data, sender, object_path, + interface_name, method_name, real_parameters, invocation); + + g_variant_unref (real_parameters); +} + +static void +bus_acquired (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + static const GDBusInterfaceVTable interface_vtable = { fake_method_call }; + DConfWriter *writer = user_data; + + g_dbus_connection_register_object (connection, "/", + "ca.desrt.dconf.Writer", + &writer_interface, &interface_vtable, + writer, NULL, NULL); +} + +static void +name_acquired (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + DConfWriter *writer = user_data; + + writer->serial = time (NULL); + writer->serial <<= 32; +} + +static void +name_lost (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + DConfWriter *writer = user_data; + + if (writer->serial != 0) + g_critical ("dbus signaled that we lost our " + "name but it wrong wrong to do so"); + else + g_critical ("unable to acquire name: '%s'", name); + + g_main_loop_quit (writer->loop); +} + +int +main (void) +{ + DConfWriter writer = { }; + + g_type_init (); + + writer.loop = g_main_loop_new (NULL, FALSE); + writer.path = g_build_filename (g_get_user_config_dir (), "dconf", NULL); + + g_bus_own_name (G_BUS_TYPE_SESSION, "ca.desrt.dconf", 0, + bus_acquired, name_acquired, name_lost, &writer, NULL); + + g_main_loop_run (writer.loop); + + return 0; +} |