[evolution-data-server/account-mgmt: 29/30] Migrate calendar XML data to key files.
- From: Matthew Barnes <mbarnes src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [evolution-data-server/account-mgmt: 29/30] Migrate calendar XML data to key files.
- Date: Fri, 18 Mar 2011 01:53:54 +0000 (UTC)
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]