[evolution-data-server/account-mgmt: 29/30] Migrate calendar XML data to key files.



commit 017f8274d9c90c6adb19ab622351f03b4df85598
Author: Matthew Barnes <mbarnes redhat com>
Date:   Thu Mar 10 22:52:23 2011 -0500

    Migrate calendar 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.

 calendar/libedata-cal/Makefile.am                  |    8 +-
 calendar/libedata-cal/e-data-cal-factory.c         |   45 +-
 calendar/libedata-cal/e-data-cal-migrate-sources.c | 1291 ++++++++++++++++++++
 3 files changed, 1324 insertions(+), 20 deletions(-)
---
diff --git a/calendar/libedata-cal/Makefile.am b/calendar/libedata-cal/Makefile.am
index d9f71cf..7bea938 100644
--- a/calendar/libedata-cal/Makefile.am
+++ b/calendar/libedata-cal/Makefile.am
@@ -94,6 +94,7 @@ e_calendar_factory_SOURCES =		\
 	e-data-cal-factory.c		\
 	e-data-cal-factory.h		\
 	e-data-cal-migrate-basedir.c	\
+	e-data-cal-migrate-sources.c	\
 	e-cal-backend-loader-factory.c	\
 	e-cal-backend-loader-factory.h
 
@@ -109,6 +110,8 @@ e_calendar_factory_CPPFLAGS = \
 	-I$(top_builddir)				\
 	-I$(top_builddir)/calendar			\
 	$(LIBICAL_CFLAGS)				\
+	$(SOUP_CFLAGS)					\
+	$(E_DATA_SERVER_CFLAGS)				\
 	$(EVOLUTION_CALENDAR_CFLAGS)
 
 e_calendar_factory_LDADD =						\
@@ -116,7 +119,10 @@ e_calendar_factory_LDADD =						\
 	$(top_builddir)/calendar/libegdbus/libegdbus-cal.la		\
 	libedata-cal-1.2.la						\
 	$(top_builddir)/libedataserver/libedataserver-1.2.la 		\
-	$(top_builddir)/libebackend/libebackend-1.2.la
+	$(top_builddir)/libebackend/libebackend-1.2.la			\
+	$(SOUP_LIBS)							\
+	$(E_DATA_SERVER_LIBS)						\
+	$(EVOLUTION_CALENDAR_LIBS)
 
 -include $(top_srcdir)/git.mk
 
diff --git a/calendar/libedata-cal/e-data-cal-factory.c b/calendar/libedata-cal/e-data-cal-factory.c
index 3052e7d..9ec6865 100644
--- a/calendar/libedata-cal/e-data-cal-factory.c
+++ b/calendar/libedata-cal/e-data-cal-factory.c
@@ -101,6 +101,7 @@ struct _EDataCalFactoryPrivate {
 
 /* Forward Declarations */
 void e_data_cal_migrate_basedir (void);
+void e_data_cal_migrate_sources (void);
 
 /* Create the EDataCalFactory error quark */
 GQuark
@@ -914,27 +915,8 @@ main (gint argc, gchar **argv)
 	E_TYPE_SOURCE_MEMO_LIST;
 	E_TYPE_SOURCE_TASK_LIST;
 
-	registry = e_source_registry_get_default ();
 	factory = g_object_new (E_TYPE_DATA_CAL_FACTORY, NULL);
 
-	g_signal_connect (
-		registry, "load-error",
-		G_CALLBACK (source_load_error), NULL);
-
-	g_signal_connect (
-		registry, "source-added",
-		G_CALLBACK (source_added), factory);
-
-	g_signal_connect (
-		registry, "source-removed",
-		G_CALLBACK (source_removed), factory);
-
-	/* Failure here is fatal.  Don't even try to keep going. */
-	if (!e_source_registry_load_sources (registry, &error)) {
-		g_error ("%s", error->message);
-		g_assert_not_reached ();
-	}
-
 	loop = g_main_loop_new (NULL, FALSE);
 
 	eol = e_offline_listener_new ();
@@ -956,6 +938,31 @@ main (gint argc, gchar **argv)
 	/* Migrate user data from ~/.evolution to XDG base directories. */
 	e_data_cal_migrate_basedir ();
 
+	/* Migrate ESource data grom 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. */
+	e_data_cal_migrate_sources ();
+
+	registry = e_source_registry_get_default ();
+
+	g_signal_connect (
+		registry, "load-error",
+		G_CALLBACK (source_load_error), NULL);
+
+	g_signal_connect (
+		registry, "source-added",
+		G_CALLBACK (source_added), factory);
+
+	g_signal_connect (
+		registry, "source-removed",
+		G_CALLBACK (source_removed), factory);
+
+	/* Failure here is fatal.  Don't even try to keep going. */
+	if (!e_source_registry_load_sources (registry, &error)) {
+		g_error ("%s", error->message);
+		g_assert_not_reached ();
+	}
+
 #ifndef G_OS_WIN32
 	setup_quit_signal ();
 #endif
diff --git a/calendar/libedata-cal/e-data-cal-migrate-sources.c b/calendar/libedata-cal/e-data-cal-migrate-sources.c
new file mode 100644
index 0000000..82c7d9c
--- /dev/null
+++ b/calendar/libedata-cal/e-data-cal-migrate-sources.c
@@ -0,0 +1,1291 @@
+/*
+ * e-data-cal-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 <libecal/e-cal.h>
+#include <libecal/e-source-calendar.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_CALENDAR		"Calendar"
+#define E_SOURCE_EXTENSION_MEMO_LIST		"Memo List"
+#define E_SOURCE_EXTENSION_TASK_LIST		"Task List"
+#define E_SOURCE_EXTENSION_ALARMS		"Alarms"
+#define E_SOURCE_EXTENSION_AUTHENTICATION	"Authentication"
+#define E_SOURCE_EXTENSION_OFFLINE		"Offline"
+#define E_SOURCE_EXTENSION_REFRESH		"Refresh"
+#define E_SOURCE_EXTENSION_SECURITY		"Security"
+#define E_SOURCE_EXTENSION_CALDAV_BACKEND	"CalDAV Backend"
+#define E_SOURCE_EXTENSION_LOCAL_BACKEND	"Local Backend"
+#define E_SOURCE_EXTENSION_WEATHER_BACKEND	"Weather 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;
+	ECalSourceType source_type;
+	const gchar *selectable_group;
+
+	/* 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 }
+	}
+};
+
+void e_data_cal_migrate_sources (void);
+
+static ParseData *
+parse_data_new (ECalSourceType source_type)
+{
+	ParseData *parse_data;
+
+	parse_data = g_slice_new0 (ParseData);
+	parse_data->state = PARSE_STATE_INITIAL;
+	parse_data->source_type = source_type;
+
+	switch (source_type) {
+		case E_CAL_SOURCE_TYPE_EVENT:
+			parse_data->selectable_group =
+				E_SOURCE_EXTENSION_CALENDAR;
+			break;
+		case E_CAL_SOURCE_TYPE_TODO:
+			parse_data->selectable_group =
+				E_SOURCE_EXTENSION_TASK_LIST;
+			break;
+		case E_CAL_SOURCE_TYPE_JOURNAL:
+			parse_data->selectable_group =
+				E_SOURCE_EXTENSION_MEMO_LIST;
+			break;
+		default:
+			g_warn_if_reached ();
+	}
+
+	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
+data_cal_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 address book 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
+data_cal_parse_commit_changes (ParseData *parse_data,
+                               GError **error)
+{
+	const gchar *data_dir;
+	const gchar *cache_dir;
+	const gchar *component;
+	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;
+
+	switch (parse_data->source_type) {
+		case E_CAL_SOURCE_TYPE_EVENT:
+			component = "calendar";
+			break;
+		case E_CAL_SOURCE_TYPE_TODO:
+			component = "tasks";
+			break;
+		case E_CAL_SOURCE_TYPE_JOURNAL:
+			component = "memos";
+			break;
+		default:
+			component = "";
+			g_warn_if_reached ();
+	}
+
+	/* 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, component, parse_data->mangled_uri, NULL);
+
+	new_directory = g_build_filename (
+		cache_dir, component, 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, component, parse_data->mangled_uri, NULL);
+
+	new_directory = g_build_filename (
+		data_dir, component, 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
+data_cal_parse_local_property (ParseData *parse_data,
+                               const gchar *property_name,
+                               const gchar *property_value)
+{
+	if (g_strcmp0 (property_name, "custom-file") == 0) {
+		gchar *uri;
+
+		/* Property value is a local filename.  Convert it to a
+		 * "file://" URI.
+		 *
+		 * Note: The key is named "CustomFile" instead of, say,
+		 * "CustomURI" because the corresponding ESourceExtension
+		 * property is a GFile.  The fact that ESource saves GFile
+		 * properties as URI strings is an implementation detail. */
+		uri = g_filename_to_uri (property_value, NULL, NULL);
+		if (uri != NULL) {
+			g_key_file_set_string (
+				parse_data->key_file,
+				E_SOURCE_EXTENSION_LOCAL_BACKEND,
+				"CustomFile", uri);
+			g_free (uri);
+		}
+
+	} else if (g_strcmp0 (property_name, "custom-file-readonly") == 0) {
+		gboolean writable_hint;
+
+		/* This overrides parse_data->writable_hint. */
+		writable_hint = !is_true (property_value);
+
+		g_key_file_set_boolean (
+			parse_data->key_file,
+			parse_data->selectable_group,
+			"WritableHint", writable_hint);
+	}
+}
+
+static void
+data_cal_parse_local_source (ParseData *parse_data)
+{
+	parse_data->property_func = data_cal_parse_local_property;
+}
+
+static void
+data_cal_parse_caldav_property (ParseData *parse_data,
+                                const gchar *property_name,
+                                const gchar *property_value)
+{
+	if (g_strcmp0 (property_name, "autoschedule") == 0) {
+		g_key_file_set_boolean (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_CALDAV_BACKEND,
+			"Autoschedule",
+			is_true (property_value));
+
+	} else if (g_strcmp0 (property_name, "usermail") == 0) {
+		g_key_file_set_string (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_CALDAV_BACKEND,
+			"Email", property_value);
+	}
+}
+
+static void
+data_cal_parse_caldav_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);
+
+	/* We may override this later if we see an "ssl" property. */
+	if (parse_data->soup_uri->port == 0)
+		parse_data->soup_uri->port = 80;
+
+	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_CALDAV_BACKEND,
+			"Path", parse_data->soup_uri->path);
+
+	parse_data->property_func = data_cal_parse_caldav_property;
+}
+
+static void
+data_cal_parse_contacts_source (ParseData *parse_data)
+{
+	/* Contacts Backend has no special properties to parse. */
+}
+
+static void
+data_cal_parse_google_property (ParseData *parse_data,
+                                const gchar *property_name,
+                                const gchar *property_value)
+{
+	if (g_strcmp0 (property_name, "username") == 0) {
+		gchar *path;
+
+		g_key_file_set_string (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_AUTHENTICATION,
+			"User", property_value);
+
+		path = g_strdup_printf (
+			"/calendar/dav/%s/events", property_value);
+
+		g_key_file_set_string (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_CALDAV_BACKEND,
+			"Path", path);
+
+		g_free (path);
+	}
+}
+
+static void
+data_cal_parse_google_source (ParseData *parse_data)
+{
+	g_key_file_set_string (
+		parse_data->key_file,
+		E_SOURCE_EXTENSION_AUTHENTICATION,
+		"Host", "www.google.com");
+
+	g_key_file_set_integer (
+		parse_data->key_file,
+		E_SOURCE_EXTENSION_AUTHENTICATION,
+		"Port", 443);
+
+	g_key_file_set_string (
+		parse_data->key_file,
+		E_SOURCE_EXTENSION_SECURITY,
+		"Method", "tls");
+
+	parse_data->property_func = data_cal_parse_google_property;
+}
+
+static void
+data_cal_parse_weather_property (ParseData *parse_data,
+                                 const gchar *property_name,
+                                 const gchar *property_value)
+{
+	/* XXX Temperature property was replaced by units... I think. */
+	if (g_strcmp0 (property_name, "temperature") == 0) {
+		gboolean metric;
+
+		metric = (g_strcmp0 (property_value, "fahrenheit") != 0);
+
+		g_key_file_set_string (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_WEATHER_BACKEND,
+			"Units", metric ? "metric" : "imperial");
+
+	} else if (g_strcmp0 (property_name, "units") == 0) {
+		gboolean metric;
+
+		metric = (g_strcmp0 (property_value, "metric") == 0);
+
+		g_key_file_set_string (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_WEATHER_BACKEND,
+			"Units", metric ? "metric" : "imperial");
+	}
+}
+
+static void
+data_cal_parse_weather_source (ParseData *parse_data)
+{
+	/* Oh man, we actually try to shove a weather location into
+	 * a URI!  The station code winds up as the host component,
+	 * and the location name winds up as the path component. */
+	if (parse_data->soup_uri->host != NULL) {
+		gchar *location;
+
+		if (parse_data->soup_uri->path != NULL)
+			location = g_strconcat (
+				parse_data->soup_uri->host,
+				parse_data->soup_uri->path, NULL);
+		else
+			location = g_strdup (parse_data->soup_uri->host);
+
+		g_key_file_set_string (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_WEATHER_BACKEND,
+			"Location", location);
+
+		g_free (location);
+	}
+
+
+	parse_data->property_func = data_cal_parse_weather_property;
+}
+
+static void
+data_cal_parse_webcal_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);
+
+	/* We may override this later if we see an "ssl" property. */
+	if (parse_data->soup_uri->port == 0)
+		parse_data->soup_uri->port = 80;
+
+	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_CALDAV_BACKEND,
+			"Path", parse_data->soup_uri->path);
+
+	/* Webcal Backend has no special properties to parse. */
+}
+
+static void
+data_cal_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
+data_cal_parse_source (ParseData *parse_data,
+                       const gchar *element_name,
+                       const gchar **attribute_names,
+                       const gchar **attribute_values,
+                       GError **error)
+{
+	const gchar *data_dir;
+	const gchar *uid;
+	const gchar *name;
+	const gchar *absolute_uri;
+	const gchar *relative_uri;
+	const gchar *color_spec;
+	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,
+		"color_spec", &color_spec,
+		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;
+
+	/* Also skip any sources with a "contacts://" base URI, which
+	 * should just be "Birthdays & Anniversaries".  We'll reset to
+	 * the built-in key file. */
+	if (g_strcmp0 (parse_data->base_uri, "contacts://") == 0)
+		return;
+
+	data_dir = e_get_user_data_dir ();
+	directory = g_build_filename (data_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);
+
+	if (color_spec != NULL)
+		g_key_file_set_string (
+			parse_data->key_file,
+			parse_data->selectable_group,
+			"Color", color_spec);
+
+	/* For Google Calendar sources we override the backend name. */
+	if (g_strcmp0 (parse_data->base_uri, "google://") == 0)
+		g_key_file_set_string (
+			parse_data->key_file,
+			E_SOURCE_GROUP_NAME,
+			"BackendName", "caldav");
+
+	/* Enabled values were stored in a separate GConf key as a list
+	 * of ESource UID strings.  It's not worth parsing a whole other
+	 * %gconf.xml file for that boolean when it's just a checkbox in
+	 * Evolution's sidebar.  Just enable everything initially. */
+	g_key_file_set_boolean (
+		parse_data->key_file,
+		parse_data->selectable_group,
+		"Enabled", TRUE);
+
+	g_key_file_set_boolean (
+		parse_data->key_file,
+		parse_data->selectable_group,
+		"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)
+		data_cal_parse_local_source (parse_data);
+
+	else if (g_strcmp0 (parse_data->base_uri, "caldav://") == 0)
+		data_cal_parse_caldav_source (parse_data);
+
+	else if (g_strcmp0 (parse_data->base_uri, "contacts://") == 0)
+		data_cal_parse_contacts_source (parse_data);
+
+	else if (g_strcmp0 (parse_data->base_uri, "google://") == 0)
+		data_cal_parse_google_source (parse_data);
+
+	else if (g_strcmp0 (parse_data->base_uri, "weather://") == 0)
+		data_cal_parse_weather_source (parse_data);
+
+	else if (g_strcmp0 (parse_data->base_uri, "webcal://") == 0)
+		data_cal_parse_webcal_source (parse_data);
+
+	data_cal_migrate_keyring_entry (parse_data);
+}
+
+static void
+data_cal_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, "alarm") == 0) {
+		g_key_file_set_boolean (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_ALARMS,
+			"IncludeMe",
+			is_true (property_value));
+
+	} else if (g_strcmp0 (property_name, "auth") == 0) {
+		g_key_file_set_string (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_AUTHENTICATION,
+			"Method",
+			is_true (property_value) ?
+			"plain/password" : "none");
+
+	} else if (g_strcmp0 (property_name, "last-notified") == 0) {
+		g_key_file_set_string (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_ALARMS,
+			"LastNotified", 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, "refresh") == 0) {
+		g_key_file_set_string (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_REFRESH,
+			"IntervalMinutes", property_value);
+
+	} else if (g_strcmp0 (property_name, "ssl") == 0) {
+		g_key_file_set_string (
+			parse_data->key_file,
+			E_SOURCE_EXTENSION_SECURITY,
+			"Method",
+			is_true (property_value) ?
+			"tls" : "none");
+
+		/* For WebDAV-based backends we set the port to 80
+		 * (http://) by default.  If we see that and we're
+		 * using a secure connection, bump the port to 443
+		 * (https://). */
+		if (parse_data->soup_uri->port == 80)
+			if (is_true (property_value))
+				g_key_file_set_integer (
+					parse_data->key_file,
+					E_SOURCE_EXTENSION_AUTHENTICATION,
+					"Port", 443);
+
+	} 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");
+
+		/* For WebDAV-based backends we set the port to 80
+		 * (http://) by default.  If we see that and we're
+		 * using a secure connection, bump the port to 443
+		 * (https://). */
+		if (parse_data->soup_uri->port == 80)
+			if (is_true (property_value))
+				g_key_file_set_integer (
+					parse_data->key_file,
+					E_SOURCE_EXTENSION_AUTHENTICATION,
+					"Port", 443);
+
+	} else if (parse_data->property_func != NULL) {
+		parse_data->property_func (
+			parse_data, property_name, property_value);
+	}
+}
+
+static void
+data_cal_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;
+
+		data_cal_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;
+
+		data_cal_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)
+			data_cal_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
+data_cal_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;
+
+				data_cal_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 = {
+	data_cal_parse_source_xml_start_element,
+	data_cal_parse_source_xml_end_element,
+	NULL,  /* text */
+	NULL,  /* passthrough */
+	NULL   /* error */
+};
+
+static void
+data_cal_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
+data_cal_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
+data_cal_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 = {
+	data_cal_parse_gconf_xml_start_element,
+	data_cal_parse_gconf_xml_end_element,
+	data_cal_parse_gconf_xml_text,
+	NULL,  /* passthrough */
+	NULL   /* error */
+};
+
+static gboolean
+data_cal_parse_gconf_xml (ECalSourceType source_type,
+                          const gchar *contents,
+                          gsize length,
+                          GError **error)
+{
+	GMarkupParseContext *context;
+	ParseData *parse_data;
+	gboolean success = FALSE;
+
+	parse_data = parse_data_new (source_type);
+
+	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;
+}
+
+void
+e_data_cal_migrate_sources (void)
+{
+	gchar *base_dir;
+	gchar *filename;
+	gchar *contents;
+	gsize length;
+	GError *error = NULL;
+
+	base_dir = g_build_filename (
+		g_get_home_dir (), ".gconf", "apps", "evolution", NULL);
+
+	g_print ("Migrating calendar sources from GConf...\n");
+
+	filename = g_build_filename (
+		base_dir, "calendar", "%gconf.xml", NULL);
+	g_file_get_contents (filename, &contents, &length, &error);
+	g_free (filename);
+
+	if (error == NULL) {
+		data_cal_parse_gconf_xml (
+			E_CAL_SOURCE_TYPE_EVENT, contents, length, &error);
+		g_free (contents);
+	}
+
+	if (error != NULL) {
+		g_printerr ("  FAILED: %s\n", error->message);
+		g_clear_error (&error);
+	}
+
+	g_print ("Migrating task list sources from GConf...\n");
+
+	filename = g_build_filename (
+		base_dir, "tasks", "%gconf.xml", NULL);
+	g_file_get_contents (filename, &contents, &length, &error);
+	g_free (filename);
+
+	if (error == NULL) {
+		data_cal_parse_gconf_xml (
+			E_CAL_SOURCE_TYPE_TODO, contents, length, &error);
+		g_free (contents);
+	}
+
+	if (error != NULL) {
+		g_printerr ("  FAILED: %s\n", error->message);
+		g_clear_error (&error);
+	}
+
+	g_print ("Migrating task list sources from GConf...\n");
+
+	filename = g_build_filename (
+		base_dir, "memos", "%gconf.xml", NULL);
+	g_file_get_contents (filename, &contents, &length, &error);
+	g_free (filename);
+
+	if (error == NULL) {
+		data_cal_parse_gconf_xml (
+			E_CAL_SOURCE_TYPE_JOURNAL, contents, length, &error);
+		g_free (contents);
+	}
+
+	if (error != NULL) {
+		g_printerr ("  FAILED: %s\n", error->message);
+		g_clear_error (&error);
+	}
+
+	g_free (base_dir);
+}



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