[dconf/wip/reorg: 464/498] Massively reorganise the client-side



commit 4eea8cb823c5a113d8209b04102f17f08df853d6
Author: Ryan Lortie <desrt desrt ca>
Date:   Mon Jul 2 00:49:46 2012 -0400

    Massively reorganise the client-side
    
    This commit represents a rather complete rethinking of DConfEngine.
    
      - the different kinds of sources are now properly abstracted.  This
        will make landing NFS support substantially easier.
    
      - there is now substantially more internal documentation
    
      - DConfEngineMessage is gone and replaced with ordinary function calls
        to be implemented by the D-Bus glue code
    
      - the GDBus glue has been factored out and is now shared between the
        client library and GSettings
    
      - the "outstanding" queue logic from the GSettings backend is now in
        the engine
    
      - all changes now go through a single API that accepts a (new)
        DConfChangeset object.  Currently this only supports the current
        operations (ie: setting and resetting).  In the future this object
        will also support the directory operations required by GSettingsList
        and will be the basis for the new approach to implementing the
        'delayed' GSettingsBackend (which will be the method by which those
        two concepts can co-exist).
    
    The (internal) API of the engine changed substantially.  This caused the
    following:
    
      - the libdconf client library has been rewritten in C.  Most of the
        complicated aspects of it (that made it more convenience to use
        Vala) are now gone.
    
      - during the rewrite of libdconf, the DConfClient API changed a bit to
        look more like a proper GObject.  It now makes GIO-style use of
        thread-default main contexts and uses GObject signals for
        notifications (instead of hand-rolled callbacks).
    
      - the GSettings backend has been substantially simplified (the
        "outstanding" logic is gone).  No externally-visible changes.
    
      - the dbus-1 backend has taken a copy of the old engine code for now
        until it can be ported to the new engine and sufficiently tested.
        No externally-visible changes.
    
      - the dconf commandline tool and dconf-editor required minor changes
        to adjust to the DConfClient API changes
    
    There is a substantial amount of cleaning up and finishing of work to be
    done.  There are many stubs remaining.  There are likely still a large
    number of bugs.

 Makefile.am                          |    2 +-
 bin/dconf-dump.vala                  |   37 +-
 bin/dconf-update.vala                |    2 +-
 bin/dconf.vala                       |    9 +-
 client/Makefile.am                   |   17 +-
 client/dconf-client.c                |   82 +--
 client/dconf-client.h                |   85 +--
 client/dconf-client.vala             |  380 ----------
 client/dconf.deps                    |    1 +
 client/dconf.vapi                    |   60 ++
 client/engine.vapi                   |   35 -
 common/dconf-changeset.c             |   15 +-
 common/dconf-changeset.h             |    3 +
 configure.ac                         |    1 +
 dbus-1/Makefile.am                   |    3 +-
 dbus-1/dconf-engine.c                |  757 +++++++++++++++++++
 dbus-1/dconf-engine.h                |  144 ++++
 editor/dconf-model.vala              |   16 +-
 engine/Makefile.am                   |   16 +-
 engine/dconf-changeset-list.h        |   38 +
 engine/dconf-engine-profile.c        |  131 ++++
 engine/dconf-engine-profile.h        |   31 +
 engine/dconf-engine-source-private.h |   31 +
 engine/dconf-engine-source-system.c  |   84 +++
 engine/dconf-engine-source-user.c    |  163 +++++
 engine/dconf-engine-source.c         |   92 +++
 engine/dconf-engine-source.h         |   67 ++
 engine/dconf-engine.c                | 1318 ++++++++++++++++++++--------------
 engine/dconf-engine.h                |  187 +++---
 gdbus/Makefile.am                    |    4 +
 gdbus/dconf-gdbus-filter.c           |  279 +++++++
 gdbus/dconf-gdbus-thread.c           |  346 +++++++++
 gsettings/Makefile.am                |    3 +-
 gsettings/dconfsettingsbackend.c     |  602 ++--------------
 34 files changed, 3278 insertions(+), 1763 deletions(-)
---
diff --git a/Makefile.am b/Makefile.am
index 6fb0701..72ba072 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,6 +1,6 @@
 ACLOCAL_AMFLAGS = -I m4 ${ACLOCAL_FLAGS}
 
-SUBDIRS = gvdb common service gsettings dbus-1 tests client bin engine docs
+SUBDIRS = gvdb common engine service gdbus gsettings dbus-1 tests client bin docs
 
 if ENABLE_EDITOR
 SUBDIRS += editor
diff --git a/bin/dconf-dump.vala b/bin/dconf-dump.vala
index 4fb8c02..135b230 100644
--- a/bin/dconf-dump.vala
+++ b/bin/dconf-dump.vala
@@ -47,48 +47,21 @@ KeyFile keyfile_from_stdin () throws Error {
 	return kf;
 }
 
-class DConfLoadState {
-	public string[] keys;
-	public Variant?[] vals;
-	int n_keys;
-	int i;
-
-	public DConfLoadState (int n) {
-		keys = new string[n + 1];
-		vals = new Variant?[n];
-		n_keys = n;
-		i = 0;
-	}
-
-	public int add (void *key, void *value) {
-		assert (i < n_keys);
-
-		keys[i] = (string) key;
-		vals[i] = (Variant) value;
-		i++;
-
-		return (int) false;
-	}
-}
-
 void dconf_load (string[] args) throws Error {
 	var dir = args[2];
 	DConf.verify_dir (dir);
 
-	var tree = new Tree<string, Variant> (strcmp);
+	var changeset = new DConf.Changeset ();
 	var kf = keyfile_from_stdin ();
 
 	foreach (var group in kf.get_groups ()) {
 		foreach (var key in kf.get_keys (group)) {
-			var rel = (group == "/" ? "" : group + "/") + key;
-			DConf.verify_rel_key (rel);
-			tree.insert (rel, Variant.parse (null, kf.get_value (group, key)));
+			var path = dir + (group == "/" ? "" : group + "/") + key;
+			DConf.verify_key (path);
+			changeset.set (path, Variant.parse (null, kf.get_value (group, key)));
 		}
 	}
 
-	DConfLoadState list = new DConfLoadState (tree.nnodes ());
-	tree.foreach (list.add);
-
 	var client = new DConf.Client ();
-	client.write_many (dir, list.keys, list.vals);
+	client.change_sync (changeset);
 }
diff --git a/bin/dconf-update.vala b/bin/dconf-update.vala
index 434b022..9fcc4ec 100644
--- a/bin/dconf-update.vala
+++ b/bin/dconf-update.vala
@@ -240,7 +240,7 @@ void update_all (string dirname) {
 	}
 }
 
-void dconf_update (string[] args) {
+void dconf_update (string[] args) throws GLib.Error {
 	update_all ("/etc/dconf/db");
 }
 
diff --git a/bin/dconf.vala b/bin/dconf.vala
index 4117d23..88db9e6 100644
--- a/bin/dconf.vala
+++ b/bin/dconf.vala
@@ -173,7 +173,7 @@ void dconf_write (string?[] args) throws Error {
 
 	DConf.verify_key (key);
 
-	client.write (key, Variant.parse (null, val));
+	client.write_sync (key, Variant.parse (null, val));
 }
 
 void dconf_reset (string?[] args) throws Error {
@@ -194,7 +194,7 @@ void dconf_reset (string?[] args) throws Error {
 		throw new OptionError.FAILED ("-f must be given to (recursively) reset entire dirs");
 	}
 
-	client.write (path, null);
+	client.write_sync (path, null);
 }
 
 void show_path (DConf.Client client, string path) {
@@ -221,12 +221,13 @@ void watch_function (DConf.Client client, string path, string[] items, string ta
 }
 
 void dconf_watch (string?[] args) throws Error {
-	var client = new DConf.Client (null, watch_function);
+	var client = new DConf.Client ();
+	client.changed.connect (watch_function);
 	var path = args[2];
 
 	DConf.verify_path (path);
 
-	client.watch (path);
+	client.watch_sync (path);
 	new MainLoop (null, false).run ();
 }
 
diff --git a/client/Makefile.am b/client/Makefile.am
index daebad3..119bf92 100644
--- a/client/Makefile.am
+++ b/client/Makefile.am
@@ -1,4 +1,4 @@
-AM_CFLAGS = -std=c89 -Wall -Wmissing-prototypes -Wwrite-strings -D__dconf_h__ -fPIC -DPIC
+AM_CFLAGS = -std=c89 -Wall -Wmissing-prototypes -Wwrite-strings -fPIC -DPIC
 CFLAGS += -Wno-error -Wno-unused-but-set-variable -Wno-unused-variable
 INCLUDES = -I$(top_srcdir)/common -I$(top_srcdir)/gvdb -I$(top_srcdir)/engine $(gio_CFLAGS)
 
@@ -21,19 +21,12 @@ dconfinclude_HEADERS = \
 	dconf-client.h	\
 	dconf.h
 
-libdconf_so_0_0_0_LDADD = $(gio_LIBS) ../common/libdconf-common-shared.a
+libdconf_so_0_0_0_LDADD = $(gio_LIBS) ../common/libdconf-common-shared.a ../engine/libdconf-engine.a ../gdbus/libdconf-gdbus.a
 libdconf_so_0_0_0_LDFLAGS = -shared -Wl,-soname=libdconf.so.0
 libdconf_so_0_0_0_SOURCES = \
-	../engine/dconf-engine.c	\
-	../gvdb/gvdb-reader.c		\
-	dconf-client.vala engine.vapi
-libdconf_so_0_0_0_VALAFLAGS = --library dconf --pkg=gio-2.0
+	dconf-client.c
 
-EXTRA_DIST = dconf.vapi extra-docs.c
-dconf.vapi: libdconf.so.0
-
-dconf.deps:
-	$(AM_V_GEN) echo gio-2.0 > dconf.deps
+EXTRA_DIST = dconf.vapi
 
 vapi_DATA = dconf.vapi dconf.deps
 vapidir = $(datadir)/vala/vapi
@@ -41,4 +34,4 @@ vapidir = $(datadir)/vala/vapi
 pkgconfigdir = $(libdir)/pkgconfig
 pkgconfig_DATA = dconf.pc
 
-CLEANFILES = dconf.deps libdconf.so.0 libdconf.so
+CLEANFILES = libdconf.so.0 libdconf.so
diff --git a/client/dconf-client.c b/client/dconf-client.c
index 1300269..ffc179e 100644
--- a/client/dconf-client.c
+++ b/client/dconf-client.c
@@ -23,10 +23,11 @@
 #include "dconf-client.h"
 
 #include "dconf-engine.h"
+#include <glib-object.h>
 
 struct _DConfClient
 {
-  GObject parent_class;
+  GObject parent_instance;
 
   DConfEngine  *engine;
   GMainContext *context;
@@ -39,7 +40,6 @@ enum
   SIGNAL_CHANGED,
   N_SIGNALS
 };
-
 static guint dconf_client_signals[N_SIGNALS];
 
 static void
@@ -64,11 +64,11 @@ dconf_client_class_init (DConfClientClass *class)
 {
   class->finalize = dconf_client_finalize;
 
-  dconf_client_signals[SIGNAL_CHANGED] =
-    g_signal_new ("changed", DCONF_TYPE_CLIENT, G_SIGNAL_RUN_FIRST,
-                  0, NULL, NULL, NULL, G_TYPE_NONE, 2,
-                  G_TYPE_STRING | G_SIGNAL_TYPE_STATIC_SCOPE,
-                  G_TYPE_STRV | G_SIGNAL_TYPE_STATIC_SCOPE);
+  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);
 }
 
 typedef struct
@@ -76,18 +76,21 @@ typedef struct
   DConfClient  *client;
   gchar        *prefix;
   gchar       **changes;
+  gchar        *tag;
 } DConfClientChange;
 
 static gboolean
-dconf_engine_emit_changed (gpointer user_data)
+dconf_client_dispatch_change_signal (gpointer user_data)
 {
   DConfClientChange *change = user_data;
 
-  g_signal_emit (change->client, dconf_client_signals[SIGNAL_CHANGED], 0, change->prefix, change->changes);
+  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_object_unref (change->client);
+  g_free (change->tag);
   g_slice_free (DConfClientChange, change);
 
   return G_SOURCE_REMOVE;
@@ -97,6 +100,7 @@ void
 dconf_engine_change_notify (DConfEngine         *engine,
                             const gchar         *prefix,
                             const gchar * const *changes,
+                            const gchar *        tag,
                             gpointer             user_data)
 {
   DConfClient *client = user_data;
@@ -105,13 +109,24 @@ dconf_engine_change_notify (DConfEngine         *engine,
   g_return_if_fail (DCONF_IS_CLIENT (client));
 
   change = g_slice_new (DConfClientChange);
+  change->client = g_object_ref (client);
   change->prefix = g_strdup (prefix);
   change->changes = g_strdupv ((gchar **) changes);
-  change->client = g_object_ref (client);
+  change->tag = g_strdup (tag);
 
-  g_main_context_invoke (client->context,
-                         dconf_engine_emit_changed,
-                         change);
+  g_main_context_invoke (client->context, dconf_client_dispatch_change_signal, change);
+}
+
+DConfClient *
+dconf_client_new (void)
+{
+  DConfClient *client;
+
+  client = g_object_new (DCONF_TYPE_CLIENT, NULL);
+  client->engine = dconf_engine_new (client);
+  client->context = g_main_context_ref_thread_default ();
+
+  return client;
 }
 
 GVariant *
@@ -125,13 +140,12 @@ dconf_client_read (DConfClient *client,
 
 gchar **
 dconf_client_list (DConfClient *client,
-                   const gchar *dir)
+                   const gchar *dir,
+                   gint        *length)
 {
   g_return_val_if_fail (DCONF_IS_CLIENT (client), NULL);
 
-  return NULL;
-
-  /*: return dconf_engine_list (client->engine, NULL, dir); */
+  return dconf_engine_list (client->engine, dir, length);
 }
 
 gboolean
@@ -143,18 +157,6 @@ dconf_client_is_writable (DConfClient *client,
   return dconf_engine_is_writable (client->engine, key);
 }
 
-static DConfChangeset *
-dconf_client_make_simple_change (const gchar *key,
-                                 GVariant    *value)
-{
-  DConfChangeset *changeset;
-
-  changeset = dconf_changeset_new ();
-  dconf_changeset_set (changeset, key, value);
-
-  return changeset;
-}
-
 gboolean
 dconf_client_write_fast (DConfClient  *client,
                          const gchar  *key,
@@ -166,7 +168,7 @@ dconf_client_write_fast (DConfClient  *client,
 
   g_return_val_if_fail (DCONF_IS_CLIENT (client), FALSE);
 
-  changeset = dconf_client_make_simple_change (key, value);
+  changeset = dconf_changeset_new_write (key, value);
   success = dconf_engine_change_fast (client->engine, changeset, error);
   dconf_changeset_unref (changeset);
 
@@ -186,7 +188,7 @@ dconf_client_write_sync (DConfClient   *client,
 
   g_return_val_if_fail (DCONF_IS_CLIENT (client), FALSE);
 
-  changeset = dconf_client_make_simple_change (key, value);
+  changeset = dconf_changeset_new_write (key, value);
   success = dconf_engine_change_sync (client->engine, changeset, tag, error);
   dconf_changeset_unref (changeset);
 
@@ -225,8 +227,8 @@ dconf_client_watch_fast (DConfClient *client,
 }
 
 void
-dconf_client_watch_sync (DConfClient   *client,
-                         const gchar   *path)
+dconf_client_watch_sync (DConfClient *client,
+                         const gchar *path)
 {
   g_return_if_fail (DCONF_IS_CLIENT (client));
 
@@ -250,15 +252,3 @@ dconf_client_unwatch_sync (DConfClient *client,
 
   dconf_engine_unwatch_sync (client->engine, path);
 }
-
-DConfClient *
-dconf_client_new (void)
-{
-  DConfClient *client;
-
-  client = g_object_new (DCONF_TYPE_CLIENT, NULL);
-  client->engine = dconf_engine_new (client);
-  client->context = g_main_context_ref_thread_default ();
-
-  return client;
-}
diff --git a/client/dconf-client.h b/client/dconf-client.h
index 802dce8..969e2ff 100644
--- a/client/dconf-client.h
+++ b/client/dconf-client.h
@@ -23,6 +23,7 @@
 #define __dconf_client_h__
 
 #include <gio/gio.h>
+#include "dconf-changeset.h"
 
 G_BEGIN_DECLS
 
@@ -33,26 +34,12 @@ G_BEGIN_DECLS
 typedef GObjectClass DConfClientClass;
 typedef struct _DConfClient DConfClient;
 
-typedef void          (*DConfWatchFunc)                                 (DConfClient          *client,
-                                                                         const gchar          *path,
-                                                                         const gchar * const  *items,
-                                                                         gint                  n_items,
-                                                                         const gchar          *tag,
-                                                                         gpointer              user_data);
-
 GType                   dconf_client_get_type                           (void);
 
-DConfClient *           dconf_client_new                                (const gchar          *profile,
-                                                                         DConfWatchFunc        watch_func,
-                                                                         gpointer              user_data,
-                                                                         GDestroyNotify        notify);
+DConfClient *           dconf_client_new                                (void);
 
 GVariant *              dconf_client_read                               (DConfClient          *client,
                                                                          const gchar          *key);
-GVariant *              dconf_client_read_default                       (DConfClient          *client,
-                                                                         const gchar          *key);
-GVariant *              dconf_client_read_no_default                    (DConfClient          *client,
-                                                                         const gchar          *key);
 
 gchar **                dconf_client_list                               (DConfClient          *client,
                                                                          const gchar          *dir,
@@ -61,70 +48,36 @@ gchar **                dconf_client_list                               (DConfCl
 gboolean                dconf_client_is_writable                        (DConfClient          *client,
                                                                          const gchar          *key);
 
-gboolean                dconf_client_write                              (DConfClient          *client,
+gboolean                dconf_client_write_fast                         (DConfClient          *client,
                                                                          const gchar          *key,
                                                                          GVariant             *value,
-                                                                         gchar               **tag,
-                                                                         GCancellable         *cancellable,
                                                                          GError              **error);
-void                    dconf_client_write_async                        (DConfClient          *client,
+gboolean                dconf_client_write_sync                         (DConfClient          *client,
                                                                          const gchar          *key,
                                                                          GVariant             *value,
-                                                                         GCancellable         *cancellable,
-                                                                         GAsyncReadyCallback   callback,
-                                                                         gpointer              user_data);
-gboolean                dconf_client_write_finish                       (DConfClient          *client,
-                                                                         GAsyncResult         *result,
                                                                          gchar               **tag,
+                                                                         GCancellable         *cancellable,
                                                                          GError              **error);
 
-gboolean                dconf_client_write_many                         (DConfClient          *client,
-                                                                         const gchar          *dir,
-                                                                         const gchar * const  *rels,
-                                                                         GVariant            **values,
-                                                                         gint                  n_values,
+gboolean                dconf_client_change_fast                        (DConfClient          *client,
+                                                                         DConfChangeset       *changeset,
+                                                                         GError              **error);
+gboolean                dconf_client_change_sync                        (DConfClient          *client,
+                                                                         DConfChangeset       *changeset,
                                                                          gchar               **tag,
                                                                          GCancellable         *cancellable,
                                                                          GError              **error);
 
-/* write_many_async currently disabled due to missing Vala functionality
-void                    dconf_client_write_many_async                   (DConfClient          *client,
-                                                                         const gchar          *dir,
-                                                                         const gchar * const  *rels,
-                                                                         GVariant            **values,
-                                                                         gint                  n_values,
-                                                                         GCancellable         *cancellable,
-                                                                         GAsyncReadyCallback   callback,
-                                                                         gpointer              user_data);
-gboolean                dconf_client_write_many_finish                  (DConfClient          *client,
-                                                                         GAsyncResult         *result,
-                                                                         gchar               **tag,
-                                                                         GError              **error);*/
+void                    dconf_client_watch_fast                         (DConfClient          *client,
+                                                                         const gchar          *path);
+void                    dconf_client_watch_sync                         (DConfClient          *client,
+                                                                         const gchar          *path);
+
+void                    dconf_client_unwatch_fast                       (DConfClient          *client,
+                                                                         const gchar          *path);
+void                    dconf_client_unwatch_sync                       (DConfClient          *client,
+                                                                         const gchar          *path);
 
-gboolean                dconf_client_watch                              (DConfClient          *client,
-                                                                         const gchar          *path,
-                                                                         GCancellable         *cancellable,
-                                                                         GError              **error);
-void                    dconf_client_watch_async                        (DConfClient          *client,
-                                                                         const gchar          *path,
-                                                                         GCancellable         *cancellable,
-                                                                         GAsyncReadyCallback   callback,
-                                                                         gpointer              user_data);
-gboolean                dconf_client_watch_finish                       (DConfClient          *client,
-                                                                         GAsyncResult         *result,
-                                                                         GError              **error);
-gboolean                dconf_client_unwatch                            (DConfClient          *client,
-                                                                         const gchar          *path,
-                                                                         GCancellable         *cancellable,
-                                                                         GError              **error);
-void                    dconf_client_unwatch_async                      (DConfClient          *client,
-                                                                         const gchar          *path,
-                                                                         GCancellable         *cancellable,
-                                                                         GAsyncReadyCallback   callback,
-                                                                         gpointer              user_data);
-gboolean                dconf_client_unwatch_finish                     (DConfClient          *client,
-                                                                         GAsyncResult         *result,
-                                                                         GError              **error);
 G_END_DECLS
 
 #endif /* __dconf_client_h__ */
diff --git a/client/dconf.deps b/client/dconf.deps
new file mode 100644
index 0000000..cd10dfd
--- /dev/null
+++ b/client/dconf.deps
@@ -0,0 +1 @@
+gio-2.0
diff --git a/client/dconf.vapi b/client/dconf.vapi
new file mode 100644
index 0000000..af293aa
--- /dev/null
+++ b/client/dconf.vapi
@@ -0,0 +1,60 @@
+/* dconf.vapi generated by valac 0.17.1.35-814b, do not modify. */
+
+namespace DConf {
+	[CCode (cheader_filename = "dconf.h")]
+	public class Client : GLib.Object {
+		public signal void changed (string prefix, string[] changes, string tag);
+
+		public Client ();
+		public GLib.Variant? read (string key);
+		public string[] list (string dir);
+		public bool is_writable (string key);
+		public void write_fast (string path, GLib.Variant? value) throws GLib.Error;
+		public void write_sync (string path, GLib.Variant? value, out string tag = null, GLib.Cancellable? cancellable = null) throws GLib.Error;
+		public void change_fast (Changeset changeset) throws GLib.Error;
+		public void change_sync (Changeset changeset, out string tag = null, GLib.Cancellable? cancellable = null) throws GLib.Error;
+		public void watch_fast (string path);
+		public void unwatch_fast (string path);
+		public void watch_sync (string path);
+		public void unwatch_sync (string path);
+	}
+
+	[Compact]
+	[CCode (ref_function = "dconf_changeset_ref", unref_function = "dconf_changeset_unref")]
+	public class Changeset {
+		public delegate bool Predicate (string path, GLib.Variant? value);
+		public Changeset ();
+		public Changeset.write (string path, GLib.Variant? value);
+		public void set (string path, GLib.Variant? value);
+		public bool get (string path, out GLib.Variant? value);
+		public bool is_similar_to (Changeset other);
+		public bool all (Predicate predicate);
+		public GLib.Variant serialise ();
+		public static Changeset deserialise (GLib.Variant serialised);
+	}
+
+	[CCode (cheader_filename = "dconf.h")]
+	public static bool is_dir (string str, GLib.Error* error = null);
+	[CCode (cheader_filename = "dconf.h")]
+	public static bool is_key (string str, GLib.Error* error = null);
+	[CCode (cheader_filename = "dconf.h")]
+	public static bool is_path (string str, GLib.Error* error = null);
+	[CCode (cheader_filename = "dconf.h")]
+	public static bool is_rel_dir (string str, GLib.Error* error = null);
+	[CCode (cheader_filename = "dconf.h")]
+	public static bool is_rel_key (string str, GLib.Error* error = null);
+	[CCode (cheader_filename = "dconf.h")]
+	public static bool is_rel_path (string str, GLib.Error* error = null);
+	[CCode (cheader_filename = "dconf.h", cname = "dconf_is_dir")]
+	public static bool verify_dir (string str) throws GLib.Error;
+	[CCode (cheader_filename = "dconf.h", cname = "dconf_is_key")]
+	public static bool verify_key (string str) throws GLib.Error;
+	[CCode (cheader_filename = "dconf.h", cname = "dconf_is_path")]
+	public static bool verify_path (string str) throws GLib.Error;
+	[CCode (cheader_filename = "dconf.h", cname = "dconf_is_rel_dir")]
+	public static bool verify_rel_dir (string str) throws GLib.Error;
+	[CCode (cheader_filename = "dconf.h", cname = "dconf_is_rel_key")]
+	public static bool verify_rel_key (string str) throws GLib.Error;
+	[CCode (cheader_filename = "dconf.h", cname = "dconf_is_rel_path")]
+	public static bool verify_rel_path (string str) throws GLib.Error;
+}
diff --git a/common/dconf-changeset.c b/common/dconf-changeset.c
index 43dbe42..8434a2e 100644
--- a/common/dconf-changeset.c
+++ b/common/dconf-changeset.c
@@ -49,6 +49,7 @@ dconf_changeset_new (void)
 
   change = g_slice_new0 (DConfChangeset);
   change->table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_variant_unref);
+  change->ref_count = 1;
 
   return change;
 }
@@ -100,7 +101,7 @@ dconf_changeset_set (DConfChangeset *change,
 {
   g_return_if_fail (change->root == NULL);
 
-  g_hash_table_insert (change->table, g_strdup (key), g_variant_ref_sink (value));
+  g_hash_table_insert (change->table, g_strdup (key), value ? g_variant_ref_sink (value) : NULL);
 }
 
 /**
@@ -410,3 +411,15 @@ dconf_changeset_deserialise (GVariant *serialised)
 
   return change;
 }
+
+DConfChangeset *
+dconf_changeset_new_write (const gchar *path,
+                           GVariant    *value)
+{
+  DConfChangeset *changeset;
+
+  changeset = dconf_changeset_new ();
+  dconf_changeset_set (changeset, path, value);
+
+  return changeset;
+}
diff --git a/common/dconf-changeset.h b/common/dconf-changeset.h
index 930c09b..de062fa 100644
--- a/common/dconf-changeset.h
+++ b/common/dconf-changeset.h
@@ -32,6 +32,9 @@ typedef gboolean     (* DConfChangesetPredicate)                        (const g
 
 DConfChangeset *        dconf_changeset_new                             (void);
 
+DConfChangeset *        dconf_changeset_new_write                       (const gchar             *key,
+                                                                         GVariant                *value);
+
 DConfChangeset *        dconf_changeset_ref                             (DConfChangeset          *changeset);
 void                    dconf_changeset_unref                           (DConfChangeset          *changeset);
 
diff --git a/configure.ac b/configure.ac
index 48fb843..2b41e78 100644
--- a/configure.ac
+++ b/configure.ac
@@ -55,6 +55,7 @@ AC_CONFIG_FILES([
   common/Makefile
   gvdb/Makefile
   engine/Makefile
+  gdbus/Makefile
   gsettings/Makefile
   dbus-1/dconf-dbus-1.pc
   client/dconf.pc
diff --git a/dbus-1/Makefile.am b/dbus-1/Makefile.am
index 1417ff4..78244e8 100644
--- a/dbus-1/Makefile.am
+++ b/dbus-1/Makefile.am
@@ -13,7 +13,8 @@ shlib_PROGRAMS = libdconf-dbus-1.so.0.0.0
 libdconf_dbus_1_so_0_0_0_LDADD = $(glib_LIBS) $(dbus_LIBS) ../common/libdconf-common-shared.a
 libdconf_dbus_1_so_0_0_0_LDFLAGS = -shared -Wl,-soname=libdconf-dbus-1.so.0
 libdconf_dbus_1_so_0_0_0_SOURCES = \
-	../engine/dconf-engine.c	\
+	dconf-engine.h			\
+	dconf-engine.c			\
 	../gvdb/gvdb-reader.c		\
 	dconf-dbus-1.c
 
diff --git a/dbus-1/dconf-engine.c b/dbus-1/dconf-engine.c
new file mode 100644
index 0000000..aedabcf
--- /dev/null
+++ b/dbus-1/dconf-engine.c
@@ -0,0 +1,757 @@
+/*
+ * 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>
+ */
+
+#define _XOPEN_SOURCE 600
+#include "dconf-shmdir.h"
+#include "dconf-engine.h"
+#include <gvdb-reader.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+
+void
+dconf_engine_message_destroy (DConfEngineMessage *dcem)
+{
+  gint i;
+
+  for (i = 0; dcem->parameters[i]; i++)
+    g_variant_unref (dcem->parameters[i]);
+  g_free (dcem->parameters);
+}
+
+void
+dconf_engine_message_copy (DConfEngineMessage *orig,
+                           DConfEngineMessage *copy)
+{
+  gint i, n;
+
+  *copy = *orig;
+
+  for (n = 0; orig->parameters[n]; n++);
+  copy->parameters = g_new (GVariant *, n + 1);
+  for (i = 0; i < n; i++)
+    copy->parameters[i] = g_variant_ref (orig->parameters[i]);
+  copy->parameters[i] = NULL;
+}
+
+static const gchar *
+dconf_engine_get_session_dir (void)
+{
+  static const gchar *session_dir;
+  static gsize initialised;
+
+  if (g_once_init_enter (&initialised))
+    {
+      session_dir = dconf_shmdir_from_environment ();
+      g_once_init_leave (&initialised, 1);
+    }
+
+  return session_dir;
+}
+
+struct _DConfEngine
+{
+  GMutex      lock;
+  guint64     state;
+
+
+  GvdbTable **gvdbs;
+  GvdbTable **lock_tables;
+  guint8    **shm;
+  gchar     **object_paths;
+  gchar      *bus_types;
+  gchar     **names;
+  gint        n_dbs;
+};
+
+static void
+dconf_engine_setup_user (DConfEngine *engine,
+                         gint         i)
+{
+  /* invariant: we never have user gvdb without shm */
+  g_assert ((engine->gvdbs[i] == NULL) >= (engine->shm[i] == NULL));
+
+  if (engine->names[i])
+    {
+      const gchar *session_dir = dconf_engine_get_session_dir ();
+
+      if (session_dir)
+        {
+          gchar *filename;
+          gint fd;
+
+          filename = g_build_filename (session_dir,
+                                       engine->names[i],
+                                       NULL);
+          fd = open (filename, O_RDWR | O_CREAT, 0600);
+          g_free (filename);
+
+          if (fd >= 0)
+            {
+              if (ftruncate (fd, 1) == 0)
+                {
+                  engine->shm[i] = mmap (NULL, 1, PROT_READ, MAP_SHARED, fd, 0);
+
+                  if (engine->shm[i] == MAP_FAILED)
+                    engine->shm[i] = NULL;
+                }
+
+              close (fd);
+            }
+        }
+
+      if (engine->shm[i])
+        {
+          gchar *filename;
+
+          filename = g_build_filename (g_get_user_config_dir (),
+                                       "dconf",
+                                       engine->names[i],
+                                       NULL);
+          engine->gvdbs[i] = gvdb_table_new (filename, FALSE, NULL);
+          g_free (filename);
+        }
+    }
+
+  g_assert ((engine->gvdbs[i] == NULL) >= (engine->shm[i] == NULL));
+}
+
+static void
+dconf_engine_refresh_user (DConfEngine *engine,
+                           gint         i)
+{
+  g_assert ((engine->gvdbs[i] == NULL) >= (engine->shm[i] == NULL));
+
+  /* if we failed the first time, fail forever */
+  if (engine->shm[i] && *engine->shm[i] == 1)
+    {
+      if (engine->gvdbs[i])
+        {
+          gvdb_table_unref (engine->gvdbs[i]);
+          engine->gvdbs[i] = NULL;
+        }
+
+      munmap (engine->shm[i], 1);
+      engine->shm[i] = NULL;
+
+      dconf_engine_setup_user (engine, i);
+      engine->state++;
+    }
+
+  g_assert ((engine->gvdbs[i] == NULL) >= (engine->shm[i] == NULL));
+}
+
+static void
+dconf_engine_refresh_system (DConfEngine *engine,
+                             gint         i)
+{
+  if (engine->gvdbs[i] && !gvdb_table_is_valid (engine->gvdbs[i]))
+    {
+      if (engine->lock_tables[i])
+        {
+          gvdb_table_unref (engine->lock_tables[i]);
+          engine->lock_tables[i] = NULL;
+        }
+
+      gvdb_table_unref (engine->gvdbs[i]);
+      engine->gvdbs[i] = NULL;
+    }
+
+  if (engine->gvdbs[i] == NULL)
+    {
+      gchar *filename = g_build_filename ("/etc/dconf/db",
+                                          engine->names[i], NULL);
+      engine->gvdbs[i] = gvdb_table_new (filename, TRUE, NULL);
+      if (engine->gvdbs[i] == NULL)
+        g_error ("Unable to open '%s', specified in dconf profile\n",
+                 filename);
+      engine->lock_tables[i] = gvdb_table_get_table (engine->gvdbs[i],
+                                                     ".locks");
+      g_free (filename);
+      engine->state++;
+    }
+}
+
+static void
+dconf_engine_refresh (DConfEngine *engine)
+{
+  gint i;
+
+  for (i = 0; i < engine->n_dbs; i++)
+    if (engine->bus_types[i] == 'e')
+      dconf_engine_refresh_user (engine, i);
+    else
+      dconf_engine_refresh_system (engine, i);
+}
+
+static void
+dconf_engine_setup (DConfEngine *engine)
+{
+  gint i;
+
+  for (i = 0; i < engine->n_dbs; i++)
+    if (engine->bus_types[i] == 'e')
+      dconf_engine_setup_user (engine, i);
+    else
+      dconf_engine_refresh_system (engine, i);
+}
+
+guint64
+dconf_engine_get_state (DConfEngine *engine)
+{
+  guint64 state;
+
+  g_mutex_lock (&engine->lock);
+
+  dconf_engine_refresh (engine);
+  state = engine->state;
+
+  g_mutex_unlock (&engine->lock);
+
+  return state;
+}
+
+static gboolean
+dconf_engine_load_profile (const gchar   *profile,
+                           gchar        **bus_types,
+                           gchar       ***names,
+                           gint          *n_dbs,
+                           GError       **error)
+{
+  gchar *filename;
+  gint allocated;
+  char line[80];
+  FILE *f;
+
+  /* DCONF_PROFILE starting with '/' gives an absolute path to a profile */
+  if (profile[0] != '/')
+    filename = g_build_filename ("/etc/dconf/profile", profile, NULL);
+  else
+    filename = g_strdup (profile);
+
+  f = fopen (filename, "r");
+
+  if (f == NULL)
+    {
+      gint saved_errno = errno;
+
+      g_set_error (error, G_FILE_ERROR,
+                   g_file_error_from_errno (saved_errno),
+                   "open '%s': %s", filename, g_strerror (saved_errno));
+      g_free (filename);
+      return FALSE;
+    }
+
+  allocated = 4;
+  *bus_types = g_new (gchar, allocated);
+  *names = g_new (gchar *, allocated);
+  *n_dbs = 0;
+
+  /* quick and dirty is good enough for now */
+  while (fgets (line, sizeof line, f))
+    {
+      const gchar *end;
+      const gchar *sep;
+
+      end = strchr (line, '\n');
+
+      if (end == NULL)
+        g_error ("long line in %s", filename);
+
+      if (end == line)
+        continue;
+
+      if (line[0] == '#')
+        continue;
+
+      if (*n_dbs == allocated)
+        {
+          allocated *= 2;
+          *names = g_renew (gchar *, *names, allocated);
+          *bus_types = g_renew (gchar, *bus_types, allocated);
+        }
+
+      sep = strchr (line, ':');
+
+      if (sep)
+        {
+          /* strings MUST be 'user-db' or 'system-db'.  we do the check
+           * this way here merely because it is the fastest.
+           */
+          (*bus_types)[*n_dbs] = (line[0] == 'u') ? 'e' : 'y';
+          (*names)[*n_dbs] = g_strndup (sep + 1, end - (sep + 1));
+        }
+      else
+        {
+          /* default is for first DB to be user and rest to be system */
+          (*bus_types)[*n_dbs] = (*n_dbs == 0) ? 'e' : 'y';
+          (*names)[*n_dbs] = g_strndup (line, end - line);
+        }
+
+      (*n_dbs)++;
+    }
+
+  *bus_types = g_renew (gchar, *bus_types, *n_dbs);
+  *names = g_renew (gchar *, *names, *n_dbs);
+  g_free (filename);
+  fclose (f);
+
+  return TRUE;
+}
+
+DConfEngine *
+dconf_engine_new (const gchar *profile)
+{
+  DConfEngine *engine;
+  gint i;
+
+  engine = g_slice_new (DConfEngine);
+  g_mutex_init (&engine->lock);
+
+  if (profile == NULL)
+    profile = getenv ("DCONF_PROFILE");
+
+  if (profile)
+    {
+      GError *error = NULL;
+
+      if (!dconf_engine_load_profile (profile, &engine->bus_types, &engine->names, &engine->n_dbs, &error))
+        g_error ("Error loading dconf profile '%s': %s\n",
+                 profile, error->message);
+    }
+  else
+    {
+      if (!dconf_engine_load_profile ("user", &engine->bus_types, &engine->names, &engine->n_dbs, NULL))
+        {
+          engine->names = g_new (gchar *, 1);
+          engine->names[0] = g_strdup ("user");
+          engine->bus_types = g_strdup ("e");
+          engine->n_dbs = 1;
+        }
+    }
+
+  if (strcmp (engine->names[0], "-") == 0)
+    {
+      g_free (engine->names[0]);
+      engine->names[0] = NULL;
+    }
+
+  engine->object_paths = g_new (gchar *, engine->n_dbs);
+  engine->gvdbs = g_new0 (GvdbTable *, engine->n_dbs);
+  engine->lock_tables = g_new0 (GvdbTable *, engine->n_dbs);
+  engine->shm = g_new0 (guint8 *, engine->n_dbs);
+  engine->state = 0;
+
+  for (i = 0; i < engine->n_dbs; i++)
+    if (engine->names[i])
+        engine->object_paths[i] = g_strjoin (NULL,
+                                             "/ca/desrt/dconf/Writer/",
+                                             engine->names[i],
+                                             NULL);
+    else
+      engine->object_paths[i] = NULL;
+
+  dconf_engine_setup (engine);
+
+  return engine;
+}
+
+void
+dconf_engine_free (DConfEngine *engine)
+{
+  gint i;
+
+  for (i = 0; i < engine->n_dbs; i++)
+    {
+      g_free (engine->object_paths[i]);
+      g_free (engine->names[i]);
+
+      if (engine->gvdbs[i])
+        gvdb_table_unref (engine->gvdbs[i]);
+
+      if (engine->lock_tables[i])
+        gvdb_table_unref (engine->lock_tables[i]);
+
+      if (engine->shm[i])
+        munmap (engine->shm[i], 1);
+    }
+
+
+  g_mutex_clear (&engine->lock);
+
+  g_free (engine->object_paths);
+  g_free (engine->bus_types);
+  g_free (engine->names);
+  g_free (engine->gvdbs);
+  g_free (engine->lock_tables);
+  g_free (engine->shm);
+
+  g_slice_free (DConfEngine, engine);
+}
+
+static GVariant *
+dconf_engine_read_internal (DConfEngine  *engine,
+                            const gchar  *key,
+                            gboolean      user,
+                            gboolean      system)
+{
+  GVariant *value = NULL;
+  gint lowest;
+  gint limit;
+  gint i;
+
+  g_mutex_lock (&engine->lock);
+
+  dconf_engine_refresh (engine);
+
+  /* Bound the search space depending on the databases that we are
+   * interested in.
+   */
+  limit = system ? engine->n_dbs : 1;
+  lowest = user ? 0 : 1;
+
+  /* We want i equal to the index of the highest database containing a
+   * lock, or i == lowest if there is no lock.  For that reason, we
+   * don't actually check the lowest database for a lock.  That makes
+   * sense, because even if it had a lock, it would not change our
+   * search policy (which would be to check the lowest one first).
+   *
+   * Note that we intentionally dishonour 'limit' here -- we want to
+   * ensure that values in the user database are always ignored when
+   * locks are present.
+   */
+  for (i = MAX (engine->n_dbs - 1, lowest); lowest < i; i--)
+    if (engine->lock_tables[i] != NULL &&
+        gvdb_table_has_value (engine->lock_tables[i], key))
+      break;
+
+  while (i < limit && value == NULL)
+    {
+      if (engine->gvdbs[i] != NULL)
+        value = gvdb_table_get_value (engine->gvdbs[i], key);
+      i++;
+    }
+
+  g_mutex_unlock (&engine->lock);
+
+  return value;
+}
+
+GVariant *
+dconf_engine_read (DConfEngine  *engine,
+                   const gchar  *key)
+{
+  return dconf_engine_read_internal (engine, key, TRUE, TRUE);
+}
+
+GVariant *
+dconf_engine_read_default (DConfEngine  *engine,
+                           const gchar  *key)
+{
+  return dconf_engine_read_internal (engine, key, FALSE, TRUE);
+}
+
+GVariant *
+dconf_engine_read_no_default (DConfEngine  *engine,
+                              const gchar  *key)
+{
+  return dconf_engine_read_internal (engine, key, TRUE, FALSE);
+}
+
+static void
+dconf_engine_make_match_rule (DConfEngine        *engine,
+                              DConfEngineMessage *dcem,
+                              const gchar        *name,
+                              const gchar        *method_name)
+{
+  gint i;
+
+  dcem->bus_name = "org.freedesktop.DBus";
+  dcem->object_path = "/org/freedesktop/DBus";
+  dcem->interface_name = "org.freedesktop.DBus";
+  dcem->method_name = method_name;
+
+  dcem->parameters = g_new (GVariant *, engine->n_dbs + 1);
+  for (i = 0; i < engine->n_dbs; i++)
+    {
+      gchar *rule;
+
+      rule = g_strdup_printf ("type='signal',"
+                              "interface='ca.desrt.dconf.Writer',"
+                              "path='%s',"
+                              "arg0path='%s'",
+                              engine->object_paths[i],
+                              name);
+      dcem->parameters[i] = g_variant_new ("(s)", rule);
+      g_variant_ref_sink (dcem->parameters[i]);
+      g_free (rule);
+    }
+  dcem->parameters[i] = NULL;
+
+  dcem->bus_types = engine->bus_types;
+  dcem->n_messages = engine->n_dbs;
+  dcem->reply_type = G_VARIANT_TYPE_UNIT;
+}
+
+void
+dconf_engine_watch (DConfEngine        *engine,
+                    const gchar        *name,
+                    DConfEngineMessage *dcem)
+{
+  dconf_engine_make_match_rule (engine, dcem, name, "AddMatch");
+}
+
+void
+dconf_engine_unwatch (DConfEngine        *engine,
+                      const gchar        *name,
+                      DConfEngineMessage *dcem)
+{
+  dconf_engine_make_match_rule (engine, dcem, name, "RemoveMatch");
+}
+
+gboolean
+dconf_engine_is_writable (DConfEngine *engine,
+                          const gchar *name)
+{
+  gboolean writable = TRUE;
+
+  /* Only check if we have more than one database */
+  if (engine->n_dbs > 1)
+    {
+      gint i;
+
+      g_mutex_lock (&engine->lock);
+
+      dconf_engine_refresh (engine);
+
+      /* Don't check for locks in the top database (i == 0). */
+      for (i = engine->n_dbs - 1; 0 < i; i--)
+        if (engine->lock_tables[i] != NULL &&
+            gvdb_table_has_value (engine->lock_tables[i], name))
+          {
+            writable = FALSE;
+            break;
+          }
+
+      g_mutex_unlock (&engine->lock);
+    }
+
+  return writable;
+}
+
+/* be conservative and fast:  false negatives are OK */
+static gboolean
+is_dbusable (GVariant *value)
+{
+  const gchar *type;
+
+  type = g_variant_get_type_string (value);
+
+  /* maybe definitely won't work.
+   * variant?  too lazy to check inside...
+   */
+  if (strchr (type, 'v') || strchr (type, 'm'))
+    return FALSE;
+
+  /* XXX: we could also check for '{}' not inside an array...
+   * but i'm not sure we want to support that anyway.
+   */
+
+  /* this will avoid any too-deeply-nested limits */
+  return strlen (type) < 32;
+}
+
+static GVariant *
+fake_maybe (GVariant *value)
+{
+  GVariantBuilder builder;
+
+  g_variant_builder_init (&builder, G_VARIANT_TYPE ("av"));
+
+  if (value != NULL)
+    {
+      if (is_dbusable (value))
+        g_variant_builder_add (&builder, "v", value);
+
+      else
+        {
+          GVariant *variant;
+          GVariant *ay;
+
+          variant = g_variant_new_variant (value);
+          ay = g_variant_new_from_data (G_VARIANT_TYPE_BYTESTRING,
+                                        g_variant_get_data (variant),
+                                        g_variant_get_size (variant),
+                                        TRUE,
+                                        (GDestroyNotify) g_variant_unref,
+                                        variant);
+          g_variant_builder_add (&builder, "v", ay);
+
+          g_variant_builder_add (&builder, "v",
+                                 g_variant_new_string ("serialised GVariant"));
+        }
+    }
+
+  return g_variant_builder_end (&builder);
+}
+
+static void
+dconf_engine_dcem (DConfEngine        *engine,
+                   DConfEngineMessage *dcem,
+                   const gchar        *method_name,
+                   const gchar        *format_string,
+                   ...)
+{
+  va_list ap;
+
+  dcem->bus_name = "ca.desrt.dconf";
+  dcem->object_path = engine->object_paths[0];
+  dcem->interface_name = "ca.desrt.dconf.Writer";
+  dcem->method_name = method_name;
+  dcem->parameters = g_new (GVariant *, 2);
+  dcem->n_messages = 1;
+
+  va_start (ap, format_string);
+  dcem->parameters[0] = g_variant_new_va (format_string, NULL, &ap);
+  g_variant_ref_sink (dcem->parameters[0]);
+  dcem->parameters[1] = NULL;
+  va_end (ap);
+
+  dcem->bus_types = engine->bus_types;
+  dcem->reply_type = G_VARIANT_TYPE ("(s)");
+}
+
+gboolean
+dconf_engine_write (DConfEngine         *engine,
+                    const gchar         *name,
+                    GVariant            *value,
+                    DConfEngineMessage  *dcem,
+                    GError             **error)
+{
+  dconf_engine_dcem (engine, dcem,
+                     "Write", "(s av)",
+                     name, fake_maybe (value));
+
+  return TRUE;
+}
+
+gboolean
+dconf_engine_write_many (DConfEngine          *engine,
+                         const gchar          *prefix,
+                         const gchar * const  *keys,
+                         GVariant            **values,
+                         DConfEngineMessage   *dcem,
+                         GError              **error)
+{
+  GVariantBuilder builder;
+  gsize i;
+
+  g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(sav)"));
+
+  for (i = 0; keys[i]; i++)
+    g_variant_builder_add (&builder, "(s av)",
+                           keys[i], fake_maybe (values[i]));
+
+  dconf_engine_dcem (engine, dcem, "WriteMany", "(sa(sav))", prefix, &builder);
+
+  return TRUE;
+}
+
+gchar **
+dconf_engine_list (DConfEngine    *engine,
+                   const gchar    *dir,
+                   gint           *length)
+{
+  gchar **list;
+
+  g_mutex_lock (&engine->lock);
+
+  dconf_engine_refresh (engine);
+
+  if (engine->gvdbs[0])
+    list = gvdb_table_list (engine->gvdbs[0], dir);
+  else
+    list = NULL;
+
+  if (list == NULL)
+    list = g_new0 (char *, 1);
+
+  if (length)
+    *length = g_strv_length (list);
+
+  g_mutex_unlock (&engine->lock);
+
+  return list;
+}
+
+gboolean
+dconf_engine_decode_notify (DConfEngine   *engine,
+                            const gchar   *anti_expose,
+                            const gchar  **path,
+                            const gchar ***rels,
+                            guint          bus_type,
+                            const gchar   *sender,
+                            const gchar   *iface,
+                            const gchar   *method,
+                            GVariant      *body)
+{
+  if (strcmp (iface, "ca.desrt.dconf.Writer") || strcmp (method, "Notify"))
+    return FALSE;
+
+  if (!g_variant_is_of_type (body, G_VARIANT_TYPE ("(sass)")))
+    return FALSE;
+
+  if (anti_expose)
+    {
+      const gchar *ae;
+
+      g_variant_get_child (body, 2, "&s", &ae);
+
+      if (strcmp (ae, anti_expose) == 0)
+        return FALSE;
+    }
+
+  g_variant_get (body, "(&s^a&ss)", path, rels, NULL);
+
+  return TRUE;
+}
+
+gboolean
+dconf_engine_decode_writability_notify (const gchar **path,
+                                        const gchar  *iface,
+                                        const gchar  *method,
+                                        GVariant     *body)
+{
+  if (strcmp (iface, "ca.desrt.dconf.Writer") ||
+      strcmp (method, "WritabilityNotify"))
+    return FALSE;
+
+  if (!g_variant_is_of_type (body, G_VARIANT_TYPE ("(s)")))
+    return FALSE;
+
+  g_variant_get_child (body, 0, "&s", path);
+
+  return TRUE;
+}
diff --git a/dbus-1/dconf-engine.h b/dbus-1/dconf-engine.h
new file mode 100644
index 0000000..25de43d
--- /dev/null
+++ b/dbus-1/dconf-engine.h
@@ -0,0 +1,144 @@
+/*
+ * 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>
+ */
+
+#ifndef __dconf_engine_h__
+#define __dconf_engine_h__
+
+#include <glib.h>
+
+typedef struct _DConfEngine DConfEngine;
+
+/**
+ * DConfEngineMessage:
+ *
+ * This structure represents a number of DBus method call messages that #DConfEngine would like to send.
+ *
+ * #DConfEngine itself is unaware of a particular DBus or main loop implementation.  As such, all requests are
+ * synchronous and non-blocking, but most of them produce a #DConfEngineMessage describing messages that must be
+ * sent in order for the operation to be completed.
+ *
+ * @bus_name, @object_path, @interface_name, @method_name specify the respective header fields of the method
+ * call.  These are always equal for all of the calls contained within a single #DConfEngineMessage.
+ *
+ * @reply_type is the expected reply type of the method call.  This is also the same for all calls contained
+ * within a single #DConfEngineMessage.
+ *
+ * @n_messages is the number of messages to send.
+ *
+ * @bus_types and @parameters are both arrays, of length @n_messages.  Each element of @bus_type is the bus type
+ * to send each method call on and each of @parameters is the body of that call.  The reason that there may be
+ * several messages is that a single dconf "watch" operation may need to send multiple DBus "AddMatch" calls
+ * (and usually to multiple busses).
+ *
+ * Each element in @bus_types is either 'y' for system bus or 'e' for session bus.
+ *
+ * A #DConfEngineMessage is always stack-allocated by the caller.  It must be cleared using
+ * dconf_engine_message_destroy() when done.  It may be copied using dconf_engine_message_copy().
+ */
+typedef struct
+{
+  const gchar         *bus_name;
+  const gchar         *object_path;
+  const gchar         *interface_name;
+  const gchar         *method_name;
+
+  gint                 n_messages;
+  GVariant           **parameters;
+  const gchar         *bus_types;
+
+  const GVariantType  *reply_type;
+} DConfEngineMessage;
+
+G_GNUC_INTERNAL
+void                    dconf_engine_message_copy                       (DConfEngineMessage      *orig,
+                                                                         DConfEngineMessage      *copy);
+G_GNUC_INTERNAL
+void                    dconf_engine_message_destroy                    (DConfEngineMessage      *message);
+
+G_GNUC_INTERNAL
+DConfEngine *           dconf_engine_new                                (const gchar             *profile);
+G_GNUC_INTERNAL
+DConfEngine *           dconf_engine_new_for_db                         (const gchar             *db_name);
+G_GNUC_INTERNAL
+guint64                 dconf_engine_get_state                          (DConfEngine             *engine);
+
+G_GNUC_INTERNAL
+void                    dconf_engine_free                               (DConfEngine             *engine);
+
+G_GNUC_INTERNAL
+GVariant *              dconf_engine_read                               (DConfEngine             *engine,
+                                                                         const gchar             *key);
+G_GNUC_INTERNAL
+GVariant *              dconf_engine_read_default                       (DConfEngine             *engine,
+                                                                         const gchar             *key);
+G_GNUC_INTERNAL
+GVariant *              dconf_engine_read_no_default                    (DConfEngine             *engine,
+                                                                         const gchar             *key);
+G_GNUC_INTERNAL
+gchar **                dconf_engine_list                               (DConfEngine             *engine,
+                                                                         const gchar             *path,
+                                                                         gint                    *length);
+
+G_GNUC_INTERNAL
+void                    dconf_engine_get_service_info                   (DConfEngine             *engine,
+                                                                         const gchar            **bus_type,
+                                                                         const gchar            **destination,
+                                                                         const gchar            **object_path);
+G_GNUC_INTERNAL
+gboolean                dconf_engine_is_writable                        (DConfEngine             *engine,
+                                                                         const gchar             *name);
+G_GNUC_INTERNAL
+gboolean                dconf_engine_write                              (DConfEngine             *engine,
+                                                                         const gchar             *key,
+                                                                         GVariant                *value,
+                                                                         DConfEngineMessage      *message,
+                                                                         GError                 **error);
+G_GNUC_INTERNAL
+gboolean                dconf_engine_write_many                         (DConfEngine             *engine,
+                                                                         const gchar             *prefix,
+                                                                         const gchar * const     *keys,
+                                                                         GVariant               **values,
+                                                                         DConfEngineMessage      *message,
+                                                                         GError                 **error);
+G_GNUC_INTERNAL
+void                    dconf_engine_watch                              (DConfEngine             *engine,
+                                                                         const gchar             *name,
+                                                                         DConfEngineMessage      *message);
+G_GNUC_INTERNAL
+void                    dconf_engine_unwatch                            (DConfEngine             *engine,
+                                                                         const gchar             *name,
+                                                                         DConfEngineMessage      *message);
+G_GNUC_INTERNAL
+gboolean                dconf_engine_decode_notify                      (DConfEngine             *engine,
+                                                                         const gchar             *anti_expose,
+                                                                         const gchar            **prefix,
+                                                                         const gchar           ***keys,
+                                                                         guint                    bus_type,
+                                                                         const gchar             *sender,
+                                                                         const gchar             *interface,
+                                                                         const gchar             *member,
+                                                                         GVariant                *body);
+G_GNUC_INTERNAL
+gboolean                dconf_engine_decode_writability_notify          (const gchar            **path,
+                                                                         const gchar             *iface,
+                                                                         const gchar             *method,
+                                                                         GVariant                *body);
+#endif /* __dconf_engine_h__ */
diff --git a/editor/dconf-model.vala b/editor/dconf-model.vala
index 46afa3e..fc772a6 100644
--- a/editor/dconf-model.vala
+++ b/editor/dconf-model.vala
@@ -52,7 +52,7 @@ public class Key : GLib.Object
             _value = value;
             try
             {
-                model.client.write(full_name, value);
+                model.client.write_sync(full_name, value);
             }
             catch (GLib.Error e)
             {
@@ -143,7 +143,7 @@ public class Key : GLib.Object
         _value = null;
         try
         {
-            model.client.write(full_name, null);
+            model.client.write_sync(full_name, null);
         }
         catch (GLib.Error e)
         {
@@ -582,16 +582,10 @@ public class SettingsModel: GLib.Object, Gtk.TreeModel
 
     public SettingsModel()
     {
-        client = new DConf.Client (null, watch_func);
+        client = new DConf.Client ();
+        client.changed.connect (watch_func);
         root = new Directory(this, null, "/", "/");
-        try
-        {
-            client.watch ("/");
-        }
-        catch (Error e)
-        {
-            warning ("Failed to watch all keys: %s", e.message);
-        }
+        client.watch_sync ("/");
 
         schemas = new SchemaList();
         try
diff --git a/engine/Makefile.am b/engine/Makefile.am
index 5228482..234a412 100644
--- a/engine/Makefile.am
+++ b/engine/Makefile.am
@@ -1,5 +1,15 @@
-dconfinclude_HEADERS = \
-	dconf-engine.h
+noinst_LIBRARIES = libdconf-engine.a
 
-EXTRA_DIST = \
+libdconf_engine_a_CFLAGS = -fPIC -DPIC -Wall
+INCLUDES = $(glib_CFLAGS) -I../gvdb -I../common
+
+libdconf_engine_a_SOURCES = \
+	../gvdb/gvdb-reader.c		\
+	dconf-engine-profile.h		\
+	dconf-engine-profile.c		\
+	dconf-engine-source.h		\
+	dconf-engine-source-user.c	\
+	dconf-engine-source-system.c	\
+	dconf-engine-source.c		\
+	dconf-engine.h			\
 	dconf-engine.c
diff --git a/engine/dconf-changeset-list.h b/engine/dconf-changeset-list.h
new file mode 100644
index 0000000..01bd0ff
--- /dev/null
+++ b/engine/dconf-changeset-list.h
@@ -0,0 +1,38 @@
+/*
+ * 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>
+ */
+
+#ifndef __dconf_changeset_list_h__
+#define __dconf_changeset_list_h__
+
+#include "dconf-changeset.h"
+
+typedef struct _DConfChangesetList                          DConfChangesetList;
+
+struct _DConfChangesetList
+{
+  GQueue queue;
+};
+
+DConfChangeset *        dconf_changeset_list_new                        (void);
+
+void                    dconf_changeset_list_free                       (DConfChangesetList *changeset_list);
+
+#endif /* __dconf_changeset_list_h__ */
diff --git a/engine/dconf-engine-profile.c b/engine/dconf-engine-profile.c
new file mode 100644
index 0000000..b1b73c0
--- /dev/null
+++ b/engine/dconf-engine-profile.c
@@ -0,0 +1,131 @@
+/*
+ * 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt desrt ca>
+ */
+
+#include "dconf-engine.h"
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "dconf-engine-source.h"
+
+static DConfEngineSource **
+dconf_engine_null_profile (gint *n_sources)
+{
+  *n_sources = 0;
+
+  return NULL;
+}
+
+static DConfEngineSource **
+dconf_engine_default_profile (gint *n_sources)
+{
+  DConfEngineSource **sources;
+
+  sources = g_new (DConfEngineSource *, 1);
+  sources[0] = dconf_engine_source_new ("user-db:user");
+  *n_sources = 1;
+
+  return sources;
+}
+
+static DConfEngineSource **
+dconf_engine_read_profile_file (FILE *file,
+                                gint *n_sources)
+{
+  DConfEngineSource **sources;
+  gchar line[80];
+  gint n = 0, a;
+
+  sources = g_new (DConfEngineSource *, (a = 16));
+
+  while (fgets (line, sizeof line, file))
+    {
+      DConfEngineSource *source;
+      const gchar *end;
+
+      end = strchr (line, '\n');
+
+      if (end == NULL)
+        {
+          g_warning ("ignoring long or unterminated line in dconf profile");
+
+          /* skip until we find the newline or EOF */
+          while (fgets (line, sizeof line, file) && !strchr (line, '\n'));
+
+          continue;
+        }
+
+      if (end == line)
+        continue;
+
+      if (line[0] == '#')
+        continue;
+
+      source = dconf_engine_source_new (line);
+
+      if (source != NULL)
+        {
+          if (n == a)
+            sources = g_renew (DConfEngineSource *, sources, a *= 2);
+
+          sources[n++] = source;
+        }
+    }
+
+  return sources;
+}
+
+DConfEngineSource **
+dconf_engine_profile_get_default (gint *n_sources)
+{
+  DConfEngineSource **sources;
+  const gchar *profile;
+  FILE *file;
+
+  profile = getenv ("DCONF_PROFILE");
+
+  if (profile == NULL)
+    return dconf_engine_default_profile (n_sources);
+
+  if (profile[0] != '/')
+    {
+      gchar *filename = g_build_filename ("/etc/dconf/profile", profile, NULL);
+      file = fopen (filename, "r");
+      g_free (filename);
+    }
+  else
+    file = fopen (profile, "r");
+
+  if (file != NULL)
+    {
+      sources = dconf_engine_read_profile_file (file, n_sources);
+      fclose (file);
+    }
+  else
+    {
+      g_warning ("unable to open named profile (%s): using the null configuration.", profile);
+      sources = dconf_engine_null_profile (n_sources);
+    }
+
+  return sources;
+}
diff --git a/engine/dconf-engine-profile.h b/engine/dconf-engine-profile.h
new file mode 100644
index 0000000..19935e8
--- /dev/null
+++ b/engine/dconf-engine-profile.h
@@ -0,0 +1,31 @@
+/*
+ * 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt desrt ca>
+ */
+
+#ifndef __dconf_engine_profile_h__
+#define __dconf_engine_profile_h__
+
+#include "dconf-engine-source.h"
+
+G_GNUC_INTERNAL
+DConfEngineSource **    dconf_engine_profile_get_default                (gint *n_sources);
+
+#endif
diff --git a/engine/dconf-engine-source-private.h b/engine/dconf-engine-source-private.h
new file mode 100644
index 0000000..1b6faad
--- /dev/null
+++ b/engine/dconf-engine-source-private.h
@@ -0,0 +1,31 @@
+/*
+ * 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt desrt ca>
+ */
+
+#ifndef __dconf_engine_source_private_h__
+#define __dconf_engine_source_private_h__
+
+#include "dconf-engine-source.h"
+
+G_GNUC_INTERNAL const DConfEngineSourceVTable dconf_engine_source_user_vtable;
+G_GNUC_INTERNAL const DConfEngineSourceVTable dconf_engine_source_system_vtable;
+
+#endif /* __dconf_engine_source_private_h__ */
diff --git a/engine/dconf-engine-source-system.c b/engine/dconf-engine-source-system.c
new file mode 100644
index 0000000..fe0d9c6
--- /dev/null
+++ b/engine/dconf-engine-source-system.c
@@ -0,0 +1,84 @@
+/*
+ * 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt desrt ca>
+ */
+
+#include "dconf-engine-source-private.h"
+
+#include <sys/mman.h>
+#include <fcntl.h>
+#include <errno.h>
+
+static gboolean
+dconf_engine_source_system_init (DConfEngineSource *source)
+{
+  source->bus_type = G_BUS_TYPE_SYSTEM;
+  source->bus_name = g_strdup ("ca.desrt.dconf");
+  source->object_path = g_strdup_printf ("/ca/desrt/dconf/Writer/%s", source->name);
+
+  return TRUE;
+}
+
+static gboolean
+dconf_engine_source_system_needs_reopen (DConfEngineSource *source)
+{
+  return !source->values || !gvdb_table_is_valid (source->values);
+}
+
+static GvdbTable *
+dconf_engine_source_system_reopen (DConfEngineSource *source)
+{
+  static gboolean did_warn;
+  GError *error = NULL;
+  GvdbTable *table;
+  gchar *filename;
+
+  filename = g_build_filename ("/etc/dconf/db", source->name, NULL);
+  table = gvdb_table_new (filename, FALSE, &error);
+
+  if (table == NULL)
+    {
+      if (!did_warn)
+        {
+          g_critical ("unable to open file '%s': %s; expect degraded performance", filename, error->message);
+          did_warn = TRUE;
+        }
+
+      g_error_free (error);
+    }
+
+  g_free (filename);
+
+  return table;
+}
+
+static void
+dconf_engine_source_system_finalize (DConfEngineSource *source)
+{
+}
+
+G_GNUC_INTERNAL
+const DConfEngineSourceVTable dconf_engine_source_system_vtable = {
+  .instance_size    = sizeof (DConfEngineSource),
+  .init             = dconf_engine_source_system_init,
+  .finalize         = dconf_engine_source_system_finalize,
+  .needs_reopen     = dconf_engine_source_system_needs_reopen,
+  .reopen           = dconf_engine_source_system_reopen
+};
diff --git a/engine/dconf-engine-source-user.c b/engine/dconf-engine-source-user.c
new file mode 100644
index 0000000..dba655f
--- /dev/null
+++ b/engine/dconf-engine-source-user.c
@@ -0,0 +1,163 @@
+/*
+ * 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt desrt ca>
+ */
+
+#include "dconf-engine-source-private.h"
+
+#include <sys/mman.h>
+#include <fcntl.h>
+#include <errno.h>
+
+typedef struct
+{
+  DConfEngineSource source;
+
+  guint8 *shm;
+} DConfEngineSourceUser;
+
+static guint8 *
+dconf_engine_source_user_open_shm (const gchar *name)
+{
+  static gchar *shmdir;
+  gchar *filename;
+  void *memory;
+  gint fd;
+
+  if (shmdir == NULL)
+    shmdir = g_build_filename (g_get_user_runtime_dir (), "dconf", NULL);
+
+  filename = g_build_filename (shmdir, name, NULL);
+  memory = NULL;
+  fd = -1;
+
+  if (g_mkdir_with_parents (shmdir, 0700) != 0)
+    {
+      g_critical ("unable to create directory '%s': %s.  dconf will not work properly.", shmdir, g_strerror (errno));
+      goto out;
+    }
+
+  fd = open (filename, O_RDWR | O_CREAT, 0600);
+  if (fd == -1)
+    {
+      g_critical ("unable to create file '%s': %s.  dconf will not work properly.", filename, g_strerror (errno));
+      goto out;
+    }
+
+  if (ftruncate (fd, 1) != 0)
+    {
+      g_critical ("failed to allocate file '%s': %s.  dconf will not work properly.", filename, g_strerror (errno));
+      goto out;
+    }
+
+  memory = mmap (NULL, 1, PROT_READ, MAP_SHARED, fd, 0);
+  g_assert (memory != NULL);
+
+  if (memory == MAP_FAILED)
+    {
+      g_critical ("failed to mmap file '%s': %s.  dconf will not work properly.", filename, g_strerror (errno));
+      memory = NULL;
+      goto out;
+    }
+
+ out:
+  g_free (filename);
+  close (fd);
+
+  return memory;
+}
+
+static GvdbTable *
+dconf_engine_source_user_open_gvdb (const gchar *name)
+{
+  GvdbTable *table;
+  gchar *filename;
+
+  /* This can fail in the normal case of the user not having any
+   * settings.  That's OK and it shouldn't be considered as an error.
+   */
+  filename = g_build_filename (g_get_user_config_dir (), "dconf", name, NULL);
+  table = gvdb_table_new (filename, FALSE, NULL);
+  g_free (filename);
+
+  return table;
+}
+
+static gboolean
+dconf_engine_source_user_init (DConfEngineSource *source)
+{
+  DConfEngineSourceUser *user_source = (DConfEngineSourceUser *) source;
+  guint8 *shm;
+
+  shm = dconf_engine_source_user_open_shm (source->name);
+
+  if (shm == NULL)
+    return FALSE;
+
+  source->bus_type = G_BUS_TYPE_SESSION;
+  source->bus_name = g_strdup ("ca.desrt.dconf");
+  source->object_path = g_strdup_printf ("/ca/desrt/dconf/Writer/%s", source->name);
+  source->writable = TRUE;
+  user_source->shm = shm;
+
+  source->values = dconf_engine_source_user_open_gvdb (source->name);
+
+  return TRUE;
+}
+
+static gboolean
+dconf_engine_source_user_needs_reopen (DConfEngineSource *source)
+{
+  DConfEngineSourceUser *user_source = (DConfEngineSourceUser *) source;
+
+  return user_source->shm && *user_source->shm;
+}
+
+static GvdbTable *
+dconf_engine_source_user_reopen (DConfEngineSource *source)
+{
+  DConfEngineSourceUser *user_source = (DConfEngineSourceUser *) source;
+
+  munmap (user_source->shm, 1);
+  user_source->shm = dconf_engine_source_user_open_shm (source->name);
+
+  if (user_source->shm)
+    return dconf_engine_source_user_open_gvdb (source->name);
+
+  return NULL;
+}
+
+static void
+dconf_engine_source_user_finalize (DConfEngineSource *source)
+{
+  DConfEngineSourceUser *user_source = (DConfEngineSourceUser *) source;
+
+  if (user_source->shm)
+    munmap (user_source->shm, 1);
+}
+
+G_GNUC_INTERNAL
+const DConfEngineSourceVTable dconf_engine_source_user_vtable = {
+  .instance_size    = sizeof (DConfEngineSourceUser),
+  .init             = dconf_engine_source_user_init,
+  .finalize         = dconf_engine_source_user_finalize,
+  .needs_reopen     = dconf_engine_source_user_needs_reopen,
+  .reopen           = dconf_engine_source_user_reopen
+};
diff --git a/engine/dconf-engine-source.c b/engine/dconf-engine-source.c
new file mode 100644
index 0000000..48cf35c
--- /dev/null
+++ b/engine/dconf-engine-source.c
@@ -0,0 +1,92 @@
+/*
+ * 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt desrt ca>
+ */
+
+#include "dconf-engine-source-private.h"
+
+#include <string.h>
+
+void
+dconf_engine_source_free (DConfEngineSource *source)
+{
+  source->vtable->finalize (source);
+  g_free (source->name);
+  g_free (source);
+}
+
+/* warning: function called through a non-compatible type [enabled by default]
+ * note: if this code is reached, the program will abort
+ */
+static void i_hate_gcc (gpointer x) { gvdb_table_unref (x); }
+
+gboolean
+dconf_engine_source_refresh (DConfEngineSource *source)
+{
+  if (source->vtable->needs_reopen (source))
+    {
+      g_clear_pointer (&source->values, i_hate_gcc);
+      g_clear_pointer (&source->locks, i_hate_gcc);
+
+      source->values = source->vtable->reopen (source);
+      if (source->values)
+        source->locks = gvdb_table_get_table (source->values, ".locks");
+
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+gboolean
+dconf_engine_source_init (DConfEngineSource *source)
+{
+  return source->vtable->init (source);
+}
+
+DConfEngineSource *
+dconf_engine_source_new (const gchar *description)
+{
+  const DConfEngineSourceVTable *vtable;
+  DConfEngineSource *source;
+
+  switch (description[0])
+    {
+    case 's':
+      vtable = &dconf_engine_source_system_vtable;
+      break;
+
+    case 'u':
+      vtable = &dconf_engine_source_user_vtable;
+      break;
+
+    default:
+      g_critical ("unknown dconf database description: %s", description);
+      return NULL;
+    }
+
+  source = g_malloc0 (vtable->instance_size);
+  source->vtable = vtable;
+  source->name = strchr (description, ':');
+  if (source->name)
+    source->name = g_strdup (source->name + 1);
+
+  return source;
+}
diff --git a/engine/dconf-engine-source.h b/engine/dconf-engine-source.h
new file mode 100644
index 0000000..6d51bc7
--- /dev/null
+++ b/engine/dconf-engine-source.h
@@ -0,0 +1,67 @@
+/*
+ * 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt desrt ca>
+ */
+
+#ifndef __dconf_engine_source_h__
+#define __dconf_engine_source_h__
+
+#include <gvdb-reader.h>
+#include <gio/gio.h>
+
+typedef struct _DConfEngineSourceVTable DConfEngineSourceVTable;
+typedef struct _DConfEngineSource DConfEngineSource;
+
+struct _DConfEngineSourceVTable
+{
+  gsize instance_size;
+
+  gboolean      (* init)             (DConfEngineSource *source);
+  void          (* finalize)         (DConfEngineSource *source);
+  gboolean      (* needs_reopen)     (DConfEngineSource *source);
+  GvdbTable *   (* reopen)           (DConfEngineSource *source);
+};
+
+struct _DConfEngineSource
+{
+  const DConfEngineSourceVTable *vtable;
+
+  GvdbTable *values;
+  GvdbTable *locks;
+  GBusType   bus_type;
+  gboolean   writable;
+  gchar     *bus_name;
+  gchar     *object_path;
+  gchar     *name;
+};
+
+G_GNUC_INTERNAL
+void                    dconf_engine_source_free                        (DConfEngineSource  *source);
+
+G_GNUC_INTERNAL
+gboolean                dconf_engine_source_refresh                     (DConfEngineSource  *source);
+
+G_GNUC_INTERNAL
+DConfEngineSource *     dconf_engine_source_new                         (const gchar        *name);
+
+G_GNUC_INTERNAL
+gboolean                dconf_engine_source_init                        (DConfEngineSource  *source);
+
+#endif /* __dconf_engine_source_h__ */
diff --git a/engine/dconf-engine.c b/engine/dconf-engine.c
index aedabcf..230d2ab 100644
--- a/engine/dconf-engine.c
+++ b/engine/dconf-engine.c
@@ -20,8 +20,8 @@
  */
 
 #define _XOPEN_SOURCE 600
-#include "dconf-shmdir.h"
 #include "dconf-engine.h"
+
 #include <gvdb-reader.h>
 #include <string.h>
 #include <stdlib.h>
@@ -31,191 +31,223 @@
 #include <fcntl.h>
 #include <sys/mman.h>
 
-void
-dconf_engine_message_destroy (DConfEngineMessage *dcem)
-{
-  gint i;
-
-  for (i = 0; dcem->parameters[i]; i++)
-    g_variant_unref (dcem->parameters[i]);
-  g_free (dcem->parameters);
-}
-
-void
-dconf_engine_message_copy (DConfEngineMessage *orig,
-                           DConfEngineMessage *copy)
-{
-  gint i, n;
-
-  *copy = *orig;
+#include "dconf-engine-profile.h"
 
-  for (n = 0; orig->parameters[n]; n++);
-  copy->parameters = g_new (GVariant *, n + 1);
-  for (i = 0; i < n; i++)
-    copy->parameters[i] = g_variant_ref (orig->parameters[i]);
-  copy->parameters[i] = NULL;
-}
-
-static const gchar *
-dconf_engine_get_session_dir (void)
-{
-  static const gchar *session_dir;
-  static gsize initialised;
-
-  if (g_once_init_enter (&initialised))
-    {
-      session_dir = dconf_shmdir_from_environment ();
-      g_once_init_leave (&initialised, 1);
-    }
+/* The engine has zero or more sources.
+ *
+ * If it has zero sources then things are very uninteresting.  Nothing
+ * is writable, nothing will ever be written and reads will always
+ * return NULL.
+ *
+ * There are two interesting cases when there is a non-zero number of
+ * sources.  Writing only ever occurs to the first source, if at all.
+ * Non-first sources are never writable.
+ *
+ * The first source may or may not be writable.  In the usual case the
+ * first source is the one in the user's home directory and is writable,
+ * but it may be that the profile was setup for read-only access to
+ * system sources only.
+ *
+ * In the case that the first source is not writable (and therefore
+ * there are no writable sources), is_writable() will always return
+ * FALSE and no writes will ever be performed.
+ *
+ * It's possible to request changes in three ways:
+ *
+ *  - synchronous: the D-Bus message is immediately sent to the
+ *    dconf service and we block until we receive the reply.  The change
+ *    signal will follow soon thereafter (when we receive the signal on
+ *    D-Bus).
+ *
+ *  - asynchronous: typical asynchronous operation: we send the request
+ *    and return immediately, notifying using a callback when the
+ *    request is completed (and the new value is in the database).  The
+ *    change signal follows in the same way as with synchronous.
+ *
+ *  - fast: we record the value locally and signal the change, returning
+ *    immediately, as if the value is already in the database (from the
+ *    viewpoint of the local process).  We keep note of the new value
+ *    locally until the service has confirmed that the write was
+ *    successful.  If the write fails, we emit a change signal.  From
+ *    the view of the program it looks like the value was successfully
+ *    changed but then quickly changed back again by some external
+ *    agent.
+ *
+ * In fast mode we have to do some management of the queue.  If we
+ * immediately put all requests "in flight" then we can end up in a
+ * situation where the application writes many values for the same key
+ * and the service is kept (needlessly) busy writing over and over to
+ * the same key for some time after the requests stop coming in.
+ *
+ * If we limit the number of in-flight requests and put the other ones
+ * into a pending queue then we can perform merging of similar changes.
+ * If we notice that an item in the pending queue writes to the same
+ * keys as the newly-added request then we can simply drop the existing
+ * request (since its effect will be nullified by the new request).
+ *
+ * We want to keep the number of in-flight requests low in order to
+ * maximise our chance of dropping pending items, but we probably want
+ * it higher than 1 so that we can pipeline to hide latency.
+ *
+ * In order to minimise complexity, all changes go first to the pending
+ * queue.  Changes are dispatched from the pending queue (and moved to
+ * the in-flight queue) when the number of requests in-flight is lower
+ * than the maximum.
+ *
+ * For both 'in_flight' and 'pending' queues we push to the tail and pop
+ * from the head.  This puts the first operation on the head and the
+ * most recent operation on the tail.
+ *
+ * Since new operation go first to the pending queue, we find the most
+ * recent operations at the tail of that queue.  Since we want to return
+ * the most-recently written value, we therefore scan for values
+ * starting at the tail of the pending queue and ending at the head of
+ * the in-flight queue.
+ *
+ * NB: I tell a lie.  Async is not supported yet.
+ *
+ * Notes about threading:
+ *
+ * The engine is oblivious to threads and main contexts.
+ *
+ * What this means is that the engine has no interaction with GMainLoop
+ * and will not schedule idles or anything of the sort.  All calls made
+ * by the engine to the client library will be made in response to
+ * incoming method calls, from the same thread as the incoming call.
+ *
+ * If dconf_engine_call_handle_reply() or
+ * dconf_engine_handle_dbus_signal() are called from 'exotic' threads
+ * (as will often be the case) then the resulting calls to
+ * dconf_engine_change_notify() will come from the same thread.  That's
+ * left for the client library to deal with.
+ *
+ * All that said, the engine is completely threadsafe.  The client
+ * library can call any method from any thread at any time -- as long as
+ * it is willing to deal with receiving the change notifies in those
+ * threads.
+ *
+ * Thread-safety is implemented using two locks.
+ *
+ * The first lock (sources_lock) protects the sources.  Although the
+ * sources are only ever read from, it is necessary to lock them because
+ * it is not safe to read during a refresh (when the source is being
+ * closed and reopened).  Accordingly, sources_lock need only be
+ * acquired when accessing the parts of the sources that are subject to
+ * change as a result of refreshes; the static parts (like bus type,
+ * object path, etc) can be accessed without holding the lock.  The
+ * 'sources' array itself (and 'n_sources') are set at construction and
+ * never change after that.
+ *
+ * The second lock (queue_lock) protects the various queues that are
+ * used to implement the "fast" writes described above.
+ *
+ * If both locks are held at the same time thne the sources lock must
+ * have been acquired first.
+ */
 
-  return session_dir;
-}
+#define MAX_IN_FLIGHT 2
 
 struct _DConfEngine
 {
-  GMutex      lock;
-  guint64     state;
+  gpointer            user_data;    /* Set at construct time */
 
+  GMutex              sources_lock; /* This lock is for the sources (ie: refreshing) and state. */
+  guint64             state;        /* Counter that changes every time a source is refreshed. */
+  DConfEngineSource **sources;      /* Array never changes, but each source changes internally. */
+  gint                n_sources;
 
-  GvdbTable **gvdbs;
-  GvdbTable **lock_tables;
-  guint8    **shm;
-  gchar     **object_paths;
-  gchar      *bus_types;
-  gchar     **names;
-  gint        n_dbs;
+  GMutex              queue_lock;   /* This lock is for pending, in_flight, last_handled */
+  GQueue              pending;      /* DConfChangeset */
+  GQueue              in_flight;    /* DConfChangeset */
+  gchar              *last_handled; /* reply tag from last item in in_flight */
 };
 
+/* When taking the sources lock we check if any of the databases have
+ * had updates.
+ *
+ * Anything that is accessing the database (even only reading) needs to
+ * be holding the lock (since refreshes could be happening in another
+ * thread), so this makes sense.
+ *
+ * We could probably optimise this to avoid checking some databases in
+ * certain cases (ie: we do not need to check the user's database when
+ * we are only interested in checking writability) but this works well
+ * enough for now and is less prone to errors.
+ *
+ * We could probably change to a reader/writer situation that is only
+ * holding the write lock when actually making changes during a refresh
+ * but the engine is probably only ever really in use by two threads at
+ * a given time (main thread doing reads, DBus worker thread clearing
+ * the queue) so it seems unlikely that lock contention will become an
+ * issue.
+ *
+ * If it does, we can revisit this...
+ */
 static void
-dconf_engine_setup_user (DConfEngine *engine,
-                         gint         i)
+dconf_engine_acquire_sources (DConfEngine *engine)
 {
-  /* invariant: we never have user gvdb without shm */
-  g_assert ((engine->gvdbs[i] == NULL) >= (engine->shm[i] == NULL));
-
-  if (engine->names[i])
-    {
-      const gchar *session_dir = dconf_engine_get_session_dir ();
+  gint i;
 
-      if (session_dir)
-        {
-          gchar *filename;
-          gint fd;
-
-          filename = g_build_filename (session_dir,
-                                       engine->names[i],
-                                       NULL);
-          fd = open (filename, O_RDWR | O_CREAT, 0600);
-          g_free (filename);
-
-          if (fd >= 0)
-            {
-              if (ftruncate (fd, 1) == 0)
-                {
-                  engine->shm[i] = mmap (NULL, 1, PROT_READ, MAP_SHARED, fd, 0);
-
-                  if (engine->shm[i] == MAP_FAILED)
-                    engine->shm[i] = NULL;
-                }
-
-              close (fd);
-            }
-        }
+  g_mutex_lock (&engine->sources_lock);
 
-      if (engine->shm[i])
-        {
-          gchar *filename;
-
-          filename = g_build_filename (g_get_user_config_dir (),
-                                       "dconf",
-                                       engine->names[i],
-                                       NULL);
-          engine->gvdbs[i] = gvdb_table_new (filename, FALSE, NULL);
-          g_free (filename);
-        }
-    }
-
-  g_assert ((engine->gvdbs[i] == NULL) >= (engine->shm[i] == NULL));
+  for (i = 0; i < engine->n_sources; i++)
+    if (dconf_engine_source_refresh (engine->sources[i]))
+      engine->state++;
 }
 
 static void
-dconf_engine_refresh_user (DConfEngine *engine,
-                           gint         i)
+dconf_engine_release_sources (DConfEngine *engine)
 {
-  g_assert ((engine->gvdbs[i] == NULL) >= (engine->shm[i] == NULL));
-
-  /* if we failed the first time, fail forever */
-  if (engine->shm[i] && *engine->shm[i] == 1)
-    {
-      if (engine->gvdbs[i])
-        {
-          gvdb_table_unref (engine->gvdbs[i]);
-          engine->gvdbs[i] = NULL;
-        }
-
-      munmap (engine->shm[i], 1);
-      engine->shm[i] = NULL;
-
-      dconf_engine_setup_user (engine, i);
-      engine->state++;
-    }
-
-  g_assert ((engine->gvdbs[i] == NULL) >= (engine->shm[i] == NULL));
+  g_mutex_unlock (&engine->sources_lock);
 }
 
 static void
-dconf_engine_refresh_system (DConfEngine *engine,
-                             gint         i)
+dconf_engine_lock_queues (DConfEngine *engine)
 {
-  if (engine->gvdbs[i] && !gvdb_table_is_valid (engine->gvdbs[i]))
-    {
-      if (engine->lock_tables[i])
-        {
-          gvdb_table_unref (engine->lock_tables[i]);
-          engine->lock_tables[i] = NULL;
-        }
-
-      gvdb_table_unref (engine->gvdbs[i]);
-      engine->gvdbs[i] = NULL;
-    }
-
-  if (engine->gvdbs[i] == NULL)
-    {
-      gchar *filename = g_build_filename ("/etc/dconf/db",
-                                          engine->names[i], NULL);
-      engine->gvdbs[i] = gvdb_table_new (filename, TRUE, NULL);
-      if (engine->gvdbs[i] == NULL)
-        g_error ("Unable to open '%s', specified in dconf profile\n",
-                 filename);
-      engine->lock_tables[i] = gvdb_table_get_table (engine->gvdbs[i],
-                                                     ".locks");
-      g_free (filename);
-      engine->state++;
-    }
+  g_mutex_lock (&engine->queue_lock);
 }
 
 static void
-dconf_engine_refresh (DConfEngine *engine)
+dconf_engine_unlock_queues (DConfEngine *engine)
+{
+  g_mutex_unlock (&engine->queue_lock);
+}
+
+DConfEngine *
+dconf_engine_new (gpointer user_data)
 {
+  DConfEngine *engine;
   gint i;
 
-  for (i = 0; i < engine->n_dbs; i++)
-    if (engine->bus_types[i] == 'e')
-      dconf_engine_refresh_user (engine, i);
-    else
-      dconf_engine_refresh_system (engine, i);
+  engine = g_slice_new0 (DConfEngine);
+  engine->user_data = user_data;
+
+  g_mutex_init (&engine->sources_lock);
+  g_mutex_init (&engine->queue_lock);
+
+  engine->sources = dconf_engine_profile_get_default (&engine->n_sources);
+
+  for (i = 0; i < engine->n_sources; i++)
+    if (!dconf_engine_source_init (engine->sources[i]))
+      g_assert_not_reached ();
+
+  return engine;
 }
 
-static void
-dconf_engine_setup (DConfEngine *engine)
+void
+dconf_engine_free (DConfEngine *engine)
 {
   gint i;
 
-  for (i = 0; i < engine->n_dbs; i++)
-    if (engine->bus_types[i] == 'e')
-      dconf_engine_setup_user (engine, i);
-    else
-      dconf_engine_refresh_system (engine, i);
+  g_mutex_clear (&engine->sources_lock);
+  g_mutex_clear (&engine->queue_lock);
+
+  for (i = 0; i < engine->n_sources; i++)
+    dconf_engine_source_free (engine->sources[i]);
+
+  g_free (engine->sources);
+
+  g_slice_free (DConfEngine, engine);
 }
 
 guint64
@@ -223,535 +255,755 @@ dconf_engine_get_state (DConfEngine *engine)
 {
   guint64 state;
 
-  g_mutex_lock (&engine->lock);
-
-  dconf_engine_refresh (engine);
+  dconf_engine_acquire_sources (engine);
   state = engine->state;
-
-  g_mutex_unlock (&engine->lock);
+  dconf_engine_release_sources (engine);
 
   return state;
 }
 
 static gboolean
-dconf_engine_load_profile (const gchar   *profile,
-                           gchar        **bus_types,
-                           gchar       ***names,
-                           gint          *n_dbs,
-                           GError       **error)
-{
-  gchar *filename;
-  gint allocated;
-  char line[80];
-  FILE *f;
-
-  /* DCONF_PROFILE starting with '/' gives an absolute path to a profile */
-  if (profile[0] != '/')
-    filename = g_build_filename ("/etc/dconf/profile", profile, NULL);
-  else
-    filename = g_strdup (profile);
-
-  f = fopen (filename, "r");
-
-  if (f == NULL)
-    {
-      gint saved_errno = errno;
+dconf_engine_is_writable_internal (DConfEngine *engine,
+                                   const gchar *key)
+{
+  gint i;
 
-      g_set_error (error, G_FILE_ERROR,
-                   g_file_error_from_errno (saved_errno),
-                   "open '%s': %s", filename, g_strerror (saved_errno));
-      g_free (filename);
+  /* We must check several things:
+   *
+   *  - we have at least one source
+   *
+   *  - the first source is writable
+   *
+   *  - the key is not locked in a non-writable (ie: non-first) source
+   */
+  if (engine->n_sources == 0)
+    return FALSE;
+
+  if (engine->sources[0]->writable == FALSE)
+    return FALSE;
+
+  /* Ignore locks in the first source.
+   *
+   * Either it is writable and therefore ignoring locks is the right
+   * thing to do, or it's non-writable and we caught that case above.
+   */
+  for (i = 1; i < engine->n_sources; i++)
+    if (engine->sources[i]->locks && gvdb_table_has_value (engine->sources[i]->locks, key))
       return FALSE;
-    }
 
-  allocated = 4;
-  *bus_types = g_new (gchar, allocated);
-  *names = g_new (gchar *, allocated);
-  *n_dbs = 0;
+  return TRUE;
+}
 
-  /* quick and dirty is good enough for now */
-  while (fgets (line, sizeof line, f))
-    {
-      const gchar *end;
-      const gchar *sep;
+gboolean
+dconf_engine_is_writable (DConfEngine *engine,
+                          const gchar *key)
+{
+  gboolean writable;
 
-      end = strchr (line, '\n');
+  dconf_engine_acquire_sources (engine);
+  writable = dconf_engine_is_writable_internal (engine, key);
+  dconf_engine_release_sources (engine);
 
-      if (end == NULL)
-        g_error ("long line in %s", filename);
+  return writable;
+}
 
-      if (end == line)
-        continue;
+static gboolean
+dconf_engine_find_key_in_queue (GQueue       *queue,
+                                const gchar  *key,
+                                GVariant    **value)
+{
+  GList *node;
 
-      if (line[0] == '#')
-        continue;
+  /* Tail to head... */
+  for (node = g_queue_peek_tail_link (queue); node; node = node->prev)
+    if (dconf_changeset_get (node->data, key, value))
+      return TRUE;
 
-      if (*n_dbs == allocated)
-        {
-          allocated *= 2;
-          *names = g_renew (gchar *, *names, allocated);
-          *bus_types = g_renew (gchar, *bus_types, allocated);
-        }
+  return FALSE;
+}
+
+GVariant *
+dconf_engine_read (DConfEngine        *engine,
+                   DConfChangesetList *read_through,
+                   const gchar        *key)
+{
+  GVariant *value = NULL;
+  gint lock_level = 0;
+  gint i;
 
-      sep = strchr (line, ':');
+  dconf_engine_acquire_sources (engine);
+
+  /* There are a number of situations that this function has to deal
+   * with and they interact in unusual ways.  We attempt to write the
+   * rules for all cases here:
+   *
+   * With respect to the steady-state condition with no locks:
+   *
+   *   This is the case where there are no changes queued, no
+   *   read_through and no locks.
+   *
+   *   The value returned is the one from the lowest-index source that
+   *   contains that value.
+   *
+   * With respect to locks:
+   *
+   *   If a lock is present (except in source #0 where it is ignored)
+   *   then we will only return a value found in the source where the
+   *   lock was present, or a higher-index source (following the normal
+   *   rule that sources with lower indexes take priority).
+   *
+   *   This statement includes read_through and queued changes.  If a
+   *   lock is found, we will ignore those.
+   *
+   * With respect to read_through and queued changed:
+   *
+   *   We only consider read_through and queued changes in the event
+   *   that we have a writable source.  This will possibly cause us to
+   *   ignore read_through and will have no real effect on the queues
+   *   (since they will be empty anyway if we have no writable source).
+   *
+   *   We only consider read_through and queued changes in the event
+   *   that we have not found any locks.
+   *
+   *   If there is a non-NULL value found in read_through or the queued
+   *   changes then we will return that value.
+   *
+   *   If there is a NULL value (ie: a reset) found in read_through or
+   *   the queued changes then we will only ignore any value found in
+   *   the first source (which must be writable, or else we would not
+   *   have been considering read_through and the queues).  This is
+   *   consistent with the fact that a reset will unset any value found
+   *   in this source but will not affect values found in lower sources.
+   *
+   *   Put another way: if a non-writable source contains a value for a
+   *   particular key then it is impossible for this function to return
+   *   NULL.
+   *
+   * We implement the above rules as follows.  We have three state
+   * tracking variables:
+   *
+   *   - lock_level: records if and where we found a lock
+   *
+   *   - found_key: records if we found the key in any queue
+   *
+   *   - value: records the value of the found key (NULL for resets)
+   *
+   * We take these steps:
+   *
+   *  1. check for lockdown.  If we find a lock then we prevent any
+   *     other sources (including read_through and pending/in-flight)
+   *     from affecting the value of the key.
+   *
+   *     We record the result of this in the lock_level variable.  Zero
+   *     means that no locks were found.  Non-zero means that a lock was
+   *     found in the source with the index given by the variable.
+   *
+   *  2. check the uncommited changes in the read_through list as the
+   *     highest priority.  This is only done if we have a writable
+   *     source and no locks were found.
+   *
+   *     If we found an entry in the read_through then we set
+   *     'found_key' to TRUE and set 'value' to the value that we found
+   *     (which will be NULL in the case of finding a reset request).
+   *
+   *  3. check our pending and in-flight "fast" changes (in that order).
+   *     This is only done if we have a writable source and no locks
+   *     were found.  It is also only done if we did not find the key in
+   *     the read_through.
+   *
+   *  4. check the first source, if there is one.
+   *
+   *     This is only done if 'found_key' is FALSE.  If 'found_key' is
+   *     TRUE then it means that the first database was writable and we
+   *     either found a value that will replace it (value != NULL) or
+   *     found a pending reset (value == NULL) that will unset it.
+   *
+   *     We only actually do this step if we have a writable first
+   *     source and no locks found, otherwise we just let step 5 do all
+   *     the checking.
+   *
+   *  5. check the remaining sources.
+   *
+   *     We do this until we have value != NULL.  Even if found_key was
+   *     TRUE, the reset that was requested will not have affected the
+   *     lower-level databases.
+   */
 
-      if (sep)
+  /* Step 1.  Check for locks.
+   *
+   * Note: i > 0 (strictly).  Ignore locks for source #0.
+   */
+  for (i = engine->n_sources - 1; i > 0; i--)
+    if (engine->sources[i]->locks && gvdb_table_has_value (engine->sources[i]->locks, key))
+      {
+        lock_level = i;
+        break;
+      }
+
+  /* Only do steps 2 to 4 if we have no locks and we have a writable source. */
+  if (!lock_level && engine->n_sources != 0 && engine->sources[0]->writable)
+    {
+      gboolean found_key = FALSE;
+
+      /* Step 2.  Check read_through. */
+      if (read_through)
+        found_key = dconf_engine_find_key_in_queue (&read_through->queue, key, &value);
+
+      /* Step 3.  Check queued changes if we didn't find it in read_through.
+       *
+       * NB: We may want to optimise this to avoid taking the lock in
+       * the case that we know both queues are empty.
+       */
+      if (!found_key)
         {
-          /* strings MUST be 'user-db' or 'system-db'.  we do the check
-           * this way here merely because it is the fastest.
+          dconf_engine_lock_queues (engine);
+
+          /* Check the pending queue first because those were submitted
+           * more recently.
            */
-          (*bus_types)[*n_dbs] = (line[0] == 'u') ? 'e' : 'y';
-          (*names)[*n_dbs] = g_strndup (sep + 1, end - (sep + 1));
-        }
-      else
-        {
-          /* default is for first DB to be user and rest to be system */
-          (*bus_types)[*n_dbs] = (*n_dbs == 0) ? 'e' : 'y';
-          (*names)[*n_dbs] = g_strndup (line, end - line);
+          found_key = dconf_engine_find_key_in_queue (&engine->pending, key, &value) &&
+                      dconf_engine_find_key_in_queue (&engine->in_flight, key, &value);
+
+          dconf_engine_unlock_queues (engine);
         }
 
-      (*n_dbs)++;
+      /* Step 4.  Check the first source. */
+      if (!found_key)
+        value = gvdb_table_get_value (engine->sources[0]->values, key);
+
+      /* We already checked source #0 (or ignored it, as appropriate).
+       *
+       * Abuse the lock_level variable to get step 5 to skip this one.
+       */
+      lock_level = 1;
     }
 
-  *bus_types = g_renew (gchar, *bus_types, *n_dbs);
-  *names = g_renew (gchar *, *names, *n_dbs);
-  g_free (filename);
-  fclose (f);
+  /* Step 5.  Check the remaining sources, until value != NULL. */
+  for (i = lock_level; value == NULL && i < engine->n_sources; i++)
+    if ((value = gvdb_table_get_value (engine->sources[i]->values, key)))
+      break;
+
+  dconf_engine_release_sources (engine);
 
-  return TRUE;
+  return value;
 }
 
-DConfEngine *
-dconf_engine_new (const gchar *profile)
+gchar **
+dconf_engine_list (DConfEngine *engine,
+                   const gchar *dir,
+                   gint        *length)
 {
-  DConfEngine *engine;
+  GHashTable *results;
+  GHashTableIter iter;
+  gchar **list;
+  gint n_items;
+  gpointer key;
   gint i;
 
-  engine = g_slice_new (DConfEngine);
-  g_mutex_init (&engine->lock);
+  /* This function is unreliable in the presence of pending changes.
+   * Here's why:
+   *
+   * Consider the case that we list("/a/") and a pending request has a
+   * reset request recorded for "/a/b/c".  The question of if "b/"
+   * should appear in the output rests on if "/a/b/d" also exists.
+   *
+   * Put another way: If "/a/b/c" is the only key in "/a/b/" then
+   * resetting it would mean that "/a/b/" stops existing (and we should
+   * not include it in the output).  If there are others keys then it
+   * will continue to exist and we should include it.
+   *
+   * Instead of trying to sort this out, we just ignore the pending
+   * requests and report what the on-disk file says.
+   */
 
-  if (profile == NULL)
-    profile = getenv ("DCONF_PROFILE");
+  results = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
 
-  if (profile)
-    {
-      GError *error = NULL;
+  dconf_engine_acquire_sources (engine);
 
-      if (!dconf_engine_load_profile (profile, &engine->bus_types, &engine->names, &engine->n_dbs, &error))
-        g_error ("Error loading dconf profile '%s': %s\n",
-                 profile, error->message);
-    }
-  else
+  for (i = 0; i < engine->n_sources; i++)
     {
-      if (!dconf_engine_load_profile ("user", &engine->bus_types, &engine->names, &engine->n_dbs, NULL))
+      gchar **partial_list;
+      gint j;
+
+      partial_list = gvdb_table_list (engine->sources[i]->values, dir);
+
+      if (partial_list != NULL)
         {
-          engine->names = g_new (gchar *, 1);
-          engine->names[0] = g_strdup ("user");
-          engine->bus_types = g_strdup ("e");
-          engine->n_dbs = 1;
+          for (j = 0; partial_list[j]; j++)
+            /* Steal the keys from the list. */
+            g_hash_table_add (results, partial_list[j]);
+
+          /* Free only the list. */
+          g_free (partial_list);
         }
     }
 
-  if (strcmp (engine->names[0], "-") == 0)
+  dconf_engine_release_sources (engine);
+
+  n_items = g_hash_table_size (results);
+  list = g_new (gchar *, n_items + 1);
+
+  i = 0;
+  g_hash_table_iter_init (&iter, results);
+  while (g_hash_table_iter_next (&iter, &key, NULL))
     {
-      g_free (engine->names[0]);
-      engine->names[0] = NULL;
+      g_hash_table_iter_steal (&iter);
+      list[i++] = key;
     }
+  list[i] = NULL;
+  g_assert_cmpint (i, ==, n_items);
 
-  engine->object_paths = g_new (gchar *, engine->n_dbs);
-  engine->gvdbs = g_new0 (GvdbTable *, engine->n_dbs);
-  engine->lock_tables = g_new0 (GvdbTable *, engine->n_dbs);
-  engine->shm = g_new0 (guint8 *, engine->n_dbs);
-  engine->state = 0;
+  if (length)
+    *length = n_items;
 
-  for (i = 0; i < engine->n_dbs; i++)
-    if (engine->names[i])
-        engine->object_paths[i] = g_strjoin (NULL,
-                                             "/ca/desrt/dconf/Writer/",
-                                             engine->names[i],
-                                             NULL);
-    else
-      engine->object_paths[i] = NULL;
+  g_hash_table_unref (results);
 
-  dconf_engine_setup (engine);
+  return list;
+}
 
-  return engine;
+typedef void (* DConfEngineCallHandleCallback) (DConfEngine  *engine,
+                                                gpointer      handle,
+                                                GVariant     *parameter,
+                                                const GError *error);
+
+struct _DConfEngineCallHandle
+{
+  DConfEngine                   *engine;
+  DConfEngineCallHandleCallback  callback;
+  const GVariantType            *expected_reply;
+};
+
+static gpointer
+dconf_engine_call_handle_new (DConfEngine                   *engine,
+                              DConfEngineCallHandleCallback  callback,
+                              const GVariantType            *expected_reply,
+                              gsize                          size)
+{
+  DConfEngineCallHandle *handle;
+
+  g_assert (engine != NULL);
+  g_assert (callback != NULL);
+  g_assert (size >= sizeof (DConfEngineCallHandle));
+
+  handle = g_malloc0 (size);
+  handle->engine = engine;
+  handle->callback = callback;
+  handle->expected_reply = expected_reply;
+
+  return handle;
 }
 
 void
-dconf_engine_free (DConfEngine *engine)
+dconf_engine_call_handle_reply (DConfEngineCallHandle *handle,
+                                GVariant              *parameter,
+                                const GError          *error)
 {
-  gint i;
-
-  for (i = 0; i < engine->n_dbs; i++)
-    {
-      g_free (engine->object_paths[i]);
-      g_free (engine->names[i]);
+  if (handle == NULL)
+    return;
 
-      if (engine->gvdbs[i])
-        gvdb_table_unref (engine->gvdbs[i]);
+  (* handle->callback) (handle->engine, handle, parameter, error);
+}
 
-      if (engine->lock_tables[i])
-        gvdb_table_unref (engine->lock_tables[i]);
+static void
+dconf_engine_call_handle_free (DConfEngineCallHandle *handle)
+{
+  g_free (handle);
+}
 
-      if (engine->shm[i])
-        munmap (engine->shm[i], 1);
-    }
+/* returns floating */
+static GVariant *
+dconf_engine_make_match_rule (DConfEngineSource *source,
+                              const gchar       *path)
+{
+  GVariant *params;
+  gchar *rule;
 
+  rule = g_strdup_printf ("type='signal',"
+                          "interface='ca.desrt.dconf.Writer',"
+                          "path='%s',"
+                          "arg0path='%s'",
+                          source->object_path,
+                          path);
 
-  g_mutex_clear (&engine->lock);
+  params = g_variant_new ("(s)", rule);
 
-  g_free (engine->object_paths);
-  g_free (engine->bus_types);
-  g_free (engine->names);
-  g_free (engine->gvdbs);
-  g_free (engine->lock_tables);
-  g_free (engine->shm);
+  g_free (rule);
 
-  g_slice_free (DConfEngine, engine);
+  return params;
 }
 
-static GVariant *
-dconf_engine_read_internal (DConfEngine  *engine,
-                            const gchar  *key,
-                            gboolean      user,
-                            gboolean      system)
+typedef struct
 {
-  GVariant *value = NULL;
-  gint lowest;
-  gint limit;
-  gint i;
+  DConfEngineCallHandle handle;
+
+  guint64 state;
+  gint    pending;
+} OutstandingWatch;
 
-  g_mutex_lock (&engine->lock);
+static void
+dconf_engine_watch_established (DConfEngine  *engine,
+                                gpointer      handle,
+                                GVariant     *reply,
+                                const GError *error)
+{
+  OutstandingWatch *ow = handle;
 
-  dconf_engine_refresh (engine);
+  /* ignore errors */
 
-  /* Bound the search space depending on the databases that we are
-   * interested in.
-   */
-  limit = system ? engine->n_dbs : 1;
-  lowest = user ? 0 : 1;
-
-  /* We want i equal to the index of the highest database containing a
-   * lock, or i == lowest if there is no lock.  For that reason, we
-   * don't actually check the lowest database for a lock.  That makes
-   * sense, because even if it had a lock, it would not change our
-   * search policy (which would be to check the lowest one first).
-   *
-   * Note that we intentionally dishonour 'limit' here -- we want to
-   * ensure that values in the user database are always ignored when
-   * locks are present.
-   */
-  for (i = MAX (engine->n_dbs - 1, lowest); lowest < i; i--)
-    if (engine->lock_tables[i] != NULL &&
-        gvdb_table_has_value (engine->lock_tables[i], key))
-      break;
+  if (--ow->pending)
+    /* more on the way... */
+    return;
 
-  while (i < limit && value == NULL)
+  if (ow->state != dconf_engine_get_state (engine))
     {
-      if (engine->gvdbs[i] != NULL)
-        value = gvdb_table_get_value (engine->gvdbs[i], key);
-      i++;
+      /* Our recorded state does not match the current state.  Something
+       * must have changed while our watch requests were on the wire.
+       *
+       * We don't know what changed, so we can just say that potentially
+       * everything changed.  This case is very rare, anyway...
+       */
+      dconf_engine_change_notify (engine, "/", NULL, engine->user_data, NULL);
     }
 
-  g_mutex_unlock (&engine->lock);
-
-  return value;
+  dconf_engine_call_handle_free (handle);
 }
 
-GVariant *
-dconf_engine_read (DConfEngine  *engine,
-                   const gchar  *key)
+void
+dconf_engine_watch_fast (DConfEngine *engine,
+                         const gchar *path)
 {
-  return dconf_engine_read_internal (engine, key, TRUE, TRUE);
-}
+  OutstandingWatch *ow;
+  gint i;
 
-GVariant *
-dconf_engine_read_default (DConfEngine  *engine,
-                           const gchar  *key)
-{
-  return dconf_engine_read_internal (engine, key, FALSE, TRUE);
+  if (engine->n_sources == 0)
+    return;
+
+  /* It's possible (although rare) that the dconf database could change
+   * while our match rule is on the wire.
+   *
+   * Since we returned immediately (suggesting to the user that the
+   * watch was already established) we could have a race.
+   *
+   * To deal with this, we use the current state counter to ensure that nothing
+   * changes while the watch requests are on the wire.
+   */
+  ow = dconf_engine_call_handle_new (engine, dconf_engine_watch_established,
+                                     G_VARIANT_TYPE_UNIT, sizeof (OutstandingWatch));
+  ow->state = dconf_engine_get_state (engine);
+  ow->pending = engine->n_sources;
+
+  for (i = 0; i < engine->n_sources; i++)
+    dconf_engine_dbus_call_async_func (engine->sources[i]->bus_type, "org.freedesktop.DBus",
+                                       "/org/freedesktop/DBus", "org.freedesktop.DBus", "AddMatch",
+                                       dconf_engine_make_match_rule (engine->sources[i], path),
+                                       &ow->handle, NULL);
 }
 
-GVariant *
-dconf_engine_read_no_default (DConfEngine  *engine,
-                              const gchar  *key)
+void
+dconf_engine_unwatch_fast (DConfEngine *engine,
+                           const gchar *path)
 {
-  return dconf_engine_read_internal (engine, key, TRUE, FALSE);
+  gint i;
+
+  for (i = 0; i < engine->n_sources; i++)
+    dconf_engine_dbus_call_async_func (engine->sources[i]->bus_type, "org.freedesktop.DBus",
+                                       "/org/freedesktop/DBus", "org.freedesktop.DBus", "RemoveMatch",
+                                       dconf_engine_make_match_rule (engine->sources[i], path), NULL, NULL);
 }
 
 static void
-dconf_engine_make_match_rule (DConfEngine        *engine,
-                              DConfEngineMessage *dcem,
-                              const gchar        *name,
-                              const gchar        *method_name)
+dconf_engine_handle_match_rule_sync (DConfEngine *engine,
+                                     const gchar *method_name,
+                                     const gchar *path)
 {
   gint i;
 
-  dcem->bus_name = "org.freedesktop.DBus";
-  dcem->object_path = "/org/freedesktop/DBus";
-  dcem->interface_name = "org.freedesktop.DBus";
-  dcem->method_name = method_name;
+  /* We need not hold any locks here because we are only touching static
+   * things: the number of sources, and static properties of each source
+   * itself.
+   *
+   * This function silently ignores all errors.
+   */
 
-  dcem->parameters = g_new (GVariant *, engine->n_dbs + 1);
-  for (i = 0; i < engine->n_dbs; i++)
+  for (i = 0; i < engine->n_sources; i++)
     {
-      gchar *rule;
-
-      rule = g_strdup_printf ("type='signal',"
-                              "interface='ca.desrt.dconf.Writer',"
-                              "path='%s',"
-                              "arg0path='%s'",
-                              engine->object_paths[i],
-                              name);
-      dcem->parameters[i] = g_variant_new ("(s)", rule);
-      g_variant_ref_sink (dcem->parameters[i]);
-      g_free (rule);
-    }
-  dcem->parameters[i] = NULL;
+      GVariant *result;
+
+      result = dconf_engine_dbus_call_sync_func (engine->sources[i]->bus_type, "org.freedesktop.DBus",
+                                                 "/org/freedesktop/DBus", "org.freedesktop.DBus", method_name,
+                                                 dconf_engine_make_match_rule (engine->sources[i], path),
+                                                 G_VARIANT_TYPE_UNIT, NULL);
 
-  dcem->bus_types = engine->bus_types;
-  dcem->n_messages = engine->n_dbs;
-  dcem->reply_type = G_VARIANT_TYPE_UNIT;
+      if (result)
+        g_variant_unref (result);
+    }
 }
 
 void
-dconf_engine_watch (DConfEngine        *engine,
-                    const gchar        *name,
-                    DConfEngineMessage *dcem)
+dconf_engine_watch_sync (DConfEngine *engine,
+                         const gchar *path)
 {
-  dconf_engine_make_match_rule (engine, dcem, name, "AddMatch");
+  dconf_engine_handle_match_rule_sync (engine, "AddMatch", path);
 }
 
 void
-dconf_engine_unwatch (DConfEngine        *engine,
-                      const gchar        *name,
-                      DConfEngineMessage *dcem)
+dconf_engine_unwatch_sync (DConfEngine *engine,
+                           const gchar *path)
 {
-  dconf_engine_make_match_rule (engine, dcem, name, "RemoveMatch");
+  dconf_engine_handle_match_rule_sync (engine, "RemoveMatch", path);
 }
 
-gboolean
-dconf_engine_is_writable (DConfEngine *engine,
-                          const gchar *name)
+typedef struct
 {
-  gboolean writable = TRUE;
+  DConfEngineCallHandle handle;
 
-  /* Only check if we have more than one database */
-  if (engine->n_dbs > 1)
-    {
-      gint i;
+  DConfChangeset *change;
+} OutstandingChange;
 
-      g_mutex_lock (&engine->lock);
+static GVariant *
+dconf_engine_prepare_change (DConfEngine     *engine,
+                             DConfChangeset  *change)
+{
+  GVariant *serialised;
 
-      dconf_engine_refresh (engine);
+  serialised = dconf_changeset_serialise (change);
 
-      /* Don't check for locks in the top database (i == 0). */
-      for (i = engine->n_dbs - 1; 0 < i; i--)
-        if (engine->lock_tables[i] != NULL &&
-            gvdb_table_has_value (engine->lock_tables[i], name))
-          {
-            writable = FALSE;
-            break;
-          }
+  return g_variant_new_from_data (G_VARIANT_TYPE ("(ay)"),
+                                  g_variant_get_data (serialised), g_variant_get_size (serialised), TRUE,
+                                  (GDestroyNotify) g_variant_unref, g_variant_ref_sink (serialised));
+}
 
-      g_mutex_unlock (&engine->lock);
-    }
+/* This function promotes changes from the pending queue to the
+ * in-flight queue by sending the appropriate D-Bus message.
+ *
+ * Of course, this is only possible when there are pending items and
+ * room in the in-flight queue.  For this reason, this function gets
+ * called in two situations:
+ *
+ *   - an item has been added to the pending queue (due to an API call)
+ *
+ *   - an item has been removed from the inflight queue (due to a D-Bus
+ *     reply having been received)
+ *
+ * It will move a maximum of one item.
+ */
+static void dconf_engine_manage_queue (DConfEngine *engine);
 
-  return writable;
+static void
+dconf_engine_emit_changes (DConfEngine    *engine,
+                           DConfChangeset *changeset)
+{
+  const gchar *prefix;
+  const gchar * const *changes;
+
+  if (dconf_changeset_describe (changeset, &prefix, &changes, NULL))
+    dconf_engine_change_notify (engine, prefix, changes, engine->user_data, NULL);
 }
 
-/* be conservative and fast:  false negatives are OK */
-static gboolean
-is_dbusable (GVariant *value)
+static void
+dconf_engine_change_completed (DConfEngine  *engine,
+                               gpointer      handle,
+                               GVariant     *reply,
+                               const GError *error)
 {
-  const gchar *type;
+  OutstandingChange *oc = handle;
+  DConfChangeset *expected;
 
-  type = g_variant_get_type_string (value);
+  dconf_engine_lock_queues (engine);
 
-  /* maybe definitely won't work.
-   * variant?  too lazy to check inside...
+  /* D-Bus guarantees ordered delivery of messages.
+   *
+   * The dconf-service handles requests in-order.
+   *
+   * The reply we just received should therefore be at the head of
+   * our 'in flight' queue.
    */
-  if (strchr (type, 'v') || strchr (type, 'm'))
-    return FALSE;
+  expected = g_queue_pop_head (&engine->in_flight);
+  g_assert (expected && oc->change == expected);
 
-  /* XXX: we could also check for '{}' not inside an array...
-   * but i'm not sure we want to support that anyway.
+  /* We just popped a change from the in-flight queue, possibly
+   * making room for another to be added.  Check that.
    */
+  dconf_engine_manage_queue (engine);
 
-  /* this will avoid any too-deeply-nested limits */
-  return strlen (type) < 32;
-}
-
-static GVariant *
-fake_maybe (GVariant *value)
-{
-  GVariantBuilder builder;
+  /* Deal with the reply we got. */
+  if (reply)
+    {
+      g_free (engine->last_handled);
+      g_variant_get (reply, "(s)", &engine->last_handled);
+    }
 
-  g_variant_builder_init (&builder, G_VARIANT_TYPE ("av"));
+  dconf_engine_unlock_queues (engine);
 
-  if (value != NULL)
+  if (error)
     {
-      if (is_dbusable (value))
-        g_variant_builder_add (&builder, "v", value);
-
-      else
-        {
-          GVariant *variant;
-          GVariant *ay;
-
-          variant = g_variant_new_variant (value);
-          ay = g_variant_new_from_data (G_VARIANT_TYPE_BYTESTRING,
-                                        g_variant_get_data (variant),
-                                        g_variant_get_size (variant),
-                                        TRUE,
-                                        (GDestroyNotify) g_variant_unref,
-                                        variant);
-          g_variant_builder_add (&builder, "v", ay);
-
-          g_variant_builder_add (&builder, "v",
-                                 g_variant_new_string ("serialised GVariant"));
-        }
+      /* Some kind of unexpected failure occured while attempting to
+       * commit the change.
+       *
+       * There's not much we can do here except to drop our local copy
+       * of the change (and notify that it is gone) and print the error
+       * message as a warning.
+       */
+      g_warning ("failed to commit changes to dconf: %s", error->message);
+      dconf_engine_emit_changes (engine, oc->change);
     }
 
-  return g_variant_builder_end (&builder);
+  dconf_changeset_unref (oc->change);
+  dconf_engine_call_handle_free (handle);
 }
 
 static void
-dconf_engine_dcem (DConfEngine        *engine,
-                   DConfEngineMessage *dcem,
-                   const gchar        *method_name,
-                   const gchar        *format_string,
-                   ...)
-{
-  va_list ap;
-
-  dcem->bus_name = "ca.desrt.dconf";
-  dcem->object_path = engine->object_paths[0];
-  dcem->interface_name = "ca.desrt.dconf.Writer";
-  dcem->method_name = method_name;
-  dcem->parameters = g_new (GVariant *, 2);
-  dcem->n_messages = 1;
-
-  va_start (ap, format_string);
-  dcem->parameters[0] = g_variant_new_va (format_string, NULL, &ap);
-  g_variant_ref_sink (dcem->parameters[0]);
-  dcem->parameters[1] = NULL;
-  va_end (ap);
-
-  dcem->bus_types = engine->bus_types;
-  dcem->reply_type = G_VARIANT_TYPE ("(s)");
-}
-
-gboolean
-dconf_engine_write (DConfEngine         *engine,
-                    const gchar         *name,
-                    GVariant            *value,
-                    DConfEngineMessage  *dcem,
-                    GError             **error)
+dconf_engine_manage_queue (DConfEngine *engine)
 {
-  dconf_engine_dcem (engine, dcem,
-                     "Write", "(s av)",
-                     name, fake_maybe (value));
-
-  return TRUE;
-}
+  if (!g_queue_is_empty (&engine->pending) && g_queue_get_length (&engine->in_flight) < MAX_IN_FLIGHT)
+    {
+      OutstandingChange *oc;
+      GVariant *parameters;
 
-gboolean
-dconf_engine_write_many (DConfEngine          *engine,
-                         const gchar          *prefix,
-                         const gchar * const  *keys,
-                         GVariant            **values,
-                         DConfEngineMessage   *dcem,
-                         GError              **error)
-{
-  GVariantBuilder builder;
-  gsize i;
+      oc = dconf_engine_call_handle_new (engine, dconf_engine_change_completed,
+                                         G_VARIANT_TYPE ("(s)"), sizeof (OutstandingChange));
 
-  g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(sav)"));
+      oc->change = g_queue_pop_head (&engine->pending);
 
-  for (i = 0; keys[i]; i++)
-    g_variant_builder_add (&builder, "(s av)",
-                           keys[i], fake_maybe (values[i]));
+      parameters = dconf_engine_prepare_change (engine, oc->change);
 
-  dconf_engine_dcem (engine, dcem, "WriteMany", "(sa(sav))", prefix, &builder);
+      dconf_engine_dbus_call_async_func (engine->sources[0]->bus_type,
+                                         engine->sources[0]->bus_name,
+                                         engine->sources[0]->object_path,
+                                         "ca.desrt.dconf.Writer", "Change",
+                                         parameters, &oc->handle, NULL);
 
-  return TRUE;
+      g_queue_push_tail (&engine->in_flight, oc->change);
+    }
 }
 
-gchar **
-dconf_engine_list (DConfEngine    *engine,
-                   const gchar    *dir,
-                   gint           *length)
+static gboolean
+dconf_engine_is_writable_changeset_predicate (const gchar *key,
+                                              GVariant    *value,
+                                              gpointer     user_data)
 {
-  gchar **list;
-
-  g_mutex_lock (&engine->lock);
+  DConfEngine *engine = user_data;
 
-  dconf_engine_refresh (engine);
+  return dconf_engine_is_writable_internal (engine, key);
+}
 
-  if (engine->gvdbs[0])
-    list = gvdb_table_list (engine->gvdbs[0], dir);
-  else
-    list = NULL;
+static gboolean
+dconf_engine_changeset_changes_only_writable_keys (DConfEngine    *engine,
+                                                   DConfChangeset *changeset,
+                                                   GError         **error)
+{
+  gboolean success = TRUE;
 
-  if (list == NULL)
-    list = g_new0 (char *, 1);
+  dconf_engine_acquire_sources (engine);
 
-  if (length)
-    *length = g_strv_length (list);
+  if (!dconf_changeset_all (changeset, dconf_engine_is_writable_changeset_predicate, engine))
+    {
+      g_set_error_literal (error, DCONF_ERROR, DCONF_ERROR_NOT_WRITABLE,
+                           "The operation attempted to modify one or more non-writable keys");
+      success = FALSE;
+    }
 
-  g_mutex_unlock (&engine->lock);
+  dconf_engine_release_sources (engine);
 
-  return list;
+  return success;
 }
 
 gboolean
-dconf_engine_decode_notify (DConfEngine   *engine,
-                            const gchar   *anti_expose,
-                            const gchar  **path,
-                            const gchar ***rels,
-                            guint          bus_type,
-                            const gchar   *sender,
-                            const gchar   *iface,
-                            const gchar   *method,
-                            GVariant      *body)
-{
-  if (strcmp (iface, "ca.desrt.dconf.Writer") || strcmp (method, "Notify"))
-    return FALSE;
+dconf_engine_change_fast (DConfEngine     *engine,
+                          DConfChangeset  *changeset,
+                          GError         **error)
+{
+  GList *node;
 
-  if (!g_variant_is_of_type (body, G_VARIANT_TYPE ("(sass)")))
+  if (!dconf_engine_changeset_changes_only_writable_keys (engine, changeset, error))
     return FALSE;
 
-  if (anti_expose)
+  /* Check for duplicates in the pending queue.
+   *
+   * Note: order doesn't really matter here since "similarity" is an
+   * equivalence class and we've ensured that there are no pairwise
+   * similar changes in the queue already (ie: at most we will have only
+   * one similar item to the one we are adding).
+   */
+  dconf_engine_lock_queues (engine);
+
+  for (node = g_queue_peek_head_link (&engine->pending); node; node = node->next)
     {
-      const gchar *ae;
+      DConfChangeset *queued_change = node->data;
 
-      g_variant_get_child (body, 2, "&s", &ae);
+      if (dconf_changeset_is_similar_to (changeset, queued_change))
+        {
+          /* We found a similar item in the queue.
+           *
+           * We want to drop the one that's in the queue already since
+           * we want our new (more recent) change to take precedence.
+           *
+           * The pending queue owned the changeset, so free it.
+           */
+          g_queue_delete_link (&engine->pending, node);
+          dconf_changeset_unref (queued_change);
 
-      if (strcmp (ae, anti_expose) == 0)
-        return FALSE;
+          /* There will only have been one, so stop looking. */
+          break;
+        }
     }
 
-  g_variant_get (body, "(&s^a&ss)", path, rels, NULL);
+  /* No matter what we're going to queue up this change, so put it in
+   * the pending queue now.
+   *
+   * There may be room in the in_flight queue, so we try to manage the
+   * queue right away in order to try to promote it there (which causes
+   * the D-Bus message to actually be sent).
+   *
+   * The change might get tossed before being sent if the loop above
+   * finds it on a future call.
+   */
+  g_queue_push_tail (&engine->pending, dconf_changeset_ref (changeset));
+  dconf_engine_manage_queue (engine);
+
+  dconf_engine_unlock_queues (engine);
+
+  /* Emit the signal after dropping the lock to avoid deadlock on re-entry. */
+  dconf_engine_emit_changes (engine, changeset);
 
   return TRUE;
 }
 
 gboolean
-dconf_engine_decode_writability_notify (const gchar **path,
-                                        const gchar  *iface,
-                                        const gchar  *method,
-                                        GVariant     *body)
+dconf_engine_change_sync (DConfEngine     *engine,
+                          DConfChangeset  *changeset,
+                          gchar          **tag,
+                          GError         **error)
 {
-  if (strcmp (iface, "ca.desrt.dconf.Writer") ||
-      strcmp (method, "WritabilityNotify"))
+  GVariant *reply;
+
+  if (!dconf_engine_changeset_changes_only_writable_keys (engine, changeset, error))
     return FALSE;
 
-  if (!g_variant_is_of_type (body, G_VARIANT_TYPE ("(s)")))
+  /* we know that we have at least one source because we checked writability */
+  reply = dconf_engine_dbus_call_sync_func (engine->sources[0]->bus_type,
+                                            engine->sources[0]->bus_name,
+                                            engine->sources[0]->object_path,
+                                            "ca.desrt.dconf.Writer", "Change",
+                                            dconf_engine_prepare_change (engine, changeset),
+                                            G_VARIANT_TYPE ("(s)"), error);
+
+  if (reply == NULL)
     return FALSE;
 
-  g_variant_get_child (body, 0, "&s", path);
+  /* g_variant_get() is okay with NULL tag */
+  g_variant_get (reply, "(s)", tag);
+  g_variant_unref (reply);
 
   return TRUE;
 }
+
+void
+dconf_engine_handle_dbus_signal (GBusType     type,
+                                 const gchar *sender,
+                                 const gchar *path,
+                                 const gchar *member,
+                                 GVariant    *body)
+{
+  g_print ("saw a sig");
+}
diff --git a/engine/dconf-engine.h b/engine/dconf-engine.h
index 25de43d..19b7097 100644
--- a/engine/dconf-engine.h
+++ b/engine/dconf-engine.h
@@ -22,123 +22,136 @@
 #ifndef __dconf_engine_h__
 #define __dconf_engine_h__
 
-#include <glib.h>
+#include "dconf-changeset-list.h"
+
+#include <gio/gio.h>
 
 typedef struct _DConfEngine DConfEngine;
 
-/**
- * DConfEngineMessage:
- *
- * This structure represents a number of DBus method call messages that #DConfEngine would like to send.
- *
- * #DConfEngine itself is unaware of a particular DBus or main loop implementation.  As such, all requests are
- * synchronous and non-blocking, but most of them produce a #DConfEngineMessage describing messages that must be
- * sent in order for the operation to be completed.
- *
- * @bus_name, @object_path, @interface_name, @method_name specify the respective header fields of the method
- * call.  These are always equal for all of the calls contained within a single #DConfEngineMessage.
- *
- * @reply_type is the expected reply type of the method call.  This is also the same for all calls contained
- * within a single #DConfEngineMessage.
- *
- * @n_messages is the number of messages to send.
- *
- * @bus_types and @parameters are both arrays, of length @n_messages.  Each element of @bus_type is the bus type
- * to send each method call on and each of @parameters is the body of that call.  The reason that there may be
- * several messages is that a single dconf "watch" operation may need to send multiple DBus "AddMatch" calls
- * (and usually to multiple busses).
- *
- * Each element in @bus_types is either 'y' for system bus or 'e' for session bus.
- *
- * A #DConfEngineMessage is always stack-allocated by the caller.  It must be cleared using
- * dconf_engine_message_destroy() when done.  It may be copied using dconf_engine_message_copy().
- */
-typedef struct
+typedef struct _DConfEngineCallHandle DConfEngineCallHandle;
+
+typedef enum
 {
-  const gchar         *bus_name;
-  const gchar         *object_path;
-  const gchar         *interface_name;
-  const gchar         *method_name;
+  DCONF_ERROR_FAILED,
+  DCONF_ERROR_NOT_WRITABLE
+} DConfEngineError;
 
-  gint                 n_messages;
-  GVariant           **parameters;
-  const gchar         *bus_types;
+#define DCONF_ERROR (g_quark_from_static_string ("dconf error quark"))
 
-  const GVariantType  *reply_type;
-} DConfEngineMessage;
+/* These functions need to be implemented by the client library */
 
+/* Sends a D-Bus message.
+ *
+ * When the reply comes back, the client library should call
+ * dconf_engine_handle_dbus_reply with the given user_data.
+ *
+ * This is called with the engine lock held.  Re-entering the engine
+ * from this function will cause a deadlock.
+ */
 G_GNUC_INTERNAL
-void                    dconf_engine_message_copy                       (DConfEngineMessage      *orig,
-                                                                         DConfEngineMessage      *copy);
+gboolean                dconf_engine_dbus_call_async_func               (GBusType                 bus_type,
+                                                                         const gchar             *bus_name,
+                                                                         const gchar             *object_path,
+                                                                         const gchar             *interface_name,
+                                                                         const gchar             *method_name,
+                                                                         GVariant                *parameters,
+                                                                         DConfEngineCallHandle   *handle,
+                                                                         GError                 **error);
+
+/* Sends a D-Bus message, synchronously.
+ *
+ * The lock is never held when calling this function (for the sake of
+ * not blocking requests in other threads) but you should have no reason
+ * to re-enter, so don't.
+ */
 G_GNUC_INTERNAL
-void                    dconf_engine_message_destroy                    (DConfEngineMessage      *message);
+GVariant *              dconf_engine_dbus_call_sync_func                (GBusType                 bus_type,
+                                                                         const gchar             *bus_name,
+                                                                         const gchar             *object_path,
+                                                                         const gchar             *interface_name,
+                                                                         const gchar             *method_name,
+                                                                         GVariant                *parameters,
+                                                                         const GVariantType      *expected_type,
+                                                                         GError                 **error);
 
+/* Notifies that a change occured.
+ *
+ * The engine lock is never held when calling this function so it is
+ * safe to run user callbacks or emit signals from this function.
+ */
 G_GNUC_INTERNAL
-DConfEngine *           dconf_engine_new                                (const gchar             *profile);
+void                    dconf_engine_change_notify                      (DConfEngine             *engine,
+                                                                         const gchar             *prefix,
+                                                                         const gchar * const     *changes,
+                                                                         const gchar             *tag,
+                                                                         gpointer                 user_data);
+
+/* These functions are implemented by the engine */
 G_GNUC_INTERNAL
-DConfEngine *           dconf_engine_new_for_db                         (const gchar             *db_name);
+void                    dconf_engine_call_handle_reply                  (DConfEngineCallHandle   *handle,
+                                                                         GVariant                *parameters,
+                                                                         const GError            *error);
+
 G_GNUC_INTERNAL
-guint64                 dconf_engine_get_state                          (DConfEngine             *engine);
+void                    dconf_engine_handle_dbus_signal                 (GBusType                 bus_type,
+                                                                         const gchar             *bus_name,
+                                                                         const gchar             *object_path,
+                                                                         const gchar             *signal_name,
+                                                                         GVariant                *parameters);
+
+G_GNUC_INTERNAL
+DConfEngine *           dconf_engine_new                                (gpointer                 user_data);
 
 G_GNUC_INTERNAL
 void                    dconf_engine_free                               (DConfEngine             *engine);
 
+/* Read API: always handled immediately */
 G_GNUC_INTERNAL
-GVariant *              dconf_engine_read                               (DConfEngine             *engine,
-                                                                         const gchar             *key);
+guint64                 dconf_engine_get_state                          (DConfEngine             *engine);
+
 G_GNUC_INTERNAL
-GVariant *              dconf_engine_read_default                       (DConfEngine             *engine,
+gboolean                dconf_engine_is_writable                        (DConfEngine             *engine,
                                                                          const gchar             *key);
+
 G_GNUC_INTERNAL
-GVariant *              dconf_engine_read_no_default                    (DConfEngine             *engine,
+GVariant *              dconf_engine_read                               (DConfEngine             *engine,
+                                                                         DConfChangesetList      *read_through,
                                                                          const gchar             *key);
+
 G_GNUC_INTERNAL
 gchar **                dconf_engine_list                               (DConfEngine             *engine,
-                                                                         const gchar             *path,
+                                                                         const gchar             *dir,
                                                                          gint                    *length);
 
+/* "Fast" API: all calls return immediately and look like they succeeded (from a local viewpoint) */
 G_GNUC_INTERNAL
-void                    dconf_engine_get_service_info                   (DConfEngine             *engine,
-                                                                         const gchar            **bus_type,
-                                                                         const gchar            **destination,
-                                                                         const gchar            **object_path);
+void                    dconf_engine_watch_fast                         (DConfEngine             *engine,
+                                                                         const gchar             *path);
+
 G_GNUC_INTERNAL
-gboolean                dconf_engine_is_writable                        (DConfEngine             *engine,
-                                                                         const gchar             *name);
+void                    dconf_engine_unwatch_fast                       (DConfEngine             *engine,
+                                                                         const gchar             *path);
+
 G_GNUC_INTERNAL
-gboolean                dconf_engine_write                              (DConfEngine             *engine,
-                                                                         const gchar             *key,
-                                                                         GVariant                *value,
-                                                                         DConfEngineMessage      *message,
+gboolean                dconf_engine_change_fast                        (DConfEngine             *engine,
+                                                                         DConfChangeset          *changeset,
                                                                          GError                 **error);
+
+/* Synchronous API: all calls block until completed */
 G_GNUC_INTERNAL
-gboolean                dconf_engine_write_many                         (DConfEngine             *engine,
-                                                                         const gchar             *prefix,
-                                                                         const gchar * const     *keys,
-                                                                         GVariant               **values,
-                                                                         DConfEngineMessage      *message,
-                                                                         GError                 **error);
+void                    dconf_engine_watch_sync                         (DConfEngine             *engine,
+                                                                         const gchar             *path);
+
 G_GNUC_INTERNAL
-void                    dconf_engine_watch                              (DConfEngine             *engine,
-                                                                         const gchar             *name,
-                                                                         DConfEngineMessage      *message);
-G_GNUC_INTERNAL
-void                    dconf_engine_unwatch                            (DConfEngine             *engine,
-                                                                         const gchar             *name,
-                                                                         DConfEngineMessage      *message);
-G_GNUC_INTERNAL
-gboolean                dconf_engine_decode_notify                      (DConfEngine             *engine,
-                                                                         const gchar             *anti_expose,
-                                                                         const gchar            **prefix,
-                                                                         const gchar           ***keys,
-                                                                         guint                    bus_type,
-                                                                         const gchar             *sender,
-                                                                         const gchar             *interface,
-                                                                         const gchar             *member,
-                                                                         GVariant                *body);
-G_GNUC_INTERNAL
-gboolean                dconf_engine_decode_writability_notify          (const gchar            **path,
-                                                                         const gchar             *iface,
-                                                                         const gchar             *method,
-                                                                         GVariant                *body);
+void                    dconf_engine_unwatch_sync                       (DConfEngine             *engine,
+                                                                         const gchar             *path);
+
+G_GNUC_INTERNAL
+gboolean                dconf_engine_change_sync                        (DConfEngine             *engine,
+                                                                         DConfChangeset          *changeset,
+                                                                         gchar                  **tag,
+                                                                         GError                 **error);
+
+/* Asynchronous API: not implemented yet (and maybe never?) */
+
 #endif /* __dconf_engine_h__ */
diff --git a/gdbus/Makefile.am b/gdbus/Makefile.am
new file mode 100644
index 0000000..3302c19
--- /dev/null
+++ b/gdbus/Makefile.am
@@ -0,0 +1,4 @@
+noinst_LIBRARIES = libdconf-gdbus.a
+INCLUDES = $(glib_CFLAGS) -I../engine -I../common
+libdconf_gdbus_a_CFLAGS = -Wall -fPIC -DPIC
+libdconf_gdbus_a_SOURCES = dconf-gdbus-thread.c
diff --git a/gdbus/dconf-gdbus-filter.c b/gdbus/dconf-gdbus-filter.c
new file mode 100644
index 0000000..a5d4400
--- /dev/null
+++ b/gdbus/dconf-gdbus-filter.c
@@ -0,0 +1,279 @@
+#include "dconf-engine.h"
+
+
+
+
+typedef struct
+{
+  gpointer data; /* either GDBusConnection or GError */
+  guint    is_error;
+  guint    waiting_for_serial;
+  GQueue   queue;
+} ConnectionState;
+
+typedef struct
+{
+  guint32                serial;
+  DConfEngineCallHandle *handle;
+} DConfGDBusCall;
+
+static ConnectionState connections[3];
+static GMutex dconf_gdbus_lock;
+
+static GBusType
+connection_state_get_bus_type (ConnectionState *state)
+{
+  return state - connections;
+}
+
+static gboolean
+connection_state_ensure_success (ConnectionState  *state,
+                                 GError          **error)
+{
+  if (state->is_error)
+    {
+      if (error)
+        *error = g_error_copy (state->data);
+    }
+
+  return TRUE;
+}
+
+static GDBusConnection *
+connection_state_get_connection (ConnectionState *state)
+{
+  g_assert (!state->is_error);
+
+  return state->data;
+}
+
+/* This function can be slow (as compared to the one below). */
+static void
+dconf_gdbus_handle_reply (ConnectionState *state,
+                          GDBusMessage    *message)
+{
+  DConfEngineCallHandle *handle;
+  GError *error = NULL;
+  GVariant *body;
+
+  g_mutex_lock (&dconf_gdbus_lock);
+  {
+    DConfGDBusCall *call;
+
+    call = g_queue_pop_head (&state->queue);
+    g_assert_cmpuint (g_dbus_message_get_serial (message), ==, call->serial);
+    handle = call->handle;
+
+    g_slice_free (DConfGDBusCall, call);
+
+    call = g_queue_peek_head (&state->queue);
+    if (call)
+      g_atomic_int_set (&state->waiting_for_serial, call->serial);
+    else
+      g_atomic_int_set (&state->waiting_for_serial, -1);
+  }
+  g_mutex_unlock (&dconf_gdbus_lock);
+
+  body = g_dbus_message_get_body (message);
+
+  if (g_dbus_message_get_message_type (message) == G_DBUS_MESSAGE_TYPE_ERROR)
+    {
+      const GVariantType *first_child_type;
+      const gchar *error_message = NULL;
+
+      first_child_type = g_variant_type_first (g_variant_get_type (body));
+
+      if (g_variant_type_equal (first_child_type, G_VARIANT_TYPE_STRING))
+        g_variant_get_child (body, 0, "&s", &error_message);
+
+      error = g_dbus_error_new_for_dbus_error (g_dbus_message_get_error_name (message), error_message);
+      body = NULL;
+    }
+
+  dconf_engine_call_handle_reply (handle, body, error);
+
+  if (error)
+    g_error_free (error);
+}
+
+/* We optimise for this function being super-efficient since it gets run
+ * on every single D-Bus message in or out.
+ *
+ * We want to bail out as quickly as possible in the case that this
+ * message does not interest us.  That means we should not hold locks or
+ * anything like that.
+ *
+ * In the case that this message _does_ interest us (which should be
+ * rare) we can take a lot more time.
+ */
+static GDBusMessage *
+dconf_gdbus_filter_function (GDBusConnection *connection,
+                             GDBusMessage    *message,
+                             gboolean         incoming,
+                             gpointer         user_data)
+{
+  ConnectionState *state = user_data;
+
+  if (incoming)
+    {
+      switch (g_dbus_message_get_message_type (message))
+        {
+        case G_DBUS_MESSAGE_TYPE_SIGNAL:
+          {
+            const gchar *interface;
+
+            interface = g_dbus_message_get_interface (message);
+            if (interface && g_str_equal (interface, "ca.desrt.dconf.Writer"))
+              dconf_engine_handle_dbus_signal (connection_state_get_bus_type (state),
+                                               g_dbus_message_get_sender (message),
+                                               g_dbus_message_get_path (message),
+                                               g_dbus_message_get_member (message),
+                                               g_dbus_message_get_body (message));
+
+            /* Others could theoretically be interested in this... */
+          }
+          break;
+
+        case G_DBUS_MESSAGE_TYPE_METHOD_RETURN:
+        case G_DBUS_MESSAGE_TYPE_ERROR:
+          if G_UNLIKELY (g_dbus_message_get_reply_serial (message) == g_atomic_int_get (&state->waiting_for_serial))
+            {
+              /* This is definitely for us. */
+              dconf_gdbus_handle_reply (state, message);
+
+              /* Nobody else should be interested in it. */
+              g_clear_object (&message);
+            }
+          break;
+
+        default:
+          break;
+        }
+    }
+
+  return message;
+}
+
+static ConnectionState *
+dconf_gdbus_get_connection_state (GBusType   bus_type,
+                                  GError   **error)
+{
+  ConnectionState *state;
+
+  g_assert (bus_type < G_N_ELEMENTS (connections));
+
+  state = &connections[bus_type];
+
+  if (g_once_init_enter (&state->data))
+    {
+      GDBusConnection *connection;
+      GError *error = NULL;
+      gpointer result;
+
+      /* This will only block the first time...
+       *
+       * Optimising this away is probably not worth the effort.
+       */
+      connection = g_bus_get_sync (bus_type, NULL, &error);
+
+      if (connection)
+        {
+          g_dbus_connection_add_filter (connection, dconf_gdbus_filter_function, state, NULL);
+          result = connection;
+          state->is_error = FALSE;
+        }
+      else
+        {
+          result = error;
+          state->is_error = TRUE;
+        }
+
+      g_once_init_leave (&state->data, result);
+    }
+
+  if (!connection_state_ensure_success (state, error))
+    return FALSE;
+
+  return state;
+}
+
+gboolean
+dconf_engine_dbus_call_async_func (GBusType                bus_type,
+                                   const gchar            *bus_name,
+                                   const gchar            *object_path,
+                                   const gchar            *interface_name,
+                                   const gchar            *method_name,
+                                   GVariant               *parameters,
+                                   DConfEngineCallHandle  *handle,
+                                   GError                **error)
+{
+  ConnectionState *state;
+  GDBusMessage *message;
+  DConfGDBusCall *call;
+  gboolean success;
+
+  state = dconf_gdbus_get_connection_state (bus_type, error);
+
+  if (state == NULL)
+    return FALSE;
+
+  message = g_dbus_message_new_method_call (bus_name, object_path, interface_name, method_name);
+  g_dbus_message_set_body (message, parameters);
+
+  g_mutex_lock (&dconf_gdbus_lock);
+
+  /* We need to set the serial in two places: state->waiting_for_serial
+   * and call->serial.
+   *
+   * g_dbus_connection_send_message() only has one out_serial parameter
+   * so we can only set one of them atomically.  We elect to set the
+   * waiting_for_serial because that is the one that is accessed from
+   * the filter function without holding the lock.
+   *
+   * The serial number in the call structure is only accessed after the
+   * lock is acquired which allows us to take our time setting it (for
+   * as long as we're still holding the lock).
+   *
+   * Also: the queue itself isn't accessed until after the lock is
+   * taken, so we can delay adding the call to the queue until we know
+   * that the sending of the message was successful.
+   */
+  success = g_dbus_connection_send_message (connection_state_get_connection (state), message,
+                                            G_DBUS_SEND_MESSAGE_FLAGS_NONE, &state->waiting_for_serial, error);
+
+  if (success)
+    {
+      call = g_slice_new (DConfGDBusCall);
+
+      call->handle = handle;
+      call->serial = state->waiting_for_serial;
+
+      g_queue_push_tail (&state->queue, call);
+    }
+
+  g_mutex_unlock (&dconf_gdbus_lock);
+
+  return success;
+}
+
+GVariant *
+dconf_engine_dbus_call_sync_func (GBusType             bus_type,
+                                  const gchar         *bus_name,
+                                  const gchar         *object_path,
+                                  const gchar         *interface_name,
+                                  const gchar         *method_name,
+                                  GVariant            *parameters,
+                                  const GVariantType  *reply_type,
+                                  GError             **error)
+{
+  ConnectionState *state;
+
+  state = dconf_gdbus_get_connection_state (bus_type, error);
+
+  if (state == NULL)
+    return NULL;
+
+  return g_dbus_connection_call_sync (connection_state_get_connection (state),
+                                      bus_name, object_path, interface_name, method_name, parameters, reply_type,
+                                      G_DBUS_CALL_FLAGS_NONE, -1, NULL, error);
+}
diff --git a/gdbus/dconf-gdbus-thread.c b/gdbus/dconf-gdbus-thread.c
new file mode 100644
index 0000000..c4a851d
--- /dev/null
+++ b/gdbus/dconf-gdbus-thread.c
@@ -0,0 +1,346 @@
+/*
+ * 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt desrt ca>
+ */
+
+#include "dconf-engine.h"
+
+/* We interact with GDBus using a worker thread just for dconf.
+ *
+ * We want to have a worker thread that's not the main thread for one
+ * main reason: we don't want to have all of our incoming signals and
+ * method call replies being delivered via the default main context
+ * (which may blocked or simply not running at all).
+ *
+ * The only question is if we should have our own thread or share the
+ * GDBus worker thread.  This file takes the approach that we should
+ * have our own thread.  See "dconf-gdbus-filter.c" for an approach that
+ * shares the worker thread with GDBus.
+ *
+ * We gain at least one advantage here that we cannot gain any other way
+ * (including sharing a worker thread with GDBus): fast startup.
+ *
+ * The first thing that happens when GSettings comes online is a D-Bus
+ * call to establish a watch.  We have to bring up the GDBusConnection.
+ * There are two ways to do that: sync and async.
+ *
+ * We can't do either of those in GDBus's worker thread (since it
+ * doesn't exist yet).  We can't do async in the main thread because the
+ * user may not be running the mainloop (as is the case for the
+ * commandline tool, for example).
+ *
+ * That leaves only one option: synchronous initialisation in the main
+ * thread.  That's what the "dconf-gdbus-filter" variant of this code
+ * does, and it's slower because of it.
+ *
+ * If we have our own worker thread then we can punt synchronous
+ * initialisation of the bus to it and return immediately.
+ *
+ * We also gain the advantage that the dconf worker thread and the GDBus
+ * worker thread can both be doing work at the same time.  This
+ * advantage is probably quite marginal (and is likely outweighed by the
+ * cost of all the punting around of messages between threads).
+ */
+
+typedef struct
+{
+  GBusType               bus_type;
+  const gchar           *bus_name;
+  const gchar           *object_path;
+  const gchar           *interface_name;
+  const gchar           *method_name;
+  GVariant              *parameters;
+  DConfEngineCallHandle *handle;
+} DConfGDBusCall;
+
+static gpointer
+dconf_gdbus_worker_thread (gpointer user_data)
+{
+  GMainContext *context = user_data;
+
+  g_main_context_push_thread_default (context);
+
+  for (;;)
+    g_main_context_iteration (context, TRUE);
+
+  /* srsly, gcc? */
+  return NULL;
+}
+
+static GMainContext *
+dconf_gdbus_get_worker_context (void)
+{
+  static GMainContext *worker_context;
+
+  if (g_once_init_enter (&worker_context))
+    {
+      GMainContext *context;
+
+      context = g_main_context_new ();
+      g_thread_new ("dconf worker", dconf_gdbus_worker_thread, context);
+      g_once_init_leave (&worker_context, context);
+    }
+
+  return worker_context;
+}
+
+static void
+dconf_gdbus_signal_handler (GDBusConnection *connection,
+                            const gchar     *sender_name,
+                            const gchar     *object_path,
+                            const gchar     *interface_name,
+                            const gchar     *signal_name,
+                            GVariant        *parameters,
+                            gpointer         user_data)
+{
+  GBusType bus_type = GPOINTER_TO_INT (user_data);
+
+  dconf_engine_handle_dbus_signal (bus_type, sender_name, object_path, signal_name, parameters);
+}
+
+/* The code to create and initialise the GDBusConnection for a
+ * particular bus type is more complicated than it should be.
+ *
+ * The complication comes from the fact that we must call
+ * g_dbus_connection_signal_subscribe() from the thread in which the
+ * signal handler will run (which in our case is the worker thread).
+ * g_main_context_push_thread_default() attempts to acquire the context,
+ * preventing us from temporarily pushing the worker's context just for
+ * the sake of setting up the subscription.
+ *
+ * We therefore always create the bus connection from the worker thread.
+ * For requests that are already in the worker thread this is a pretty
+ * simple affair.
+ *
+ * For requests in other threads (ie: synchronous calls) we have to poke
+ * the worker to instantiate the bus for us (if it doesn't already
+ * exist).  We do that by using g_main_context_invoke() to schedule a
+ * dummy request in the worker and then we wait on a GCond until we see
+ * that the bus has been created.
+ *
+ * An attempt to get a particular bus can go one of two ways:
+ *
+ *   - success: we end up with a GDBusConnection.
+ *
+ *   - failure: we end up with a GError.
+ *
+ * One way or another we put the result in dconf_gdbus_get_bus_data[] so
+ * that we only have one pointer value to check.  We know what type of
+ * result it is by dconf_gdbus_get_bus_is_error[].
+ */
+
+static GMutex   dconf_gdbus_get_bus_lock;
+static GCond    dconf_gdbus_get_bus_cond;
+static gpointer dconf_gdbus_get_bus_data[5];
+static gboolean dconf_gdbus_get_bus_is_error[5];
+
+static GDBusConnection *
+dconf_gdbus_get_bus_common (GBusType       bus_type,
+                            const GError **error)
+{
+  if (dconf_gdbus_get_bus_is_error[bus_type])
+    {
+      *error = dconf_gdbus_get_bus_data[bus_type];
+
+      return NULL;
+    }
+
+  return dconf_gdbus_get_bus_data[bus_type];
+}
+
+static GDBusConnection *
+dconf_gdbus_get_bus_in_worker (GBusType       bus_type,
+                               const GError **error)
+{
+  g_assert_cmpint (bus_type, <, G_N_ELEMENTS (dconf_gdbus_get_bus_data));
+
+  /* We're in the worker thread and only the worker thread can ever set
+   * this variable so there is no need to take a lock.
+   */
+  if (dconf_gdbus_get_bus_data[bus_type] == NULL)
+    {
+      GDBusConnection *connection;
+      GError *error = NULL;
+      gpointer result;
+
+      connection = g_bus_get_sync (bus_type, NULL, &error);
+
+      if (connection)
+        {
+          g_dbus_connection_signal_subscribe (connection, NULL, "ca.desrt.dconf.Writer",
+                                              NULL, NULL, NULL, G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
+                                              dconf_gdbus_signal_handler, GINT_TO_POINTER (bus_type), NULL);
+          dconf_gdbus_get_bus_is_error[bus_type] = FALSE;
+          result = connection;
+        }
+      else
+        {
+          dconf_gdbus_get_bus_is_error[bus_type] = TRUE;
+          result = error;
+        }
+
+      g_assert (result != NULL);
+
+      /* It's possible that another thread was waiting for us to do
+       * this on its behalf.  Wake it up.
+       *
+       * The other thread cannot actually wake up until we release the
+       * mutex below so we have a guarantee that this CPU will have
+       * flushed all outstanding writes.  The other CPU has to acquire
+       * the lock so it cannot have done any out-of-order reads either.
+       */
+      g_mutex_lock (&dconf_gdbus_get_bus_lock);
+      dconf_gdbus_get_bus_data[bus_type] = result;
+      g_cond_broadcast (&dconf_gdbus_get_bus_cond);
+      g_mutex_unlock (&dconf_gdbus_get_bus_lock);
+    }
+
+  return dconf_gdbus_get_bus_common (bus_type, error);
+}
+
+static void
+dconf_gdbus_method_call_done (GObject      *source,
+                              GAsyncResult *result,
+                              gpointer      user_data)
+{
+  GDBusConnection *connection = G_DBUS_CONNECTION (source);
+  DConfEngineCallHandle *handle = user_data;
+  GError *error = NULL;
+  GVariant *reply;
+
+  reply = g_dbus_connection_call_finish (connection, result, &error);
+  dconf_engine_call_handle_reply (handle, reply, error);
+  g_clear_pointer (&reply, g_variant_unref);
+  g_clear_error (&error);
+}
+
+static gboolean
+dconf_gdbus_method_call (gpointer user_data)
+{
+  DConfGDBusCall *call = user_data;
+  GDBusConnection *connection;
+  const GError *error = NULL;
+
+  connection = dconf_gdbus_get_bus_in_worker (call->bus_type, &error);
+
+  if (connection)
+    g_dbus_connection_call (connection, call->bus_name, call->object_path, call->interface_name,
+                            call->method_name, call->parameters, NULL, G_DBUS_CALL_FLAGS_NONE,
+                            -1, NULL, dconf_gdbus_method_call_done, call->handle);
+
+  else
+    dconf_engine_call_handle_reply (call->handle, NULL, error);
+
+  g_variant_unref (call->parameters);
+  g_slice_free (DConfGDBusCall, call);
+
+  return FALSE;
+}
+
+gboolean
+dconf_engine_dbus_call_async_func (GBusType                bus_type,
+                                   const gchar            *bus_name,
+                                   const gchar            *object_path,
+                                   const gchar            *interface_name,
+                                   const gchar            *method_name,
+                                   GVariant               *parameters,
+                                   DConfEngineCallHandle  *handle,
+                                   GError                **error)
+{
+  DConfGDBusCall *call;
+
+  call = g_slice_new (DConfGDBusCall);
+  call->bus_type = bus_type;
+  call->bus_name = bus_name;
+  call->object_path = object_path;
+  call->interface_name = interface_name;
+  call->method_name = method_name;
+  call->parameters = g_variant_ref (parameters);
+  call->handle = handle;
+
+  g_main_context_invoke (dconf_gdbus_get_worker_context (), dconf_gdbus_method_call, call);
+
+  return TRUE;
+}
+
+/* Dummy function to force the bus into existence in the worker. */
+static gboolean
+dconf_gdbus_summon_bus (gpointer user_data)
+{
+  GBusType bus_type = GPOINTER_TO_INT (user_data);
+
+  dconf_gdbus_get_bus_in_worker (bus_type, NULL);
+
+  return G_SOURCE_REMOVE;
+}
+
+static GDBusConnection *
+dconf_gdbus_get_bus_for_sync (GBusType       bus_type,
+                              const GError **error)
+{
+  g_assert_cmpint (bus_type, <, G_N_ELEMENTS (dconf_gdbus_get_bus_data));
+
+  /* I'm not 100% sure we have to lock as much as we do here, but let's
+   * play it safe.
+   *
+   * This codepath is only hit on synchronous calls anyway.  You're
+   * probably not doing those if you care a lot about performance.
+   */
+  g_mutex_lock (&dconf_gdbus_get_bus_lock);
+  if (dconf_gdbus_get_bus_data[bus_type] == NULL)
+    {
+      g_main_context_invoke (dconf_gdbus_get_worker_context (),
+                             dconf_gdbus_summon_bus,
+                             GINT_TO_POINTER (bus_type));
+
+      while (dconf_gdbus_get_bus_data[bus_type] == NULL)
+        g_cond_wait (&dconf_gdbus_get_bus_cond, &dconf_gdbus_get_bus_lock);
+    }
+  g_mutex_unlock (&dconf_gdbus_get_bus_lock);
+
+  return dconf_gdbus_get_bus_common (bus_type, error);
+}
+
+GVariant *
+dconf_engine_dbus_call_sync_func (GBusType             bus_type,
+                                  const gchar         *bus_name,
+                                  const gchar         *object_path,
+                                  const gchar         *interface_name,
+                                  const gchar         *method_name,
+                                  GVariant            *parameters,
+                                  const GVariantType  *reply_type,
+                                  GError             **error)
+{
+  const GError *inner_error = NULL;
+  GDBusConnection *connection;
+
+  connection = dconf_gdbus_get_bus_for_sync (bus_type, &inner_error);
+
+  if (connection == NULL)
+    {
+      if (error)
+        *error = g_error_copy (inner_error);
+
+      return NULL;
+    }
+
+  return g_dbus_connection_call_sync (connection, bus_name, object_path, interface_name, method_name,
+                                      parameters, reply_type, G_DBUS_CALL_FLAGS_NONE, -1, NULL, error);
+}
diff --git a/gsettings/Makefile.am b/gsettings/Makefile.am
index e35926e..7baf7c2 100644
--- a/gsettings/Makefile.am
+++ b/gsettings/Makefile.am
@@ -3,10 +3,9 @@ INCLUDES = -I$(top_srcdir)/common -I$(top_srcdir)/gvdb -I$(top_srcdir)/engine $(
 
 giomodules_PROGRAMS = libdconfsettings.so
 
-libdconfsettings_so_LDADD = $(gio_LIBS) ../common/libdconf-common-shared.a
+libdconfsettings_so_LDADD = $(gio_LIBS) ../engine/libdconf-engine.a ../gdbus/libdconf-gdbus.a
 libdconfsettings_so_LDFLAGS = -shared
 libdconfsettings_so_SOURCES = \
-	../engine/dconf-engine.c	\
 	../gvdb/gvdb-reader.c		\
 	dconfcontext.h			\
 	dconfcontext.c			\
diff --git a/gsettings/dconfsettingsbackend.c b/gsettings/dconfsettingsbackend.c
index cdf0550..85aef4e 100644
--- a/gsettings/dconfsettingsbackend.c
+++ b/gsettings/dconfsettingsbackend.c
@@ -29,411 +29,15 @@
 #include "dconfcontext.h"
 
 typedef GSettingsBackendClass DConfSettingsBackendClass;
-typedef struct _Outstanding Outstanding;
 
 typedef struct
 {
   GSettingsBackend backend;
-
-  GDBusConnection *session_bus;
-  GDBusConnection *system_bus;
-  guint session_subscription;
-  guint system_subscription;
-
-  Outstanding *outstanding;
-  gchar *anti_expose_tag;
-
-  DConfEngine *engine;
-  GMutex lock;
-  GCond sync_cond;
-  gint sync_waiters;
+  DConfEngine     *engine;
 } DConfSettingsBackend;
 
 static GType dconf_settings_backend_get_type (void);
-G_DEFINE_TYPE (DConfSettingsBackend,
-               dconf_settings_backend,
-               G_TYPE_SETTINGS_BACKEND)
-
-static void
-dconf_settings_backend_signal (GDBusConnection *connection,
-                               const gchar     *sender_name,
-                               const gchar     *object_path,
-                               const gchar     *interface_name,
-                               const gchar     *signal_name,
-                               GVariant        *parameters,
-                               gpointer         user_data)
-{
-  DConfSettingsBackend *dcsb = user_data;
-  const gchar *anti_expose;
-  const gchar **rels;
-  const gchar *path;
-  gchar bus_type;
-
-  if (connection == dcsb->session_bus)
-    {
-      anti_expose = dcsb->anti_expose_tag;
-      bus_type = 'e';
-    }
-
-  else if (connection == dcsb->system_bus)
-    {
-      anti_expose = NULL;
-      bus_type = 'y';
-    }
-
-  else
-    g_assert_not_reached ();
-
-  if (dconf_engine_decode_notify (dcsb->engine, anti_expose,
-                                  &path, &rels, bus_type,
-                                  sender_name, interface_name,
-                                  signal_name, parameters))
-    {
-      GSettingsBackend *backend = G_SETTINGS_BACKEND (dcsb);
-
-      if (!g_str_has_suffix (path, "/"))
-        g_settings_backend_changed (backend, path, NULL);
-
-      else if (rels[0] == NULL)
-        g_settings_backend_path_changed (backend, path, NULL);
-
-      else
-        g_settings_backend_keys_changed (backend, path, rels, NULL);
-
-      g_free (rels);
-    }
-
-  if (dconf_engine_decode_writability_notify (&path, interface_name,
-                                              signal_name, parameters))
-    {
-      GSettingsBackend *backend = G_SETTINGS_BACKEND (dcsb);
-
-      if (g_str_has_suffix (path, "/"))
-        {
-          g_settings_backend_path_writable_changed (backend, path);
-          g_settings_backend_path_changed (backend, path, NULL);
-        }
-
-      else
-        {
-          g_settings_backend_writable_changed (backend, path);
-          g_settings_backend_changed (backend, path, NULL);
-        }
-    }
-}
-
-static void
-dconf_settings_backend_send (DConfSettingsBackend *dcsb,
-                             DConfEngineMessage   *dcem,
-                             GAsyncReadyCallback   callback,
-                             gpointer              user_data)
-{
-  GDBusConnection *connection;
-  gint i;
-
-  for (i = 0; i < dcem->n_messages; i++)
-    {
-      GError *error = NULL;
-
-      switch (dcem->bus_types[i])
-        {
-        case 'e':
-          if (dcsb->session_bus == NULL && callback)
-            {
-              dcsb->session_bus =
-                g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
-
-              if (dcsb->session_bus != NULL)
-                dcsb->session_subscription =
-                  g_dbus_connection_signal_subscribe (dcsb->session_bus, NULL,
-                                                      "ca.desrt.dconf.Writer",
-                                                      NULL, NULL, NULL,
-                                                      G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
-                                                      dconf_settings_backend_signal,
-                                                      dcsb, NULL);
-            }
-          connection = dcsb->session_bus;
-          break;
-
-        case 'y':
-          if (dcsb->system_bus == NULL && callback)
-            {
-              dcsb->system_bus =
-                g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
-
-              if (dcsb->system_bus != NULL)
-                dcsb->system_subscription =
-                  g_dbus_connection_signal_subscribe (dcsb->system_bus, NULL,
-                                                      "ca.desrt.dconf.Writer",
-                                                      NULL, NULL, NULL,
-                                                      G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
-                                                      dconf_settings_backend_signal,
-                                                      dcsb, NULL);
-            }
-          connection = dcsb->system_bus;
-          break;
-
-        default:
-          g_assert_not_reached ();
-        }
-
-      if (connection == NULL && callback != NULL)
-        {
-          g_assert (error != NULL);
-
-          g_warning ("%s", error->message);
-          g_error_free (error);
-
-          callback (NULL, NULL, user_data);
-        }
-
-      if (connection != NULL)
-        g_dbus_connection_call (connection,
-                                dcem->bus_name,
-                                dcem->object_path,
-                                dcem->interface_name,
-                                dcem->method_name,
-                                dcem->parameters[i],
-                                dcem->reply_type,
-                                0, 120000, NULL, callback, user_data);
-    }
-}
-
-static GVariant *
-dconf_settings_backend_send_finish (GObject      *source,
-                                    GAsyncResult *result)
-{
-  GError *error = NULL;
-  GVariant *reply;
-
-  if (source == NULL)
-    return NULL;
-
-  reply = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source),
-                                         result, &error);
-
-  if (reply == NULL)
-    {
-      /* This should only be hit in the case that something is seriously
-       * wrong with the installation (ie: the service can't be started,
-       * etc).  Bug #641768 requests some notification of these kinds of
-       * situations in the context of the gsettings(1) commandline tool,
-       * so a g_warning() is appropriate here.
-       */
-
-      g_warning ("%s", error->message);
-      g_error_free (error);
-    }
-
-  return reply;
-}
-
-struct _Outstanding
-{
-  Outstanding *next;
-
-  DConfSettingsBackend *dcsb;
-  DConfEngineMessage    dcem;
-
-  gchar    *set_key;
-  GVariant *set_value;
-  GTree    *tree;
-};
-
-static void
-dconf_settings_backend_outstanding_returned (GObject      *source,
-                                             GAsyncResult *result,
-                                             gpointer      user_data)
-{
-  Outstanding *outstanding = user_data;
-  DConfSettingsBackend *dcsb;
-  GVariant *reply;
-
-  dcsb = outstanding->dcsb;
-
-  /* One way or another we no longer need this hooked into the list.
-   */
-  g_mutex_lock (&dcsb->lock);
-  {
-    Outstanding **tmp;
-
-    for (tmp = &dcsb->outstanding; tmp; tmp = &(*tmp)->next)
-      if (*tmp == outstanding)
-        {
-          *tmp = outstanding->next;
-          break;
-        }
-
-    if (dcsb->outstanding == NULL && dcsb->sync_waiters)
-      g_cond_broadcast (&dcsb->sync_cond);
-  }
-  g_mutex_unlock (&dcsb->lock);
-
-  reply = dconf_settings_backend_send_finish (source, result);
-
-  if (reply)
-    {
-      /* Success.
-       *
-       * We want to ensure that we don't emit an extra change
-       * notification signal when we see the signal that the service is
-       * about to send, so store the tag so we know to ignore it when
-       * the signal comes.
-       *
-       * No thread safety issue here since this variable is only
-       * accessed from the worker thread.
-       */
-      g_free (dcsb->anti_expose_tag);
-      g_variant_get_child (reply, 0, "s", &dcsb->anti_expose_tag);
-      g_variant_unref (reply);
-    }
-  else
-    {
-      /* An error of some kind.
-       *
-       * We already removed the outstanding entry from the list, so the
-       * unmodified database is now visible to the client.  Change
-       * notify so that they see it.
-       */
-      if (outstanding->set_key)
-        g_settings_backend_changed (G_SETTINGS_BACKEND (dcsb),
-                                    outstanding->set_key, NULL);
-
-      else
-        g_settings_backend_changed_tree (G_SETTINGS_BACKEND (dcsb),
-                                         outstanding->tree, NULL);
-    }
-
-  dconf_engine_message_destroy (&outstanding->dcem);
-  g_object_unref (outstanding->dcsb);
-  g_free (outstanding->set_key);
-
-  if (outstanding->set_value)
-    g_variant_unref (outstanding->set_value);
-
-  if (outstanding->tree)
-    g_tree_unref (outstanding->tree);
-
-  g_slice_free (Outstanding, outstanding);
-}
-
-static gboolean
-dconf_settings_backend_send_outstanding (gpointer data)
-{
-  Outstanding *outstanding = data;
-
-  dconf_settings_backend_send (outstanding->dcsb,
-                               &outstanding->dcem,
-                               dconf_settings_backend_outstanding_returned,
-                               outstanding);
-
-  return FALSE;
-}
-
-static void
-dconf_settings_backend_queue (DConfSettingsBackend *dcsb,
-                              DConfEngineMessage   *dcem,
-                              const gchar          *set_key,
-                              GVariant             *set_value,
-                              GTree                *tree)
-{
-  Outstanding *outstanding;
-
-  outstanding = g_slice_new (Outstanding);
-  outstanding->dcsb = g_object_ref (dcsb);
-  outstanding->dcem = *dcem;
-
-  outstanding->set_key = g_strdup (set_key);
-  outstanding->set_value = set_value ? g_variant_ref_sink (set_value) : NULL;
-  outstanding->tree = tree ? g_tree_ref (tree) : NULL;
-
-  g_mutex_lock (&dcsb->lock);
-  outstanding->next = dcsb->outstanding;
-  dcsb->outstanding = outstanding;
-  g_mutex_unlock (&dcsb->lock);
-
-  g_main_context_invoke (dconf_context_get (),
-                         dconf_settings_backend_send_outstanding,
-                         outstanding);
-}
-
-static gboolean
-dconf_settings_backend_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_settings_backend_scan_outstanding (DConfSettingsBackend  *backend,
-                                         const gchar           *key,
-                                         GVariant             **value)
-{
-  gboolean found = FALSE;
-  Outstanding *node;
-  gsize length;
-
-  length = strlen (key);
-
-  if G_LIKELY (backend->outstanding == NULL)
-    return FALSE;
-
-  g_mutex_lock (&backend->lock);
-
-  for (node = backend->outstanding; node; node = node->next)
-    {
-      if (node->set_key)
-        {
-          if (strcmp (key, node->set_key) == 0)
-            {
-              if (node->set_value != NULL)
-                *value = g_variant_ref (node->set_value);
-              else
-                *value = NULL;
-
-              found = TRUE;
-              break;
-            }
-        }
-
-      else
-        {
-          gpointer result;
-
-          if (dconf_settings_backend_scan_outstanding_tree (node->tree, key,
-                                                            length, &result))
-            {
-              if (result)
-                *value = g_variant_ref (result);
-              else
-                *value = NULL;
-
-              found = TRUE;
-              break;
-            }
-        }
-    }
-
-  g_mutex_unlock (&backend->lock);
-
-  return found;
-}
+G_DEFINE_TYPE (DConfSettingsBackend, dconf_settings_backend, G_TYPE_SETTINGS_BACKEND)
 
 static GVariant *
 dconf_settings_backend_read (GSettingsBackend   *backend,
@@ -443,17 +47,9 @@ dconf_settings_backend_read (GSettingsBackend   *backend,
 {
   DConfSettingsBackend *dcsb = (DConfSettingsBackend *) backend;
 
-  if (!default_value)
-    {
-      GVariant *value;
+  /* XXX default value */
 
-      if (dconf_settings_backend_scan_outstanding (dcsb, key, &value))
-        return value;
-
-      return dconf_engine_read (dcsb->engine, key);
-    }
-  else
-    return dconf_engine_read_default (dcsb->engine, key);
+  return dconf_engine_read (dcsb->engine, NULL, key);
 }
 
 static gboolean
@@ -463,15 +59,26 @@ dconf_settings_backend_write (GSettingsBackend *backend,
                               gpointer          origin_tag)
 {
   DConfSettingsBackend *dcsb = (DConfSettingsBackend *) backend;
-  DConfEngineMessage dcem;
+  DConfChangeset *change;
+  gboolean success;
 
-  if (!dconf_engine_write (dcsb->engine, key, value, &dcem, NULL))
-    return FALSE;
+  change = dconf_changeset_new ();
+  dconf_changeset_set (change, key, value);
+
+  success = dconf_engine_change_fast (dcsb->engine, change, NULL);
+  dconf_changeset_unref (change);
+
+  return success;
+}
 
-  dconf_settings_backend_queue (dcsb, &dcem, key, value, NULL);
-  g_settings_backend_changed (backend, key, origin_tag);
+static gboolean
+dconf_settings_backend_add_to_changeset (gpointer key,
+                                         gpointer value,
+                                         gpointer data)
+{
+  dconf_changeset_set (data, key, value);
 
-  return TRUE;
+  return FALSE;
 }
 
 static gboolean
@@ -480,24 +87,14 @@ dconf_settings_backend_write_tree (GSettingsBackend *backend,
                                    gpointer          origin_tag)
 {
   DConfSettingsBackend *dcsb = (DConfSettingsBackend *) backend;
-  DConfEngineMessage dcem;
-  const gchar **keys;
-  GVariant **values;
+  DConfChangeset *change;
   gboolean success;
-  gchar *prefix;
 
-  g_settings_backend_flatten_tree (tree, &prefix, &keys, &values);
+  change= dconf_changeset_new ();
+  g_tree_foreach (tree, dconf_settings_backend_add_to_changeset, change);
 
-  if ((success = dconf_engine_write_many (dcsb->engine, prefix,
-                                          keys, values, &dcem, NULL)))
-    {
-      dconf_settings_backend_queue (dcsb, &dcem, NULL, NULL, tree);
-      g_settings_backend_keys_changed (backend, prefix, keys, origin_tag);
-    }
-
-  g_free (prefix);
-  g_free (values);
-  g_free (keys);
+  success = dconf_engine_change_fast (dcsb->engine, change, NULL);
+  dconf_changeset_unref (change);
 
   return success;
 }
@@ -519,111 +116,13 @@ dconf_settings_backend_get_writable (GSettingsBackend *backend,
   return dconf_engine_is_writable (dcsb->engine, name);
 }
 
-typedef struct
-{
-  DConfSettingsBackend *dcsb;
-  guint64 state;
-  gchar *name;
-  gint outstanding;
-} OutstandingWatch;
-
-static OutstandingWatch *
-outstanding_watch_new (DConfSettingsBackend *dcsb,
-                       const gchar          *name)
-{
-  OutstandingWatch *watch;
-
-  watch = g_slice_new (OutstandingWatch);
-  watch->dcsb = g_object_ref (dcsb);
-  watch->state = dconf_engine_get_state (dcsb->engine);
-  watch->outstanding = 0;
-  watch->name = g_strdup (name);
-
-  return watch;
-}
-
-static void
-outstanding_watch_free (OutstandingWatch *watch)
-{
-  if (--watch->outstanding == 0)
-    {
-      g_object_unref (watch->dcsb);
-      g_free (watch->name);
-
-      g_slice_free (OutstandingWatch, watch);
-    }
-}
-
-static void
-add_match_done (GObject      *source,
-                GAsyncResult *result,
-                gpointer      user_data)
-{
-  OutstandingWatch *watch = user_data;
-  GError *error = NULL;
-  GVariant *reply;
-
-  /* couldn't connect to DBus */
-  if (source == NULL)
-    {
-      outstanding_watch_free (watch);
-      return;
-    }
-
-  reply = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source),
-                                         result, &error);
-
-  if (reply == NULL)
-    {
-      g_critical ("DBus AddMatch for dconf path '%s': %s",
-                  watch->name, error->message);
-      outstanding_watch_free (watch);
-      g_error_free (error);
-
-      return;
-    }
-
-  else
-    g_variant_unref (reply); /* it is just an empty tuple */
-
-  /* In the normal case we can just free everything and be done.
-   *
-   * There is a fleeting chance, however, that the database has changed
-   * in the meantime.  In that case we can only assume that the subject
-   * of this watch changed in that time period and emit a signal to that
-   * effect.
-   */
-  if (dconf_engine_get_state (watch->dcsb->engine) != watch->state)
-    g_settings_backend_path_changed (G_SETTINGS_BACKEND (watch->dcsb),
-                                     watch->name, NULL);
-
-  outstanding_watch_free (watch);
-}
-
-static gboolean
-dconf_settings_backend_subscribe_context_func (gpointer data)
-{
-  OutstandingWatch *watch = data;
-  DConfEngineMessage dcem;
-
-  dconf_engine_watch (watch->dcsb->engine, watch->name, &dcem);
-  watch->outstanding = dcem.n_messages;
-
-  dconf_settings_backend_send (watch->dcsb, &dcem, add_match_done, watch);
-  dconf_engine_message_destroy (&dcem);
-
-  return FALSE;
-}
-
 static void
 dconf_settings_backend_subscribe (GSettingsBackend *backend,
                                   const gchar      *name)
 {
   DConfSettingsBackend *dcsb = (DConfSettingsBackend *) backend;
 
-  g_main_context_invoke (dconf_context_get (),
-                         dconf_settings_backend_subscribe_context_func,
-                         outstanding_watch_new (dcsb, name));
+  dconf_engine_watch_fast (dcsb->engine, name);
 }
 
 static void
@@ -631,42 +130,40 @@ dconf_settings_backend_unsubscribe (GSettingsBackend *backend,
                                     const gchar      *name)
 {
   DConfSettingsBackend *dcsb = (DConfSettingsBackend *) backend;
-  DConfEngineMessage dcem;
 
-  dconf_engine_unwatch (dcsb->engine, name, &dcem);
-  dconf_settings_backend_send (dcsb, &dcem, NULL, NULL);
-  dconf_engine_message_destroy (&dcem);
+  dconf_engine_unwatch_fast (dcsb->engine, name);
 }
 
 static void
 dconf_settings_backend_sync (GSettingsBackend *backend)
 {
-  DConfSettingsBackend *dcsb = (DConfSettingsBackend *) backend;
-
-  if (!dcsb->outstanding)
-    return;
-
-  g_mutex_lock (&dcsb->lock);
-
-  dcsb->sync_waiters++;
-  while (dcsb->outstanding)
-    g_cond_wait (&dcsb->sync_cond, &dcsb->lock);
-  dcsb->sync_waiters--;
-
-  g_mutex_unlock (&dcsb->lock);
+  /* XXX implement sync */
 }
 
 static void
 dconf_settings_backend_init (DConfSettingsBackend *dcsb)
 {
-  dcsb->engine = dconf_engine_new (NULL);
-  g_mutex_init (&dcsb->lock);
-  g_cond_init (&dcsb->sync_cond);
+  dcsb->engine = dconf_engine_new (dcsb);
+}
+
+static void
+dconf_settings_backend_finalize (GObject *object)
+{
+  DConfSettingsBackend *dcsb = (DConfSettingsBackend *) object;
+
+  dconf_engine_free (dcsb->engine);
+
+  G_OBJECT_CLASS (dconf_settings_backend_parent_class)
+    ->finalize (object);
 }
 
 static void
 dconf_settings_backend_class_init (GSettingsBackendClass *class)
 {
+  GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+  object_class->finalize = dconf_settings_backend_finalize;
+
   class->read = dconf_settings_backend_read;
   class->write = dconf_settings_backend_write;
   class->write_tree = dconf_settings_backend_write_tree;
@@ -697,3 +194,12 @@ g_io_module_query (void)
 {
   return g_strsplit (G_SETTINGS_BACKEND_EXTENSION_POINT_NAME, "!", 0);
 }
+
+void
+dconf_engine_change_notify (DConfEngine         *engine,
+                            const gchar         *prefix,
+                            const gchar * const *changes,
+                            const gchar         *tag,
+                            gpointer             user_data)
+{
+}



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]