/*
* Copyright (C) 2010 Red Hat, Inc.
*
* This program 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 License, 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: Matthias Clasen
*/
#include
#include
#include
#include
#include
#include
#include
#include
#define GCONF_ENABLE_INTERNALS
#include
#include
static gboolean changed = FALSE;
static gboolean verbose = FALSE;
static gboolean dry_run = FALSE;
/* We only want to migrate settings that were in writable databases (in
* order to avoid 'lifting' system defaults into the user's database).
*
* We also want to perform the access to those readwrite databases
* readonly, since we're not making any changes.
*
* For that reason, we compile our own source list by scanning the
* default list. We take all :readwrite: sources, changing them to
* :readonly: as we do so.
*/
static GSList *
get_writable_source_path (void)
{
GSList *result = NULL;
gchar *config_file;
GSList *addresses;
GSList *node;
config_file = g_strconcat (GCONF_CONFDIR, "/path", NULL);
addresses = gconf_load_source_path (config_file, NULL);
g_free (config_file);
for (node = addresses; node; node = node->next)
{
const gchar *address = node->data;
const gchar *match;
match = strstr (address, ":readwrite:");
if (match)
{
gchar *copy;
copy = g_malloc (strlen (address) - 1 + 1);
memcpy (copy, address, match - address);
memcpy (copy + (match - address), ":readonly:", 10);
strcpy (copy + (match - address) + 10, match + 11);
result = g_slist_prepend (result, copy);
}
}
result = g_slist_reverse (result);
gconf_address_list_free (addresses);
return result;
}
static GConfClient *
get_writable_client (void)
{
GConfEngine *engine;
GSList *addresses;
addresses = get_writable_source_path ();
if (!addresses) {
g_printf("No writable gconf locations found\n");
exit (1);
}
engine = gconf_engine_get_local_for_addresses (addresses, NULL);
gconf_address_list_free (addresses);
return gconf_client_get_for_engine (engine);
}
static gboolean
type_uint32 (GSettings *settings,
const gchar *key)
{
const GVariantType *type;
GVariant *value;
value = g_settings_get_value (settings, key);
type = g_variant_get_type (value);
g_variant_unref (value);
return g_variant_type_equal (type, G_VARIANT_TYPE_UINT32);
}
static gboolean
handle_file (const gchar *filename)
{
GKeyFile *keyfile;
GConfClient *client;
GConfValue *value;
gint i, j;
gchar *gconf_key;
gchar **groups;
gchar **keys;
GVariantBuilder *builder;
GVariant *v;
const gchar *s;
gchar *str;
gint ii;
GSList *list, *l;
GSettingsSchemaSource *source;
GSettingsSchema *schema;
GSettings *settings;
GError *error;
keyfile = g_key_file_new ();
error = NULL;
if (!g_key_file_load_from_file (keyfile, filename, 0, &error))
{
if (verbose)
g_printerr ("%s: %s\n", filename, error->message);
g_error_free (error);
g_key_file_free (keyfile);
return FALSE;
}
client = get_writable_client ();
source = g_settings_schema_source_get_default ();
groups = g_key_file_get_groups (keyfile, NULL);
for (i = 0; groups[i]; i++)
{
gchar **schema_path;
schema_path = g_strsplit (groups[i], ":", 2);
schema = g_settings_schema_source_lookup (source, schema_path[0], TRUE);
if (schema == NULL)
{
if (verbose)
{
g_print ("Schema '%s' not found, skipping\n", schema_path[0]);
}
g_strfreev (schema_path);
continue;
}
g_settings_schema_unref (schema);
if (verbose)
{
g_print ("Collecting settings for schema '%s'\n", schema_path[0]);
if (schema_path[1])
g_print ("for storage at '%s'\n", schema_path[1]);
}
if (schema_path[1] != NULL)
{
char *compat_path_alloced = NULL;
char *compat_path;
/* Work around broken .convert files:
https://bugzilla.gnome.org/show_bug.cgi?id=704802
*/
if (!g_str_has_suffix (schema_path[1], "/"))
{
g_warning ("Schema file '%s' has missing trailing / in '%s'",
filename, schema_path[1]);
compat_path = compat_path_alloced = g_strconcat (schema_path[1], "/", NULL);
}
else
compat_path = schema_path[1];
settings = g_settings_new_with_path (schema_path[0], compat_path);
g_free (compat_path_alloced);
}
else
settings = g_settings_new (schema_path[0]);
g_settings_delay (settings);
error = NULL;
if ((keys = g_key_file_get_keys (keyfile, groups[i], NULL, &error)) == NULL)
{
g_printerr ("%s", error->message);
g_error_free (error);
continue;
}
for (j = 0; keys[j]; j++)
{
if (strchr (keys[j], '/') != 0)
{
g_printerr ("Key '%s' contains a '/'\n", keys[j]);
continue;
}
error = NULL;
if ((gconf_key = g_key_file_get_string (keyfile, groups[i], keys[j], &error)) == NULL)
{
g_printerr ("%s", error->message);
g_error_free (error);
continue;
}
error = NULL;
if ((value = gconf_client_get_without_default (client, gconf_key, &error)) == NULL)
{
if (error)
{
g_printerr ("Failed to get GConf key '%s': %s\n",
gconf_key, error->message);
g_error_free (error);
}
else
{
if (verbose)
g_print ("Skipping GConf key '%s', no user value\n",
gconf_key);
}
g_free (gconf_key);
continue;
}
switch (value->type)
{
case GCONF_VALUE_STRING:
if (dry_run)
g_print ("Set key '%s' to string '%s'\n", keys[j],
gconf_value_get_string (value));
else
g_settings_set (settings, keys[j], "s",
gconf_value_get_string (value));
break;
case GCONF_VALUE_INT:
if (dry_run)
g_print ("Set key '%s' to integer '%d'\n",
keys[j], gconf_value_get_int (value));
else
{
GVariant *range;
gchar *type;
range = g_settings_get_range (settings, keys[j]);
g_variant_get (range, "(&sv)", &type, NULL);
if (strcmp (type, "enum") == 0)
g_settings_set_enum (settings, keys[j], gconf_value_get_int (value));
else if (strcmp (type, "flags") == 0)
g_settings_set_flags (settings, keys[j], gconf_value_get_int (value));
else if (type_uint32 (settings, keys[j]))
g_settings_set (settings, keys[j], "u",
gconf_value_get_int (value));
else
g_settings_set (settings, keys[j], "i",
gconf_value_get_int (value));
g_variant_unref (range);
}
break;
case GCONF_VALUE_BOOL:
if (dry_run)
g_print ("Set key '%s' to boolean '%d'\n",
keys[j], gconf_value_get_bool (value));
else
g_settings_set (settings, keys[j], "b",
gconf_value_get_bool (value));
break;
case GCONF_VALUE_FLOAT:
if (dry_run)
g_print ("Set key '%s' to double '%g'\n",
keys[j], gconf_value_get_float (value));
else
g_settings_set (settings, keys[j], "d",
gconf_value_get_float (value));
break;
case GCONF_VALUE_LIST:
switch (gconf_value_get_list_type (value))
{
case GCONF_VALUE_STRING:
builder = g_variant_builder_new (G_VARIANT_TYPE_ARRAY);
list = gconf_value_get_list (value);
if (list != NULL)
{
for (l = list; l; l = l->next)
{
GConfValue *lv = l->data;
s = gconf_value_get_string (lv);
g_variant_builder_add (builder, "s", s);
}
v = g_variant_new ("as", builder);
}
else
v = g_variant_new_array (G_VARIANT_TYPE_STRING, NULL, 0);
g_variant_ref_sink (v);
if (dry_run)
{
str = g_variant_print (v, FALSE);
g_print ("Set key '%s' to a list of strings: %s\n",
keys[j], str);
g_free (str);
}
else
g_settings_set_value (settings, keys[j], v);
g_variant_unref (v);
g_variant_builder_unref (builder);
break;
case GCONF_VALUE_INT:
builder = g_variant_builder_new (G_VARIANT_TYPE_ARRAY);
list = gconf_value_get_list (value);
if (list != NULL)
{
for (l = list; l; l = l->next)
{
GConfValue *lv = l->data;
ii = gconf_value_get_int (lv);
g_variant_builder_add (builder, "i", ii);
}
v = g_variant_new ("ai", builder);
}
else
v = g_variant_new_array (G_VARIANT_TYPE_INT32, NULL, 0);
g_variant_ref_sink (v);
if (dry_run)
{
str = g_variant_print (v, FALSE);
g_print ("Set key '%s' to a list of integers: %s\n",
keys[j], str);
g_free (str);
}
else
g_settings_set_value (settings, keys[j], v);
g_variant_unref (v);
g_variant_builder_unref (builder);
break;
default:
g_printerr ("Keys of type 'list of %s' not handled yet\n",
gconf_value_type_to_string (gconf_value_get_list_type (value)));
break;
}
break;
default:
g_printerr ("Keys of type %s not handled yet\n",
gconf_value_type_to_string (value->type));
break;
}
gconf_value_free (value);
g_free (gconf_key);
}
g_strfreev (keys);
if (!dry_run)
g_settings_apply (settings);
g_object_unref (settings);
g_strfreev (schema_path);
}
g_strfreev (groups);
g_object_unref (client);
return TRUE;
}
static gboolean
handle_dir (const gchar *dirname,
time_t stored_mtime,
GHashTable *converted)
{
time_t dir_mtime;
struct stat statbuf;
GDir *dir;
const gchar *name;
gchar *filename;
GError *error;
/* If the directory is not newer, exit */
if (stat (dirname, &statbuf) == 0)
dir_mtime = statbuf.st_mtime;
else
{
if (verbose)
g_print ("Directory '%s' does not exist, nothing to do\n", dirname);
return TRUE;
}
if (dir_mtime <= stored_mtime)
{
if (verbose)
g_print ("Directory '%s' all uptodate, nothing to do\n", dirname);
return TRUE;
}
error = NULL;
dir = g_dir_open (dirname, 0, &error);
if (dir == NULL)
{
g_printerr ("Failed to open '%s': %s\n", dirname, error->message);
return FALSE;
}
while ((name = g_dir_read_name (dir)) != NULL)
{
if (g_hash_table_lookup (converted, name))
{
if (verbose)
g_print ("File '%s already converted, skipping\n", name);
goto next;
}
filename = g_build_filename (dirname, name, NULL);
if (handle_file (filename))
{
gchar *myname = g_strdup (name);
/* Add the the file to the converted list */
g_hash_table_insert (converted, myname, myname);
changed = TRUE;
}
g_free (filename);
next: ;
}
return TRUE;
}
/* get_string_set() and set_string_set() could be GKeyFile API */
static GHashTable *
get_string_set (GKeyFile *keyfile,
const gchar *group,
const gchar *key,
GError **error)
{
GHashTable *converted;
gchar **list;
gint i;
list = g_key_file_get_string_list (keyfile, group, key, NULL, error);
if (list == NULL)
return NULL;
converted = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
for (i = 0; list[i]; i++)
g_hash_table_insert (converted, list[i], list[i]);
/* The hashtable now owns the strings, so only free the array */
g_free (list);
return converted;
}
static void
set_string_set (GKeyFile *keyfile,
const gchar *group,
const gchar *key,
GHashTable *set)
{
GHashTableIter iter;
GString *list;
gpointer item;
list = g_string_new (NULL);
g_hash_table_iter_init (&iter, set);
while (g_hash_table_iter_next (&iter, &item, NULL))
g_string_append_printf (list, "%s;", (const gchar *) item);
g_key_file_set_value (keyfile, group, key, list->str);
g_string_free (list, TRUE);
}
static GHashTable *
load_state (time_t *mtime)
{
GHashTable *converted;
GHashTable *tmp;
gchar *filename;
GKeyFile *keyfile;
GError *error;
gchar *str;
converted = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
*mtime = 0;
filename = g_build_filename (g_get_user_data_dir (), "gsettings-data-convert", NULL);
keyfile = g_key_file_new ();
/* ensure file exists */
if (!g_file_test (filename, G_FILE_TEST_EXISTS))
return converted;
error = NULL;
if (!g_key_file_load_from_file (keyfile, filename, 0, &error))
{
g_printerr ("%s: %s\n", filename, error->message);
g_error_free (error);
return converted;
}
error = NULL;
if ((str = g_key_file_get_string (keyfile, "State", "timestamp", &error)) == NULL)
{
g_printerr ("%s\n", error->message);
g_error_free (error);
}
else
{
*mtime = (time_t)g_ascii_strtoll (str, NULL, 0);
g_free (str);
}
error = NULL;
if ((tmp = get_string_set (keyfile, "State", "converted", &error)) == NULL)
{
g_printerr ("%s\n", error->message);
g_error_free (error);
}
else
{
g_hash_table_unref (converted);
converted = tmp;
}
g_key_file_free (keyfile);
g_free (filename);
return converted;
}
static gboolean
save_state (GHashTable *converted)
{
gchar *filename;
GKeyFile *keyfile;
gchar *str;
GError *error;
gboolean result;
/* Make sure the state directory exists */
if (g_mkdir_with_parents (g_get_user_data_dir (), 0755))
{
g_printerr ("Failed to create directory %s: %s\n",
g_get_user_data_dir (), g_strerror (errno));
return FALSE;
}
filename = g_build_filename (g_get_user_data_dir (), "gsettings-data-convert", NULL);
keyfile = g_key_file_new ();
str = g_strdup_printf ("%ld", time (NULL));
g_key_file_set_string (keyfile,
"State", "timestamp", str);
g_free (str);
set_string_set (keyfile, "State", "converted", converted);
str = g_key_file_to_data (keyfile, NULL, NULL);
g_key_file_free (keyfile);
error = NULL;
if (!g_file_set_contents (filename, str, -1, &error))
{
g_printerr ("%s\n", error->message);
g_error_free (error);
result = FALSE;
}
else
result = TRUE;
g_free (filename);
g_free (str);
return result;
}
int
main (int argc, char *argv[])
{
time_t stored_mtime;
const gchar * const *data_dirs;
gint i;
GError *error;
GHashTable *converted;
GOptionContext *context;
const gchar *extra_file = NULL;
GOptionEntry entries[] = {
{ "verbose", 0, 0, G_OPTION_ARG_NONE, &verbose, "show verbose messages", NULL },
{ "dry-run", 0, 0, G_OPTION_ARG_NONE, &dry_run, "do not perform any changes", NULL },
{ "file", 0, 0, G_OPTION_ARG_STRING, &extra_file, "perform conversions from an extra file", NULL },
{ NULL }
};
g_type_init();
context = g_option_context_new ("");
g_option_context_set_summary (context,
"Migrate settings from the users GConf database to GSettings.");
g_option_context_add_main_entries (context, entries, NULL);
error = NULL;
if (!g_option_context_parse (context, &argc, &argv, &error))
{
g_printerr ("%s\n", error->message);
return 1;
}
converted = load_state (&stored_mtime);
if (extra_file)
{
gchar *base;
base = g_path_get_basename (extra_file);
if (g_hash_table_lookup (converted, base))
{
if (verbose)
g_print ("'%s' is already converted. Skipping.\n", base);
}
else
{
if (handle_file (extra_file))
{
gchar *myname = g_strdup (base);
g_hash_table_insert (converted, myname, myname);
changed = TRUE;
}
}
g_free (base);
}
data_dirs = g_get_system_data_dirs ();
for (i = 0; data_dirs[i]; i++)
{
gchar *convert_dir;
convert_dir = g_build_filename (data_dirs[i], "GConf", "gsettings", NULL);
if (!handle_dir (convert_dir, stored_mtime, converted))
return 1;
g_free (convert_dir);
}
if (changed && !dry_run)
{
if (!save_state (converted))
return 1;
}
return 0;
}