summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRyan Lortie <desrt@desrt.ca>2010-04-28 13:23:32 -0500
committerRyan Lortie <desrt@desrt.ca>2010-04-28 13:23:32 -0500
commit00a39d453558d82a5d8845e552e2b2648f71ae37 (patch)
tree72e9f362763da62f44f7588b1c1ea0c6ee8fc525
downloaddconf-00a39d453558d82a5d8845e552e2b2648f71ae37.tar.gz
Initial commit
-rw-r--r--.gitignore21
-rw-r--r--Makefile.am1
-rwxr-xr-xautogen.sh21
-rw-r--r--configure.ac22
-rw-r--r--dconf.doap19
-rw-r--r--gsettings/Makefile.am11
-rw-r--r--gsettings/dconfdatabase.c456
-rw-r--r--gsettings/dconfdatabase.h34
-rw-r--r--gsettings/dconfsettingsbackend.c141
-rw-r--r--gvdb/Makefile.am6
-rw-r--r--service/.gitignore2
-rw-r--r--service/Makefile.am20
-rw-r--r--service/dconf-rebuilder.c183
-rw-r--r--service/dconf-rebuilder.h8
-rw-r--r--service/service.c332
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;
+}