/*
* Copyright © 2012 Canonical Limited
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the licence, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see .
*
* Author: Ryan Lortie
*/
#include "config.h"
#include "dconf-service.h"
#include "dconf-generated.h"
#include "dconf-writer.h"
#include "dconf-blame.h"
#include
#include
#include
typedef GApplicationClass DConfServiceClass;
typedef struct
{
GApplication parent_instance;
GIOExtensionPoint *extension_point;
DConfBlame *blame;
GHashTable *writers;
GArray *subtree_ids;
gboolean released;
} DConfService;
G_DEFINE_TYPE (DConfService, dconf_service, G_TYPE_APPLICATION)
static gboolean
dconf_service_signalled (gpointer user_data)
{
DConfService *service = user_data;
if (!service->released)
g_application_release (G_APPLICATION (service));
service->released = TRUE;
return G_SOURCE_REMOVE;
}
static gchar **
string_set_free (GHashTable *set)
{
GHashTableIter iter;
gchar **result;
gint n_items;
gpointer key;
gint i = 0;
n_items = g_hash_table_size (set);
result = g_new (gchar *, n_items + 1);
g_hash_table_iter_init (&iter, set);
while (g_hash_table_iter_next (&iter, &key, NULL))
{
result[i++] = key;
g_hash_table_iter_steal (&iter);
}
result[i] = NULL;
g_assert_cmpint (n_items, ==, i);
g_hash_table_unref (set);
return result;
}
static GHashTable *
string_set_new (void)
{
return g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
}
static void
string_set_add (GHashTable *set,
const gchar *string)
{
g_hash_table_add (set, g_strdup (string));
}
static GType
dconf_service_find_writer_type (DConfService *service,
const gchar *object_path,
GHashTable **writers)
{
GIOExtension *extension;
const gchar *path;
GHashTable *table;
path = object_path + strlen ("/ca/desrt/dconf");
g_assert (*path == '/');
path++;
extension = g_io_extension_point_get_extension_by_name (service->extension_point, path);
g_assert (extension != NULL);
table = g_hash_table_lookup (service->writers, path);
if (table == NULL)
{
table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
g_hash_table_insert (service->writers, g_strdup (path), table);
}
*writers = table;
return g_io_extension_get_type (extension);
}
static gchar **
dconf_service_subtree_enumerate (GDBusConnection *connection,
const gchar *sender,
const gchar *object_path,
gpointer user_data)
{
DConfService *service = user_data;
GHashTableIter iter;
GHashTable *writers;
GType writer_type;
GHashTable *set;
gpointer key;
set = string_set_new ();
writer_type = dconf_service_find_writer_type (service, object_path, &writers);
g_hash_table_iter_init (&iter, writers);
while (g_hash_table_iter_next (&iter, &key, NULL))
string_set_add (set, key);
dconf_writer_list (writer_type, set);
return string_set_free (set);
}
static 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;
GHashTable *writers;
GType writer_type;
writer_type = dconf_service_find_writer_type (service, base_path, &writers);
writer = g_hash_table_lookup (writers, name);
if (writer == NULL)
{
GError *error = NULL;
gchar *object_path;
writer = dconf_writer_new (writer_type, name);
g_hash_table_insert (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;
}
static 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;
GList *node;
guint id;
service->extension_point = g_io_extension_point_register ("dconf-backend");
g_io_extension_point_set_required_type (service->extension_point, DCONF_TYPE_WRITER);
g_io_extension_point_implement ("dconf-backend", DCONF_TYPE_WRITER, "Writer", 0);
g_io_extension_point_implement ("dconf-backend", DCONF_TYPE_KEYFILE_WRITER, "keyfile", 0);
g_io_extension_point_implement ("dconf-backend", DCONF_TYPE_SHM_WRITER, "shm", 0);
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);
}
for (node = g_io_extension_point_get_extensions (service->extension_point); node; node = node->next)
{
gchar *path;
path = g_strconcat ("/ca/desrt/dconf/", g_io_extension_get_name (node->data), NULL);
id = g_dbus_connection_register_subtree (connection, path, &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);
g_array_append_vals (service->subtree_ids, &id, 1);
g_free (path);
}
return TRUE;
}
static void
dconf_service_dbus_unregister (GApplication *application,
GDBusConnection *connection,
const gchar *object_path)
{
DConfService *service = DCONF_SERVICE (application);
gint i;
if (service->blame)
{
g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (service->blame));
g_object_unref (service->blame);
service->blame = NULL;
}
for (i = 0; i < service->subtree_ids->len; i++)
g_dbus_connection_unregister_subtree (connection, g_array_index (service->subtree_ids, guint, i));
g_array_set_size (service->subtree_ids, 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);
service->subtree_ids = g_array_new (FALSE, TRUE, sizeof (guint));
}
static void
dconf_service_finalize (GObject *object)
{
DConfService *service = (DConfService *) object;
g_assert_cmpint (service->subtree_ids->len, ==, 0);
g_array_free (service->subtree_ids, TRUE);
G_OBJECT_CLASS (dconf_service_parent_class)->finalize (object);
}
static void
dconf_service_class_init (GApplicationClass *class)
{
GObjectClass *object_class = G_OBJECT_CLASS (class);
object_class->finalize = dconf_service_finalize;
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)
{
return g_object_new (DCONF_TYPE_SERVICE,
"application-id", "ca.desrt.dconf",
"flags", G_APPLICATION_IS_SERVICE,
NULL);
}