diff options
author | Ryan Lortie <desrt@desrt.ca> | 2013-01-11 13:27:05 -0500 |
---|---|---|
committer | Ryan Lortie <desrt@desrt.ca> | 2013-01-11 13:27:05 -0500 |
commit | 95af265b47eaec1ca4095fab750e03cde74b5114 (patch) | |
tree | 5019ec4f542650c034dfe22c4fe3ebe9b231374e /service | |
parent | e583885253b48aed82cb5a980aae705f772b1383 (diff) | |
download | dconf-95af265b47eaec1ca4095fab750e03cde74b5114.tar.gz |
service: add a keyfile writer
It is now possible to have keyfile-based dconf databases.
Diffstat (limited to 'service')
-rw-r--r-- | service/Makefile.am | 1 | ||||
-rw-r--r-- | service/dconf-keyfile-writer.c | 361 | ||||
-rw-r--r-- | service/dconf-service.c | 1 | ||||
-rw-r--r-- | service/dconf-writer.h | 7 |
4 files changed, 368 insertions, 2 deletions
diff --git a/service/Makefile.am b/service/Makefile.am index 7e27885..8d32517 100644 --- a/service/Makefile.am +++ b/service/Makefile.am @@ -21,6 +21,7 @@ dconf_service_SOURCES = \ dconf-service.h \ dconf-writer.h \ dconf-writer.c \ + dconf-keyfile-writer.c \ dconf-shm-writer.c \ main.c diff --git a/service/dconf-keyfile-writer.c b/service/dconf-keyfile-writer.c new file mode 100644 index 0000000..4851c9e --- /dev/null +++ b/service/dconf-keyfile-writer.c @@ -0,0 +1,361 @@ +/* + * 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-writer.h" + +#include <string.h> + +typedef DConfWriterClass DConfKeyfileWriterClass; + +typedef struct +{ + DConfWriter parent_instance; + gchar *filename; + GKeyFile *keyfile; +} DConfKeyfileWriter; + +G_DEFINE_TYPE (DConfKeyfileWriter, dconf_keyfile_writer, DCONF_TYPE_WRITER) + +DConfChangeset * +dconf_keyfile_to_changeset (GKeyFile *keyfile, + const gchar *filename_fyi) +{ + DConfChangeset *changeset; + gchar **groups; + gint i; + + changeset = dconf_changeset_new_database (NULL); + + groups = g_key_file_get_groups (keyfile, NULL); + for (i = 0; groups[i]; i++) + { + const gchar *group = groups[i]; + gchar *key_prefix; + gchar **keys; + gint j; + + /* Special case the [/] group to be able to contain keys at the + * root (/a, /b, etc.). All others must not start or end with a + * slash (ie: group [x/y] contains keys such as /x/y/z). + */ + if (!g_str_equal (group, "/")) + { + if (g_str_has_prefix (group, "/") || g_str_has_suffix (group, "/") || strstr (group, "//")) + { + g_warning ("%s: ignoring invalid group name: %s\n", filename_fyi, group); + continue; + } + + key_prefix = g_strconcat ("/", group, "/", NULL); + } + else + key_prefix = g_strdup ("/"); + + keys = g_key_file_get_keys (keyfile, group, NULL, NULL); + g_assert (keys != NULL); + + for (j = 0; keys[j]; j++) + { + const gchar *key = keys[j]; + GError *error = NULL; + gchar *value_str; + GVariant *value; + gchar *path; + + if (strchr (key, '/')) + { + g_warning ("%s: [%s]: ignoring invalid key name: %s\n", filename_fyi, group, key); + continue; + } + + value_str = g_key_file_get_value (keyfile, group, key, NULL); + g_assert (value_str != NULL); + + value = g_variant_parse (NULL, value_str, NULL, NULL, &error); + g_free (value_str); + + if (value == NULL) + { + g_warning ("%s: [%s]: %s: skipping invalid value: %s (%s)\n", + filename_fyi, group, key, value_str, error->message); + g_error_free (error); + continue; + } + + path = g_strconcat (key_prefix, key, NULL); + dconf_changeset_set (changeset, path, value); + g_variant_unref (value); + g_free (path); + } + + g_free (key_prefix); + g_strfreev (keys); + } + + g_strfreev (groups); + + return changeset; +} + +static void +dconf_keyfile_writer_list (GHashTable *set) +{ +} + +static gboolean +dconf_keyfile_writer_begin (DConfWriter *writer, + GError **error) +{ + DConfKeyfileWriter *kfw = (DConfKeyfileWriter *) writer; + GError *local_error = NULL; + DConfChangeset *contents; + DConfChangeset *changes; + + if (kfw->filename == NULL) + kfw->filename = g_build_filename (g_get_user_config_dir (), "dconf-keyfile", + dconf_writer_get_name (writer), NULL); + + kfw->keyfile = g_key_file_new (); + + if (!g_key_file_load_from_file (kfw->keyfile, kfw->filename, G_KEY_FILE_KEEP_COMMENTS, &local_error)) + { + if (!g_error_matches (local_error, G_FILE_ERROR, G_FILE_ERROR_NOENT)) + { + g_clear_pointer (&kfw->keyfile, g_key_file_free); + g_propagate_error (error, local_error); + return FALSE; + } + + g_clear_error (&local_error); + } + + if (!DCONF_WRITER_CLASS (dconf_keyfile_writer_parent_class)->begin (writer, error)) + { + g_clear_pointer (&kfw->keyfile, g_key_file_free); + return FALSE; + } + + /* Diff the keyfile to the current contents of the database and apply + * any changes that we notice. + * + * This will catch both the case of people outside of the service + * making changes to the file and also the case of starting for the + * first time. + */ + contents = dconf_keyfile_to_changeset (kfw->keyfile, kfw->filename); + changes = dconf_writer_diff (writer, contents); + + if (changes) + { + DCONF_WRITER_CLASS (dconf_keyfile_writer_parent_class)->change (writer, changes, ""); + dconf_changeset_unref (changes); + } + + dconf_changeset_unref (contents); + + return TRUE; +} + +static void +dconf_keyfile_writer_change (DConfWriter *writer, + DConfChangeset *changeset, + const gchar *tag) +{ + DConfKeyfileWriter *kfw = (DConfKeyfileWriter *) writer; + const gchar *prefix; + const gchar * const *paths; + GVariant * const *values; + guint n, i; + + DCONF_WRITER_CLASS (dconf_keyfile_writer_parent_class)->change (writer, changeset, tag); + + n = dconf_changeset_describe (changeset, &prefix, &paths, &values); + + for (i = 0; i < n; i++) + { + gchar *path = g_strconcat (prefix, paths[i], NULL); + GVariant *value = values[i]; + + if (g_str_equal (path, "/")) + { + g_assert (value == NULL); + + /* This is a request to reset everything. + * + * Easiest way to do this: + */ + g_key_file_free (kfw->keyfile); + kfw->keyfile = g_key_file_new (); + } + else if (g_str_has_suffix (path, "/")) + { + gchar *group_to_remove; + gchar **groups; + gint i; + + g_assert (value == NULL); + + /* Time to do a path reset. + * + * We must reset the group for the path plus any "subgroups". + * + * We dealt with the case of "/" above, so we know we have + * something with at least a separate leading and trailing slash, + * with the group name in the middle. + */ + group_to_remove = g_strndup (path + 1, strlen (path) - 2); + g_key_file_remove_group (kfw->keyfile, group_to_remove, NULL); + g_free (group_to_remove); + + /* Now the rest... + * + * For this case we check if the group is prefixed by the path + * given to us, including the trailing slash (but not the leading + * one). That means a reset on "/a/" (group "[a]") will match + * group "[a/b]" but not will not match group "[another]". + */ + groups = g_key_file_get_groups (kfw->keyfile, NULL); + for (i = 0; groups[i]; i++) + if (g_str_has_prefix (groups[i], path + 1)) /* remove only leading slash */ + g_key_file_remove_group (kfw->keyfile, groups[i], NULL); + g_strfreev (groups); + } + else + { + /* A simple set or reset of a single key. */ + const gchar *last_slash; + gchar *group; + gchar *key; + + last_slash = strrchr (path, '/'); + + /* If the last slash is the first one then the group will be the + * special case: [/]. Otherwise we remove the leading and + * trailing slashes. + */ + if (last_slash != path) + group = g_strndup (path + 1, last_slash - (path + 1)); + else + group = g_strdup ("/"); + + /* Key is the non-empty part following the last slash (we know + * that it's non-empty because we dealt with strings ending with + * '/' above). + */ + key = g_strdup (last_slash + 1); + + if (value != NULL) + { + gchar *printed; + + printed = g_variant_print (value, TRUE); + g_key_file_set_value (kfw->keyfile, group, key, printed); + g_free (printed); + } + else + g_key_file_remove_key (kfw->keyfile, group, key, NULL); + + g_free (group); + g_free (key); + } + + g_free (path); + } +} + +static gboolean +dconf_keyfile_writer_commit (DConfWriter *writer, + GError **error) +{ + DConfKeyfileWriter *kfw = (DConfKeyfileWriter *) writer; + + /* Pretty simple. Write the keyfile. */ + { + gchar *data; + gsize size; + + /* docs say: "Note that this function never reports an error" */ + data = g_key_file_to_data (kfw->keyfile, &size, NULL); + if (!g_file_set_contents (kfw->filename, data, size, error)) + { + gchar *dirname; + + /* Maybe it failed because the directory doesn't exist. Try + * again, after mkdir(). + */ + dirname = g_path_get_dirname (kfw->filename); + g_mkdir_with_parents (dirname, 0777); + g_free (dirname); + + g_clear_error (error); + if (!g_file_set_contents (kfw->filename, data, size, error)) + { + g_free (data); + return FALSE; + } + } + + g_free (data); + } + + /* Failing to update the shm file after writing the keyfile is + * unlikely to occur. It can only happen if the runtime dir hits + * quota. + * + * If it does happen, we're in a bit of a bad spot because the on-disk + * keyfile is now out-of-sync with the contents of the shm file. We + * fail the write because the apps will see the old values in the shm + * file. + * + * Meanwhile we keep the on-disk keyfile as-is. The next time we open + * it we will notice that it's not in sync with the shm file and we'll + * try to merge the two as if the changes were made by an outsider. + * Eventually that may succeed... If it doesn't, what can we do? + */ + return DCONF_WRITER_CLASS (dconf_keyfile_writer_parent_class)->commit (writer, error); +} + +static void +dconf_keyfile_writer_end (DConfWriter *writer) +{ + DConfKeyfileWriter *kfw = (DConfKeyfileWriter *) writer; + + DCONF_WRITER_CLASS (dconf_keyfile_writer_parent_class)->end (writer); + + g_clear_pointer (&kfw->keyfile, g_key_file_free); +} + +static void +dconf_keyfile_writer_init (DConfKeyfileWriter *writer) +{ + dconf_writer_set_basepath (DCONF_WRITER (writer), "keyfile"); +} + +static void +dconf_keyfile_writer_class_init (DConfWriterClass *class) +{ + class->list = dconf_keyfile_writer_list; + class->begin = dconf_keyfile_writer_begin; + class->change = dconf_keyfile_writer_change; + class->commit = dconf_keyfile_writer_commit; + class->end = dconf_keyfile_writer_end; +} diff --git a/service/dconf-service.c b/service/dconf-service.c index 950a4cf..d6bcefd 100644 --- a/service/dconf-service.c +++ b/service/dconf-service.c @@ -231,6 +231,7 @@ dconf_service_dbus_register (GApplication *application, 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 (); diff --git a/service/dconf-writer.h b/service/dconf-writer.h index 32a7d4b..3435bdd 100644 --- a/service/dconf-writer.h +++ b/service/dconf-writer.h @@ -28,7 +28,6 @@ #include "dconf-generated.h" #define DCONF_TYPE_WRITER (dconf_writer_get_type ()) -#define DCONF_TYPE_SHM_WRITER (dconf_shm_writer_get_type ()) #define DCONF_WRITER(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), \ DCONF_TYPE_WRITER, DConfWriter)) #define DCONF_WRITER_CLASS(class) (G_TYPE_CHECK_CLASS_CAST ((class), \ @@ -70,7 +69,6 @@ struct _DConfWriter GType dconf_writer_get_type (void); -GType dconf_shm_writer_get_type (void); void dconf_writer_set_basepath (DConfWriter *writer, const gchar *name); @@ -83,4 +81,9 @@ void dconf_writer_list (GType GDBusInterfaceSkeleton *dconf_writer_new (GType type, const gchar *name); +#define DCONF_TYPE_SHM_WRITER (dconf_shm_writer_get_type ()) +GType dconf_shm_writer_get_type (void); +#define DCONF_TYPE_KEYFILE_WRITER (dconf_keyfile_writer_get_type ()) +GType dconf_keyfile_writer_get_type (void); + #endif /* __dconf_writer_h__ */ |