[dconf] service: rewrite



commit 324df561617b777848b149416f0f6c0c3801b2d8
Author: Ryan Lortie <desrt desrt ca>
Date:   Tue Oct 23 10:11:08 2012 +0200

    service: rewrite
    
    Rewrite the dconf-service using gdbus-codegen and generally cleaning
    things up a lot.  The DConfWriter class can now be reasonably subclassed
    to create more complex types of dconf databases (such as ones that
    are stored in the local runtime dir and synced up with an NFS home
    directory).
    
    Keep a cache of the keys in the database (instead of re-reading it every
    time we try to make a change).
    
    Drop support for the old D-Bus interface (now that we are two stable
    releases since it was used).
    
    Modify the commandline tool for 'dconf blame' to call the new interface.

 bin/dconf.vala                         |    2 +-
 configure.ac                           |    2 +-
 service/.gitignore                     |    2 +
 service/Makefile.am                    |   18 +-
 service/ca.desrt.dconf.xml             |   23 ++
 service/dconf-blame.c                  |  189 ++++++++++
 service/dconf-blame.h                  |   40 ++
 service/dconf-interfaces.c             |   94 -----
 service/dconf-rebuilder.c              |  168 ---------
 service/dconf-rebuilder.h              |   34 --
 service/dconf-service.c                |  237 +++++++++++++
 service/dconf-service.h                |   36 ++
 service/dconf-state.c                  |   82 -----
 service/dconf-state.h                  |   23 --
 service/dconf-writer.c                 |  486 +++++++++++++++++++++----
 service/dconf-writer.h                 |   31 +--
 service/{dconf-interfaces.h => main.c} |   17 +-
 service/service.c                      |  608 --------------------------------
 18 files changed, 971 insertions(+), 1121 deletions(-)
---
diff --git a/bin/dconf.vala b/bin/dconf.vala
index 646b275..f07bce3 100644
--- a/bin/dconf.vala
+++ b/bin/dconf.vala
@@ -228,7 +228,7 @@ void dconf_watch (string?[] args) throws Error {
 
 void dconf_blame (string?[] args) throws Error {
 	var connection = Bus.get_sync (BusType.SESSION, null);
-	var reply = connection.call_sync ("ca.desrt.dconf", "/ca/desrt/dconf/Writer", "ca.desrt.dconf.WriterInfo", "Blame",
+	var reply = connection.call_sync ("ca.desrt.dconf", "/ca/desrt/dconf", "ca.desrt.dconf.ServiceInfo", "Blame",
 	                                  null, new VariantType ("(s)"), DBusCallFlags.NONE, -1, null);
 	print ("%s", reply.get_child_value (0).get_string (null));
 }
diff --git a/configure.ac b/configure.ac
index 1932485..9507aac 100644
--- a/configure.ac
+++ b/configure.ac
@@ -41,7 +41,7 @@ GTK_DOC_CHECK([1.15])
 
 # Dependencies
 PKG_CHECK_MODULES(glib, glib-2.0 >= 2.33.3)
-PKG_CHECK_MODULES(gio, gio-2.0)
+PKG_CHECK_MODULES(gio, gio-unix-2.0)
 PKG_CHECK_MODULES(dbus, dbus-1)
 
 AC_ARG_ENABLE(editor,
diff --git a/service/.gitignore b/service/.gitignore
index aefa930..f511a9a 100644
--- a/service/.gitignore
+++ b/service/.gitignore
@@ -1,2 +1,4 @@
 dconf-service
 ca.desrt.dconf.service
+dconf-generated.h
+dconf-generated.c
diff --git a/service/Makefile.am b/service/Makefile.am
index fe3afaa..c2dd6f7 100644
--- a/service/Makefile.am
+++ b/service/Makefile.am
@@ -12,18 +12,22 @@ dconf_service_LDADD = \
 	$(gio_LIBS)
 
 dconf_service_SOURCES = \
-	dconf-interfaces.h		\
-	dconf-interfaces.c		\
-	dconf-rebuilder.h		\
-	dconf-rebuilder.c		\
+	$(BUILT_SOURCES)		\
+	dconf-blame.c			\
+	dconf-blame.h			\
+	dconf-service.c			\
+	dconf-service.h			\
 	dconf-writer.h			\
 	dconf-writer.c			\
-	dconf-state.h			\
-	dconf-state.c			\
-	service.c
+	main.c
+
+dconf-generated.h dconf-generated.c: ca.desrt.dconf.xml Makefile
+	gdbus-codegen --generate-c-code dconf-generated --c-namespace DConfDBus --interface-prefix=ca.desrt.dconf. $(srcdir)/ca.desrt.dconf.xml
 
 DISTCLEANFILES = ca.desrt.dconf.service
 
+BUILT_SOURCES = dconf-generated.c dconf-generated.h
+
 ca.desrt.dconf.service: Makefile
 	$(AM_V_GEN) (echo '[D-BUS Service]'; \
 	             echo 'Name=ca.desrt.dconf'; \
diff --git a/service/ca.desrt.dconf.xml b/service/ca.desrt.dconf.xml
new file mode 100644
index 0000000..3273d5d
--- /dev/null
+++ b/service/ca.desrt.dconf.xml
@@ -0,0 +1,23 @@
+<node>
+  <interface name='ca.desrt.dconf.Writer'>
+    <method name='Init'/>
+    <method name='Change'>
+      <arg name='blob' direction='in' type='ay'>
+        <annotation name='org.gtk.GDBus.C.ForceGVariant' value='1'/>
+      </arg>
+      <arg name='tag' direction='out' type='s'/>
+    </method>
+    <signal name='Notify'>
+      <annotation name='org.gtk.GDBus.C.Name' value='NotifySignal'/>
+      <arg name='prefix' direction='out' type='s'/>
+      <arg name='changes' direction='out' type='as'/>
+      <arg name='tag' direction='out' type='s'/>
+    </signal>
+  </interface>
+
+  <interface name='ca.desrt.dconf.ServiceInfo'>
+    <method name='Blame'>
+      <arg name='blame' direction='out' type='s'/>
+    </method>
+  </interface>
+</node>
diff --git a/service/dconf-blame.c b/service/dconf-blame.c
new file mode 100644
index 0000000..920ef08
--- /dev/null
+++ b/service/dconf-blame.c
@@ -0,0 +1,189 @@
+/*
+ * 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-blame.h"
+
+#include "dconf-generated.h"
+
+#include <string.h>
+#include <fcntl.h>
+
+typedef DConfDBusServiceInfoSkeletonClass DConfBlameClass;
+struct _DConfBlame
+{
+  DConfDBusServiceInfoSkeleton parent_instance;
+
+  GString *blame_info;
+};
+
+static void dconf_blame_iface_init (DConfDBusServiceInfoIface *iface);
+G_DEFINE_TYPE_WITH_CODE (DConfBlame, dconf_blame, DCONF_DBUS_TYPE_SERVICE_INFO_SKELETON,
+                         G_IMPLEMENT_INTERFACE (DCONF_DBUS_TYPE_SERVICE_INFO, dconf_blame_iface_init))
+
+#include "../common/dconf-changeset.h"
+#include "dconf-writer.h"
+
+void
+dconf_blame_record (GDBusMethodInvocation *invocation)
+{
+  DConfBlame *blame = dconf_blame_get ();
+  GError *error = NULL;
+  GVariant *parameters;
+  GVariant *reply;
+  GString *info;
+
+  if (!blame)
+    return;
+
+  if (blame->blame_info->len)
+    g_string_append (blame->blame_info, "\n====================================================================\n");
+
+  info = blame->blame_info;
+
+  g_string_append_printf (info, "Sender: %s\n", g_dbus_method_invocation_get_sender (invocation));
+  g_string_append_printf (info, "Object path: %s\n", g_dbus_method_invocation_get_object_path (invocation));
+  g_string_append_printf (info, "Method: %s\n", g_dbus_method_invocation_get_method_name (invocation));
+
+  if ((parameters = g_dbus_method_invocation_get_parameters (invocation)))
+    {
+      gchar *tmp;
+
+      tmp = g_variant_print (parameters, FALSE);
+      g_string_append_printf (info, "Parameters: %s\n", tmp);
+      g_free (tmp);
+    }
+
+  reply = g_dbus_connection_call_sync (g_dbus_method_invocation_get_connection (invocation),
+                                       "org.freedesktop.DBus", "/", "org.freedesktop.DBus",
+                                       "GetConnectionUnixProcessID",
+                                       g_variant_new ("(s)", g_dbus_method_invocation_get_sender (invocation)),
+                                       G_VARIANT_TYPE ("(u)"), G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error);
+
+  if (reply != NULL)
+    {
+      guint pid;
+
+      g_variant_get (reply, "(u)", &pid);
+      g_string_append_printf (info, "PID: %u\n", pid);
+      g_variant_unref (reply);
+    }
+  else
+    {
+      g_string_append_printf (info, "Unable to acquire PID: %s\n", error->message);
+      g_error_free (error);
+    }
+
+  {
+    const gchar * const ps_fx[] = { "ps", "fx", NULL };
+    gchar *result_out;
+    gchar *result_err;
+    gint status;
+
+    if (g_spawn_sync (NULL, (gchar **) ps_fx, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL,
+                      &result_out, &result_err, &status, &error))
+      {
+        g_string_append (info, "\n=== Process table from time of call follows ('ps fx') ===\n");
+        g_string_append (info, result_out);
+        g_string_append (info, result_err);
+        g_string_append_printf (info, "\nps exit status: %u\n", status);
+      }
+    else
+      {
+        g_string_append_printf (info, "\nUnable to spawn 'ps fx': %s\n", error->message);
+        g_error_free (error);
+      }
+  }
+}
+
+static gboolean
+dconf_blame_handle_blame (DConfDBusServiceInfo  *info,
+                          GDBusMethodInvocation *invocation)
+{
+  DConfBlame *blame = DCONF_BLAME (info);
+
+  dconf_blame_record (invocation);
+
+  g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", blame->blame_info->str));
+
+  return TRUE;
+}
+
+static void
+dconf_blame_init (DConfBlame *blame)
+{
+  blame->blame_info = g_string_new (NULL);
+}
+
+static void
+dconf_blame_class_init (DConfBlameClass *class)
+{
+}
+
+static void
+dconf_blame_iface_init (DConfDBusServiceInfoIface *iface)
+{
+  iface->handle_blame = dconf_blame_handle_blame;
+}
+
+static gboolean
+dconf_blame_enabled (void)
+{
+  gint fd;
+
+  if (getenv ("DCONF_BLAME"))
+    return TRUE;
+
+  fd = open ("/proc/cmdline", O_RDONLY);
+  if (fd != -1)
+    {
+      gchar buffer[1024];
+      gssize s;
+
+      s = read (fd, buffer, sizeof buffer - 1);
+      close (fd);
+
+      if (0 < s && s < sizeof buffer)
+        {
+          buffer[s] = '\0';
+          if (strstr (buffer, "DCONF_BLAME"))
+            return TRUE;
+        }
+    }
+
+  return FALSE;
+}
+
+DConfBlame *
+dconf_blame_get (void)
+{
+  static DConfBlame *blame;
+  static gboolean checked;
+
+  if (!checked)
+    {
+      if (dconf_blame_enabled ())
+        blame = g_object_new (DCONF_TYPE_BLAME, NULL);
+
+      checked = TRUE;
+    }
+
+  return blame;
+}
diff --git a/service/dconf-blame.h b/service/dconf-blame.h
new file mode 100644
index 0000000..07cf85b
--- /dev/null
+++ b/service/dconf-blame.h
@@ -0,0 +1,40 @@
+/*
+ * 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_blame_h__
+#define __dconf_blame_h__
+
+typedef struct _DConfBlame DConfBlame;
+
+#include <gio/gio.h>
+
+#define DCONF_TYPE_BLAME                                    (dconf_blame_get_type ())
+#define DCONF_BLAME(inst)                                   (G_TYPE_CHECK_INSTANCE_CAST ((inst),                     \
+                                                             DCONF_TYPE_BLAME, DConfBlame))
+#define DCONF_IS_BLAME(inst)                                (G_TYPE_CHECK_INSTANCE_TYPE ((inst),                     \
+                                                             DCONF_TYPE_BLAME))
+#define DCONF_BLAME_GET_CLASS(inst)                         (G_TYPE_INSTANCE_GET_CLASS ((inst),                      \
+                                                             DCONF_TYPE_BLAME, DConfBlameClass))
+
+DConfBlame             *dconf_blame_get                                 (void);
+void                    dconf_blame_record                              (GDBusMethodInvocation *invocation);
+
+#endif /* __dconf_blame_h__ */
diff --git a/service/dconf-service.c b/service/dconf-service.c
new file mode 100644
index 0000000..088a9ba
--- /dev/null
+++ b/service/dconf-service.c
@@ -0,0 +1,237 @@
+/*
+ * 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-service.h"
+
+#include "dconf-generated.h"
+#include "dconf-writer.h"
+#include "dconf-blame.h"
+
+#include <string.h>
+#include <fcntl.h>
+
+typedef GApplicationClass DConfServiceClass;
+typedef struct
+{
+  GApplication parent_instance;
+
+  DConfBlame  *blame;
+  GHashTable  *writers;
+  guint        subtree_id;
+} DConfService;
+
+G_DEFINE_TYPE (DConfService, dconf_service, G_TYPE_APPLICATION)
+
+static gboolean
+dconf_service_signalled (gpointer user_data)
+{
+  DConfService *service = user_data;
+
+  g_application_release (G_APPLICATION (service));
+
+  return G_SOURCE_REMOVE;
+}
+
+static gchar **
+dconf_service_subtree_enumerate (GDBusConnection *connection,
+                                 const gchar     *sender,
+                                 const gchar     *object_path,
+                                 gpointer         user_data)
+{
+  DConfService *service = user_data;
+  GHashTableIter iter;
+  gchar **result;
+  gpointer key;
+  gint n_items;
+  gint i = 0;
+
+  n_items = g_hash_table_size (service->writers);
+  result = g_new (gchar *, n_items + 1);
+
+  g_hash_table_iter_init (&iter, service->writers);
+  while (g_hash_table_iter_next (&iter, &key, NULL))
+    result[i++] = g_strdup (key);
+  result[i] = NULL;
+
+  g_assert_cmpint (n_items, ==, i);
+
+  return result;
+}
+
+GDBusInterfaceInfo **
+dconf_service_subtree_introspect (GDBusConnection *connection,
+                                  const gchar     *sender,
+                                  const gchar     *object_path,
+                                  const gchar     *node,
+                                  gpointer         user_data)
+{
+  GDBusInterfaceInfo **result;
+
+  if (node == NULL)
+    return NULL;
+
+  result = g_new (GDBusInterfaceInfo *, 2);
+  result[0] = dconf_dbus_writer_interface_info ();
+  result[1] = NULL;
+
+  return result;
+}
+
+static gpointer
+dconf_service_get_writer (DConfService    *service,
+                          GDBusConnection *connection,
+                          const gchar     *base_path,
+                          const gchar     *name)
+{
+  GDBusInterfaceSkeleton *writer;
+
+  writer = g_hash_table_lookup (service->writers, name);
+
+  if (writer == NULL)
+    {
+      GError *error = NULL;
+      gchar *object_path;
+
+      writer = dconf_writer_new (name);
+      g_hash_table_insert (service->writers, g_strdup (name), writer);
+      object_path = g_strjoin ("/", base_path, name, NULL);
+      g_dbus_interface_skeleton_export (writer, connection, object_path, &error);
+      g_assert_no_error (error);
+      g_free (object_path);
+    }
+
+  return writer;
+}
+
+const GDBusInterfaceVTable *
+dconf_service_subtree_dispatch (GDBusConnection *connection,
+                                const gchar     *sender,
+                                const gchar     *object_path,
+                                const gchar     *interface_name,
+                                const gchar     *node,
+                                gpointer        *out_user_data,
+                                gpointer         user_data)
+{
+  DConfService *service = user_data;
+
+  g_assert_cmpstr (interface_name, ==, "ca.desrt.dconf.Writer");
+  g_assert (node != NULL);
+
+  *out_user_data = dconf_service_get_writer (service, connection, object_path, node);
+
+  return g_dbus_interface_skeleton_get_vtable (*out_user_data);
+}
+
+static gboolean
+dconf_service_dbus_register (GApplication     *application,
+                             GDBusConnection  *connection,
+                             const gchar      *object_path,
+                             GError          **error)
+{
+  const GDBusSubtreeVTable subtree_vtable = {
+    dconf_service_subtree_enumerate,
+    dconf_service_subtree_introspect,
+    dconf_service_subtree_dispatch
+  };
+  DConfService *service = DCONF_SERVICE (application);
+  GError *local_error = NULL;
+
+  service->blame = dconf_blame_get ();
+  if (service->blame)
+    {
+      g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (service->blame),
+                                        connection, object_path, &local_error);
+      g_assert_no_error (local_error);
+    }
+
+  service->subtree_id = g_dbus_connection_register_subtree (connection, "/ca/desrt/dconf/Writer", &subtree_vtable,
+                                                            G_DBUS_SUBTREE_FLAGS_DISPATCH_TO_UNENUMERATED_NODES,
+                                                            g_object_ref (service), g_object_unref, &local_error);
+  g_assert_no_error (local_error);
+
+  return TRUE;
+}
+
+static void
+dconf_service_dbus_unregister (GApplication    *application,
+                               GDBusConnection *connection,
+                               const gchar     *object_path)
+{
+  DConfService *service = DCONF_SERVICE (application);
+
+  if (service->blame)
+    {
+      g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (service->blame));
+      g_object_unref (service->blame);
+      service->blame = NULL;
+    }
+
+  g_dbus_connection_unregister_subtree (connection, service->subtree_id);
+  service->subtree_id = 0;
+}
+
+static void
+dconf_service_startup (GApplication *application)
+{
+  DConfService *service = DCONF_SERVICE (application);
+
+  G_APPLICATION_CLASS (dconf_service_parent_class)
+    ->startup (application);
+
+  g_unix_signal_add (SIGTERM, dconf_service_signalled, service);
+  g_unix_signal_add (SIGINT, dconf_service_signalled, service);
+  g_unix_signal_add (SIGHUP, dconf_service_signalled, service);
+
+  g_application_hold (application);
+}
+
+static void
+dconf_service_shutdown (GApplication *application)
+{
+  G_APPLICATION_CLASS (dconf_service_parent_class)
+    ->shutdown (application);
+}
+
+static void
+dconf_service_init (DConfService *service)
+{
+  service->writers = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
+}
+
+static void
+dconf_service_class_init (GApplicationClass *class)
+{
+  class->dbus_register = dconf_service_dbus_register;
+  class->dbus_unregister = dconf_service_dbus_unregister;
+  class->startup = dconf_service_startup;
+  class->shutdown = dconf_service_shutdown;
+}
+
+GApplication *
+dconf_service_new (void)
+{
+  g_type_init ();
+
+  return g_object_new (DCONF_TYPE_SERVICE,
+                       "application-id", "ca.desrt.dconf",
+                       "flags", G_APPLICATION_IS_SERVICE,
+                       NULL);
+}
diff --git a/service/dconf-service.h b/service/dconf-service.h
new file mode 100644
index 0000000..221a24f
--- /dev/null
+++ b/service/dconf-service.h
@@ -0,0 +1,36 @@
+/*
+ * 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_service_h__
+#define __dconf_service_h__
+
+#include <gio/gio.h>
+
+#define DCONF_TYPE_SERVICE                                  (dconf_service_get_type ())
+#define DCONF_SERVICE(inst)                                 (G_TYPE_CHECK_INSTANCE_CAST ((inst),                     \
+                                                             DCONF_TYPE_SERVICE, DConfService))
+#define DCONF_IS_SERVICE(inst)                              (G_TYPE_CHECK_INSTANCE_TYPE ((inst),                     \
+                                                             DCONF_TYPE_SERVICE))
+
+GType                   dconf_service_get_type                          (void);
+GApplication *          dconf_service_new                               (void);
+
+#endif /* __dconf_service_h__ */
diff --git a/service/dconf-writer.c b/service/dconf-writer.c
index c6cda13..4cf800e 100644
--- a/service/dconf-writer.c
+++ b/service/dconf-writer.c
@@ -21,138 +21,474 @@
 
 #include "dconf-writer.h"
 
-#include "dconf-rebuilder.h"
-#include "dconf-state.h"
 #include "../shm/dconf-shm.h"
 
 #include <stdlib.h>
 #include <unistd.h>
+#include <string.h>
 #include <fcntl.h>
 #include <errno.h>
 #include <stdio.h>
 
-struct OPAQUE_TYPE__DConfWriter
+#include "dconf-generated.h"
+#include "../common/dconf-changeset.h"
+#include "../gvdb/gvdb-builder.h"
+#include "../gvdb/gvdb-reader.h"
+
+typedef struct
 {
-  DConfState *state;
+  DConfDBusWriterSkeleton parent_instance;
+  gchar *filename;
+  gboolean native;
   gchar *name;
-  gchar *path;
-};
+  guint64 tag;
+
+  GHashTable *uncommited_values;
+  GHashTable *commited_values;
+
+  GQueue uncommited_changes;
+  GQueue commited_changes;
+} DConfWriter;
+
+typedef struct
+{
+  DConfChangeset *changeset;
+  gchar          *tag;
+} TaggedChange;
+
+typedef struct
+{
+  DConfDBusWriterSkeletonClass parent_instance;
+
+  gboolean (* begin)  (DConfWriter     *writer,
+                       GError         **error);
+  void     (* change) (DConfWriter     *writer,
+                       DConfChangeset  *changeset,
+                       const gchar     *tag);
+  gboolean (* commit) (DConfWriter     *writer,
+                       GError         **error);
+  void     (* end)    (DConfWriter     *writer);
+} DConfWriterClass;
+
+
+static void dconf_writer_iface_init (DConfDBusWriterIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (DConfWriter, dconf_writer, DCONF_DBUS_TYPE_WRITER_SKELETON,
+                         G_IMPLEMENT_INTERFACE (DCONF_DBUS_TYPE_WRITER, dconf_writer_iface_init))
+
+static GvdbItem *
+dconf_writer_get_parent (GHashTable  *table,
+                         const gchar *key)
+{
+  GvdbItem *grandparent, *parent;
+  gchar *parent_name;
+  gint len;
+
+  if (g_str_equal (key, "/"))
+    return NULL;
+
+  len = strlen (key);
+  if (key[len - 1] == '/')
+    len--;
+
+  while (key[len - 1] != '/')
+    len--;
+
+  parent_name = g_strndup (key, len);
+  parent = g_hash_table_lookup (table, parent_name);
+
+  if (parent == NULL)
+    {
+      parent = gvdb_hash_table_insert (table, parent_name);
+
+      grandparent = dconf_writer_get_parent (table, parent_name);
+
+      if (grandparent != NULL)
+        gvdb_item_set_parent (parent, grandparent);
+    }
+
+  g_free (parent_name);
+
+  return parent;
+}
+
+static GHashTable *
+dconf_writer_new_value_table (GHashTable *copy_from)
+{
+  GHashTable *table;
+
+  table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_variant_unref);
+
+  if (copy_from)
+    {
+      GHashTableIter iter;
+      gpointer key, value;
+
+      g_hash_table_iter_init (&iter, copy_from);
+      while (g_hash_table_iter_next (&iter, &key, &value))
+        g_hash_table_insert (table, g_strdup (key), g_variant_ref (value));
+    }
+
+  return table;
+}
+
+static gchar *
+dconf_writer_get_tag (DConfWriter *writer)
+{
+  GDBusConnection *connection;
+
+  connection = g_dbus_interface_skeleton_get_connection (G_DBUS_INTERFACE_SKELETON (writer));
+
+  return g_strdup_printf ("%s:%s:%" G_GUINT64_FORMAT,
+                          g_dbus_connection_get_unique_name (connection),
+                          writer->name, writer->tag++);
+}
 
-/* Each element must only contain the ASCII characters "[A-Z][a-z][0-9]_"
- */
 static gboolean
-is_valid_dbus_path_element (const gchar *string)
+dconf_writer_real_begin (DConfWriter  *writer,
+                         GError      **error)
 {
-  gint i;
+  /* If this is the first time, populate the value table with the
+   * existing values.
+   */
+  if (writer->commited_values == NULL)
+    {
+      GError *my_error = NULL;
+      GvdbTable *table;
+      gchar **names;
+      gint n_names;
+      gint i;
+
+      table = gvdb_table_new (writer->filename, FALSE, &my_error);
+
+      /* It is perfectly fine if the file does not exist -- then it's
+       * just empty.
+       */
+      if (g_error_matches (my_error, G_FILE_ERROR, G_FILE_ERROR_NOENT))
+        g_clear_error (&my_error);
+
+      /* Otherwise, we should report errors to prevent ourselves from
+       * overwriting the database in other situations...
+       */
+      if (my_error)
+        {
+          g_propagate_prefixed_error (error, my_error, "Cannot open dconf database: ");
+          return FALSE;
+        }
+
+      /* Only initialise once we know we are in a non-error situation */
+      writer->commited_values = dconf_writer_new_value_table (NULL);
+
+      /* Fill the table up with the initial state */
+      names = gvdb_table_get_names (table, &n_names);
+      for (i = 0; i < n_names; i++)
+        {
+          if (dconf_is_key (names[i], NULL))
+            {
+              GVariant *value;
+
+              value = gvdb_table_get_value (table, names[i]);
+
+              if (value != NULL)
+                {
+                  g_hash_table_insert (writer->commited_values, names[i], value);
+                  names[i] = NULL;
+                }
+            }
+
+          g_free (names[i]);
+        }
+
+      gvdb_table_unref (table);
+      g_free (names);
+    }
 
-  for (i = 0; string[i]; i++)
-    if (!g_ascii_isalnum (string[i]) && string[i] != '_')
-      return FALSE;
+  writer->uncommited_values = dconf_writer_new_value_table (writer->commited_values);
 
   return TRUE;
 }
 
-gchar **
-dconf_writer_list_existing (void)
+static void
+dconf_writer_real_change (DConfWriter    *writer,
+                          DConfChangeset *changeset,
+                          const gchar    *tag)
+{
+  const gchar *prefix;
+  const gchar * const *keys;
+  GVariant * const *values;
+  int n_items;
+  gint i;
+
+  n_items = dconf_changeset_describe (changeset, &prefix, &keys, &values);
+
+  for (i = 0; i < n_items; i++)
+    {
+      gchar *path = g_strconcat (prefix, keys[i], NULL);
+
+      /* Check if we are performing a path reset */
+      if (g_str_has_suffix (path, "/"))
+        {
+          GHashTableIter iter;
+          gpointer key;
+
+          g_assert (values[i] == NULL);
+
+          /* A path reset is really a request to delete all keys that
+           * have a name starting with the reset path.
+           */
+          g_hash_table_iter_init (&iter, writer->uncommited_values);
+          while (g_hash_table_iter_next (&iter, &key, NULL))
+            if (g_str_has_prefix (key, path))
+              g_hash_table_iter_remove (&iter);
+        }
+
+      if (values[i] != NULL)
+        g_hash_table_insert (writer->uncommited_values, g_strdup (path), g_variant_ref (values[i]));
+      else
+        g_hash_table_remove (writer->uncommited_values, path);
+
+      g_free (path);
+    }
+
+  if (tag)
+    {
+      TaggedChange *change;
+
+      change = g_slice_new (TaggedChange);
+      change->changeset = dconf_changeset_ref (changeset);
+      change->tag = g_strdup (tag);
+
+      g_queue_push_tail (&writer->uncommited_changes, change);
+    }
+}
+
+static gboolean
+dconf_writer_real_commit (DConfWriter  *writer,
+                          GError      **error)
 {
-  GPtrArray *array;
-  gchar *path;
-  GDir *dir;
+  GHashTableIter iter;
+  gpointer key, value;
+  GHashTable *gvdb;
+  gboolean success;
 
-  path = g_build_filename (g_get_user_config_dir (), "dconf", NULL);
-  array = g_ptr_array_new ();
+  gvdb = gvdb_hash_table_new (NULL, NULL);
 
-  if ((dir = g_dir_open (path, 0, NULL)))
+  g_hash_table_iter_init (&iter, writer->uncommited_values);
+  while (g_hash_table_iter_next (&iter, &key, &value))
     {
-      const gchar *name;
+      GvdbItem *item;
 
-      while ((name = g_dir_read_name (dir)))
-        if (is_valid_dbus_path_element (name))
-          g_ptr_array_add (array, g_strdup (name));
+      g_assert (g_hash_table_lookup (gvdb, key) == NULL);
+      item = gvdb_hash_table_insert (gvdb, key);
+      gvdb_item_set_parent (item, dconf_writer_get_parent (gvdb, key));
+      gvdb_item_set_value (item, value);
     }
 
-  g_ptr_array_add (array, NULL);
-  g_free (path);
+  success = gvdb_table_write_contents (gvdb, writer->filename, FALSE, error);
+
+  if (success && writer->native)
+    dconf_shm_flag (writer->name);
+
+  if (writer->commited_values)
+    g_hash_table_unref (writer->commited_values);
+  writer->commited_values = g_hash_table_ref (writer->uncommited_values);
+
+  {
+    GQueue empty_queue = G_QUEUE_INIT;
+
+    g_assert (g_queue_is_empty (&writer->commited_changes));
+    writer->commited_changes = writer->uncommited_changes;
+    writer->uncommited_changes = empty_queue;
+  }
 
-  return (gchar **) g_ptr_array_free (array, FALSE);
+  return success;
+}
+
+static void
+dconf_writer_real_end (DConfWriter *writer)
+{
+  while (!g_queue_is_empty (&writer->uncommited_changes))
+    {
+      TaggedChange *change = g_queue_pop_head (&writer->commited_changes);
+      g_free (change->tag);
+      g_slice_free (TaggedChange, change);
+    }
+
+  while (!g_queue_is_empty (&writer->commited_changes))
+    {
+      TaggedChange *change = g_queue_pop_head (&writer->commited_changes);
+      const gchar *prefix;
+      const gchar * const *paths;
+
+      dconf_changeset_describe (change->changeset, &prefix, &paths, NULL);
+      dconf_dbus_writer_emit_notify_signal (DCONF_DBUS_WRITER (writer), prefix, paths, change->tag);
+      dconf_changeset_unref (change->changeset);
+      g_free (change->tag);
+      g_slice_free (TaggedChange, change);
+    }
+
+  g_clear_pointer (&writer->uncommited_values, g_hash_table_unref);
 }
 
 gboolean
-dconf_writer_write (DConfWriter  *writer,
-                    const gchar  *name,
-                    GVariant     *value,
+dconf_writer_begin (DConfWriter  *writer,
                     GError      **error)
 {
-  if (!dconf_rebuilder_rebuild (writer->path, "", &name, &value, 1, error))
-    return FALSE;
-
-  dconf_shm_flag (writer->name);
+  return DCONF_WRITER_GET_CLASS (writer)->begin (writer, error);
+}
 
-  return TRUE;
+void
+dconf_writer_change (DConfWriter    *writer,
+                     DConfChangeset *changeset,
+                     const gchar    *tag)
+{
+  DCONF_WRITER_GET_CLASS (writer)->change (writer, changeset, tag);
 }
 
 gboolean
-dconf_writer_write_many (DConfWriter          *writer,
-                         const gchar          *prefix,
-                         const gchar * const  *keys,
-                         GVariant * const     *values,
-                         gsize                 n_items,
-                         GError              **error)
+dconf_writer_commit (DConfWriter  *writer,
+                     GError      **error)
 {
-  if (!dconf_rebuilder_rebuild (writer->path, prefix, keys,
-                                values, n_items, error))
-    return FALSE;
+  return DCONF_WRITER_GET_CLASS (writer)->commit (writer, error);
+}
+
+void
+dconf_writer_end (DConfWriter *writer)
+{
+  return DCONF_WRITER_GET_CLASS (writer)->end (writer);
+}
+
+static gboolean
+dconf_writer_handle_init (DConfDBusWriter       *dbus_writer,
+                          GDBusMethodInvocation *invocation)
+{
+  DConfWriter *writer = DCONF_WRITER (dbus_writer);
+  GError *error = NULL;
+
+  dconf_blame_record (invocation);
+
+  dconf_writer_begin (writer, &error) && dconf_writer_commit (writer, &error);
+
+  if (error)
+    {
+      g_dbus_method_invocation_return_gerror (invocation, error);
+      g_error_free (error);
+    }
 
-  dconf_shm_flag (writer->name);
+  else
+    g_dbus_method_invocation_return_value (invocation, NULL);
+
+  dconf_writer_end (writer);
 
   return TRUE;
 }
 
-gboolean
-dconf_writer_change (DConfWriter     *writer,
-                     DConfChangeset  *change,
-                     GError         **error)
+static gboolean
+dconf_writer_handle_change (DConfDBusWriter       *dbus_writer,
+                            GDBusMethodInvocation *invocation,
+                            GVariant              *blob)
 {
-  const gchar * const *keys;
-  GVariant * const *values;
-  const gchar *prefix;
-  gint n_items;
+  DConfWriter *writer = DCONF_WRITER (dbus_writer);
+  DConfChangeset *changeset;
+  GError *error = NULL;
+  GVariant *tmp, *args;
+  gchar *tag;
+
+  dconf_blame_record (invocation);
+
+  tmp = g_variant_new_from_data (G_VARIANT_TYPE ("a{smv}"),
+                                 g_variant_get_data (blob), g_variant_get_size (blob), FALSE,
+                                 (GDestroyNotify) g_variant_unref, g_variant_ref (blob));
+  g_variant_ref_sink (tmp);
+  args = g_variant_get_normal_form (tmp);
+  g_variant_unref (tmp);
+
+  changeset = dconf_changeset_deserialise (args);
+  g_variant_unref (args);
 
-  n_items = dconf_changeset_describe (change, &prefix, &keys, &values);
+  tag = dconf_writer_get_tag (writer);
 
-  if (!n_items)
-    return TRUE;
+  if (!dconf_writer_begin (writer, &error))
+    goto out;
 
-  if (!dconf_rebuilder_rebuild (writer->path, prefix, keys, values, n_items, error))
-    return FALSE;
+  dconf_writer_change (writer, changeset, tag);
 
-  dconf_shm_flag (writer->name);
+  if (!dconf_writer_commit (writer, &error))
+    goto out;
+
+out:
+  if (error)
+    {
+      g_dbus_method_invocation_return_gerror (invocation, error);
+      g_error_free (error);
+    }
+
+  else
+    g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", tag));
+
+  g_free (tag);
+
+  dconf_writer_end (writer);
 
   return TRUE;
 }
 
-const gchar *
-dconf_writer_get_name (DConfWriter *writer)
+static void
+dconf_writer_iface_init (DConfDBusWriterIface *iface)
 {
-  return writer->name;
+  iface->handle_init = dconf_writer_handle_init;
+  iface->handle_change = dconf_writer_handle_change;
 }
 
-DConfState *
-dconf_writer_get_state (DConfWriter *writer)
+static void
+dconf_writer_init (DConfWriter *writer)
 {
-  return writer->state;
+  writer->native = TRUE;
 }
 
-DConfWriter *
-dconf_writer_new (DConfState  *state,
-                  const gchar *name)
+static void
+dconf_writer_set_property (GObject *object, guint prop_id,
+                           const GValue *value, GParamSpec *pspec)
 {
-  DConfWriter *writer;
+  DConfWriter *writer = DCONF_WRITER (object);
+
+  g_assert_cmpint (prop_id, ==, 1);
 
-  writer = g_slice_new (DConfWriter);
-  writer->state = state;
-  writer->path = g_build_filename (state->db_dir, name, NULL);
-  writer->name = g_strdup (name);
+  g_assert (!writer->name);
+  writer->name = g_value_dup_string (value);
 
-  return writer;
+  if (writer->native)
+    writer->filename = g_build_filename (g_get_user_config_dir (), "dconf", writer->name, NULL);
+  else
+    writer->filename = g_build_filename (g_get_user_runtime_dir (), "dconf", writer->name, NULL);
+}
+
+static void
+dconf_writer_class_init (DConfWriterClass *class)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+  object_class->set_property = dconf_writer_set_property;
+
+  class->begin = dconf_writer_real_begin;
+  class->change = dconf_writer_real_change;
+  class->commit = dconf_writer_real_commit;
+  class->end = dconf_writer_real_end;
+
+  g_object_class_install_property (object_class, 1,
+                                   g_param_spec_string ("name", "name", "name", NULL,
+                                                        G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_WRITABLE));
+}
+
+const gchar *
+dconf_writer_get_name (DConfWriter *writer)
+{
+  return writer->name;
+}
+
+GDBusInterfaceSkeleton *
+dconf_writer_new (const gchar *name)
+{
+  return g_object_new (DCONF_TYPE_WRITER, "name", name, NULL);
 }
diff --git a/service/dconf-writer.h b/service/dconf-writer.h
index 8f391fe..08183e7 100644
--- a/service/dconf-writer.h
+++ b/service/dconf-writer.h
@@ -22,29 +22,16 @@
 #ifndef __dconf_writer_h__
 #define __dconf_writer_h__
 
-#include "../common/dconf-changeset.h"
-#include "dconf-state.h"
+#include <gio/gio.h>
 
-typedef struct OPAQUE_TYPE__DConfWriter DConfWriter;
+#define DCONF_TYPE_WRITER                                   (dconf_writer_get_type ())
+#define DCONF_WRITER(inst)                                  (G_TYPE_CHECK_INSTANCE_CAST ((inst),                     \
+                                                             DCONF_TYPE_WRITER, DConfWriter))
+#define DCONF_IS_WRITER(inst)                               (G_TYPE_CHECK_INSTANCE_TYPE ((inst),                     \
+                                                             DCONF_TYPE_WRITER))
+#define DCONF_WRITER_GET_CLASS(inst)                        (G_TYPE_INSTANCE_GET_CLASS ((inst),                      \
+                                                             DCONF_TYPE_WRITER, DConfWriterClass))
 
-gchar **                dconf_writer_list_existing                      (void);
-DConfWriter *           dconf_writer_new                                (DConfState           *state,
-                                                                         const gchar          *name);
-DConfState *            dconf_writer_get_state                          (DConfWriter          *writer);
-const gchar *           dconf_writer_get_name                           (DConfWriter          *writer);
-gboolean                dconf_writer_write                              (DConfWriter          *writer,
-                                                                         const gchar          *name,
-                                                                         GVariant             *value,
-                                                                         GError              **error);
-gboolean                dconf_writer_write_many                         (DConfWriter          *writer,
-                                                                         const gchar          *prefix,
-                                                                         const gchar * const  *keys,
-                                                                         GVariant * const     *values,
-                                                                         gsize n_items,
-                                                                         GError              **error);
-
-gboolean                dconf_writer_change                             (DConfWriter          *writer,
-                                                                         DConfChangeset       *change,
-                                                                         GError              **error);
+GDBusInterfaceSkeleton *dconf_writer_new                                (const gchar *filename);
 
 #endif /* __dconf_writer_h__ */
diff --git a/service/dconf-interfaces.h b/service/main.c
similarity index 78%
rename from service/dconf-interfaces.h
rename to service/main.c
index 9f159b1..023abe3 100644
--- a/service/dconf-interfaces.h
+++ b/service/main.c
@@ -19,12 +19,17 @@
  * Author: Ryan Lortie <desrt desrt ca>
  */
 
-#ifndef __dconf_interfaces_h__
-#define __dconf_interfaces_h__
+#include "dconf-service.h"
 
-#include <gio/gio.h>
+int
+main (int argc, char **argv)
+{
+  GApplication *app;
+  gint status;
 
-extern const GDBusInterfaceInfo ca_desrt_dconf_Writer;
-extern const GDBusInterfaceInfo ca_desrt_dconf_WriterInfo;
+  app = dconf_service_new ();
+  status = g_application_run (app, argc, argv);
+  g_object_unref (app);
 
-#endif /* __dconf_interfaces_h__ */
+  return status;
+}



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