[evolution-data-server/account-mgmt: 32/38] Migrate addressbook XML data to key files.



commit 429bb82baf93e4742e52c415b1b277ccd926b2c2
Author: Matthew Barnes <mbarnes redhat com>
Date:   Fri Mar 4 14:06:01 2011 -0500

    Migrate addressbook XML data to key files.
    
    The migration code reads raw %gconf.xml files, generates equivalent key
    files from the XML data, and renames data and cache directories from the
    source's URI to its UID.

 services/evolution-addressbook-factory/Makefile.am |    5 +
 ...evolution-addressbook-factory-migrate-sources.c | 1145 ++++++++++++++++++++
 .../evolution-addressbook-factory.c                |    6 +
 3 files changed, 1156 insertions(+), 0 deletions(-)
---
diff --git a/services/evolution-addressbook-factory/Makefile.am b/services/evolution-addressbook-factory/Makefile.am
index ff2416f..6f94962 100644
--- a/services/evolution-addressbook-factory/Makefile.am
+++ b/services/evolution-addressbook-factory/Makefile.am
@@ -19,13 +19,16 @@ evolution_addressbook_factory_CPPFLAGS = \
 	-I$(top_builddir) \
 	-I$(top_builddir)/addressbook \
 	$(EVOLUTION_ADDRESSBOOK_CFLAGS) \
+	$(GNOME_KEYRING_CFLAGS) \
 	$(FACTORY_GTK_CFLAGS) \
+	$(SOUP_CFLAGS) \
 	$(GOA_CFLAGS) \
 	$(NULL)
 
 evolution_addressbook_factory_SOURCES = \
 	evolution-addressbook-factory.c \
 	evolution-addressbook-factory-migrate-basedir.c \
+	evolution-addressbook-factory-migrate-sources.c \
 	$(NULL)
 
 evolution_addressbook_factory_LDADD = \
@@ -33,7 +36,9 @@ evolution_addressbook_factory_LDADD = \
 	$(top_builddir)/libebackend/libebackend-1.2.la \
 	$(top_builddir)/libedataserver/libedataserver-1.2.la \
 	$(EVOLUTION_ADDRESSBOOK_LIBS) \
+	$(GNOME_KEYRING_LIBS) \
 	$(FACTORY_GTK_LIBS) \
+	$(SOUP_LIBS) \
 	$(GOA_LIBS) \
 	$(NULL)
 
diff --git a/services/evolution-addressbook-factory/evolution-addressbook-factory-migrate-sources.c b/services/evolution-addressbook-factory/evolution-addressbook-factory-migrate-sources.c
new file mode 100644
index 0000000..7b07387
--- /dev/null
+++ b/services/evolution-addressbook-factory/evolution-addressbook-factory-migrate-sources.c
@@ -0,0 +1,1145 @@
+/*
+ * e-data-book-migrate-sources.c
+ *
+ * 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) version 3.
+ *
+ * This program 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <glib/gstdio.h>
+#include <libsoup/soup.h>
+#include <gnome-keyring.h>
+
+#include <libebook/e-source-address-book.h>
+#include <libedataserver/e-data-server-util.h>
+
+/* These constants are collected from various e-source-*.h files
+ * throughout evolution-data-server and known extension packages. */
+#define E_SOURCE_GROUP_NAME			"Data Source"
+#define E_SOURCE_EXTENSION_ADDRESS_BOOK		"Address Book"
+#define E_SOURCE_EXTENSION_AUTHENTICATION	"Authentication"
+#define E_SOURCE_EXTENSION_AUTOCOMPLETE		"Autocomplete"
+#define E_SOURCE_EXTENSION_OFFLINE		"Offline"
+#define E_SOURCE_EXTENSION_REFRESH		"Refresh"
+#define E_SOURCE_EXTENSION_SECURITY		"Security"
+#define E_SOURCE_EXTENSION_CONTACTS_BACKEND	"Contacts Backend"
+#define E_SOURCE_EXTENSION_LDAP_BACKEND		"LDAP Backend"
+#define E_SOURCE_EXTENSION_VCF_BACKEND		"VCF Backend"
+#define E_SOURCE_EXTENSION_WEBDAV_BACKEND	"WebDAV Backend"
+
+/* These constants are copied from e-source-password.c. */
+#define KEYRING_ITEM_ATTRIBUTE_NAME		"e-source-uid"
+#define KEYRING_ITEM_DISPLAY_FORMAT		"Evolution Data Source %s"
+
+typedef struct _ParseData ParseData;
+
+typedef void		(*PropertyFunc)		(ParseData *parse_data,
+						 const gchar *property_name,
+						 const gchar *property_value);
+
+typedef enum {
+	PARSE_STATE_INITIAL,
+
+	PARSE_STATE_IN_GCONF,		/* GConf XML */
+	PARSE_STATE_IN_SOURCES_ENTRY,	/* GConf XML */
+	PARSE_STATE_IN_SOURCES_VALUE,	/* GConf XML */
+
+	PARSE_STATE_IN_GROUP,		/* ESource XML */
+	PARSE_STATE_IN_SOURCE,		/* ESource XML */
+	PARSE_STATE_IN_PROPERTIES	/* ESource XML */
+} ParseState;
+
+struct _ParseData {
+	ParseState state;
+
+	/* Set by <group> tags. */
+	gchar *base_uri;
+	gboolean writable_hint;
+
+	/* Set by <source> tags. */
+	gchar *filename;
+	gchar *mangled_uri;
+	GKeyFile *key_file;
+	SoupURI *soup_uri;
+	PropertyFunc property_func;
+};
+
+static GnomeKeyringPasswordSchema schema = {
+	GNOME_KEYRING_ITEM_GENERIC_SECRET,
+	{
+		{ KEYRING_ITEM_ATTRIBUTE_NAME,
+		  GNOME_KEYRING_ATTRIBUTE_TYPE_STRING },
+		{ NULL, 0 }
+	}
+};
+
+/* Forward Declarations */
+void evolution_addressbook_factory_migrate_sources (void);
+
+static ParseData *
+parse_data_new (void)
+{
+	ParseData *parse_data;
+
+	parse_data = g_slice_new0 (ParseData);
+	parse_data->state = PARSE_STATE_INITIAL;
+
+	return parse_data;
+}
+
+static void
+parse_data_free (ParseData *parse_data)
+{
+	/* Normally the allocated data in ParseData is freed and the
+	 * pointers are cleared before we get here.  But if an error
+	 * occurred we may leave data behind.  This cleans it up. */
+
+	g_free (parse_data->base_uri);
+	g_free (parse_data->filename);
+	g_free (parse_data->mangled_uri);
+
+	if (parse_data->key_file != NULL)
+		g_key_file_free (parse_data->key_file);
+
+	if (parse_data->soup_uri != NULL)
+		soup_uri_free (parse_data->soup_uri);
+
+	g_slice_free (ParseData, parse_data);
+}
+
+static gboolean
+is_true (const gchar *string)
+{
+	return  (g_ascii_strcasecmp (string, "1") == 0) ||
+		(g_ascii_strcasecmp (string, "true") == 0);
+}
+
+static void
+migrate_keyring_entry (ParseData *parse_data)
+{
+	GnomeKeyringAttributeList *attributes;
+	GList *found_list = NULL;
+	gchar *display_name;
+	gchar *uid;
+
+	/* This is a best-effort routine, so we don't really care about
+	 * errors.  We leave the old keyring entry in place since it may
+	 * be reused for calendar or mail migration. */
+
+	uid = g_path_get_basename (parse_data->filename);
+	display_name = g_strdup_printf (KEYRING_ITEM_DISPLAY_FORMAT, uid);
+
+	attributes = gnome_keyring_attribute_list_new ();
+
+	gnome_keyring_attribute_list_append_string (
+		attributes, "application", "Evolution");
+	if (parse_data->soup_uri->user != NULL)
+		gnome_keyring_attribute_list_append_string (
+			attributes, "user", parse_data->soup_uri->user);
+	if (parse_data->soup_uri->host != NULL)
+		gnome_keyring_attribute_list_append_string (
+			attributes, "server", parse_data->soup_uri->host);
+	if (parse_data->soup_uri->scheme != NULL)
+		gnome_keyring_attribute_list_append_string (
+			attributes, "protocol", parse_data->soup_uri->scheme);
+
+	gnome_keyring_find_items_sync (
+		GNOME_KEYRING_ITEM_NETWORK_PASSWORD, attributes, &found_list);
+
+	/* Pick the first match we find. */
+	if (found_list != NULL) {
+		GnomeKeyringFound *found = found_list->data;
+
+		/* Sanity check. */
+		g_return_if_fail (found->secret != NULL);
+
+		gnome_keyring_store_password_sync (
+			&schema, GNOME_KEYRING_DEFAULT, display_name,
+			found->secret, KEYRING_ITEM_ATTRIBUTE_NAME, uid, NULL);
+	}
+
+	gnome_keyring_attribute_list_free (attributes);
+	gnome_keyring_found_list_free (found_list);
+
+	g_free (display_name);
+	g_free (uid);
+}
+
+static gboolean
+migrate_parse_commit_changes (ParseData *parse_data,
+                              GError **error)
+{
+	const gchar *data_dir;
+	const gchar *cache_dir;
+	gchar *old_directory;
+	gchar *new_directory;
+	gchar *contents;
+	gchar *uid;
+	gsize length;
+	gboolean success;
+	gboolean old_directory_exists;
+	gboolean new_directory_exists;
+
+	data_dir = e_get_user_data_dir ();
+	cache_dir = e_get_user_cache_dir ();
+
+	uid = g_path_get_basename (parse_data->filename);
+
+	g_print ("  * Source: %s\n", uid);
+
+	g_print ("    Writing key file...\n");
+
+	/* Save the key file contents to disk. */
+	contents = g_key_file_to_data (
+		parse_data->key_file, &length, NULL);
+	success = g_file_set_contents (
+		parse_data->filename, contents, length, error);
+	g_free (contents);
+
+	if (!success)
+		goto exit;
+
+	/* Rename the source's cache directory from its mangled URI
+	 * to its UID.  The key file's basename is also the UID.  All
+	 * source types but "local" should have cache directories. */
+
+	old_directory = g_build_filename (
+		cache_dir, "addressbook", parse_data->mangled_uri, NULL);
+
+	new_directory = g_build_filename (
+		cache_dir, "addressbook", uid, NULL);
+
+	old_directory_exists = g_file_test (old_directory, G_FILE_TEST_EXISTS);
+	new_directory_exists = g_file_test (new_directory, G_FILE_TEST_EXISTS);
+
+	g_print (
+		"    Checking for old cache dir '%s'... %s\n",
+		old_directory,
+		old_directory_exists ?  "found" : "not found");
+
+	if (old_directory_exists) {
+		g_print (
+			"    Checking for new cache dir '%s'... %s\n",
+			new_directory,
+			new_directory_exists ? "found" : "not found");
+
+		if (new_directory_exists)
+			g_print ("    Skipping cache directory rename.\n");
+		else {
+			g_print ("    Renaming old cache directory...\n");
+			if (g_rename (old_directory, new_directory) < 0) {
+				g_set_error (
+					error, G_FILE_ERROR,
+					g_file_error_from_errno (errno),
+					"%s", g_strerror (errno));
+				success = FALSE;
+			}
+		}
+	}
+
+	g_free (old_directory);
+	g_free (new_directory);
+
+	if (!success)
+		goto exit;
+
+	/* Rename the source's local data directory from its mangled
+	 * URI to its UID.  The key file's basename is also the UID.
+	 * Only "local" sources have local data directores. */
+
+	old_directory = g_build_filename (
+		data_dir, "addressbook", parse_data->mangled_uri, NULL);
+
+	new_directory = g_build_filename (
+		data_dir, "addressbook", uid, NULL);
+
+	old_directory_exists = g_file_test (old_directory, G_FILE_TEST_EXISTS);
+	new_directory_exists = g_file_test (new_directory, G_FILE_TEST_EXISTS);
+
+	g_print (
+		"    Checking for old data dir '%s'... %s\n",
+		old_directory,
+		old_directory_exists ?  "found" : "not found");
+
+	if (old_directory_exists) {
+		g_print (
+			"    Checking for new data dir '%s'... %s\n",
+			new_directory,
+			new_directory_exists ? "found" : "not found");
+
+		if (new_directory_exists)
+			g_print ("    Skipping data directory rename.\n");
+		else {
+			g_print ("    Renaming old data directory...\n");
+			if (g_rename (old_directory, new_directory) < 0) {
+				g_set_error (
+					error, G_FILE_ERROR,
+					g_file_error_from_errno (errno),
+					"%s", g_strerror (errno));
+				success = FALSE;
+			}
+		}
+	}
+
+	g_free (old_directory);
+	g_free (new_directory);
+
+exit:
+	g_free (uid);
+
+	return success;
+}
+
+static void
+migrate_parse_local_source (ParseData *parse_data)
+{
+	/* Local Backend has no special properties to parse. */
+}
+
+static void
+migrate_parse_google_property (ParseData *parse_data,
+                               const gchar *property_name,
+                               const gchar *property_value)
+{
+	if (g_strcmp0 (property_name, "refresh-interval") == 0) {
+		guint64 interval_seconds;
+
+		interval_seconds =
+			g_ascii_strtoull (property_value, NULL, 10);
+
+		if (interval_seconds >= 60) {
+			g_key_file_set_boolean (
+				parse_data->key_file,
+				E_SOURCE_EXTENSION_REFRESH,
+				"Enabled", TRUE);
+			g_key_file_set_uint64 (
+				parse_data->key_file,
+				E_SOURCE_EXTENSION_REFRESH,
+				"IntervalMinutes",
+				interval_seconds / 60);
+		}
+
+	} else if (g_strcmp0 (property_name, "ssl") == 0) {
+		g_key_file_set_string (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_AUTHENTICATION,
+			"Method",
+			is_true (property_value) ?
+			"tls" : "none");
+
+	} else if (g_strcmp0 (property_name, "username") == 0) {
+		g_key_file_set_string (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_AUTHENTICATION,
+			"User", property_value);
+	}
+}
+
+static void
+migrate_parse_google_source (ParseData *parse_data)
+{
+	parse_data->property_func = migrate_parse_google_property;
+}
+
+static void
+migrate_parse_ldap_property (ParseData *parse_data,
+                             const gchar *property_name,
+                             const gchar *property_value)
+{
+	if (g_strcmp0 (property_name, "can-browse") == 0) {
+		g_key_file_set_boolean (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_LDAP_BACKEND,
+			"CanBrowse",
+			is_true (property_value));
+
+	/* This is an integer value, but we can use the string as is. */
+	} else if (g_strcmp0 (property_name, "limit") == 0) {
+		g_key_file_set_string (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_LDAP_BACKEND,
+			"Limit", property_value);
+
+	} else if (g_strcmp0 (property_name, "ssl") == 0) {
+		if (g_strcmp0 (property_value, "always") == 0)
+			g_key_file_set_string (
+				parse_data->key_file,
+				E_SOURCE_EXTENSION_SECURITY,
+				"Method", "starttls");
+		else if (g_strcmp0 (property_value, "whenever_possible") == 0)
+			g_key_file_set_string (
+				parse_data->key_file,
+				E_SOURCE_EXTENSION_SECURITY,
+				"Method", "ldaps");
+	}
+}
+
+static void
+migrate_parse_ldap_source (ParseData *parse_data)
+{
+	if (parse_data->soup_uri->host != NULL)
+		g_key_file_set_string (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_AUTHENTICATION,
+			"Host", parse_data->soup_uri->host);
+
+	if (parse_data->soup_uri->port != 0)
+		g_key_file_set_integer (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_AUTHENTICATION,
+			"Port", parse_data->soup_uri->port);
+
+	if (parse_data->soup_uri->user != NULL)
+		g_key_file_set_string (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_AUTHENTICATION,
+			"User", parse_data->soup_uri->user);
+
+	/* Skip the leading slash on the URI path to get the RootDn. */
+	if (parse_data->soup_uri->path != NULL)
+		g_key_file_set_string (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_LDAP_BACKEND,
+			"RootDn", parse_data->soup_uri->path + 1);
+
+	if (g_strcmp0 (parse_data->soup_uri->query, "?sub?") == 0)
+		g_key_file_set_string (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_LDAP_BACKEND,
+			"Scope", "subtree");
+
+	if (g_strcmp0 (parse_data->soup_uri->query, "?one?") == 0)
+		g_key_file_set_string (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_LDAP_BACKEND,
+			"Scope", "onelevel");
+
+	parse_data->property_func = migrate_parse_ldap_property;
+}
+
+static void
+migrate_parse_vcf_source (ParseData *parse_data)
+{
+	if (parse_data->soup_uri->path != NULL)
+		g_key_file_set_string (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_VCF_BACKEND,
+			"Path", parse_data->soup_uri->path);
+
+	/* VCF Backend has no special properties to parse. */
+}
+
+static void
+migrate_parse_webdav_property (ParseData *parse_data,
+                               const gchar *property_name,
+                               const gchar *property_value)
+{
+	if (g_strcmp0 (property_name, "avoid_ifmatch") == 0) {
+		g_key_file_set_boolean (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_WEBDAV_BACKEND,
+			"AvoidIfmatch",
+			is_true (property_value));
+	}
+}
+
+static void
+migrate_parse_webdav_source (ParseData *parse_data)
+{
+	if (parse_data->soup_uri->host != NULL)
+		g_key_file_set_string (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_AUTHENTICATION,
+			"Host", parse_data->soup_uri->host);
+
+	if (parse_data->soup_uri->port != 0)
+		g_key_file_set_integer (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_AUTHENTICATION,
+			"Port", parse_data->soup_uri->port);
+
+	if (parse_data->soup_uri->user != NULL)
+		g_key_file_set_string (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_AUTHENTICATION,
+			"User", parse_data->soup_uri->user);
+
+	if (parse_data->soup_uri->path != NULL)
+		g_key_file_set_string (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_WEBDAV_BACKEND,
+			"Path", parse_data->soup_uri->path);
+
+	parse_data->property_func = migrate_parse_webdav_property;
+}
+
+static void
+migrate_parse_group (ParseData *parse_data,
+                     const gchar *element_name,
+                     const gchar **attribute_names,
+                     const gchar **attribute_values,
+                     GError **error)
+{
+	const gchar *base_uri;
+	gboolean readonly;
+	gboolean success;
+
+	success = g_markup_collect_attributes (
+		element_name,
+		attribute_names,
+		attribute_values,
+		error,
+		G_MARKUP_COLLECT_STRING,
+		"uid", NULL,
+		G_MARKUP_COLLECT_STRING,
+		"name", NULL,
+		G_MARKUP_COLLECT_STRING,
+		"base_uri", &base_uri,
+		G_MARKUP_COLLECT_BOOLEAN,
+		"readonly", &readonly,
+		G_MARKUP_COLLECT_INVALID);
+
+	if (!success)
+		return;
+
+	/* Convert "file://" schemes to "local:". */
+	if (g_strcmp0 (base_uri, "file://") == 0)
+		base_uri = "local:";
+
+	parse_data->base_uri = g_strdup (base_uri);
+	parse_data->writable_hint = !readonly;
+}
+
+static void
+migrate_parse_source (ParseData *parse_data,
+                      const gchar *element_name,
+                      const gchar **attribute_names,
+                      const gchar **attribute_values,
+                      GError **error)
+{
+	const gchar *uid;
+	const gchar *name;
+	const gchar *config_dir;
+	const gchar *absolute_uri;
+	const gchar *relative_uri;
+	gchar *directory;
+	gchar *parent, *cp;
+	gchar *uri_string;
+	gboolean success;
+
+	success = g_markup_collect_attributes (
+		element_name,
+		attribute_names,
+		attribute_values,
+		error,
+		G_MARKUP_COLLECT_STRING,
+		"uid", &uid,
+		G_MARKUP_COLLECT_STRING,
+		"name", &name,
+		G_MARKUP_COLLECT_STRING,
+		"relative_uri", &relative_uri,
+		G_MARKUP_COLLECT_STRING |
+		G_MARKUP_COLLECT_OPTIONAL,
+		"uri", &absolute_uri,
+		G_MARKUP_COLLECT_INVALID);
+
+	if (!success)
+		return;
+
+	/* Don't try and migrate the "system" source, we'll reset to
+	 * the built-in key file.  The "system" source is a special case
+	 * that combines address book, calendar, task list and memo list
+	 * groups into one file, so it breaks the migration logic here.
+	 * Means the user may lose a custom color.  Not a big deal. */
+	if (g_strcmp0 (relative_uri, "system") == 0)
+		return;
+
+	config_dir = e_get_user_config_dir ();
+	directory = g_build_filename (config_dir, "sources", NULL);
+	g_mkdir_with_parents (directory, 0700);
+
+	parse_data->filename = g_build_filename (directory, uid, NULL);
+
+	g_free (directory);
+
+	/* If the file already exists, skip this source.  It may be that we
+	 * already migrated it, in which case we don't want to overwrite it. */
+	if (g_file_test (parse_data->filename, G_FILE_TEST_EXISTS))
+		return;
+
+	parse_data->key_file = g_key_file_new ();
+
+	/* Trim ':' or '://' off the base_uri to get the parent name. */
+	parent = g_strdup (parse_data->base_uri);
+	if ((cp = strchr (parent, ':')) != NULL)
+		*cp = '\0';
+
+	g_key_file_set_string (
+		parse_data->key_file,
+		E_SOURCE_GROUP_NAME,
+		"DisplayName", name);
+
+	g_key_file_set_string (
+		parse_data->key_file,
+		E_SOURCE_GROUP_NAME,
+		"Parent", parent);
+
+	g_key_file_set_boolean (
+		parse_data->key_file,
+		E_SOURCE_EXTENSION_ADDRESS_BOOK,
+		"WritableHint", parse_data->writable_hint);
+
+	g_free (parent);
+
+	/* Prefer absolute URIs over relative URIs.  All these
+	 * other strange rules are for backward-compatibility. */
+	if (absolute_uri != NULL)
+		uri_string = g_strdup (absolute_uri);
+	else if (g_str_has_suffix (parse_data->base_uri, "/"))
+		uri_string = g_strconcat (
+			parse_data->base_uri, relative_uri, NULL);
+	else if (g_strcmp0 (parse_data->base_uri, "local:") == 0)
+		uri_string = g_strconcat (
+			parse_data->base_uri, relative_uri, NULL);
+	else
+		uri_string = g_strconcat (
+			parse_data->base_uri, "/", relative_uri, NULL);
+
+	parse_data->soup_uri = soup_uri_new (uri_string);
+
+	/* Mangle the URI to not contain invalid characters.  We'll need
+	 * this later to rename the source's cache and data directories. */
+	parse_data->mangled_uri = g_strdelimit (uri_string, ":/", '_');
+
+	/* g_strdelimit() modifies the input string in place, so ParseData
+	 * now owns 'uri_string'.  Clear the pointer to emphasize that. */
+	uri_string = NULL;
+
+	if (parse_data->soup_uri == NULL) {
+		g_warning (
+			"  Failed to parse source URI: %s",
+			(absolute_uri != NULL) ? absolute_uri : relative_uri);
+		g_key_file_free (parse_data->key_file);
+		parse_data->key_file = NULL;
+		return;
+	}
+
+	if (g_strcmp0 (parse_data->base_uri, "local:") == 0)
+		migrate_parse_local_source (parse_data);
+
+	else if (g_strcmp0 (parse_data->base_uri, "google://") == 0)
+		migrate_parse_google_source (parse_data);
+
+	else if (g_strcmp0 (parse_data->base_uri, "ldap://";) == 0)
+		migrate_parse_ldap_source (parse_data);
+
+	else if (g_strcmp0 (parse_data->base_uri, "vcf://") == 0)
+		migrate_parse_vcf_source (parse_data);
+
+	else if (g_strcmp0 (parse_data->base_uri, "webdav://") == 0)
+		migrate_parse_webdav_source (parse_data);
+
+	migrate_keyring_entry (parse_data);
+}
+
+static void
+migrate_parse_property (ParseData *parse_data,
+                        const gchar *element_name,
+                        const gchar **attribute_names,
+                        const gchar **attribute_values,
+                        GError **error)
+{
+	const gchar *property_name;
+	const gchar *property_value;
+	gboolean success;
+
+	success = g_markup_collect_attributes (
+		element_name,
+		attribute_names,
+		attribute_values,
+		error,
+		G_MARKUP_COLLECT_STRING,
+		"name", &property_name,
+		G_MARKUP_COLLECT_STRING,
+		"value", &property_value,
+		G_MARKUP_COLLECT_INVALID);
+
+	if (!success)
+		return;
+
+	if (g_strcmp0 (property_name, "auth") == 0) {
+		g_key_file_set_string (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_AUTHENTICATION,
+			"Method", property_value);
+
+	} else if (g_strcmp0 (property_name, "completion") == 0) {
+		g_key_file_set_boolean (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_AUTOCOMPLETE,
+			"IncludeMe",
+			is_true (property_value));
+
+	} else if (g_strcmp0 (property_name, "offline_sync") == 0) {
+		g_key_file_set_boolean (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_OFFLINE,
+			"StaySynchronized",
+			is_true (property_value));
+
+	} else if (g_strcmp0 (property_name, "remember_password") == 0) {
+		g_key_file_set_boolean (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_AUTHENTICATION,
+			"RememberPassword",
+			is_true (property_value));
+
+	} else if (g_strcmp0 (property_name, "use_ssl") == 0) {
+		g_key_file_set_string (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_SECURITY,
+			"Method",
+			is_true (property_value) ?
+			"tls" : "none");
+
+	} else if (g_strcmp0 (property_name, "use-in-contacts-calendar") == 0) {
+		g_key_file_set_boolean (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_CONTACTS_BACKEND,
+			"IncludeMe",
+			is_true (property_value));
+
+	} else if (parse_data->property_func != NULL) {
+		parse_data->property_func (
+			parse_data, property_name, property_value);
+	}
+}
+
+static void
+migrate_parse_source_xml_start_element (GMarkupParseContext *context,
+                                        const gchar *element_name,
+                                        const gchar **attribute_names,
+                                        const gchar **attribute_values,
+                                        gpointer user_data,
+                                        GError **error)
+{
+	ParseData *parse_data = user_data;
+
+	if (g_strcmp0 (element_name, "group") == 0) {
+		if (parse_data->state != PARSE_STATE_IN_SOURCES_VALUE)
+			goto invalid_content;
+
+		parse_data->state = PARSE_STATE_IN_GROUP;
+
+		migrate_parse_group (
+			parse_data,
+			element_name,
+			attribute_names,
+			attribute_values,
+			error);
+
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "source") == 0) {
+		if (parse_data->state != PARSE_STATE_IN_GROUP)
+			goto invalid_content;
+
+		parse_data->state = PARSE_STATE_IN_SOURCE;
+
+		migrate_parse_source (
+			parse_data,
+			element_name,
+			attribute_names,
+			attribute_values,
+			error);
+
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "properties") == 0) {
+		/* Disregard group properties, we're only
+		 * interested in source properties. */
+		if (parse_data->state == PARSE_STATE_IN_GROUP)
+			return;
+
+		if (parse_data->state != PARSE_STATE_IN_SOURCE)
+			goto invalid_content;
+
+		parse_data->state = PARSE_STATE_IN_PROPERTIES;
+
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "property") == 0) {
+		/* Disregard group properties, we're only
+		 * interested in source properties. */
+		if (parse_data->state == PARSE_STATE_IN_GROUP)
+			return;
+
+		if (parse_data->state != PARSE_STATE_IN_PROPERTIES)
+			goto invalid_content;
+
+		/* The key file will be NULL if we decided to skip it.
+		 * e.g. A file with the same UID may already exist. */
+		if (parse_data->key_file != NULL)
+			migrate_parse_property (
+				parse_data,
+				element_name,
+				attribute_names,
+				attribute_values,
+				error);
+
+		return;
+	}
+
+	g_set_error (
+		error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+		"Unknown element <%s>", element_name);
+
+	return;
+
+invalid_content:
+
+	g_set_error (
+		error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
+		"Element <%s> at unexpected location", element_name);
+}
+
+static void
+migrate_parse_source_xml_end_element (GMarkupParseContext *context,
+                                      const gchar *element_name,
+                                      gpointer user_data,
+                                      GError **error)
+{
+	ParseData *parse_data = user_data;
+
+	if (g_strcmp0 (element_name, "group") == 0) {
+		if (parse_data->state == PARSE_STATE_IN_GROUP) {
+			parse_data->state = PARSE_STATE_IN_SOURCES_VALUE;
+
+			/* Clean up <group> tag data. */
+
+			g_free (parse_data->base_uri);
+			parse_data->base_uri = NULL;
+		}
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "source") == 0) {
+		if (parse_data->state == PARSE_STATE_IN_SOURCE) {
+			parse_data->state = PARSE_STATE_IN_GROUP;
+
+			/* Clean up <source> tag data. */
+
+			/* The key file will be NULL if we decided to skip it.
+			 * e.g. A file with the same UID may already exist. */
+			if (parse_data->key_file != NULL) {
+				GError *local_error = NULL;
+
+				migrate_parse_commit_changes (
+					parse_data, &local_error);
+
+				if (local_error != NULL) {
+					g_printerr (
+						"  FAILED: %s\n",
+						local_error->message);
+					g_error_free (local_error);
+				}
+
+				g_key_file_free (parse_data->key_file);
+				parse_data->key_file = NULL;
+			}
+
+			g_free (parse_data->filename);
+			parse_data->filename = NULL;
+
+			g_free (parse_data->mangled_uri);
+			parse_data->mangled_uri = NULL;
+
+			if (parse_data->soup_uri != NULL) {
+				soup_uri_free (parse_data->soup_uri);
+				parse_data->soup_uri = NULL;
+			}
+
+			parse_data->property_func = NULL;
+		}
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "properties") == 0) {
+		if (parse_data->state == PARSE_STATE_IN_PROPERTIES)
+			parse_data->state = PARSE_STATE_IN_SOURCE;
+		return;
+	}
+}
+
+static GMarkupParser source_xml_parser = {
+	migrate_parse_source_xml_start_element,
+	migrate_parse_source_xml_end_element,
+	NULL,  /* text */
+	NULL,  /* passthrough */
+	NULL   /* error */
+};
+
+static void
+migrate_parse_gconf_xml_start_element (GMarkupParseContext *context,
+                                       const gchar *element_name,
+                                       const gchar **attribute_names,
+                                       const gchar **attribute_values,
+                                       gpointer user_data,
+                                       GError **error)
+{
+	ParseData *parse_data = user_data;
+
+	if (g_strcmp0 (element_name, "gconf") == 0) {
+		if (parse_data->state != PARSE_STATE_INITIAL)
+			goto invalid_content;
+
+		parse_data->state = PARSE_STATE_IN_GCONF;
+
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "entry") == 0) {
+		const gchar *name;
+		gboolean success;
+
+		if (parse_data->state != PARSE_STATE_IN_GCONF)
+			goto invalid_content;
+
+		success = g_markup_collect_attributes (
+			element_name,
+			attribute_names,
+			attribute_values,
+			error,
+			G_MARKUP_COLLECT_STRING,
+			"name", &name,
+			G_MARKUP_COLLECT_STRING,
+			"mtime", NULL,
+			G_MARKUP_COLLECT_STRING |
+			G_MARKUP_COLLECT_OPTIONAL,
+			"type", NULL,
+			G_MARKUP_COLLECT_STRING |
+			G_MARKUP_COLLECT_OPTIONAL,
+			"ltype", NULL,
+			G_MARKUP_COLLECT_STRING |
+			G_MARKUP_COLLECT_OPTIONAL,
+			"schema", NULL,
+			G_MARKUP_COLLECT_INVALID);
+
+		if (success && g_strcmp0 (name, "sources") == 0)
+			parse_data->state = PARSE_STATE_IN_SOURCES_ENTRY;
+
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "li") == 0) {
+		if (parse_data->state != PARSE_STATE_IN_SOURCES_ENTRY)
+			goto invalid_content;
+
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "stringvalue") == 0) {
+		if (parse_data->state != PARSE_STATE_IN_SOURCES_ENTRY)
+			goto invalid_content;
+
+		parse_data->state = PARSE_STATE_IN_SOURCES_VALUE;
+
+		return;
+	}
+
+	g_set_error (
+		error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+		"Unknown element <%s>", element_name);
+
+	return;
+
+invalid_content:
+
+	g_set_error (
+		error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
+		"Element <%s> at unexpected location", element_name);
+}
+
+static void
+migrate_parse_gconf_xml_end_element (GMarkupParseContext *context,
+                                     const gchar *element_name,
+                                     gpointer user_data,
+                                     GError **error)
+{
+	ParseData *parse_data = user_data;
+
+	if (g_strcmp0 (element_name, "gconf") == 0) {
+		if (parse_data->state == PARSE_STATE_IN_GCONF)
+			parse_data->state = PARSE_STATE_INITIAL;
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "entry") == 0) {
+		if (parse_data->state == PARSE_STATE_IN_SOURCES_ENTRY)
+			parse_data->state = PARSE_STATE_IN_GCONF;
+		return;
+	}
+
+	if (g_strcmp0 (element_name, "stringvalue") == 0) {
+		if (parse_data->state == PARSE_STATE_IN_SOURCES_VALUE)
+			parse_data->state = PARSE_STATE_IN_SOURCES_ENTRY;
+		return;
+	}
+}
+
+static void
+migrate_parse_gconf_xml_text (GMarkupParseContext *context,
+                              const gchar *text,
+                              gsize length,
+                              gpointer user_data,
+                              GError **error)
+{
+	ParseData *parse_data = user_data;
+
+	if (parse_data->state != PARSE_STATE_IN_SOURCES_VALUE)
+		return;
+
+	/* The source data is encoded XML stuffed into GConf XML (yuck!).
+	 * Fortunately GMarkupParseContext decodes the XML for us, so we
+	 * just have to feed it to a nested GMarkupParseContext. */
+
+	context = g_markup_parse_context_new (
+		&source_xml_parser, 0, parse_data, NULL);
+
+	if (g_markup_parse_context_parse (context, text, length, error))
+		g_markup_parse_context_end_parse (context, error);
+
+	g_markup_parse_context_free (context);
+}
+
+static GMarkupParser gconf_xml_parser = {
+	migrate_parse_gconf_xml_start_element,
+	migrate_parse_gconf_xml_end_element,
+	migrate_parse_gconf_xml_text,
+	NULL,  /* passthrough */
+	NULL   /* error */
+};
+
+static gboolean
+migrate_parse_gconf_xml (const gchar *contents,
+                         gsize length,
+                         GError **error)
+{
+	GMarkupParseContext *context;
+	ParseData *parse_data;
+	gboolean success = FALSE;
+
+	parse_data = parse_data_new ();
+
+	context = g_markup_parse_context_new (
+		&gconf_xml_parser, 0, parse_data,
+		(GDestroyNotify) parse_data_free);
+
+	if (g_markup_parse_context_parse (context, contents, length, error))
+		if (g_markup_parse_context_end_parse (context, error))
+			success = TRUE;
+
+	g_markup_parse_context_free (context);
+
+	return success;
+}
+
+static void
+migrate_remove_gconf_xml (const gchar *gconf_key,
+                          const gchar *gconf_xml)
+{
+	/* Remove the GConf string list so the user is not haunted by
+	 * old data sources being resurrected from leftover GConf data.
+	 * Also delete the %gconf.xml file itself.  If gconfd is running
+	 * then it will just recreate the file from memory when it exits
+	 * (which is why we invoke gconftool-2), otherwise the file will
+	 * stay deleted. */
+
+	gchar *path_to_program;
+
+	path_to_program = g_find_program_in_path ("gconftool-2");
+
+	if (path_to_program != NULL) {
+		gchar *command_line;
+		GError *error = NULL;
+
+		command_line = g_strjoin (
+			" ",
+			path_to_program,
+			"--set",
+			"--type=list",
+			"--list-type=string",
+			gconf_key, "[]", NULL);
+
+		/* We don't really care if the command worked or not,
+		 * just check that the program got spawned successfully. */
+		if (!g_spawn_command_line_async (command_line, &error)) {
+			g_printerr (
+				"Failed to spawn '%s': %s\n",
+				path_to_program, error->message);
+			g_error_free (error);
+		}
+
+		g_free (path_to_program);
+		g_free (command_line);
+	}
+
+	if (g_file_test (gconf_xml, G_FILE_TEST_IS_REGULAR)) {
+		if (g_remove (gconf_xml) == -1) {
+			g_printerr (
+				"Failed to remove '%s': %s\n",
+				gconf_xml, g_strerror (errno));
+		}
+	}
+}
+
+void
+evolution_addressbook_factory_migrate_sources (void)
+{
+	gchar *base_dir;
+	gchar *contents;
+	gchar *gconf_xml;
+	gsize length;
+	const gchar *gconf_key;
+	GError *error = NULL;
+
+	base_dir = g_build_filename (
+		g_get_home_dir (), ".gconf", "apps", "evolution", NULL);
+
+	g_print ("Migrating addressbook sources from GConf...\n");
+
+	gconf_xml = g_build_filename (
+		base_dir, "addressbook", "%gconf.xml", NULL);
+	g_file_get_contents (gconf_xml, &contents, &length, &error);
+
+	if (error == NULL) {
+		migrate_parse_gconf_xml (contents, length, &error);
+		g_free (contents);
+	}
+
+	if (error == NULL) {
+		gconf_key = "/apps/evolution/addressbook/sources";
+		migrate_remove_gconf_xml (gconf_key, gconf_xml);
+	} else {
+		g_printerr ("  FAILED: %s\n", error->message);
+		g_clear_error (&error);
+	}
+
+	g_free (gconf_xml);
+	g_free (base_dir);
+}
diff --git a/services/evolution-addressbook-factory/evolution-addressbook-factory.c b/services/evolution-addressbook-factory/evolution-addressbook-factory.c
index 4577e8c..7b36c59 100644
--- a/services/evolution-addressbook-factory/evolution-addressbook-factory.c
+++ b/services/evolution-addressbook-factory/evolution-addressbook-factory.c
@@ -50,6 +50,7 @@ static GOptionEntry entries[] = {
 
 /* Forward Declarations */
 void evolution_addressbook_factory_migrate_basedir (void);
+void evolution_addressbook_factory_migrate_sources (void);
 
 gint
 main (gint argc,
@@ -114,6 +115,11 @@ main (gint argc,
 	/* Migrate user data from ~/.evolution to XDG base directories. */
 	evolution_addressbook_factory_migrate_basedir ();
 
+	/* Migrate ESource data from GConf XML blobs to key files.
+	 * Do this AFTER XDG base directory migration since the key
+	 * files are saved according to XDG base directory settings. */
+	evolution_addressbook_factory_migrate_sources ();
+
 	server = e_data_book_factory_new (NULL, &error);
 
 	if (error != NULL) {



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