From 6d1aea1b231c120441061c2046157b40e34f8e3a Mon Sep 17 00:00:00 2001 From: Matthew Barnes Date: Sun, 5 Oct 2008 04:12:09 +0000 Subject: Support migration in the new shell design. Some code got duplicated for calendars and tasks. Made a note to revisit. svn path=/branches/kill-bonobo/; revision=36560 --- addressbook/gui/component/Makefile.am | 4 +- addressbook/gui/component/addressbook-migrate.c | 1230 ------------------- addressbook/gui/component/addressbook-migrate.h | 40 - .../gui/component/e-book-shell-module-migrate.c | 1230 +++++++++++++++++++ .../gui/component/e-book-shell-module-migrate.h | 41 + addressbook/gui/component/e-book-shell-module.c | 52 +- .../gui/component/e-book-shell-view-private.c | 2 +- calendar/gui/Makefile.am | 2 - calendar/gui/e-cal-event.c | 22 +- calendar/gui/e-cal-event.h | 16 +- calendar/gui/migration.c | 1245 -------------------- calendar/gui/migration.h | 37 - calendar/modules/e-cal-shell-module-migrate.c | 785 ++++++++++++ calendar/modules/e-cal-shell-module-migrate.h | 38 + calendar/modules/e-cal-shell-module.c | 7 +- calendar/modules/e-memo-shell-module-migrate.c | 257 ++++ calendar/modules/e-memo-shell-module-migrate.h | 38 + calendar/modules/e-memo-shell-module.c | 7 +- calendar/modules/e-task-shell-module-migrate.c | 664 +++++++++++ calendar/modules/e-task-shell-module-migrate.h | 38 + calendar/modules/e-task-shell-module.c | 7 +- calendar/modules/e-task-shell-sidebar.c | 2 +- doc/reference/shell/Makefile.am | 3 + doc/reference/shell/eshell-sections.txt | 4 + doc/reference/shell/tmpl/e-shell-module.sgml | 14 + doc/reference/shell/tmpl/e-shell.sgml | 16 + shell/Makefile.am | 2 + shell/e-shell-migrate.c | 339 ++++++ shell/e-shell-migrate.h | 45 + shell/e-shell-module.c | 35 + shell/e-shell-module.h | 14 + shell/e-shell.c | 43 + shell/e-shell.h | 1 + shell/test/e-test-shell-module.c | 39 +- 34 files changed, 3702 insertions(+), 2617 deletions(-) delete mode 100644 addressbook/gui/component/addressbook-migrate.c delete mode 100644 addressbook/gui/component/addressbook-migrate.h create mode 100644 addressbook/gui/component/e-book-shell-module-migrate.c create mode 100644 addressbook/gui/component/e-book-shell-module-migrate.h delete mode 100644 calendar/gui/migration.c delete mode 100644 calendar/gui/migration.h create mode 100644 calendar/modules/e-cal-shell-module-migrate.c create mode 100644 calendar/modules/e-cal-shell-module-migrate.h create mode 100644 calendar/modules/e-memo-shell-module-migrate.c create mode 100644 calendar/modules/e-memo-shell-module-migrate.h create mode 100644 calendar/modules/e-task-shell-module-migrate.c create mode 100644 calendar/modules/e-task-shell-module-migrate.h create mode 100644 shell/e-shell-migrate.c create mode 100644 shell/e-shell-migrate.h diff --git a/addressbook/gui/component/Makefile.am b/addressbook/gui/component/Makefile.am index 0df52c0336..9bfdabe13d 100644 --- a/addressbook/gui/component/Makefile.am +++ b/addressbook/gui/component/Makefile.am @@ -28,8 +28,6 @@ module_LTLIBRARIES = libevolution-addressbook.la libevolution_addressbook_la_SOURCES = \ addressbook-config.c \ addressbook-config.h \ - addressbook-migrate.c \ - addressbook-migrate.h \ autocompletion-config.c \ autocompletion-config.h \ addressbook.c \ @@ -37,6 +35,8 @@ libevolution_addressbook_la_SOURCES = \ e-book-shell-content.c \ e-book-shell-content.h \ e-book-shell-module.c \ + e-book-shell-module-migrate.c \ + e-book-shell-module-migrate.h \ e-book-shell-sidebar.c \ e-book-shell-sidebar.h \ e-book-shell-view.c \ diff --git a/addressbook/gui/component/addressbook-migrate.c b/addressbook/gui/component/addressbook-migrate.c deleted file mode 100644 index c77c70e2b8..0000000000 --- a/addressbook/gui/component/addressbook-migrate.c +++ /dev/null @@ -1,1230 +0,0 @@ -/* - * 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 - * - * - * Authors: - * Chris Toshok - * - * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) - * - */ - -#include - -#include -#include -#include -#include -#include -#include - -#include -#include - -#include - -#include -#include -#include - -#include - -#include "e-util/e-util.h" -#include "e-util/e-util-private.h" -#include "e-util/e-xml-utils.h" -#include "e-util/e-folder-map.h" - -#include "addressbook-migrate.h" - -/*#define SLOW_MIGRATION*/ - -typedef struct { - /* this hash table maps old folder uris to new uids. It's - build in migrate_contact_folder and it's used in - migrate_completion_folders. */ - GHashTable *folder_uid_map; - - ESourceList *source_list; - - const gchar *data_dir; - - GtkWidget *window; - GtkWidget *label; - GtkWidget *folder_label; - GtkWidget *progress; -} MigrationContext; - -static void -setup_progress_dialog (MigrationContext *context) -{ - GtkWidget *vbox, *hbox; - - context->window = gtk_window_new (GTK_WINDOW_TOPLEVEL); - gtk_window_set_title (GTK_WINDOW (context->window), _("Migrating...")); - gtk_window_set_modal (GTK_WINDOW (context->window), TRUE); - gtk_container_set_border_width (GTK_CONTAINER (context->window), 6); - - vbox = gtk_vbox_new (FALSE, 6); - gtk_widget_show (vbox); - gtk_container_add (GTK_CONTAINER (context->window), vbox); - - context->label = gtk_label_new (""); - gtk_label_set_line_wrap (GTK_LABEL (context->label), TRUE); - gtk_widget_show (context->label); - gtk_box_pack_start_defaults (GTK_BOX (vbox), context->label); - - hbox = gtk_hbox_new (FALSE, 6); - gtk_widget_show (hbox); - gtk_box_pack_start_defaults (GTK_BOX (vbox), hbox); - - context->folder_label = gtk_label_new (""); - gtk_widget_show (context->folder_label); - gtk_box_pack_start_defaults (GTK_BOX (hbox), context->folder_label); - - context->progress = gtk_progress_bar_new (); - gtk_widget_show (context->progress); - gtk_box_pack_start_defaults (GTK_BOX (hbox), context->progress); - - gtk_widget_show (context->window); -} - -static void -dialog_close (MigrationContext *context) -{ - gtk_widget_destroy (context->window); -} - -static void -dialog_set_label (MigrationContext *context, const char *str) -{ - gtk_label_set_text (GTK_LABEL (context->label), str); - - while (gtk_events_pending ()) - gtk_main_iteration (); - -#ifdef SLOW_MIGRATION - sleep (1); -#endif -} - -static void -dialog_set_folder_name (MigrationContext *context, const char *folder_name) -{ - char *text; - - text = g_strdup_printf (_("Migrating '%s':"), folder_name); - gtk_label_set_text (GTK_LABEL (context->folder_label), text); - g_free (text); - - gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (context->progress), 0.0); - - while (gtk_events_pending ()) - gtk_main_iteration (); - -#ifdef SLOW_MIGRATION - sleep (1); -#endif -} - -static void -dialog_set_progress (MigrationContext *context, double percent) -{ - char text[5]; - - snprintf (text, sizeof (text), "%d%%", (int) (percent * 100.0f)); - - gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (context->progress), percent); - gtk_progress_bar_set_text (GTK_PROGRESS_BAR (context->progress), text); - - while (gtk_events_pending ()) - gtk_main_iteration (); - -#ifdef SLOW_MIGRATION - sleep (1); -#endif -} - -static gboolean -check_for_conflict (ESourceGroup *group, char *name) -{ - GSList *sources; - GSList *s; - - sources = e_source_group_peek_sources (group); - - for (s = sources; s; s = s->next) { - ESource *source = E_SOURCE (s->data); - - if (!strcmp (e_source_peek_name (source), name)) - return TRUE; - } - - return FALSE; -} - -static char * -get_source_name (ESourceGroup *group, const char *path) -{ -#ifndef G_OS_WIN32 - char **p = g_strsplit (path, "/", 0); -#else - char **p = g_strsplit_set (path, "\\/", 0); -#endif - int i, j, starting_index; - int num_elements; - gboolean conflict; - GString *s = g_string_new (""); - - for (i = 0; p[i]; i ++) ; - - num_elements = i; - i--; - - /* p[i] is now the last path element */ - - /* check if it conflicts */ - starting_index = i; - do { - g_string_assign (s, ""); - for (j = starting_index; j < num_elements; j += 2) { - if (j != starting_index) - g_string_append_c (s, '_'); - g_string_append (s, p[j]); - } - - conflict = check_for_conflict (group, s->str); - - - /* if there was a conflict back up 2 levels (skipping the /subfolder/ element) */ - if (conflict) - starting_index -= 2; - - /* we always break out if we can't go any further, - regardless of whether or not we conflict. */ - if (starting_index < 0) - break; - - } while (conflict); - - g_strfreev (p); - - return g_string_free (s, FALSE); -} - -static void -migrate_contacts (MigrationContext *context, EBook *old_book, EBook *new_book) -{ - EBookQuery *query = e_book_query_any_field_contains (""); - GList *l, *contacts; - int num_added = 0; - int num_contacts; - - /* both books are loaded, start the actual migration */ - e_book_get_contacts (old_book, query, &contacts, NULL); - e_book_query_unref (query); - - num_contacts = g_list_length (contacts); - for (l = contacts; l; l = l->next) { - EContact *contact = l->data; - GError *e = NULL; - GList *attrs, *attr; - - /* do some last minute massaging of the contact's attributes */ - - attrs = e_vcard_get_attributes (E_VCARD (contact)); - for (attr = attrs; attr;) { - EVCardAttribute *a = attr->data; - - /* evo 1.4 used the non-standard X-EVOLUTION-OFFICE attribute, - evo 1.5 uses the third element in the ORG list attribute. */ - if (!strcmp ("X-EVOLUTION-OFFICE", e_vcard_attribute_get_name (a))) { - GList *v = e_vcard_attribute_get_values (a); - GList *next_attr; - - if (v && v->data) - e_contact_set (contact, E_CONTACT_OFFICE, v->data); - - next_attr = attr->next; - e_vcard_remove_attribute (E_VCARD (contact), a); - attr = next_attr; - } - /* evo 1.4 didn't put TYPE=VOICE in for phone numbers. - evo 1.5 does. - - so we search through the attribute params for - either TYPE=VOICE or TYPE=FAX. If we find - either we do nothing. If we find neither, we - add TYPE=VOICE. - */ - else if (!strcmp ("TEL", e_vcard_attribute_get_name (a))) { - GList *params, *param; - gboolean found = FALSE; - - params = e_vcard_attribute_get_params (a); - for (param = params; param; param = param->next) { - EVCardAttributeParam *p = param->data; - if (!strcmp (EVC_TYPE, e_vcard_attribute_param_get_name (p))) { - GList *v = e_vcard_attribute_param_get_values (p); - while (v && v->data) { - if (!strcmp ("VOICE", v->data) - || !strcmp ("FAX", v->data)) { - found = TRUE; - break; - } - v = v->next; - } - } - } - - if (!found) - e_vcard_attribute_add_param_with_value (a, - e_vcard_attribute_param_new (EVC_TYPE), - "VOICE"); - attr = attr->next; - } - /* Replace "POSTAL" (1.4) addresses with "OTHER" (1.5) */ - else if (!strcmp ("ADR", e_vcard_attribute_get_name (a))) { - GList *params, *param; - gboolean found = FALSE; - EVCardAttributeParam *p; - - params = e_vcard_attribute_get_params (a); - for (param = params; param; param = param->next) { - p = param->data; - if (!strcmp (EVC_TYPE, e_vcard_attribute_param_get_name (p))) { - GList *v = e_vcard_attribute_param_get_values (p); - while (v && v->data ) { - if (!strcmp ("POSTAL", v->data)) { - found = TRUE; - break; - } - v = v->next; - } - if (found) - break; - } - } - - if (found) { - e_vcard_attribute_param_remove_values (p); - e_vcard_attribute_param_add_value (p, "OTHER"); - } - - attr = attr->next; - } - /* this is kinda gross. The new vcard parser - needs ';'s to be escaped by \'s. but the - 1.4 vcard generator would put unescaped xml - (including entities like >) in the value - of attributes, so we need to go through and - escape those ';'s. */ - else if (!strcmp ("EMAIL", e_vcard_attribute_get_name (a))) { - GList *params; - GList *v = e_vcard_attribute_get_values (a); - - /* Add TYPE=OTHER if there is no type set */ - params = e_vcard_attribute_get_params (a); - if (!params) - e_vcard_attribute_add_param_with_value (a, - e_vcard_attribute_param_new (EVC_TYPE), - "OTHER"); - - if (v && v->data) { - if (!strncmp ((char*)v->data, "data); - if (v->next) - g_string_append_c (str, ';'); - v = v->next; - } - - e_vcard_attribute_remove_values (a); - e_vcard_attribute_add_value (a, str->str); - g_string_free (str, TRUE); - } - } - - attr = attr->next; - } - else { - attr = attr->next; - } - } - - if (!e_book_add_contact (new_book, - contact, - &e)) - g_warning ("contact add failed: `%s'", e->message); - - num_added ++; - - dialog_set_progress (context, (double)num_added / num_contacts); - } - - g_list_foreach (contacts, (GFunc)g_object_unref, NULL); - g_list_free (contacts); -} - -static void -migrate_contact_folder_to_source (MigrationContext *context, char *old_path, ESource *new_source) -{ - char *old_uri = g_filename_to_uri (old_path, NULL, NULL); - GError *e = NULL; - - EBook *old_book = NULL, *new_book = NULL; - ESource *old_source; - ESourceGroup *group; - - group = e_source_group_new ("", old_uri); - old_source = e_source_new ("", ""); - e_source_group_add_source (group, old_source, -1); - - dialog_set_folder_name (context, e_source_peek_name (new_source)); - - old_book = e_book_new (old_source, &e); - if (!old_book - || !e_book_open (old_book, TRUE, &e)) { - g_warning ("failed to load source book for migration: `%s'", e->message); - goto finish; - } - - new_book = e_book_new (new_source, &e); - if (!new_book - || !e_book_open (new_book, FALSE, &e)) { - g_warning ("failed to load destination book for migration: `%s'", e->message); - goto finish; - } - - migrate_contacts (context, old_book, new_book); - - finish: - g_object_unref (old_source); - g_object_unref (group); - if (old_book) - g_object_unref (old_book); - if (new_book) - g_object_unref (new_book); - g_free (old_uri); -} - -static void -migrate_contact_folder (MigrationContext *context, char *old_path, ESourceGroup *dest_group, char *source_name) -{ - ESource *new_source; - - new_source = e_source_new (source_name, source_name); - e_source_set_relative_uri (new_source, e_source_peek_uid (new_source)); - e_source_group_add_source (dest_group, new_source, -1); - - g_hash_table_insert (context->folder_uid_map, g_strdup (old_path), g_strdup (e_source_peek_uid (new_source))); - - migrate_contact_folder_to_source (context, old_path, new_source); - - g_object_unref (new_source); -} - -#define LDAP_BASE_URI "ldap://" -#define PERSONAL_RELATIVE_URI "system" - -static void -create_groups (MigrationContext *context, - ESourceGroup **on_this_computer, - ESourceGroup **on_ldap_servers, - ESource **personal_source) -{ - GSList *groups; - ESourceGroup *group; - char *base_uri, *base_uri_proto; - - *on_this_computer = NULL; - *on_ldap_servers = NULL; - *personal_source = NULL; - - base_uri = g_build_filename (context->data_dir, "local", NULL); - - base_uri_proto = g_filename_to_uri (base_uri, NULL, NULL); - - groups = e_source_list_peek_groups (context->source_list); - if (groups) { - /* groups are already there, we need to search for things... */ - GSList *g; - - for (g = groups; g; g = g->next) { - - group = E_SOURCE_GROUP (g->data); - - if (!*on_this_computer && !strcmp (base_uri_proto, e_source_group_peek_base_uri (group))) - *on_this_computer = g_object_ref (group); - else if (!*on_ldap_servers && !strcmp (LDAP_BASE_URI, e_source_group_peek_base_uri (group))) - *on_ldap_servers = g_object_ref (group); - } - } - - if (*on_this_computer) { - /* make sure "Personal" shows up as a source under - this group */ - GSList *sources = e_source_group_peek_sources (*on_this_computer); - GSList *s; - for (s = sources; s; s = s->next) { - ESource *source = E_SOURCE (s->data); - const gchar *relative_uri; - - relative_uri = e_source_peek_relative_uri (source); - if (relative_uri == NULL) - continue; - if (!strcmp (PERSONAL_RELATIVE_URI, relative_uri)) { - *personal_source = g_object_ref (source); - break; - } - } - } - else { - /* create the local source group */ - group = e_source_group_new (_("On This Computer"), base_uri_proto); - e_source_list_add_group (context->source_list, group, -1); - - *on_this_computer = group; - } - - if (!*personal_source) { - /* Create the default Person addressbook */ - ESource *source = e_source_new (_("Personal"), PERSONAL_RELATIVE_URI); - e_source_group_add_source (*on_this_computer, source, -1); - - e_source_set_property (source, "completion", "true"); - - *personal_source = source; - } - - if (!*on_ldap_servers) { - /* Create the LDAP source group */ - group = e_source_group_new (_("On LDAP Servers"), LDAP_BASE_URI); - e_source_list_add_group (context->source_list, group, -1); - - *on_ldap_servers = group; - } - - g_free (base_uri_proto); - g_free (base_uri); -} - -static gboolean -migrate_local_folders (MigrationContext *context, ESourceGroup *on_this_computer, ESource *personal_source) -{ - char *old_path = NULL; - GSList *dirs, *l; - char *local_contact_folder = NULL; - - old_path = g_strdup_printf ("%s/evolution/local", g_get_home_dir ()); - - dirs = e_folder_map_local_folders (old_path, "contacts"); - - /* migrate the local addressbook first, to local/system */ - local_contact_folder = g_build_filename (g_get_home_dir (), - "evolution", "local", "Contacts", - NULL); - - for (l = dirs; l; l = l->next) { - char *source_name; - /* we handle the system folder differently */ - if (personal_source && !strcmp ((char*)l->data, local_contact_folder)) { - g_hash_table_insert (context->folder_uid_map, g_strdup (l->data), g_strdup (e_source_peek_uid (personal_source))); - migrate_contact_folder_to_source (context, local_contact_folder, personal_source); - continue; - } - - source_name = get_source_name (on_this_computer, (char*)l->data); - migrate_contact_folder (context, l->data, on_this_computer, source_name); - g_free (source_name); - } - - g_slist_foreach (dirs, (GFunc)g_free, NULL); - g_slist_free (dirs); - g_free (local_contact_folder); - g_free (old_path); - - return TRUE; -} - -static char * -get_string_child (xmlNode *node, - const char *name) -{ - xmlNode *p; - xmlChar *xml_string; - char *retval; - - p = e_xml_get_child_by_name (node, (xmlChar *) name); - if (p == NULL) - return NULL; - - p = e_xml_get_child_by_name (p, (xmlChar *) "text"); - if (p == NULL) /* there's no text between the tags, return the empty string */ - return g_strdup(""); - - xml_string = xmlNodeListGetString (node->doc, p, 1); - retval = g_strdup ((char *) xml_string); - xmlFree (xml_string); - - return retval; -} - -static int -get_integer_child (xmlNode *node, - const char *name, - int defval) -{ - xmlNode *p; - xmlChar *xml_string; - int retval; - - p = e_xml_get_child_by_name (node, (xmlChar *) name); - if (p == NULL) - return defval; - - p = e_xml_get_child_by_name (p, (xmlChar *) "text"); - if (p == NULL) /* there's no text between the tags, return the default */ - return defval; - - xml_string = xmlNodeListGetString (node->doc, p, 1); - retval = atoi ((char *)xml_string); - xmlFree (xml_string); - - return retval; -} - -static gboolean -migrate_ldap_servers (MigrationContext *context, ESourceGroup *on_ldap_servers) -{ - char *sources_xml = g_strdup_printf ("%s/evolution/addressbook-sources.xml", - g_get_home_dir ()); - - printf ("trying to migrate from %s\n", sources_xml); - - if (g_file_test (sources_xml, G_FILE_TEST_EXISTS)) { - xmlDoc *doc = xmlParseFile (sources_xml); - xmlNode *root; - xmlNode *child; - int num_contactservers; - int servernum; - - if (!doc) - return FALSE; - - root = xmlDocGetRootElement (doc); - if (root == NULL || strcmp ((const char *)root->name, "addressbooks") != 0) { - xmlFreeDoc (doc); - return FALSE; - } - - /* count the number of servers, so we can give progress */ - num_contactservers = 0; - for (child = root->children; child; child = child->next) { - if (!strcmp ((const char *)child->name, "contactserver")) { - num_contactservers++; - } - } - printf ("found %d contact servers to migrate\n", num_contactservers); - - dialog_set_folder_name (context, _("LDAP Servers")); - - servernum = 0; - for (child = root->children; child; child = child->next) { - if (!strcmp ((const char *)child->name, "contactserver")) { - char *port, *host, *rootdn, *scope, *authmethod, *ssl; - char *emailaddr, *binddn, *limitstr; - int limit; - char *name, *description; - GString *uri = g_string_new (""); - ESource *source; - - name = get_string_child (child, "name"); - description = get_string_child (child, "description"); - port = get_string_child (child, "port"); - host = get_string_child (child, "host"); - rootdn = get_string_child (child, "rootdn"); - scope = get_string_child (child, "scope"); - authmethod = get_string_child (child, "authmethod"); - ssl = get_string_child (child, "ssl"); - emailaddr = get_string_child (child, "emailaddr"); - binddn = get_string_child (child, "binddn"); - limit = get_integer_child (child, "limit", 100); - limitstr = g_strdup_printf ("%d", limit); - - g_string_append_printf (uri, - "%s:%s/%s?"/*trigraph prevention*/"?%s", - host, port, rootdn, scope); - - source = e_source_new (name, uri->str); - e_source_set_property (source, "description", description); - e_source_set_property (source, "limit", limitstr); - e_source_set_property (source, "ssl", ssl); - e_source_set_property (source, "auth", authmethod); - if (emailaddr) - e_source_set_property (source, "email_addr", emailaddr); - if (binddn) - e_source_set_property (source, "binddn", binddn); - - e_source_group_add_source (on_ldap_servers, source, -1); - - g_string_free (uri, TRUE); - g_free (port); - g_free (host); - g_free (rootdn); - g_free (scope); - g_free (authmethod); - g_free (ssl); - g_free (emailaddr); - g_free (binddn); - g_free (limitstr); - g_free (name); - g_free (description); - - servernum++; - dialog_set_progress (context, (double)servernum/num_contactservers); - } - } - - xmlFreeDoc (doc); - } - - g_free (sources_xml); - - return TRUE; -} - -static ESource* -get_source_by_name (ESourceList *source_list, const char *name) -{ - GSList *groups; - GSList *g; - - groups = e_source_list_peek_groups (source_list); - if (!groups) - return NULL; - - for (g = groups; g; g = g->next) { - GSList *sources; - GSList *s; - ESourceGroup *group = E_SOURCE_GROUP (g->data); - - sources = e_source_group_peek_sources (group); - if (!sources) - continue; - - for (s = sources; s; s = s->next) { - ESource *source = E_SOURCE (s->data); - const char *source_name = e_source_peek_name (source); - - if (!strcmp (name, source_name)) - return source; - } - } - - return NULL; -} - -static gboolean -migrate_completion_folders (MigrationContext *context) -{ - GConfClient *client; - const gchar *key; - gchar *uris_xml; - - printf ("trying to migrate completion folders\n"); - - client = gconf_client_get_default (); - key = "/apps/evolution/addressbook/completion/uris"; - uris_xml = gconf_client_get_string (client, key, NULL); - g_object_unref (client); - - if (uris_xml) { - xmlDoc *doc = xmlParseMemory (uris_xml, strlen (uris_xml)); - xmlNode *root; - xmlNode *child; - - if (!doc) - return FALSE; - - dialog_set_folder_name (context, _("Autocompletion Settings")); - - root = xmlDocGetRootElement (doc); - if (root == NULL || strcmp ((const char *)root->name, "EvolutionFolderList") != 0) { - xmlFreeDoc (doc); - return FALSE; - } - - for (child = root->children; child; child = child->next) { - if (!strcmp ((const char *)child->name, "folder")) { - char *physical_uri = e_xml_get_string_prop_by_name (child, (const unsigned char *)"physical-uri"); - ESource *source = NULL; - - /* if the physical uri is file://... - we look it up in our folder_uid_map - hashtable. If it's a folder we - converted over, we should get back - a uid we can search for. - - if the physical_uri is anything - else, we strip off the args - (anything after ;) before searching - for the uri. */ - - if (!strncmp (physical_uri, "file://", 7)) { - char *filename = g_filename_from_uri (physical_uri, NULL, NULL); - char *uid = NULL; - - if (filename) - uid = g_hash_table_lookup (context->folder_uid_map, - filename); - g_free (filename); - if (uid) - source = e_source_list_peek_source_by_uid (context->source_list, uid); - } - else { - char *name = e_xml_get_string_prop_by_name (child, (const unsigned char *)"display-name"); - - source = get_source_by_name (context->source_list, name); - - g_free (name); - } - - if (source) { - e_source_set_property (source, "completion", "true"); - } - else { - g_warning ("found completion folder with uri `%s' that " - "doesn't correspond to anything we migrated.", physical_uri); - } - - g_free (physical_uri); - } - } - - g_free (uris_xml); - } - else { - g_message ("no completion folder settings to migrate"); - } - - return TRUE; -} - -static void -migrate_contact_lists_for_local_folders (MigrationContext *context, ESourceGroup *on_this_computer) -{ - GSList *sources, *s; - - sources = e_source_group_peek_sources (on_this_computer); - for (s = sources; s; s = s->next) { - ESource *source = s->data; - EBook *book; - EBookQuery *query; - GList *l, *contacts; - int num_contacts, num_converted; - - dialog_set_folder_name (context, e_source_peek_name (source)); - - book = e_book_new (source, NULL); - if (!book - || !e_book_open (book, TRUE, NULL)) { - char *uri = e_source_get_uri (source); - g_warning ("failed to migrate contact lists for source %s", uri); - g_free (uri); - continue; - } - - query = e_book_query_any_field_contains (""); - e_book_get_contacts (book, query, &contacts, NULL); - e_book_query_unref (query); - - num_converted = 0; - num_contacts = g_list_length (contacts); - for (l = contacts; l; l = l->next) { - EContact *contact = l->data; - GError *e = NULL; - GList *attrs, *attr; - gboolean converted = FALSE; - - attrs = e_contact_get_attributes (contact, E_CONTACT_EMAIL); - for (attr = attrs; attr; attr = attr->next) { - EVCardAttribute *a = attr->data; - GList *v = e_vcard_attribute_get_values (a); - - if (v && v->data) { - if (!strncmp ((char*)v->data, "data); - - e_destination_export_to_vcard_attribute (dest, a); - - g_object_unref (dest); - - converted = TRUE; - } - } - } - - if (converted) { - e_contact_set_attributes (contact, E_CONTACT_EMAIL, attrs); - - if (!e_book_commit_contact (book, - contact, - &e)) - g_warning ("contact commit failed: `%s'", e->message); - } - - num_converted ++; - - dialog_set_progress (context, (double)num_converted / num_contacts); - } - - g_list_foreach (contacts, (GFunc)g_object_unref, NULL); - g_list_free (contacts); - - g_object_unref (book); - } -} - -static void -migrate_company_phone_for_local_folders (MigrationContext *context, ESourceGroup *on_this_computer) -{ - GSList *sources, *s; - - sources = e_source_group_peek_sources (on_this_computer); - for (s = sources; s; s = s->next) { - ESource *source = s->data; - EBook *book; - EBookQuery *query; - GList *l, *contacts; - int num_contacts, num_converted; - - dialog_set_folder_name (context, e_source_peek_name (source)); - - book = e_book_new (source, NULL); - if (!book - || !e_book_open (book, TRUE, NULL)) { - char *uri = e_source_get_uri (source); - g_warning ("failed to migrate company phone numbers for source %s", uri); - g_free (uri); - continue; - } - - query = e_book_query_any_field_contains (""); - e_book_get_contacts (book, query, &contacts, NULL); - e_book_query_unref (query); - - num_converted = 0; - num_contacts = g_list_length (contacts); - for (l = contacts; l; l = l->next) { - EContact *contact = l->data; - GError *e = NULL; - GList *attrs, *attr; - gboolean converted = FALSE; - int num_work_voice = 0; - - attrs = e_vcard_get_attributes (E_VCARD (contact)); - for (attr = attrs; attr;) { - EVCardAttribute *a = attr->data; - GList *next_attr = attr->next; - - if (!strcmp ("TEL", e_vcard_attribute_get_name (a))) { - GList *params, *param; - gboolean found_voice = FALSE; - gboolean found_work = FALSE; - - params = e_vcard_attribute_get_params (a); - for (param = params; param; param = param->next) { - EVCardAttributeParam *p = param->data; - if (!strcmp (EVC_TYPE, e_vcard_attribute_param_get_name (p))) { - GList *v = e_vcard_attribute_param_get_values (p); - while (v && v->data) { - if (!strcmp ("VOICE", v->data)) - found_voice = TRUE; - else if (!strcmp ("WORK", v->data)) - found_work = TRUE; - v = v->next; - } - } - - if (found_work && found_voice) - num_work_voice++; - - if (num_work_voice == 3) { - GList *v = e_vcard_attribute_get_values (a); - - if (v && v->data) - e_contact_set (contact, E_CONTACT_PHONE_COMPANY, v->data); - - e_vcard_remove_attribute (E_VCARD (contact), a); - - converted = TRUE; - break; - } - } - } - - attr = next_attr; - - if (converted) - break; - } - - if (converted) { - if (!e_book_commit_contact (book, - contact, - &e)) - g_warning ("contact commit failed: `%s'", e->message); - } - - num_converted ++; - - dialog_set_progress (context, (double)num_converted / num_contacts); - } - - g_list_foreach (contacts, (GFunc)g_object_unref, NULL); - g_list_free (contacts); - - g_object_unref (book); - } -} - -static void -migrate_pilot_data (const char *old_path, const char *new_path) -{ - const char *dent; - const char *ext; - char *filename; - GDir *dir; - - if (!(dir = g_dir_open (old_path, 0, NULL))) - return; - - while ((dent = g_dir_read_name (dir))) { - if ((!strncmp (dent, "pilot-map-", 10) && - ((ext = strrchr (dent, '.')) && !strcmp (ext, ".xml"))) || - (!strncmp (dent, "pilot-sync-evolution-addressbook-", 33) && - ((ext = strrchr (dent, '.')) && !strcmp (ext, ".db")))) { - /* src and dest file formats are identical for both map and changelog files */ - unsigned char inbuf[4096]; - size_t nread, nwritten; - int fd0, fd1; - ssize_t n; - - filename = g_build_filename (old_path, dent, NULL); - if ((fd0 = g_open (filename, O_RDONLY | O_BINARY, 0)) == -1) { - g_free (filename); - continue; - } - - g_free (filename); - filename = g_build_filename (new_path, dent, NULL); - if ((fd1 = g_open (filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666)) == -1) { - g_free (filename); - close (fd0); - continue; - } - - do { - do { - n = read (fd0, inbuf, sizeof (inbuf)); - } while (n == -1 && errno == EINTR); - - if (n < 1) - break; - - nread = n; - nwritten = 0; - do { - do { - n = write (fd1, inbuf + nwritten, nread - nwritten); - } while (n == -1 && errno == EINTR); - - if (n > 0) - nwritten += n; - } while (nwritten < nread && n != -1); - - if (n == -1) - break; - } while (1); - - if (n != -1) - n = fsync (fd1); - - if (n == -1) { - g_warning ("Failed to migrate %s: %s", dent, strerror (errno)); - g_unlink (filename); - } - - close (fd0); - close (fd1); - g_free (filename); - } - } - - g_dir_close (dir); -} - -static MigrationContext * -migration_context_new (const gchar *data_dir) -{ - MigrationContext *context = g_new (MigrationContext, 1); - - /* set up the mapping from old uris to new uids */ - context->folder_uid_map = g_hash_table_new_full ( - g_str_hash, g_str_equal, - (GDestroyNotify) g_free, - (GDestroyNotify) g_free); - - e_book_get_addressbooks (&context->source_list, NULL); - - context->data_dir = data_dir; - - return context; -} - -static void -migration_context_free (MigrationContext *context) -{ - e_source_list_sync (context->source_list, NULL); - - g_hash_table_destroy (context->folder_uid_map); - - g_object_unref (context->source_list); - - g_free (context); -} - -gboolean -addressbook_migrate (EShellModule *shell_module, - gint major, - gint minor, - gint revision, - GError **error) -{ - ESourceGroup *on_this_computer; - ESourceGroup *on_ldap_servers; - ESource *personal_source; - MigrationContext *context; - gboolean need_dialog = FALSE; - const gchar *data_dir; - - printf ("addressbook_migrate (%d.%d.%d)\n", major, minor, revision); - - g_return_val_if_fail (E_IS_SHELL_MODULE (shell_module), FALSE); - - data_dir = e_shell_module_get_data_dir (shell_module); - context = migration_context_new (data_dir); - - /* we call this unconditionally now - create_groups either - creates the groups/sources or it finds the necessary - groups/sources. */ - create_groups (context, &on_this_computer, &on_ldap_servers, &personal_source); - - /* figure out if we need the dialog displayed */ - if (major == 1 - /* we only need the most recent upgrade point here. - further decomposition will happen below. */ - && (minor < 5 || (minor == 5 && revision <= 10))) - need_dialog = TRUE; - - if (need_dialog) - setup_progress_dialog (context); - - if (major == 1) { - - if (minor < 5 || (minor == 5 && revision <= 2)) { - /* initialize our dialog */ - dialog_set_label (context, - _("The location and hierarchy of the Evolution contact " - "folders has changed since Evolution 1.x.\n\nPlease be " - "patient while Evolution migrates your folders...")); - - if (on_this_computer) - migrate_local_folders (context, on_this_computer, personal_source); - if (on_ldap_servers) - migrate_ldap_servers (context, on_ldap_servers); - - migrate_completion_folders (context); - } - - if (minor < 5 || (minor == 5 && revision <= 7)) { - dialog_set_label (context, - _("The format of mailing list contacts has changed.\n\n" - "Please be patient while Evolution migrates your " - "folders...")); - - migrate_contact_lists_for_local_folders (context, on_this_computer); - } - - if (minor < 5 || (minor == 5 && revision <= 8)) { - dialog_set_label (context, - _("The way Evolution stores some phone numbers has changed.\n\n" - "Please be patient while Evolution migrates your " - "folders...")); - - migrate_company_phone_for_local_folders (context, on_this_computer); - } - - if (minor < 5 || (minor == 5 && revision <= 10)) { - char *old_path, *new_path; - - dialog_set_label (context, _("Evolution's Palm Sync changelog and map files have changed.\n\n" - "Please be patient while Evolution migrates your Pilot Sync data...")); - - old_path = g_build_filename (g_get_home_dir (), "evolution", "local", "Contacts", NULL); - new_path = g_build_filename (data_dir, "local", "system", NULL); - migrate_pilot_data (old_path, new_path); - g_free (new_path); - g_free (old_path); - } - - /* we only need to do this next step if people ran - older versions of 1.5. We need to clear out the - absolute URI's that were assigned to ESources - during one phase of development, as they take - precedent over relative uris (but aren't updated - when editing an ESource). */ - if (minor == 5 && revision <= 11) { - GSList *g; - for (g = e_source_list_peek_groups (context->source_list); g; g = g->next) { - ESourceGroup *group = g->data; - GSList *s; - - for (s = e_source_group_peek_sources (group); s; s = s->next) { - ESource *source = s->data; - e_source_set_absolute_uri (source, NULL); - } - } - } - } - - if (need_dialog) - dialog_close (context); - - if (on_this_computer) - g_object_unref (on_this_computer); - if (on_ldap_servers) - g_object_unref (on_ldap_servers); - if (personal_source) - g_object_unref (personal_source); - - - migration_context_free (context); - - return TRUE; -} diff --git a/addressbook/gui/component/addressbook-migrate.h b/addressbook/gui/component/addressbook-migrate.h deleted file mode 100644 index 189978b212..0000000000 --- a/addressbook/gui/component/addressbook-migrate.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * - * 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 - * - * - * Authors: - * Chris Toshok (toshok@ximian.com) - * - * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) - * - */ - -#ifndef _ADDRESSBOOK_MIGRATE_H_ -#define _ADDRESSBOOK_MIGRATE_H_ - -#include -#include - -G_BEGIN_DECLS - -gboolean addressbook_migrate (EShellModule *shell_module, - gint major, - gint minor, - gint revision, - GError **error); - -G_END_DECLS - -#endif /* _ADDRESSBOOK_MIGRATE_H_ */ diff --git a/addressbook/gui/component/e-book-shell-module-migrate.c b/addressbook/gui/component/e-book-shell-module-migrate.c new file mode 100644 index 0000000000..d0b41cf661 --- /dev/null +++ b/addressbook/gui/component/e-book-shell-module-migrate.c @@ -0,0 +1,1230 @@ +/* + * e-book-shell-module-migrate.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 + * + * + * Authors: + * Chris Toshok + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include +#include + +#include + +#include "e-util/e-util.h" +#include "e-util/e-util-private.h" +#include "e-util/e-xml-utils.h" +#include "e-util/e-folder-map.h" + +#include "e-book-shell-module-migrate.h" + +/*#define SLOW_MIGRATION*/ + +typedef struct { + /* this hash table maps old folder uris to new uids. It's + build in migrate_contact_folder and it's used in + migrate_completion_folders. */ + GHashTable *folder_uid_map; + + ESourceList *source_list; + + const gchar *data_dir; + + GtkWidget *window; + GtkWidget *label; + GtkWidget *folder_label; + GtkWidget *progress; +} MigrationContext; + +static void +setup_progress_dialog (MigrationContext *context) +{ + GtkWidget *vbox, *hbox; + + context->window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_title (GTK_WINDOW (context->window), _("Migrating...")); + gtk_window_set_modal (GTK_WINDOW (context->window), TRUE); + gtk_container_set_border_width (GTK_CONTAINER (context->window), 6); + + vbox = gtk_vbox_new (FALSE, 6); + gtk_widget_show (vbox); + gtk_container_add (GTK_CONTAINER (context->window), vbox); + + context->label = gtk_label_new (""); + gtk_label_set_line_wrap (GTK_LABEL (context->label), TRUE); + gtk_widget_show (context->label); + gtk_box_pack_start_defaults (GTK_BOX (vbox), context->label); + + hbox = gtk_hbox_new (FALSE, 6); + gtk_widget_show (hbox); + gtk_box_pack_start_defaults (GTK_BOX (vbox), hbox); + + context->folder_label = gtk_label_new (""); + gtk_widget_show (context->folder_label); + gtk_box_pack_start_defaults (GTK_BOX (hbox), context->folder_label); + + context->progress = gtk_progress_bar_new (); + gtk_widget_show (context->progress); + gtk_box_pack_start_defaults (GTK_BOX (hbox), context->progress); + + gtk_widget_show (context->window); +} + +static void +dialog_close (MigrationContext *context) +{ + gtk_widget_destroy (context->window); +} + +static void +dialog_set_label (MigrationContext *context, const char *str) +{ + gtk_label_set_text (GTK_LABEL (context->label), str); + + while (gtk_events_pending ()) + gtk_main_iteration (); + +#ifdef SLOW_MIGRATION + sleep (1); +#endif +} + +static void +dialog_set_folder_name (MigrationContext *context, const char *folder_name) +{ + char *text; + + text = g_strdup_printf (_("Migrating '%s':"), folder_name); + gtk_label_set_text (GTK_LABEL (context->folder_label), text); + g_free (text); + + gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (context->progress), 0.0); + + while (gtk_events_pending ()) + gtk_main_iteration (); + +#ifdef SLOW_MIGRATION + sleep (1); +#endif +} + +static void +dialog_set_progress (MigrationContext *context, double percent) +{ + char text[5]; + + snprintf (text, sizeof (text), "%d%%", (int) (percent * 100.0f)); + + gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (context->progress), percent); + gtk_progress_bar_set_text (GTK_PROGRESS_BAR (context->progress), text); + + while (gtk_events_pending ()) + gtk_main_iteration (); + +#ifdef SLOW_MIGRATION + sleep (1); +#endif +} + +static gboolean +check_for_conflict (ESourceGroup *group, char *name) +{ + GSList *sources; + GSList *s; + + sources = e_source_group_peek_sources (group); + + for (s = sources; s; s = s->next) { + ESource *source = E_SOURCE (s->data); + + if (!strcmp (e_source_peek_name (source), name)) + return TRUE; + } + + return FALSE; +} + +static char * +get_source_name (ESourceGroup *group, const char *path) +{ +#ifndef G_OS_WIN32 + char **p = g_strsplit (path, "/", 0); +#else + char **p = g_strsplit_set (path, "\\/", 0); +#endif + int i, j, starting_index; + int num_elements; + gboolean conflict; + GString *s = g_string_new (""); + + for (i = 0; p[i]; i ++) ; + + num_elements = i; + i--; + + /* p[i] is now the last path element */ + + /* check if it conflicts */ + starting_index = i; + do { + g_string_assign (s, ""); + for (j = starting_index; j < num_elements; j += 2) { + if (j != starting_index) + g_string_append_c (s, '_'); + g_string_append (s, p[j]); + } + + conflict = check_for_conflict (group, s->str); + + + /* if there was a conflict back up 2 levels (skipping the /subfolder/ element) */ + if (conflict) + starting_index -= 2; + + /* we always break out if we can't go any further, + regardless of whether or not we conflict. */ + if (starting_index < 0) + break; + + } while (conflict); + + g_strfreev (p); + + return g_string_free (s, FALSE); +} + +static void +migrate_contacts (MigrationContext *context, EBook *old_book, EBook *new_book) +{ + EBookQuery *query = e_book_query_any_field_contains (""); + GList *l, *contacts; + int num_added = 0; + int num_contacts; + + /* both books are loaded, start the actual migration */ + e_book_get_contacts (old_book, query, &contacts, NULL); + e_book_query_unref (query); + + num_contacts = g_list_length (contacts); + for (l = contacts; l; l = l->next) { + EContact *contact = l->data; + GError *e = NULL; + GList *attrs, *attr; + + /* do some last minute massaging of the contact's attributes */ + + attrs = e_vcard_get_attributes (E_VCARD (contact)); + for (attr = attrs; attr;) { + EVCardAttribute *a = attr->data; + + /* evo 1.4 used the non-standard X-EVOLUTION-OFFICE attribute, + evo 1.5 uses the third element in the ORG list attribute. */ + if (!strcmp ("X-EVOLUTION-OFFICE", e_vcard_attribute_get_name (a))) { + GList *v = e_vcard_attribute_get_values (a); + GList *next_attr; + + if (v && v->data) + e_contact_set (contact, E_CONTACT_OFFICE, v->data); + + next_attr = attr->next; + e_vcard_remove_attribute (E_VCARD (contact), a); + attr = next_attr; + } + /* evo 1.4 didn't put TYPE=VOICE in for phone numbers. + evo 1.5 does. + + so we search through the attribute params for + either TYPE=VOICE or TYPE=FAX. If we find + either we do nothing. If we find neither, we + add TYPE=VOICE. + */ + else if (!strcmp ("TEL", e_vcard_attribute_get_name (a))) { + GList *params, *param; + gboolean found = FALSE; + + params = e_vcard_attribute_get_params (a); + for (param = params; param; param = param->next) { + EVCardAttributeParam *p = param->data; + if (!strcmp (EVC_TYPE, e_vcard_attribute_param_get_name (p))) { + GList *v = e_vcard_attribute_param_get_values (p); + while (v && v->data) { + if (!strcmp ("VOICE", v->data) + || !strcmp ("FAX", v->data)) { + found = TRUE; + break; + } + v = v->next; + } + } + } + + if (!found) + e_vcard_attribute_add_param_with_value (a, + e_vcard_attribute_param_new (EVC_TYPE), + "VOICE"); + attr = attr->next; + } + /* Replace "POSTAL" (1.4) addresses with "OTHER" (1.5) */ + else if (!strcmp ("ADR", e_vcard_attribute_get_name (a))) { + GList *params, *param; + gboolean found = FALSE; + EVCardAttributeParam *p; + + params = e_vcard_attribute_get_params (a); + for (param = params; param; param = param->next) { + p = param->data; + if (!strcmp (EVC_TYPE, e_vcard_attribute_param_get_name (p))) { + GList *v = e_vcard_attribute_param_get_values (p); + while (v && v->data ) { + if (!strcmp ("POSTAL", v->data)) { + found = TRUE; + break; + } + v = v->next; + } + if (found) + break; + } + } + + if (found) { + e_vcard_attribute_param_remove_values (p); + e_vcard_attribute_param_add_value (p, "OTHER"); + } + + attr = attr->next; + } + /* this is kinda gross. The new vcard parser + needs ';'s to be escaped by \'s. but the + 1.4 vcard generator would put unescaped xml + (including entities like >) in the value + of attributes, so we need to go through and + escape those ';'s. */ + else if (!strcmp ("EMAIL", e_vcard_attribute_get_name (a))) { + GList *params; + GList *v = e_vcard_attribute_get_values (a); + + /* Add TYPE=OTHER if there is no type set */ + params = e_vcard_attribute_get_params (a); + if (!params) + e_vcard_attribute_add_param_with_value (a, + e_vcard_attribute_param_new (EVC_TYPE), + "OTHER"); + + if (v && v->data) { + if (!strncmp ((char*)v->data, "data); + if (v->next) + g_string_append_c (str, ';'); + v = v->next; + } + + e_vcard_attribute_remove_values (a); + e_vcard_attribute_add_value (a, str->str); + g_string_free (str, TRUE); + } + } + + attr = attr->next; + } + else { + attr = attr->next; + } + } + + if (!e_book_add_contact (new_book, + contact, + &e)) + g_warning ("contact add failed: `%s'", e->message); + + num_added ++; + + dialog_set_progress (context, (double)num_added / num_contacts); + } + + g_list_foreach (contacts, (GFunc)g_object_unref, NULL); + g_list_free (contacts); +} + +static void +migrate_contact_folder_to_source (MigrationContext *context, char *old_path, ESource *new_source) +{ + char *old_uri = g_filename_to_uri (old_path, NULL, NULL); + GError *e = NULL; + + EBook *old_book = NULL, *new_book = NULL; + ESource *old_source; + ESourceGroup *group; + + group = e_source_group_new ("", old_uri); + old_source = e_source_new ("", ""); + e_source_group_add_source (group, old_source, -1); + + dialog_set_folder_name (context, e_source_peek_name (new_source)); + + old_book = e_book_new (old_source, &e); + if (!old_book + || !e_book_open (old_book, TRUE, &e)) { + g_warning ("failed to load source book for migration: `%s'", e->message); + goto finish; + } + + new_book = e_book_new (new_source, &e); + if (!new_book + || !e_book_open (new_book, FALSE, &e)) { + g_warning ("failed to load destination book for migration: `%s'", e->message); + goto finish; + } + + migrate_contacts (context, old_book, new_book); + + finish: + g_object_unref (old_source); + g_object_unref (group); + if (old_book) + g_object_unref (old_book); + if (new_book) + g_object_unref (new_book); + g_free (old_uri); +} + +static void +migrate_contact_folder (MigrationContext *context, char *old_path, ESourceGroup *dest_group, char *source_name) +{ + ESource *new_source; + + new_source = e_source_new (source_name, source_name); + e_source_set_relative_uri (new_source, e_source_peek_uid (new_source)); + e_source_group_add_source (dest_group, new_source, -1); + + g_hash_table_insert (context->folder_uid_map, g_strdup (old_path), g_strdup (e_source_peek_uid (new_source))); + + migrate_contact_folder_to_source (context, old_path, new_source); + + g_object_unref (new_source); +} + +#define LDAP_BASE_URI "ldap://" +#define PERSONAL_RELATIVE_URI "system" + +static void +create_groups (MigrationContext *context, + ESourceGroup **on_this_computer, + ESourceGroup **on_ldap_servers, + ESource **personal_source) +{ + GSList *groups; + ESourceGroup *group; + char *base_uri, *base_uri_proto; + + *on_this_computer = NULL; + *on_ldap_servers = NULL; + *personal_source = NULL; + + base_uri = g_build_filename (context->data_dir, "local", NULL); + + base_uri_proto = g_filename_to_uri (base_uri, NULL, NULL); + + groups = e_source_list_peek_groups (context->source_list); + if (groups) { + /* groups are already there, we need to search for things... */ + GSList *g; + + for (g = groups; g; g = g->next) { + + group = E_SOURCE_GROUP (g->data); + + if (!*on_this_computer && !strcmp (base_uri_proto, e_source_group_peek_base_uri (group))) + *on_this_computer = g_object_ref (group); + else if (!*on_ldap_servers && !strcmp (LDAP_BASE_URI, e_source_group_peek_base_uri (group))) + *on_ldap_servers = g_object_ref (group); + } + } + + if (*on_this_computer) { + /* make sure "Personal" shows up as a source under + this group */ + GSList *sources = e_source_group_peek_sources (*on_this_computer); + GSList *s; + for (s = sources; s; s = s->next) { + ESource *source = E_SOURCE (s->data); + const gchar *relative_uri; + + relative_uri = e_source_peek_relative_uri (source); + if (relative_uri == NULL) + continue; + if (!strcmp (PERSONAL_RELATIVE_URI, relative_uri)) { + *personal_source = g_object_ref (source); + break; + } + } + } + else { + /* create the local source group */ + group = e_source_group_new (_("On This Computer"), base_uri_proto); + e_source_list_add_group (context->source_list, group, -1); + + *on_this_computer = group; + } + + if (!*personal_source) { + /* Create the default Person addressbook */ + ESource *source = e_source_new (_("Personal"), PERSONAL_RELATIVE_URI); + e_source_group_add_source (*on_this_computer, source, -1); + + e_source_set_property (source, "completion", "true"); + + *personal_source = source; + } + + if (!*on_ldap_servers) { + /* Create the LDAP source group */ + group = e_source_group_new (_("On LDAP Servers"), LDAP_BASE_URI); + e_source_list_add_group (context->source_list, group, -1); + + *on_ldap_servers = group; + } + + g_free (base_uri_proto); + g_free (base_uri); +} + +static gboolean +migrate_local_folders (MigrationContext *context, ESourceGroup *on_this_computer, ESource *personal_source) +{ + char *old_path = NULL; + GSList *dirs, *l; + char *local_contact_folder = NULL; + + old_path = g_strdup_printf ("%s/evolution/local", g_get_home_dir ()); + + dirs = e_folder_map_local_folders (old_path, "contacts"); + + /* migrate the local addressbook first, to local/system */ + local_contact_folder = g_build_filename (g_get_home_dir (), + "evolution", "local", "Contacts", + NULL); + + for (l = dirs; l; l = l->next) { + char *source_name; + /* we handle the system folder differently */ + if (personal_source && !strcmp ((char*)l->data, local_contact_folder)) { + g_hash_table_insert (context->folder_uid_map, g_strdup (l->data), g_strdup (e_source_peek_uid (personal_source))); + migrate_contact_folder_to_source (context, local_contact_folder, personal_source); + continue; + } + + source_name = get_source_name (on_this_computer, (char*)l->data); + migrate_contact_folder (context, l->data, on_this_computer, source_name); + g_free (source_name); + } + + g_slist_foreach (dirs, (GFunc)g_free, NULL); + g_slist_free (dirs); + g_free (local_contact_folder); + g_free (old_path); + + return TRUE; +} + +static char * +get_string_child (xmlNode *node, + const char *name) +{ + xmlNode *p; + xmlChar *xml_string; + char *retval; + + p = e_xml_get_child_by_name (node, (xmlChar *) name); + if (p == NULL) + return NULL; + + p = e_xml_get_child_by_name (p, (xmlChar *) "text"); + if (p == NULL) /* there's no text between the tags, return the empty string */ + return g_strdup(""); + + xml_string = xmlNodeListGetString (node->doc, p, 1); + retval = g_strdup ((char *) xml_string); + xmlFree (xml_string); + + return retval; +} + +static int +get_integer_child (xmlNode *node, + const char *name, + int defval) +{ + xmlNode *p; + xmlChar *xml_string; + int retval; + + p = e_xml_get_child_by_name (node, (xmlChar *) name); + if (p == NULL) + return defval; + + p = e_xml_get_child_by_name (p, (xmlChar *) "text"); + if (p == NULL) /* there's no text between the tags, return the default */ + return defval; + + xml_string = xmlNodeListGetString (node->doc, p, 1); + retval = atoi ((char *)xml_string); + xmlFree (xml_string); + + return retval; +} + +static gboolean +migrate_ldap_servers (MigrationContext *context, ESourceGroup *on_ldap_servers) +{ + char *sources_xml = g_strdup_printf ("%s/evolution/addressbook-sources.xml", + g_get_home_dir ()); + + printf ("trying to migrate from %s\n", sources_xml); + + if (g_file_test (sources_xml, G_FILE_TEST_EXISTS)) { + xmlDoc *doc = xmlParseFile (sources_xml); + xmlNode *root; + xmlNode *child; + int num_contactservers; + int servernum; + + if (!doc) + return FALSE; + + root = xmlDocGetRootElement (doc); + if (root == NULL || strcmp ((const char *)root->name, "addressbooks") != 0) { + xmlFreeDoc (doc); + return FALSE; + } + + /* count the number of servers, so we can give progress */ + num_contactservers = 0; + for (child = root->children; child; child = child->next) { + if (!strcmp ((const char *)child->name, "contactserver")) { + num_contactservers++; + } + } + printf ("found %d contact servers to migrate\n", num_contactservers); + + dialog_set_folder_name (context, _("LDAP Servers")); + + servernum = 0; + for (child = root->children; child; child = child->next) { + if (!strcmp ((const char *)child->name, "contactserver")) { + char *port, *host, *rootdn, *scope, *authmethod, *ssl; + char *emailaddr, *binddn, *limitstr; + int limit; + char *name, *description; + GString *uri = g_string_new (""); + ESource *source; + + name = get_string_child (child, "name"); + description = get_string_child (child, "description"); + port = get_string_child (child, "port"); + host = get_string_child (child, "host"); + rootdn = get_string_child (child, "rootdn"); + scope = get_string_child (child, "scope"); + authmethod = get_string_child (child, "authmethod"); + ssl = get_string_child (child, "ssl"); + emailaddr = get_string_child (child, "emailaddr"); + binddn = get_string_child (child, "binddn"); + limit = get_integer_child (child, "limit", 100); + limitstr = g_strdup_printf ("%d", limit); + + g_string_append_printf (uri, + "%s:%s/%s?"/*trigraph prevention*/"?%s", + host, port, rootdn, scope); + + source = e_source_new (name, uri->str); + e_source_set_property (source, "description", description); + e_source_set_property (source, "limit", limitstr); + e_source_set_property (source, "ssl", ssl); + e_source_set_property (source, "auth", authmethod); + if (emailaddr) + e_source_set_property (source, "email_addr", emailaddr); + if (binddn) + e_source_set_property (source, "binddn", binddn); + + e_source_group_add_source (on_ldap_servers, source, -1); + + g_string_free (uri, TRUE); + g_free (port); + g_free (host); + g_free (rootdn); + g_free (scope); + g_free (authmethod); + g_free (ssl); + g_free (emailaddr); + g_free (binddn); + g_free (limitstr); + g_free (name); + g_free (description); + + servernum++; + dialog_set_progress (context, (double)servernum/num_contactservers); + } + } + + xmlFreeDoc (doc); + } + + g_free (sources_xml); + + return TRUE; +} + +static ESource* +get_source_by_name (ESourceList *source_list, const char *name) +{ + GSList *groups; + GSList *g; + + groups = e_source_list_peek_groups (source_list); + if (!groups) + return NULL; + + for (g = groups; g; g = g->next) { + GSList *sources; + GSList *s; + ESourceGroup *group = E_SOURCE_GROUP (g->data); + + sources = e_source_group_peek_sources (group); + if (!sources) + continue; + + for (s = sources; s; s = s->next) { + ESource *source = E_SOURCE (s->data); + const char *source_name = e_source_peek_name (source); + + if (!strcmp (name, source_name)) + return source; + } + } + + return NULL; +} + +static gboolean +migrate_completion_folders (MigrationContext *context) +{ + GConfClient *client; + const gchar *key; + gchar *uris_xml; + + printf ("trying to migrate completion folders\n"); + + client = gconf_client_get_default (); + key = "/apps/evolution/addressbook/completion/uris"; + uris_xml = gconf_client_get_string (client, key, NULL); + g_object_unref (client); + + if (uris_xml) { + xmlDoc *doc = xmlParseMemory (uris_xml, strlen (uris_xml)); + xmlNode *root; + xmlNode *child; + + if (!doc) + return FALSE; + + dialog_set_folder_name (context, _("Autocompletion Settings")); + + root = xmlDocGetRootElement (doc); + if (root == NULL || strcmp ((const char *)root->name, "EvolutionFolderList") != 0) { + xmlFreeDoc (doc); + return FALSE; + } + + for (child = root->children; child; child = child->next) { + if (!strcmp ((const char *)child->name, "folder")) { + char *physical_uri = e_xml_get_string_prop_by_name (child, (const unsigned char *)"physical-uri"); + ESource *source = NULL; + + /* if the physical uri is file://... + we look it up in our folder_uid_map + hashtable. If it's a folder we + converted over, we should get back + a uid we can search for. + + if the physical_uri is anything + else, we strip off the args + (anything after ;) before searching + for the uri. */ + + if (!strncmp (physical_uri, "file://", 7)) { + char *filename = g_filename_from_uri (physical_uri, NULL, NULL); + char *uid = NULL; + + if (filename) + uid = g_hash_table_lookup (context->folder_uid_map, + filename); + g_free (filename); + if (uid) + source = e_source_list_peek_source_by_uid (context->source_list, uid); + } + else { + char *name = e_xml_get_string_prop_by_name (child, (const unsigned char *)"display-name"); + + source = get_source_by_name (context->source_list, name); + + g_free (name); + } + + if (source) { + e_source_set_property (source, "completion", "true"); + } + else { + g_warning ("found completion folder with uri `%s' that " + "doesn't correspond to anything we migrated.", physical_uri); + } + + g_free (physical_uri); + } + } + + g_free (uris_xml); + } + else { + g_message ("no completion folder settings to migrate"); + } + + return TRUE; +} + +static void +migrate_contact_lists_for_local_folders (MigrationContext *context, ESourceGroup *on_this_computer) +{ + GSList *sources, *s; + + sources = e_source_group_peek_sources (on_this_computer); + for (s = sources; s; s = s->next) { + ESource *source = s->data; + EBook *book; + EBookQuery *query; + GList *l, *contacts; + int num_contacts, num_converted; + + dialog_set_folder_name (context, e_source_peek_name (source)); + + book = e_book_new (source, NULL); + if (!book + || !e_book_open (book, TRUE, NULL)) { + char *uri = e_source_get_uri (source); + g_warning ("failed to migrate contact lists for source %s", uri); + g_free (uri); + continue; + } + + query = e_book_query_any_field_contains (""); + e_book_get_contacts (book, query, &contacts, NULL); + e_book_query_unref (query); + + num_converted = 0; + num_contacts = g_list_length (contacts); + for (l = contacts; l; l = l->next) { + EContact *contact = l->data; + GError *e = NULL; + GList *attrs, *attr; + gboolean converted = FALSE; + + attrs = e_contact_get_attributes (contact, E_CONTACT_EMAIL); + for (attr = attrs; attr; attr = attr->next) { + EVCardAttribute *a = attr->data; + GList *v = e_vcard_attribute_get_values (a); + + if (v && v->data) { + if (!strncmp ((char*)v->data, "data); + + e_destination_export_to_vcard_attribute (dest, a); + + g_object_unref (dest); + + converted = TRUE; + } + } + } + + if (converted) { + e_contact_set_attributes (contact, E_CONTACT_EMAIL, attrs); + + if (!e_book_commit_contact (book, + contact, + &e)) + g_warning ("contact commit failed: `%s'", e->message); + } + + num_converted ++; + + dialog_set_progress (context, (double)num_converted / num_contacts); + } + + g_list_foreach (contacts, (GFunc)g_object_unref, NULL); + g_list_free (contacts); + + g_object_unref (book); + } +} + +static void +migrate_company_phone_for_local_folders (MigrationContext *context, ESourceGroup *on_this_computer) +{ + GSList *sources, *s; + + sources = e_source_group_peek_sources (on_this_computer); + for (s = sources; s; s = s->next) { + ESource *source = s->data; + EBook *book; + EBookQuery *query; + GList *l, *contacts; + int num_contacts, num_converted; + + dialog_set_folder_name (context, e_source_peek_name (source)); + + book = e_book_new (source, NULL); + if (!book + || !e_book_open (book, TRUE, NULL)) { + char *uri = e_source_get_uri (source); + g_warning ("failed to migrate company phone numbers for source %s", uri); + g_free (uri); + continue; + } + + query = e_book_query_any_field_contains (""); + e_book_get_contacts (book, query, &contacts, NULL); + e_book_query_unref (query); + + num_converted = 0; + num_contacts = g_list_length (contacts); + for (l = contacts; l; l = l->next) { + EContact *contact = l->data; + GError *e = NULL; + GList *attrs, *attr; + gboolean converted = FALSE; + int num_work_voice = 0; + + attrs = e_vcard_get_attributes (E_VCARD (contact)); + for (attr = attrs; attr;) { + EVCardAttribute *a = attr->data; + GList *next_attr = attr->next; + + if (!strcmp ("TEL", e_vcard_attribute_get_name (a))) { + GList *params, *param; + gboolean found_voice = FALSE; + gboolean found_work = FALSE; + + params = e_vcard_attribute_get_params (a); + for (param = params; param; param = param->next) { + EVCardAttributeParam *p = param->data; + if (!strcmp (EVC_TYPE, e_vcard_attribute_param_get_name (p))) { + GList *v = e_vcard_attribute_param_get_values (p); + while (v && v->data) { + if (!strcmp ("VOICE", v->data)) + found_voice = TRUE; + else if (!strcmp ("WORK", v->data)) + found_work = TRUE; + v = v->next; + } + } + + if (found_work && found_voice) + num_work_voice++; + + if (num_work_voice == 3) { + GList *v = e_vcard_attribute_get_values (a); + + if (v && v->data) + e_contact_set (contact, E_CONTACT_PHONE_COMPANY, v->data); + + e_vcard_remove_attribute (E_VCARD (contact), a); + + converted = TRUE; + break; + } + } + } + + attr = next_attr; + + if (converted) + break; + } + + if (converted) { + if (!e_book_commit_contact (book, + contact, + &e)) + g_warning ("contact commit failed: `%s'", e->message); + } + + num_converted ++; + + dialog_set_progress (context, (double)num_converted / num_contacts); + } + + g_list_foreach (contacts, (GFunc)g_object_unref, NULL); + g_list_free (contacts); + + g_object_unref (book); + } +} + +static void +migrate_pilot_data (const char *old_path, const char *new_path) +{ + const char *dent; + const char *ext; + char *filename; + GDir *dir; + + if (!(dir = g_dir_open (old_path, 0, NULL))) + return; + + while ((dent = g_dir_read_name (dir))) { + if ((!strncmp (dent, "pilot-map-", 10) && + ((ext = strrchr (dent, '.')) && !strcmp (ext, ".xml"))) || + (!strncmp (dent, "pilot-sync-evolution-addressbook-", 33) && + ((ext = strrchr (dent, '.')) && !strcmp (ext, ".db")))) { + /* src and dest file formats are identical for both map and changelog files */ + unsigned char inbuf[4096]; + size_t nread, nwritten; + int fd0, fd1; + ssize_t n; + + filename = g_build_filename (old_path, dent, NULL); + if ((fd0 = g_open (filename, O_RDONLY | O_BINARY, 0)) == -1) { + g_free (filename); + continue; + } + + g_free (filename); + filename = g_build_filename (new_path, dent, NULL); + if ((fd1 = g_open (filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666)) == -1) { + g_free (filename); + close (fd0); + continue; + } + + do { + do { + n = read (fd0, inbuf, sizeof (inbuf)); + } while (n == -1 && errno == EINTR); + + if (n < 1) + break; + + nread = n; + nwritten = 0; + do { + do { + n = write (fd1, inbuf + nwritten, nread - nwritten); + } while (n == -1 && errno == EINTR); + + if (n > 0) + nwritten += n; + } while (nwritten < nread && n != -1); + + if (n == -1) + break; + } while (1); + + if (n != -1) + n = fsync (fd1); + + if (n == -1) { + g_warning ("Failed to migrate %s: %s", dent, strerror (errno)); + g_unlink (filename); + } + + close (fd0); + close (fd1); + g_free (filename); + } + } + + g_dir_close (dir); +} + +static MigrationContext * +migration_context_new (const gchar *data_dir) +{ + MigrationContext *context = g_new (MigrationContext, 1); + + /* set up the mapping from old uris to new uids */ + context->folder_uid_map = g_hash_table_new_full ( + g_str_hash, g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) g_free); + + e_book_get_addressbooks (&context->source_list, NULL); + + context->data_dir = data_dir; + + return context; +} + +static void +migration_context_free (MigrationContext *context) +{ + e_source_list_sync (context->source_list, NULL); + + g_hash_table_destroy (context->folder_uid_map); + + g_object_unref (context->source_list); + + g_free (context); +} + +gboolean +e_book_shell_module_migrate (EShellModule *shell_module, + gint major, + gint minor, + gint micro, + GError **error) +{ + ESourceGroup *on_this_computer; + ESourceGroup *on_ldap_servers; + ESource *personal_source; + MigrationContext *context; + gboolean need_dialog = FALSE; + const gchar *data_dir; + + g_return_val_if_fail (E_IS_SHELL_MODULE (shell_module), FALSE); + + data_dir = e_shell_module_get_data_dir (shell_module); + context = migration_context_new (data_dir); + + /* we call this unconditionally now - create_groups either + creates the groups/sources or it finds the necessary + groups/sources. */ + create_groups (context, &on_this_computer, &on_ldap_servers, &personal_source); + + /* figure out if we need the dialog displayed */ + if (major == 1 + /* we only need the most recent upgrade point here. + further decomposition will happen below. */ + && (minor < 5 || (minor == 5 && micro <= 10))) + need_dialog = TRUE; + + if (need_dialog) + setup_progress_dialog (context); + + if (major == 1) { + + if (minor < 5 || (minor == 5 && micro <= 2)) { + /* initialize our dialog */ + dialog_set_label (context, + _("The location and hierarchy of the Evolution contact " + "folders has changed since Evolution 1.x.\n\nPlease be " + "patient while Evolution migrates your folders...")); + + if (on_this_computer) + migrate_local_folders (context, on_this_computer, personal_source); + if (on_ldap_servers) + migrate_ldap_servers (context, on_ldap_servers); + + migrate_completion_folders (context); + } + + if (minor < 5 || (minor == 5 && micro <= 7)) { + dialog_set_label (context, + _("The format of mailing list contacts has changed.\n\n" + "Please be patient while Evolution migrates your " + "folders...")); + + migrate_contact_lists_for_local_folders (context, on_this_computer); + } + + if (minor < 5 || (minor == 5 && micro <= 8)) { + dialog_set_label (context, + _("The way Evolution stores some phone numbers has changed.\n\n" + "Please be patient while Evolution migrates your " + "folders...")); + + migrate_company_phone_for_local_folders (context, on_this_computer); + } + + if (minor < 5 || (minor == 5 && micro <= 10)) { + char *old_path, *new_path; + + dialog_set_label (context, _("Evolution's Palm Sync changelog and map files have changed.\n\n" + "Please be patient while Evolution migrates your Pilot Sync data...")); + + old_path = g_build_filename (g_get_home_dir (), "evolution", "local", "Contacts", NULL); + new_path = g_build_filename (data_dir, "local", "system", NULL); + migrate_pilot_data (old_path, new_path); + g_free (new_path); + g_free (old_path); + } + + /* we only need to do this next step if people ran + older versions of 1.5. We need to clear out the + absolute URI's that were assigned to ESources + during one phase of development, as they take + precedent over relative uris (but aren't updated + when editing an ESource). */ + if (minor == 5 && micro <= 11) { + GSList *g; + for (g = e_source_list_peek_groups (context->source_list); g; g = g->next) { + ESourceGroup *group = g->data; + GSList *s; + + for (s = e_source_group_peek_sources (group); s; s = s->next) { + ESource *source = s->data; + e_source_set_absolute_uri (source, NULL); + } + } + } + } + + if (need_dialog) + dialog_close (context); + + if (on_this_computer) + g_object_unref (on_this_computer); + if (on_ldap_servers) + g_object_unref (on_ldap_servers); + if (personal_source) + g_object_unref (personal_source); + + + migration_context_free (context); + + return TRUE; +} diff --git a/addressbook/gui/component/e-book-shell-module-migrate.h b/addressbook/gui/component/e-book-shell-module-migrate.h new file mode 100644 index 0000000000..8e19c73989 --- /dev/null +++ b/addressbook/gui/component/e-book-shell-module-migrate.h @@ -0,0 +1,41 @@ +/* + * e-book-shell-module-migrate.h + * + * 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 + * + * + * Authors: + * Chris Toshok (toshok@ximian.com) + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef E_BOOK_SHELL_MODULE_MIGRATE_H +#define E_BOOK_SHELL_MODULE_MIGRATE_H + +#include +#include + +G_BEGIN_DECLS + +gboolean e_book_shell_module_migrate (EShellModule *shell_module, + gint major, + gint minor, + gint micro, + GError **error); + +G_END_DECLS + +#endif /* E_BOOK_SHELL_MODULE_MIGRATE_H */ diff --git a/addressbook/gui/component/e-book-shell-module.c b/addressbook/gui/component/e-book-shell-module.c index e533a1a864..276e6f0a24 100644 --- a/addressbook/gui/component/e-book-shell-module.c +++ b/addressbook/gui/component/e-book-shell-module.c @@ -27,19 +27,21 @@ #include #include -#include -#include -#include +#include "shell/e-shell.h" +#include "shell/e-shell-module.h" +#include "shell/e-shell-window.h" -#include -#include +#include "e-util/e-import.h" +#include "addressbook/importers/evolution-addressbook-importers.h" #include #include -#include #include #include +#include "e-book-shell-view.h" +#include "e-book-shell-module-migrate.h" + #define MODULE_NAME "addressbook" #define MODULE_ALIASES "contacts" #define MODULE_SCHEMES "" @@ -52,7 +54,7 @@ void e_shell_module_init (GTypeModule *type_module); static void -book_module_ensure_sources (EShellModule *shell_module) +book_shell_module_ensure_sources (EShellModule *shell_module) { /* XXX This is basically the same algorithm across all modules. * Maybe we could somehow integrate this into EShellModule? */ @@ -182,7 +184,7 @@ book_module_ensure_sources (EShellModule *shell_module) } static void -book_module_init_importers (void) +book_shell_module_init_importers (void) { EImportClass *import_class; EImportImporter *importer; @@ -206,9 +208,9 @@ book_module_init_importers (void) } static void -book_module_book_loaded_cb (EBook *book, - EBookStatus status, - gpointer user_data) +book_shell_module_book_loaded_cb (EBook *book, + EBookStatus status, + gpointer user_data) { EContact *contact; GtkAction *action; @@ -266,7 +268,8 @@ action_contact_new_cb (GtkAction *action, if (book == NULL) book = e_book_new_default_addressbook (NULL); - e_book_async_open (book, FALSE, book_module_book_loaded_cb, action); + e_book_async_open ( + book, FALSE, book_shell_module_book_loaded_cb, action); } static void @@ -304,21 +307,21 @@ static GtkActionEntry source_entries[] = { }; static gboolean -book_module_is_busy (EShellModule *shell_module) +book_shell_module_is_busy (EShellModule *shell_module) { return !eab_editor_request_close_all (); } static gboolean -book_module_shutdown (EShellModule *shell_module) +book_shell_module_shutdown (EShellModule *shell_module) { /* FIXME */ return TRUE; } static gboolean -book_module_handle_uri (EShellModule *shell_module, - const gchar *uri) +book_shell_module_handle_uri (EShellModule *shell_module, + const gchar *uri) { EUri *euri; const gchar *cp; @@ -383,8 +386,8 @@ book_module_handle_uri (EShellModule *shell_module, } static void -book_module_window_created (EShellModule *shell_module, - EShellWindow *shell_window) +book_shell_module_window_created (EShellModule *shell_module, + EShellWindow *shell_window) { const gchar *module_name; @@ -407,8 +410,9 @@ static EShellModuleInfo module_info = { MODULE_SORT_ORDER, /* Methods */ - book_module_is_busy, - book_module_shutdown + book_shell_module_is_busy, + book_shell_module_shutdown, + e_book_shell_module_migrate }; void @@ -425,18 +429,18 @@ e_shell_module_init (GTypeModule *type_module) e_shell_module_set_info (shell_module, &module_info); - book_module_init_importers (); - book_module_ensure_sources (shell_module); + book_shell_module_init_importers (); + book_shell_module_ensure_sources (shell_module); e_plugin_hook_register_type (eab_config_get_type ()); g_signal_connect_swapped ( shell, "handle-uri", - G_CALLBACK (book_module_handle_uri), shell_module); + G_CALLBACK (book_shell_module_handle_uri), shell_module); g_signal_connect_swapped ( shell, "window-created", - G_CALLBACK (book_module_window_created), shell_module); + G_CALLBACK (book_shell_module_window_created), shell_module); autocompletion_config_init (); } diff --git a/addressbook/gui/component/e-book-shell-view-private.c b/addressbook/gui/component/e-book-shell-view-private.c index bbabb6d1cd..c3eecb5a8a 100644 --- a/addressbook/gui/component/e-book-shell-view-private.c +++ b/addressbook/gui/component/e-book-shell-view-private.c @@ -70,7 +70,7 @@ selection_change (EBookShellView *book_shell_view, EShellView *shell_view; gint n_selected; - shell_view = E_SHELL_VIEW (shell_view); + shell_view = E_SHELL_VIEW (book_shell_view); book_shell_content = book_shell_view->priv->book_shell_content; current_view = e_book_shell_content_get_current_view (book_shell_content); diff --git a/calendar/gui/Makefile.am b/calendar/gui/Makefile.am index 1edc28d0a7..4cc002fb8a 100644 --- a/calendar/gui/Makefile.am +++ b/calendar/gui/Makefile.am @@ -50,8 +50,6 @@ privsolib_LTLIBRARIES = libcal-gui.la # cal-search-bar.c # cal-search-bar.h # main.c -# migration.c -# migration.h libcal_gui_la_SOURCES = \ e-calendar-view.c \ diff --git a/calendar/gui/e-cal-event.c b/calendar/gui/e-cal-event.c index 6e295ab9a8..ecd1ea5b7e 100644 --- a/calendar/gui/e-cal-event.c +++ b/calendar/gui/e-cal-event.c @@ -43,10 +43,10 @@ static void ece_target_free (EEvent *ev, EEventTarget *t) { switch (t->type) { - case E_CAL_EVENT_TARGET_COMPONENT: { - ECalEventTargetComponent *s = (ECalEventTargetComponent *) t; - if (s->component) - g_object_unref (s->component); + case E_CAL_EVENT_TARGET_MODULE: { + ECalEventTargetModule *s = (ECalEventTargetModule *) t; + if (s->shell_module) + g_object_unref (s->shell_module); break; } } @@ -92,12 +92,12 @@ e_cal_event_peek (void) return e_cal_event; } -ECalEventTargetComponent * -e_cal_event_target_new_component (ECalEvent *ece, struct _CalendarComponent *component, guint32 flags) +ECalEventTargetModule * +e_cal_event_target_new_module (ECalEvent *ece, EShellModule *shell_module, guint32 flags) { - ECalEventTargetComponent *t = e_event_target_new (&ece->event, E_CAL_EVENT_TARGET_COMPONENT, sizeof (*t)); + ECalEventTargetModule *t = e_event_target_new (&ece->event, E_CAL_EVENT_TARGET_MODULE, sizeof (*t)); - t->component = g_object_ref (component); + t->shell_module = g_object_ref (shell_module); t->target.mask = ~flags; return t; @@ -107,13 +107,13 @@ e_cal_event_target_new_component (ECalEvent *ece, struct _CalendarComponent *com static void *eceh_parent_class; -static const EEventHookTargetMask eceh_component_masks[] = { - { "migration", E_CAL_EVENT_COMPONENT_MIGRATION }, +static const EEventHookTargetMask eceh_module_masks[] = { + { "migration", E_CAL_EVENT_MODULE_MIGRATION }, { NULL }, }; static const EEventHookTargetMap eceh_targets[] = { - { "component", E_CAL_EVENT_TARGET_COMPONENT, eceh_component_masks }, + { "module", E_CAL_EVENT_TARGET_MODULE, eceh_module_masks }, { NULL }, }; diff --git a/calendar/gui/e-cal-event.h b/calendar/gui/e-cal-event.h index 4fbaa394ec..6aade251aa 100644 --- a/calendar/gui/e-cal-event.h +++ b/calendar/gui/e-cal-event.h @@ -27,6 +27,7 @@ #include #include "e-util/e-event.h" +#include "shell/e-shell-module.h" #ifdef __cplusplus extern "C" { @@ -37,20 +38,19 @@ typedef struct _ECalEvent ECalEvent; typedef struct _ECalEventClass ECalEventClass; enum _e_cal_event_target_t { - E_CAL_EVENT_TARGET_COMPONENT, + E_CAL_EVENT_TARGET_MODULE, }; -/* Flags that describe TARGET_COMPONENT */ +/* Flags that describe TARGET_MODULE */ enum { - E_CAL_EVENT_COMPONENT_MIGRATION = 1 << 0, + E_CAL_EVENT_MODULE_MIGRATION = 1 << 0, }; -typedef struct _ECalEventTargetComponent ECalEventTargetComponent; +typedef struct _ECalEventTargetModule ECalEventTargetModule; -struct _ECalEventTargetComponent { +struct _ECalEventTargetModule { EEventTarget target; - - struct _CalendarComponent *component; + EShellModule *shell_module; }; struct _ECalEvent { @@ -65,7 +65,7 @@ struct _ECalEventClass { GType e_cal_event_get_type (void); ECalEvent* e_cal_event_peek (void); -ECalEventTargetComponent* e_cal_event_target_new_component (ECalEvent *ece, struct _CalendarComponent *component, guint32 flags); +ECalEventTargetModule* e_cal_event_target_new_module (ECalEvent *ece, EShellModule *shell_module, guint32 flags); /* ********************************************************************** */ diff --git a/calendar/gui/migration.c b/calendar/gui/migration.c deleted file mode 100644 index b6174202d2..0000000000 --- a/calendar/gui/migration.c +++ /dev/null @@ -1,1245 +0,0 @@ -/* - * 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 - * - * - * Authors: - * Rodrigo Moya - * - * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) - * - */ - -#include - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include - -#include -#include -#include -#include -#include - -#include "e-util/e-bconf-map.h" -#include "e-util/e-folder-map.h" -#include "e-util/e-util-private.h" - -#include "calendar-config-keys.h" -#include "calendar-config.h" -#include "e-cal-event.h" -#include "migration.h" - -#ifndef G_OS_WIN32 - -/* No previous versions have been available on Win32, so don't - * bother with upgrade support from 1.x on Win32. - */ - -static e_gconf_map_t calendar_display_map[] = { - /* /Calendar/Display */ - { "Timezone", "calendar/display/timezone", E_GCONF_MAP_STRING }, - { "Use24HourFormat", "calendar/display/use_24hour_format", E_GCONF_MAP_BOOL }, - { "WeekStartDay", "calendar/display/week_start_day", E_GCONF_MAP_INT }, - { "DayStartHour", "calendar/display/day_start_hour", E_GCONF_MAP_INT }, - { "DayStartMinute", "calendar/display/day_start_minute", E_GCONF_MAP_INT }, - { "DayEndHour", "calendar/display/day_end_hour", E_GCONF_MAP_INT }, - { "DayEndMinute", "calendar/display/day_end_minute", E_GCONF_MAP_INT }, - { "TimeDivisions", "calendar/display/time_divisions", E_GCONF_MAP_INT }, - { "View", "calendar/display/default_view", E_GCONF_MAP_INT }, - { "HPanePosition", "calendar/display/hpane_position", E_GCONF_MAP_FLOAT }, - { "VPanePosition", "calendar/display/vpane_position", E_GCONF_MAP_FLOAT }, - { "MonthHPanePosition", "calendar/display/month_hpane_position", E_GCONF_MAP_FLOAT }, - { "MonthVPanePosition", "calendar/display/month_vpane_position", E_GCONF_MAP_FLOAT }, - { "CompressWeekend", "calendar/display/compress_weekend", E_GCONF_MAP_BOOL }, - { "ShowEventEndTime", "calendar/display/show_event_end", E_GCONF_MAP_BOOL }, - { "WorkingDays", "calendar/display/working_days", E_GCONF_MAP_INT }, - { NULL }, -}; - -static e_gconf_map_t calendar_tasks_map[] = { - /* /Calendar/Tasks */ - { "HideCompletedTasks", "calendar/tasks/hide_completed", E_GCONF_MAP_BOOL }, - { "HideCompletedTasksUnits", "calendar/tasks/hide_completed_units", E_GCONF_MAP_STRING }, - { "HideCompletedTasksValue", "calendar/tasks/hide_completed_value", E_GCONF_MAP_INT }, - { NULL }, -}; - -static e_gconf_map_t calendar_tasks_colours_map[] = { - /* /Calendar/Tasks/Colors */ - { "TasksDueToday", "calendar/tasks/colors/due_today", E_GCONF_MAP_STRING }, - { "TasksOverDue", "calendar/tasks/colors/overdue", E_GCONF_MAP_STRING }, - { "TasksDueToday", "calendar/tasks/colors/due_today", E_GCONF_MAP_STRING }, - { NULL }, -}; - -static e_gconf_map_t calendar_other_map[] = { - /* /Calendar/Other */ - { "ConfirmDelete", "calendar/prompts/confirm_delete", E_GCONF_MAP_BOOL }, - { "ConfirmExpunge", "calendar/prompts/confirm_purge", E_GCONF_MAP_BOOL }, - { "UseDefaultReminder", "calendar/other/use_default_reminder", E_GCONF_MAP_BOOL }, - { "DefaultReminderInterval", "calendar/other/default_reminder_interval", E_GCONF_MAP_INT }, - { "DefaultReminderUnits", "calendar/other/default_reminder_units", E_GCONF_MAP_STRING }, - { NULL }, -}; - -static e_gconf_map_t calendar_datenavigator_map[] = { - /* /Calendar/DateNavigator */ - { "ShowWeekNumbers", "calendar/date_navigator/show_week_numbers", E_GCONF_MAP_BOOL }, - { NULL }, -}; - -static e_gconf_map_t calendar_alarmnotify_map[] = { - /* /Calendar/AlarmNotify */ - { "LastNotificationTime", "calendar/notify/last_notification_time", E_GCONF_MAP_INT }, - { "CalendarToLoad%i", "calendar/notify/calendars", E_GCONF_MAP_STRING|E_GCONF_MAP_LIST }, - { "BlessedProgram%i", "calendar/notify/programs", E_GCONF_MAP_STRING|E_GCONF_MAP_LIST }, - { NULL }, -}; - -static e_gconf_map_list_t calendar_remap_list[] = { - - { "/Calendar/Display", calendar_display_map }, - { "/Calendar/Other/Map", calendar_other_map }, - { "/Calendar/DateNavigator", calendar_datenavigator_map }, - { "/Calendar/AlarmNotify", calendar_alarmnotify_map }, - - { NULL }, -}; - -static e_gconf_map_list_t task_remap_list[] = { - - { "/Calendar/Tasks", calendar_tasks_map }, - { "/Calendar/Tasks/Colors", calendar_tasks_colours_map }, - - { NULL }, -}; - -static GtkWidget *window; -static GtkLabel *label; -static GtkProgressBar *progress; - -static void -setup_progress_dialog (gboolean tasks) -{ - GtkWidget *vbox, *hbox, *w; - - window = gtk_window_new (GTK_WINDOW_TOPLEVEL); - gtk_window_set_title ((GtkWindow *) window, _("Migrating...")); - gtk_window_set_modal ((GtkWindow *) window, TRUE); - gtk_container_set_border_width ((GtkContainer *) window, 6); - - vbox = gtk_vbox_new (FALSE, 6); - gtk_widget_show (vbox); - gtk_container_add ((GtkContainer *) window, vbox); - - if (tasks) - w = gtk_label_new (_("The location and hierarchy of the Evolution task " - "folders has changed since Evolution 1.x.\n\nPlease be " - "patient while Evolution migrates your folders...")); - else - w = gtk_label_new (_("The location and hierarchy of the Evolution calendar " - "folders has changed since Evolution 1.x.\n\nPlease be " - "patient while Evolution migrates your folders...")); - - gtk_label_set_line_wrap ((GtkLabel *) w, TRUE); - gtk_widget_show (w); - gtk_box_pack_start_defaults ((GtkBox *) vbox, w); - - hbox = gtk_hbox_new (FALSE, 6); - gtk_widget_show (hbox); - gtk_box_pack_start_defaults ((GtkBox *) vbox, hbox); - - label = (GtkLabel *) gtk_label_new (""); - gtk_widget_show ((GtkWidget *) label); - gtk_box_pack_start_defaults ((GtkBox *) hbox, (GtkWidget *) label); - - progress = (GtkProgressBar *) gtk_progress_bar_new (); - gtk_widget_show ((GtkWidget *) progress); - gtk_box_pack_start_defaults ((GtkBox *) hbox, (GtkWidget *) progress); - - gtk_widget_show (window); -} - -static void -dialog_close (void) -{ - gtk_widget_destroy ((GtkWidget *) window); -} - -static void -dialog_set_folder_name (const char *folder_name) -{ - char *text; - - text = g_strdup_printf (_("Migrating '%s':"), folder_name); - gtk_label_set_text (label, text); - g_free (text); - - gtk_progress_bar_set_fraction (progress, 0.0); - - while (gtk_events_pending ()) - gtk_main_iteration (); -} - -static void -dialog_set_progress (double percent) -{ - char text[5]; - - snprintf (text, sizeof (text), "%d%%", (int) (percent * 100.0f)); - - gtk_progress_bar_set_fraction (progress, percent); - gtk_progress_bar_set_text (progress, text); - - while (gtk_events_pending ()) - gtk_main_iteration (); -} - -static gboolean -check_for_conflict (ESourceGroup *group, char *name) -{ - GSList *sources; - GSList *s; - - sources = e_source_group_peek_sources (group); - - for (s = sources; s; s = s->next) { - ESource *source = E_SOURCE (s->data); - - if (!strcmp (e_source_peek_name (source), name)) - return TRUE; - } - - return FALSE; -} - -static char * -get_source_name (ESourceGroup *group, const char *path) -{ - char **p = g_strsplit (path, "/", 0); - int i, j, starting_index; - int num_elements; - gboolean conflict; - GString *s = g_string_new (NULL); - - for (i = 0; p[i]; i ++) ; - - num_elements = i; - i--; - - /* p[i] is now the last path element */ - - /* check if it conflicts */ - starting_index = i; - do { - for (j = starting_index; j < num_elements; j += 2) { - if (j != starting_index) - g_string_append_c (s, '_'); - g_string_append (s, p[j]); - } - - conflict = check_for_conflict (group, s->str); - - - /* if there was a conflict back up 2 levels (skipping the /subfolder/ element) */ - if (conflict) - starting_index -= 2; - - /* we always break out if we can't go any further, - regardless of whether or not we conflict. */ - if (starting_index < 0) - break; - - } while (conflict); - g_strfreev (p); - - return g_string_free (s, FALSE); -} - -static gboolean -migrate_ical (ECal *old_ecal, ECal *new_ecal) -{ - GList *l, *objects; - int num_added = 0; - int num_objects; - gboolean retval = TRUE; - - /* both ecals are loaded, start the actual migration */ - if (!e_cal_get_object_list (old_ecal, "#t", &objects, NULL)) - return FALSE; - - num_objects = g_list_length (objects); - for (l = objects; l; l = l->next) { - icalcomponent *ical_comp = l->data; - GError *error = NULL; - - if (!e_cal_create_object (new_ecal, ical_comp, NULL, &error)) { - g_warning ("Migration of object failed: %s", error->message); - retval = FALSE; - } - - g_clear_error (&error); - - num_added ++; - dialog_set_progress ((double)num_added / num_objects); - } - - g_list_foreach (objects, (GFunc) icalcomponent_free, NULL); - g_list_free (objects); - - return retval; -} - -static gboolean -migrate_ical_folder_to_source (char *old_path, ESource *new_source, ECalSourceType type) -{ - ECal *old_ecal = NULL, *new_ecal = NULL; - ESource *old_source; - ESourceGroup *group; - char *old_uri = g_strdup_printf ("file://%s", old_path); - GError *error = NULL; - gboolean retval = FALSE; - - group = e_source_group_new ("", old_uri); - old_source = e_source_new ("", ""); - e_source_group_add_source (group, old_source, -1); - - dialog_set_folder_name (e_source_peek_name (new_source)); - - if (!(old_ecal = e_cal_new (old_source, type))) { - g_warning ("could not find a backend for '%s'", e_source_get_uri (old_source)); - goto finish; - } - if (!e_cal_open (old_ecal, FALSE, &error)) { - g_warning ("failed to load source ecal for migration: '%s' (%s)", error->message, - e_source_get_uri (old_source)); - goto finish; - } - - if (!(new_ecal = e_cal_new (new_source, type))) { - g_warning ("could not find a backend for '%s'", e_source_get_uri (new_source)); - goto finish; - } - if (!e_cal_open (new_ecal, FALSE, &error)) { - g_warning ("failed to load destination ecal for migration: '%s' (%s)", error->message, - e_source_get_uri (new_source)); - goto finish; - } - - retval = migrate_ical (old_ecal, new_ecal); - -finish: - g_clear_error (&error); - if (old_ecal) - g_object_unref (old_ecal); - g_object_unref (group); - if (new_ecal) - g_object_unref (new_ecal); - g_free (old_uri); - - return retval; -} - -static gboolean -migrate_ical_folder (char *old_path, ESourceGroup *dest_group, char *source_name, ECalSourceType type) -{ - ESource *new_source; - gboolean retval; - - new_source = e_source_new (source_name, source_name); - e_source_set_relative_uri (new_source, e_source_peek_uid (new_source)); - e_source_group_add_source (dest_group, new_source, -1); - - retval = migrate_ical_folder_to_source (old_path, new_source, type); - - g_object_unref (new_source); - - return retval; -} - -#endif /* !G_OS_WIN32 */ - -#define WEBCAL_BASE_URI "webcal://" -#define CONTACTS_BASE_URI "contacts://" -#define BAD_CONTACTS_BASE_URI "contact://" -#define PERSONAL_RELATIVE_URI "system" -#define GROUPWISE_BASE_URI "groupwise://" - -static ESourceGroup * -create_calendar_contact_source (ESourceList *source_list) -{ - ESourceGroup *group; - ESource *source; - - /* Create the contacts group */ - group = e_source_group_new (_("Contacts"), CONTACTS_BASE_URI); - e_source_list_add_group (source_list, group, -1); - - source = e_source_new (_("Birthdays & Anniversaries"), "/"); - e_source_group_add_source (group, source, -1); - g_object_unref (source); - - e_source_set_color_spec (source, "#FED4D3"); - e_source_group_set_readonly (group, TRUE); - - return group; -} - -static void -create_calendar_sources (CalendarComponent *component, - ESourceList *source_list, - ESourceGroup **on_this_computer, - ESource **personal_source, - ESourceGroup **on_the_web, - ESourceGroup **contacts) -{ - GSList *groups; - ESourceGroup *group; - char *base_uri, *base_uri_proto; - const gchar *base_dir; - - *on_this_computer = NULL; - *on_the_web = NULL; - *contacts = NULL; - *personal_source = NULL; - - base_dir = calendar_component_peek_base_directory (component); - base_uri = g_build_filename (base_dir, "local", NULL); - - base_uri_proto = g_filename_to_uri (base_uri, NULL, NULL); - - groups = e_source_list_peek_groups (source_list); - if (groups) { - /* groups are already there, we need to search for things... */ - GSList *g; - - for (g = groups; g; g = g->next) { - - group = E_SOURCE_GROUP (g->data); - - if (!strcmp (BAD_CONTACTS_BASE_URI, e_source_group_peek_base_uri (group))) - e_source_group_set_base_uri (group, CONTACTS_BASE_URI); - - if (!strcmp (base_uri, e_source_group_peek_base_uri (group))) - e_source_group_set_base_uri (group, base_uri_proto); - - if (!*on_this_computer && !strcmp (base_uri_proto, e_source_group_peek_base_uri (group))) - *on_this_computer = g_object_ref (group); - else if (!*on_the_web && !strcmp (WEBCAL_BASE_URI, e_source_group_peek_base_uri (group))) - *on_the_web = g_object_ref (group); - else if (!*contacts && !strcmp (CONTACTS_BASE_URI, e_source_group_peek_base_uri (group))) - *contacts = g_object_ref (group); - } - } - - if (*on_this_computer) { - /* make sure "Personal" shows up as a source under - this group */ - GSList *sources = e_source_group_peek_sources (*on_this_computer); - GSList *s; - for (s = sources; s; s = s->next) { - ESource *source = E_SOURCE (s->data); - const gchar *relative_uri; - - relative_uri = e_source_peek_relative_uri (source); - if (relative_uri == NULL) - continue; - if (!strcmp (PERSONAL_RELATIVE_URI, relative_uri)) { - *personal_source = g_object_ref (source); - break; - } - } - } else { - /* create the local source group */ - group = e_source_group_new (_("On This Computer"), base_uri_proto); - e_source_list_add_group (source_list, group, -1); - - *on_this_computer = group; - } - - if (!*personal_source) { - char *primary_calendar = calendar_config_get_primary_calendar (); - - /* Create the default Person calendar */ - ESource *source = e_source_new (_("Personal"), PERSONAL_RELATIVE_URI); - e_source_group_add_source (*on_this_computer, source, -1); - - if (!primary_calendar && !calendar_config_get_calendars_selected ()) { - GSList selected; - - calendar_config_set_primary_calendar (e_source_peek_uid (source)); - - selected.data = (gpointer)e_source_peek_uid (source); - selected.next = NULL; - calendar_config_set_calendars_selected (&selected); - } - - g_free (primary_calendar); - e_source_set_color_spec (source, "#BECEDD"); - *personal_source = source; - } - - if (!*on_the_web) { - /* Create the Webcal source group */ - group = e_source_group_new (_("On The Web"), WEBCAL_BASE_URI); - e_source_list_add_group (source_list, group, -1); - - *on_the_web = group; - } - - if (!*contacts) { - group = create_calendar_contact_source (source_list); - - *contacts = group; - } - - g_free (base_uri_proto); - g_free (base_uri); -} - -static void -create_task_sources (TasksComponent *component, - ESourceList *source_list, - ESourceGroup **on_this_computer, - ESourceGroup **on_the_web, - ESource **personal_source) -{ - GSList *groups; - ESourceGroup *group; - char *base_uri, *base_uri_proto; - const gchar *base_dir; - - *on_this_computer = NULL; - *on_the_web = NULL; - *personal_source = NULL; - - base_dir = tasks_component_peek_base_directory (component); - base_uri = g_build_filename (base_dir, "local", NULL); - - base_uri_proto = g_filename_to_uri (base_uri, NULL, NULL); - - groups = e_source_list_peek_groups (source_list); - if (groups) { - /* groups are already there, we need to search for things... */ - GSList *g; - - for (g = groups; g; g = g->next) { - - group = E_SOURCE_GROUP (g->data); - - if (!*on_this_computer && !strcmp (base_uri_proto, e_source_group_peek_base_uri (group))) - *on_this_computer = g_object_ref (group); - else if (!*on_the_web && !strcmp (WEBCAL_BASE_URI, e_source_group_peek_base_uri (group))) - *on_the_web = g_object_ref (group); - } - } - - if (*on_this_computer) { - /* make sure "Personal" shows up as a source under - this group */ - GSList *sources = e_source_group_peek_sources (*on_this_computer); - GSList *s; - for (s = sources; s; s = s->next) { - ESource *source = E_SOURCE (s->data); - const gchar *relative_uri; - - relative_uri = e_source_peek_relative_uri (source); - if (relative_uri == NULL) - continue; - if (!strcmp (PERSONAL_RELATIVE_URI, relative_uri)) { - *personal_source = g_object_ref (source); - break; - } - } - } else { - /* create the local source group */ - group = e_source_group_new (_("On This Computer"), base_uri_proto); - e_source_list_add_group (source_list, group, -1); - - *on_this_computer = group; - } - - if (!*personal_source) { - /* Create the default Person task list */ - ESource *source = e_source_new (_("Personal"), PERSONAL_RELATIVE_URI); - e_source_group_add_source (*on_this_computer, source, -1); - - if (!calendar_config_get_primary_tasks () && !calendar_config_get_tasks_selected ()) { - GSList selected; - - calendar_config_set_primary_tasks (e_source_peek_uid (source)); - - selected.data = (gpointer)e_source_peek_uid (source); - selected.next = NULL; - calendar_config_set_tasks_selected (&selected); - } - - e_source_set_color_spec (source, "#BECEDD"); - *personal_source = source; - } - - if (!*on_the_web) { - /* Create the Webcal source group */ - group = e_source_group_new (_("On The Web"), WEBCAL_BASE_URI); - e_source_list_add_group (source_list, group, -1); - - *on_the_web = group; - } - - g_free (base_uri_proto); - g_free (base_uri); -} - -#ifndef G_OS_WIN32 - -static void -migrate_pilot_db_key (const char *key, gpointer user_data) -{ - EXmlHash *xmlhash = user_data; - - e_xmlhash_add (xmlhash, key, ""); -} - -static void -migrate_pilot_data (const char *component, const char *conduit, const char *old_path, const char *new_path) -{ - char *changelog, *map; - const char *dent; - const char *ext; - char *filename; - GDir *dir; - - if (!(dir = g_dir_open (old_path, 0, NULL))) - return; - - map = g_alloca (12 + strlen (conduit)); - sprintf (map, "pilot-map-%s-", conduit); - - changelog = g_alloca (24 + strlen (conduit)); - sprintf (changelog, "pilot-sync-evolution-%s-", conduit); - - while ((dent = g_dir_read_name (dir))) { - if (!strncmp (dent, map, strlen (map)) && - ((ext = strrchr (dent, '.')) && !strcmp (ext, ".xml"))) { - /* pilot map file - src and dest file formats are identical */ - unsigned char inbuf[4096]; - size_t nread, nwritten; - int fd0, fd1; - ssize_t n; - - filename = g_build_filename (old_path, dent, NULL); - if ((fd0 = g_open (filename, O_RDONLY|O_BINARY, 0)) == -1) { - g_free (filename); - continue; - } - - g_free (filename); - filename = g_build_filename (new_path, dent, NULL); - if ((fd1 = g_open (filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666)) == -1) { - g_free (filename); - close (fd0); - continue; - } - - do { - do { - n = read (fd0, inbuf, sizeof (inbuf)); - } while (n == -1 && errno == EINTR); - - if (n < 1) - break; - - nread = n; - nwritten = 0; - do { - do { - n = write (fd1, inbuf + nwritten, nread - nwritten); - } while (n == -1 && errno == EINTR); - - if (n > 0) - nwritten += n; - } while (nwritten < nread && n != -1); - - if (n == -1) - break; - } while (1); - - if (n != -1) - n = fsync (fd1); - - if (n == -1) { - g_warning ("Failed to migrate %s: %s", dent, strerror (errno)); - g_unlink (filename); - } - - close (fd0); - close (fd1); - g_free (filename); - } else if (!strncmp (dent, changelog, strlen (changelog)) && - ((ext = strrchr (dent, '.')) && !strcmp (ext, ".db"))) { - /* src and dest formats differ, src format is db3 while dest format is xml */ - EXmlHash *xmlhash; - EDbHash *dbhash; - struct stat st; - - filename = g_build_filename (old_path, dent, NULL); - if (g_stat (filename, &st) == -1) { - g_free (filename); - continue; - } - - dbhash = e_dbhash_new (filename); - g_free (filename); - - filename = g_strdup_printf ("%s/%s.ics-%s", new_path, component, dent); - if (g_stat (filename, &st) != -1) - g_unlink (filename); - xmlhash = e_xmlhash_new (filename); - g_free (filename); - - e_dbhash_foreach_key (dbhash, migrate_pilot_db_key, xmlhash); - - e_dbhash_destroy (dbhash); - - e_xmlhash_write (xmlhash); - e_xmlhash_destroy (xmlhash); - } - } - - g_dir_close (dir); -} - -#endif - -gboolean -migrate_calendars (CalendarComponent *component, int major, int minor, int revision, GError **err) -{ - ESourceGroup *on_this_computer = NULL, *on_the_web = NULL, *contacts = NULL; - ESource *personal_source = NULL; - ECalEvent *ece; - ECalEventTargetComponent *target; - gboolean retval = FALSE; - - /* we call this unconditionally now - create_groups either - creates the groups/sources or it finds the necessary - groups/sources. */ - create_calendar_sources (component, calendar_component_peek_source_list (component), &on_this_computer, &personal_source, &on_the_web, &contacts); - -#ifndef G_OS_WIN32 - if (major == 1) { - xmlDocPtr config_doc = NULL; - char *conf_file; - struct stat st; - - conf_file = g_build_filename (g_get_home_dir (), "evolution", "config.xmldb", NULL); - if (lstat (conf_file, &st) == 0 && S_ISREG (st.st_mode)) - config_doc = xmlParseFile (conf_file); - g_free (conf_file); - - if (config_doc && minor <= 2) { - GConfClient *gconf; - int res = 0; - - /* move bonobo config to gconf */ - gconf = gconf_client_get_default (); - - res = e_bconf_import (gconf, config_doc, calendar_remap_list); - - g_object_unref (gconf); - - xmlFreeDoc(config_doc); - - if (res != 0) { - /* FIXME: set proper domain/code */ - g_set_error(err, 0, 0, _("Unable to migrate old settings from evolution/config.xmldb")); - goto fail; - } - } - - if (minor <= 4) { - GSList *migration_dirs, *l; - char *path, *local_cal_folder; - - setup_progress_dialog (FALSE); - - path = g_build_filename (g_get_home_dir (), "evolution", "local", NULL); - migration_dirs = e_folder_map_local_folders (path, "calendar"); - local_cal_folder = g_build_filename (path, "Calendar", NULL); - g_free (path); - - if (personal_source) - migrate_ical_folder_to_source (local_cal_folder, personal_source, E_CAL_SOURCE_TYPE_EVENT); - - for (l = migration_dirs; l; l = l->next) { - char *source_name; - - if (personal_source && !strcmp ((char*)l->data, local_cal_folder)) - continue; - - source_name = get_source_name (on_this_computer, (char*)l->data); - - if (!migrate_ical_folder (l->data, on_this_computer, source_name, E_CAL_SOURCE_TYPE_EVENT)) { - /* FIXME: domain/code */ - g_set_error(err, 0, 0, _("Unable to migrate calendar `%s'"), source_name); - g_free(source_name); - goto fail; - } - - g_free (source_name); - } - - g_free (local_cal_folder); - - dialog_close (); - } - - if (minor <= 4 || (minor == 5 && revision < 5)) { - GConfClient *gconf; - GConfValue *gconf_val; - int i; - const char *keys[] = { - CALENDAR_CONFIG_HPANE_POS, - CALENDAR_CONFIG_VPANE_POS, - CALENDAR_CONFIG_MONTH_HPANE_POS, - CALENDAR_CONFIG_MONTH_VPANE_POS, - NULL - }; - - gconf = gconf_client_get_default (); - - for (i = 0; keys[i]; i++) { - gconf_val = gconf_client_get (gconf, keys[i], NULL); - if (gconf_val) { - if (gconf_val->type != GCONF_VALUE_INT) - gconf_client_unset (gconf, keys[i], NULL); - gconf_value_free (gconf_val); - } - } - - g_object_unref (gconf); - } - - if (minor < 5 || (minor == 5 && revision <= 10)) { - char *old_path, *new_path; - - old_path = g_build_filename (g_get_home_dir (), "evolution", "local", "Calendar", NULL); - new_path = g_build_filename (calendar_component_peek_base_directory (component), - "local", "system", NULL); - migrate_pilot_data ("calendar", "calendar", old_path, new_path); - g_free (new_path); - g_free (old_path); - } - - /* we only need to do this next step if people ran - older versions of 1.5. We need to clear out the - absolute URI's that were assigned to ESources - during one phase of development, as they take - precedent over relative uris (but aren't updated - when editing an ESource). */ - if (minor == 5 && revision <= 11) { - GSList *g; - for (g = e_source_list_peek_groups (calendar_component_peek_source_list (component)); g; g = g->next) { - ESourceGroup *group = g->data; - GSList *s; - - for (s = e_source_group_peek_sources (group); s; s = s->next) { - ESource *source = s->data; - e_source_set_absolute_uri (source, NULL); - } - } - } - - } -#endif /* !G_OS_WIN32 */ - - e_source_list_sync (calendar_component_peek_source_list (component), NULL); - - /** @Event: component.migration - * @Title: Migration step in component initialization - * @Target: ECalEventTargetComponent - * - * component.migration is emitted during the calendar component - * initialization process. This allows new calendar backend types - * to be distributed as an e-d-s backend and a plugin without - * reaching their grubby little fingers into migration.c - */ - /* Fire off migration event */ - ece = e_cal_event_peek (); - target = e_cal_event_target_new_component (ece, calendar_component_peek (), 0); - e_event_emit ((EEvent *) ece, "component.migration", (EEventTarget *) target); - - retval = TRUE; -fail: - if (on_this_computer) - g_object_unref (on_this_computer); - if (on_the_web) - g_object_unref (on_the_web); - if (contacts) - g_object_unref (contacts); - if (personal_source) - g_object_unref (personal_source); - - return retval; -} - -gboolean -migrate_tasks (TasksComponent *component, int major, int minor, int revision, GError **err) -{ - ESourceGroup *on_this_computer = NULL; - ESourceGroup *on_the_web = NULL; - ESource *personal_source = NULL; - gboolean retval = FALSE; - - /* we call this unconditionally now - create_groups either - creates the groups/sources or it finds the necessary - groups/sources. */ - create_task_sources (component, tasks_component_peek_source_list (component), &on_this_computer, &on_the_web, &personal_source); - -#ifndef G_OS_WIN32 - if (major == 1) { - xmlDocPtr config_doc = NULL; - char *conf_file; - - conf_file = g_build_filename (g_get_home_dir (), "evolution", "config.xmldb", NULL); - if (g_file_test (conf_file, G_FILE_TEST_IS_REGULAR)) - config_doc = e_xml_parse_file (conf_file); - g_free (conf_file); - - if (config_doc && minor <= 2) { - GConfClient *gconf; - int res = 0; - - /* move bonobo config to gconf */ - gconf = gconf_client_get_default (); - - res = e_bconf_import (gconf, config_doc, task_remap_list); - - g_object_unref (gconf); - - xmlFreeDoc(config_doc); - - if (res != 0) { - g_set_error(err, 0, 0, _("Unable to migrate old settings from evolution/config.xmldb")); - goto fail; - } - } - - if (minor <= 4) { - GSList *migration_dirs, *l; - char *path, *local_task_folder; - - setup_progress_dialog (TRUE); - - path = g_build_filename (g_get_home_dir (), "evolution", "local", NULL); - migration_dirs = e_folder_map_local_folders (path, "tasks"); - local_task_folder = g_build_filename (path, "Tasks", NULL); - g_free (path); - - if (personal_source) - migrate_ical_folder_to_source (local_task_folder, personal_source, E_CAL_SOURCE_TYPE_TODO); - - for (l = migration_dirs; l; l = l->next) { - char *source_name; - - if (personal_source && !strcmp ((char*)l->data, local_task_folder)) - continue; - - source_name = get_source_name (on_this_computer, (char*)l->data); - - if (!migrate_ical_folder (l->data, on_this_computer, source_name, E_CAL_SOURCE_TYPE_TODO)) { - /* FIXME: domain/code */ - g_set_error(err, 0, 0, _("Unable to migrate tasks `%s'"), source_name); - g_free(source_name); - goto fail; - } - - g_free (source_name); - } - - g_free (local_task_folder); - - dialog_close (); - } - - if (minor < 5 || (minor == 5 && revision <= 10)) { - char *old_path, *new_path; - - old_path = g_build_filename (g_get_home_dir (), "evolution", "local", "Tasks", NULL); - new_path = g_build_filename (tasks_component_peek_base_directory (component), - "local", "system", NULL); - migrate_pilot_data ("tasks", "todo", old_path, new_path); - g_free (new_path); - g_free (old_path); - } - - /* we only need to do this next step if people ran - older versions of 1.5. We need to clear out the - absolute URI's that were assigned to ESources - during one phase of development, as they take - precedent over relative uris (but aren't updated - when editing an ESource). */ - if (minor == 5 && revision <= 11) { - GSList *g; - for (g = e_source_list_peek_groups (tasks_component_peek_source_list (component)); g; g = g->next) { - ESourceGroup *group = g->data; - GSList *s; - - for (s = e_source_group_peek_sources (group); s; s = s->next) { - ESource *source = s->data; - e_source_set_absolute_uri (source, NULL); - } - } - } - } -#endif /* !G_OS_WIN32 */ - e_source_list_sync (tasks_component_peek_source_list (component), NULL); - retval = TRUE; -fail: - if (on_this_computer) - g_object_unref (on_this_computer); - if (on_the_web) - g_object_unref (on_the_web); - if (personal_source) - g_object_unref (personal_source); - - return retval; -} - -/******************************************************************************************************** - * - * MEMOS - * - ********************************************************************************************************/ - -static void -create_memo_sources (MemosComponent *component, - ESourceList *source_list, - ESourceGroup **on_this_computer, - ESourceGroup **on_the_web, - ESource **personal_source) -{ - GSList *groups; - ESourceGroup *group; - char *base_uri, *base_uri_proto; - const gchar *base_dir; - - *on_this_computer = NULL; - *on_the_web = NULL; - *personal_source = NULL; - - base_dir = memos_component_peek_base_directory (component); - base_uri = g_build_filename (base_dir, "local", NULL); - - base_uri_proto = g_filename_to_uri (base_uri, NULL, NULL); - - groups = e_source_list_peek_groups (source_list); - if (groups) { - /* groups are already there, we need to search for things... */ - GSList *g; - - for (g = groups; g; g = g->next) { - - group = E_SOURCE_GROUP (g->data); - - if (!*on_this_computer && !strcmp (base_uri_proto, e_source_group_peek_base_uri (group))) - *on_this_computer = g_object_ref (group); - else if (!*on_the_web && !strcmp (WEBCAL_BASE_URI, e_source_group_peek_base_uri (group))) - *on_the_web = g_object_ref (group); - } - } - - if (*on_this_computer) { - /* make sure "Personal" shows up as a source under - this group */ - GSList *sources = e_source_group_peek_sources (*on_this_computer); - GSList *s; - for (s = sources; s; s = s->next) { - ESource *source = E_SOURCE (s->data); - const gchar *relative_uri; - - relative_uri = e_source_peek_relative_uri (source); - if (relative_uri == NULL) - continue; - if (!strcmp (PERSONAL_RELATIVE_URI, relative_uri)) { - *personal_source = g_object_ref (source); - break; - } - } - } else { - /* create the local source group */ - group = e_source_group_new (_("On This Computer"), base_uri_proto); - e_source_list_add_group (source_list, group, -1); - - *on_this_computer = group; - } - - if (!*personal_source) { - /* Create the default Person task list */ - ESource *source = e_source_new (_("Personal"), PERSONAL_RELATIVE_URI); - e_source_group_add_source (*on_this_computer, source, -1); - - if (!calendar_config_get_primary_memos () && !calendar_config_get_memos_selected ()) { - GSList selected; - - calendar_config_set_primary_memos (e_source_peek_uid (source)); - - selected.data = (gpointer)e_source_peek_uid (source); - selected.next = NULL; - calendar_config_set_memos_selected (&selected); - } - - e_source_set_color_spec (source, "#BECEDD"); - *personal_source = source; - } - - if (!*on_the_web) { - /* Create the Webcal source group */ - group = e_source_group_new (_("On The Web"), WEBCAL_BASE_URI); - e_source_list_add_group (source_list, group, -1); - - *on_the_web = group; - } - - g_free (base_uri_proto); - g_free (base_uri); -} - -static gboolean -is_groupwise_account (EAccount *account) -{ - if (account->source->url != NULL) { - return g_str_has_prefix (account->source->url, GROUPWISE_BASE_URI); - } else { - return FALSE; - } -} - -static void -add_gw_esource (ESourceList *source_list, const char *group_name, const char *source_name, CamelURL *url, GConfClient *client) -{ - ESourceGroup *group; - ESource *source; - GSList *ids, *temp ; - GError *error = NULL; - char *relative_uri; - const char *soap_port; - const char * use_ssl; - const char *poa_address; - const char *offline_sync; - - - poa_address = url->host; - if (!poa_address || strlen (poa_address) ==0) - return; - soap_port = camel_url_get_param (url, "soap_port"); - - if (!soap_port || strlen (soap_port) == 0) - soap_port = "7191"; - - use_ssl = camel_url_get_param (url, "use_ssl"); - offline_sync = camel_url_get_param (url, "offline_sync"); - - group = e_source_group_new (group_name, GROUPWISE_BASE_URI); - if (!e_source_list_add_group (source_list, group, -1)) - return; - relative_uri = g_strdup_printf ("%s@%s/", url->user, poa_address); - - source = e_source_new (source_name, relative_uri); - e_source_set_property (source, "auth", "1"); - e_source_set_property (source, "username", url->user); - e_source_set_property (source, "port", camel_url_get_param (url, "soap_port")); - e_source_set_property (source, "auth-domain", "Groupwise"); - e_source_set_property (source, "use_ssl", use_ssl); - e_source_set_property (source, "offline_sync", offline_sync ? "1" : "0" ); - - e_source_set_color_spec (source, "#EEBC60"); - e_source_group_add_source (group, source, -1); - - ids = gconf_client_get_list (client, CALENDAR_CONFIG_MEMOS_SELECTED_MEMOS, GCONF_VALUE_STRING, &error); - if ( error != NULL ) { - g_warning("%s (%s) %s\n", G_STRLOC, G_STRFUNC, error->message); - g_error_free(error); - } - ids = g_slist_append (ids, g_strdup (e_source_peek_uid (source))); - gconf_client_set_list (client, CALENDAR_CONFIG_MEMOS_SELECTED_MEMOS, GCONF_VALUE_STRING, ids, NULL); - temp = ids; - for (; temp != NULL; temp = g_slist_next (temp)) - g_free (temp->data); - - g_slist_free (ids); - g_object_unref (source); - g_object_unref (group); - g_free (relative_uri); -} - -gboolean -migrate_memos (MemosComponent *component, int major, int minor, int revision, struct _GError **err) -{ - ESourceGroup *on_this_computer = NULL; - ESourceGroup *on_the_web = NULL; - ESource *personal_source = NULL; - ESourceList *source_list = NULL; - gboolean retval = FALSE; - - source_list = memos_component_peek_source_list (component); - - /* we call this unconditionally now - create_groups either - creates the groups/sources or it finds the necessary - groups/sources. */ - create_memo_sources (component, source_list, &on_this_computer, &on_the_web, &personal_source); - - /* Migration for Gw accounts between versions < 2.8 */ - if (major == 2 && minor < 8) { - EAccountList *al; - EAccount *a; - CamelURL *url; - EIterator *it; - GConfClient *gconf_client = gconf_client_get_default (); - al = e_account_list_new (gconf_client); - for (it = e_list_get_iterator((EList *)al); - e_iterator_is_valid(it); - e_iterator_next(it)) { - a = (EAccount *) e_iterator_get(it); - if (!a->enabled || !is_groupwise_account (a)) - continue; - url = camel_url_new (a->source->url, NULL); - add_gw_esource (source_list, a->name, _("Notes"), url, gconf_client); - camel_url_free (url); - } - g_object_unref (al); - g_object_unref (gconf_client); - } - - e_source_list_sync (source_list, NULL); - retval = TRUE; - - if (on_this_computer) - g_object_unref (on_this_computer); - if (on_the_web) - g_object_unref (on_the_web); - if (personal_source) - g_object_unref (personal_source); - - return retval; -} diff --git a/calendar/gui/migration.h b/calendar/gui/migration.h deleted file mode 100644 index 4073e31fac..0000000000 --- a/calendar/gui/migration.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * - * 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 - * - * - * Authors: - * Rodrigo Moya - * - * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) - * - */ - -#ifndef MIGRATION_H -#define MIGRATION_H - -#include -#include "calendar-component.h" -#include "tasks-component.h" -#include "memos-component.h" - -struct _GError; - -gboolean migrate_calendars (CalendarComponent *component, int major, int minor, int revision, struct _GError **err); -gboolean migrate_tasks (TasksComponent *component, int major, int minor, int revision, struct _GError **err); -gboolean migrate_memos (MemosComponent *component, int major, int minor, int revision, struct _GError **err); -#endif diff --git a/calendar/modules/e-cal-shell-module-migrate.c b/calendar/modules/e-cal-shell-module-migrate.c new file mode 100644 index 0000000000..0c5d02ce26 --- /dev/null +++ b/calendar/modules/e-cal-shell-module-migrate.c @@ -0,0 +1,785 @@ +/* + * e-cal-shell-module-migrate.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 + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#include "e-cal-shell-module-migrate.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "e-util/e-bconf-map.h" +#include "e-util/e-folder-map.h" +#include "e-util/e-util-private.h" +#include "calendar/gui/calendar-config.h" +#include "calendar/gui/calendar-config-keys.h" +#include "calendar/gui/e-cal-event.h" + +#define WEBCAL_BASE_URI "webcal://" +#define CONTACTS_BASE_URI "contacts://" +#define BAD_CONTACTS_BASE_URI "contact://" +#define PERSONAL_RELATIVE_URI "system" + +static e_gconf_map_t calendar_display_map[] = { + /* /Calendar/Display */ + { "Timezone", "calendar/display/timezone", E_GCONF_MAP_STRING }, + { "Use24HourFormat", "calendar/display/use_24hour_format", E_GCONF_MAP_BOOL }, + { "WeekStartDay", "calendar/display/week_start_day", E_GCONF_MAP_INT }, + { "DayStartHour", "calendar/display/day_start_hour", E_GCONF_MAP_INT }, + { "DayStartMinute", "calendar/display/day_start_minute", E_GCONF_MAP_INT }, + { "DayEndHour", "calendar/display/day_end_hour", E_GCONF_MAP_INT }, + { "DayEndMinute", "calendar/display/day_end_minute", E_GCONF_MAP_INT }, + { "TimeDivisions", "calendar/display/time_divisions", E_GCONF_MAP_INT }, + { "View", "calendar/display/default_view", E_GCONF_MAP_INT }, + { "HPanePosition", "calendar/display/hpane_position", E_GCONF_MAP_FLOAT }, + { "VPanePosition", "calendar/display/vpane_position", E_GCONF_MAP_FLOAT }, + { "MonthHPanePosition", "calendar/display/month_hpane_position", E_GCONF_MAP_FLOAT }, + { "MonthVPanePosition", "calendar/display/month_vpane_position", E_GCONF_MAP_FLOAT }, + { "CompressWeekend", "calendar/display/compress_weekend", E_GCONF_MAP_BOOL }, + { "ShowEventEndTime", "calendar/display/show_event_end", E_GCONF_MAP_BOOL }, + { "WorkingDays", "calendar/display/working_days", E_GCONF_MAP_INT }, + { NULL }, +}; + +static e_gconf_map_t calendar_other_map[] = { + /* /Calendar/Other */ + { "ConfirmDelete", "calendar/prompts/confirm_delete", E_GCONF_MAP_BOOL }, + { "ConfirmExpunge", "calendar/prompts/confirm_purge", E_GCONF_MAP_BOOL }, + { "UseDefaultReminder", "calendar/other/use_default_reminder", E_GCONF_MAP_BOOL }, + { "DefaultReminderInterval", "calendar/other/default_reminder_interval", E_GCONF_MAP_INT }, + { "DefaultReminderUnits", "calendar/other/default_reminder_units", E_GCONF_MAP_STRING }, + { NULL }, +}; + +static e_gconf_map_t calendar_datenavigator_map[] = { + /* /Calendar/DateNavigator */ + { "ShowWeekNumbers", "calendar/date_navigator/show_week_numbers", E_GCONF_MAP_BOOL }, + { NULL }, +}; + +static e_gconf_map_t calendar_alarmnotify_map[] = { + /* /Calendar/AlarmNotify */ + { "LastNotificationTime", "calendar/notify/last_notification_time", E_GCONF_MAP_INT }, + { "CalendarToLoad%i", "calendar/notify/calendars", E_GCONF_MAP_STRING|E_GCONF_MAP_LIST }, + { "BlessedProgram%i", "calendar/notify/programs", E_GCONF_MAP_STRING|E_GCONF_MAP_LIST }, + { NULL }, +}; + +static e_gconf_map_list_t calendar_remap_list[] = { + + { "/Calendar/Display", calendar_display_map }, + { "/Calendar/Other/Map", calendar_other_map }, + { "/Calendar/DateNavigator", calendar_datenavigator_map }, + { "/Calendar/AlarmNotify", calendar_alarmnotify_map }, + + { NULL }, +}; + +static GtkWidget *window; +static GtkLabel *label; +static GtkProgressBar *progress; + +#ifndef G_OS_WIN32 + +/* No previous versions have been available on Win32, so don't + * bother with upgrade support from 1.x on Win32. + */ + +static void +setup_progress_dialog (void) +{ + GtkWidget *vbox, *hbox, *w; + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_title ((GtkWindow *) window, _("Migrating...")); + gtk_window_set_modal ((GtkWindow *) window, TRUE); + gtk_container_set_border_width ((GtkContainer *) window, 6); + + vbox = gtk_vbox_new (FALSE, 6); + gtk_widget_show (vbox); + gtk_container_add ((GtkContainer *) window, vbox); + + w = gtk_label_new (_("The location and hierarchy of the Evolution calendar " + "folders has changed since Evolution 1.x.\n\nPlease be " + "patient while Evolution migrates your folders...")); + + gtk_label_set_line_wrap ((GtkLabel *) w, TRUE); + gtk_widget_show (w); + gtk_box_pack_start_defaults ((GtkBox *) vbox, w); + + hbox = gtk_hbox_new (FALSE, 6); + gtk_widget_show (hbox); + gtk_box_pack_start_defaults ((GtkBox *) vbox, hbox); + + label = (GtkLabel *) gtk_label_new (""); + gtk_widget_show ((GtkWidget *) label); + gtk_box_pack_start_defaults ((GtkBox *) hbox, (GtkWidget *) label); + + progress = (GtkProgressBar *) gtk_progress_bar_new (); + gtk_widget_show ((GtkWidget *) progress); + gtk_box_pack_start_defaults ((GtkBox *) hbox, (GtkWidget *) progress); + + gtk_widget_show (window); +} + +static void +dialog_close (void) +{ + gtk_widget_destroy ((GtkWidget *) window); +} + +static void +dialog_set_folder_name (const char *folder_name) +{ + char *text; + + text = g_strdup_printf (_("Migrating '%s':"), folder_name); + gtk_label_set_text (label, text); + g_free (text); + + gtk_progress_bar_set_fraction (progress, 0.0); + + while (gtk_events_pending ()) + gtk_main_iteration (); +} + +static void +dialog_set_progress (double percent) +{ + char text[5]; + + snprintf (text, sizeof (text), "%d%%", (int) (percent * 100.0f)); + + gtk_progress_bar_set_fraction (progress, percent); + gtk_progress_bar_set_text (progress, text); + + while (gtk_events_pending ()) + gtk_main_iteration (); +} + +static gboolean +check_for_conflict (ESourceGroup *group, char *name) +{ + GSList *sources; + GSList *s; + + sources = e_source_group_peek_sources (group); + + for (s = sources; s; s = s->next) { + ESource *source = E_SOURCE (s->data); + + if (!strcmp (e_source_peek_name (source), name)) + return TRUE; + } + + return FALSE; +} + +static char * +get_source_name (ESourceGroup *group, const char *path) +{ + char **p = g_strsplit (path, "/", 0); + int i, j, starting_index; + int num_elements; + gboolean conflict; + GString *s = g_string_new (NULL); + + for (i = 0; p[i]; i ++) ; + + num_elements = i; + i--; + + /* p[i] is now the last path element */ + + /* check if it conflicts */ + starting_index = i; + do { + for (j = starting_index; j < num_elements; j += 2) { + if (j != starting_index) + g_string_append_c (s, '_'); + g_string_append (s, p[j]); + } + + conflict = check_for_conflict (group, s->str); + + + /* if there was a conflict back up 2 levels (skipping the /subfolder/ element) */ + if (conflict) + starting_index -= 2; + + /* we always break out if we can't go any further, + regardless of whether or not we conflict. */ + if (starting_index < 0) + break; + + } while (conflict); + g_strfreev (p); + + return g_string_free (s, FALSE); +} + +static gboolean +migrate_ical (ECal *old_ecal, ECal *new_ecal) +{ + GList *l, *objects; + int num_added = 0; + int num_objects; + gboolean retval = TRUE; + + /* both ecals are loaded, start the actual migration */ + if (!e_cal_get_object_list (old_ecal, "#t", &objects, NULL)) + return FALSE; + + num_objects = g_list_length (objects); + for (l = objects; l; l = l->next) { + icalcomponent *ical_comp = l->data; + GError *error = NULL; + + if (!e_cal_create_object (new_ecal, ical_comp, NULL, &error)) { + g_warning ("Migration of object failed: %s", error->message); + retval = FALSE; + } + + g_clear_error (&error); + + num_added ++; + dialog_set_progress ((double)num_added / num_objects); + } + + g_list_foreach (objects, (GFunc) icalcomponent_free, NULL); + g_list_free (objects); + + return retval; +} + +static gboolean +migrate_ical_folder_to_source (char *old_path, ESource *new_source, ECalSourceType type) +{ + ECal *old_ecal = NULL, *new_ecal = NULL; + ESource *old_source; + ESourceGroup *group; + char *old_uri = g_strdup_printf ("file://%s", old_path); + GError *error = NULL; + gboolean retval = FALSE; + + group = e_source_group_new ("", old_uri); + old_source = e_source_new ("", ""); + e_source_group_add_source (group, old_source, -1); + + dialog_set_folder_name (e_source_peek_name (new_source)); + + if (!(old_ecal = e_cal_new (old_source, type))) { + g_warning ("could not find a backend for '%s'", e_source_get_uri (old_source)); + goto finish; + } + if (!e_cal_open (old_ecal, FALSE, &error)) { + g_warning ("failed to load source ecal for migration: '%s' (%s)", error->message, + e_source_get_uri (old_source)); + goto finish; + } + + if (!(new_ecal = e_cal_new (new_source, type))) { + g_warning ("could not find a backend for '%s'", e_source_get_uri (new_source)); + goto finish; + } + if (!e_cal_open (new_ecal, FALSE, &error)) { + g_warning ("failed to load destination ecal for migration: '%s' (%s)", error->message, + e_source_get_uri (new_source)); + goto finish; + } + + retval = migrate_ical (old_ecal, new_ecal); + +finish: + g_clear_error (&error); + if (old_ecal) + g_object_unref (old_ecal); + g_object_unref (group); + if (new_ecal) + g_object_unref (new_ecal); + g_free (old_uri); + + return retval; +} + +static gboolean +migrate_ical_folder (char *old_path, ESourceGroup *dest_group, char *source_name, ECalSourceType type) +{ + ESource *new_source; + gboolean retval; + + new_source = e_source_new (source_name, source_name); + e_source_set_relative_uri (new_source, e_source_peek_uid (new_source)); + e_source_group_add_source (dest_group, new_source, -1); + + retval = migrate_ical_folder_to_source (old_path, new_source, type); + + g_object_unref (new_source); + + return retval; +} + +#endif /* !G_OS_WIN32 */ + +#ifndef G_OS_WIN32 + +static void +migrate_pilot_db_key (const char *key, gpointer user_data) +{ + EXmlHash *xmlhash = user_data; + + e_xmlhash_add (xmlhash, key, ""); +} + +static void +migrate_pilot_data (const char *component, const char *conduit, const char *old_path, const char *new_path) +{ + char *changelog, *map; + const char *dent; + const char *ext; + char *filename; + GDir *dir; + + if (!(dir = g_dir_open (old_path, 0, NULL))) + return; + + map = g_alloca (12 + strlen (conduit)); + sprintf (map, "pilot-map-%s-", conduit); + + changelog = g_alloca (24 + strlen (conduit)); + sprintf (changelog, "pilot-sync-evolution-%s-", conduit); + + while ((dent = g_dir_read_name (dir))) { + if (!strncmp (dent, map, strlen (map)) && + ((ext = strrchr (dent, '.')) && !strcmp (ext, ".xml"))) { + /* pilot map file - src and dest file formats are identical */ + unsigned char inbuf[4096]; + size_t nread, nwritten; + int fd0, fd1; + ssize_t n; + + filename = g_build_filename (old_path, dent, NULL); + if ((fd0 = g_open (filename, O_RDONLY|O_BINARY, 0)) == -1) { + g_free (filename); + continue; + } + + g_free (filename); + filename = g_build_filename (new_path, dent, NULL); + if ((fd1 = g_open (filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666)) == -1) { + g_free (filename); + close (fd0); + continue; + } + + do { + do { + n = read (fd0, inbuf, sizeof (inbuf)); + } while (n == -1 && errno == EINTR); + + if (n < 1) + break; + + nread = n; + nwritten = 0; + do { + do { + n = write (fd1, inbuf + nwritten, nread - nwritten); + } while (n == -1 && errno == EINTR); + + if (n > 0) + nwritten += n; + } while (nwritten < nread && n != -1); + + if (n == -1) + break; + } while (1); + + if (n != -1) + n = fsync (fd1); + + if (n == -1) { + g_warning ("Failed to migrate %s: %s", dent, strerror (errno)); + g_unlink (filename); + } + + close (fd0); + close (fd1); + g_free (filename); + } else if (!strncmp (dent, changelog, strlen (changelog)) && + ((ext = strrchr (dent, '.')) && !strcmp (ext, ".db"))) { + /* src and dest formats differ, src format is db3 while dest format is xml */ + EXmlHash *xmlhash; + EDbHash *dbhash; + struct stat st; + + filename = g_build_filename (old_path, dent, NULL); + if (g_stat (filename, &st) == -1) { + g_free (filename); + continue; + } + + dbhash = e_dbhash_new (filename); + g_free (filename); + + filename = g_strdup_printf ("%s/%s.ics-%s", new_path, component, dent); + if (g_stat (filename, &st) != -1) + g_unlink (filename); + xmlhash = e_xmlhash_new (filename); + g_free (filename); + + e_dbhash_foreach_key (dbhash, migrate_pilot_db_key, xmlhash); + + e_dbhash_destroy (dbhash); + + e_xmlhash_write (xmlhash); + e_xmlhash_destroy (xmlhash); + } + } + + g_dir_close (dir); +} + +#endif + +static ESourceGroup * +create_calendar_contact_source (ESourceList *source_list) +{ + ESourceGroup *group; + ESource *source; + + /* Create the contacts group */ + group = e_source_group_new (_("Contacts"), CONTACTS_BASE_URI); + e_source_list_add_group (source_list, group, -1); + + source = e_source_new (_("Birthdays & Anniversaries"), "/"); + e_source_group_add_source (group, source, -1); + g_object_unref (source); + + e_source_set_color_spec (source, "#FED4D3"); + e_source_group_set_readonly (group, TRUE); + + return group; +} + +static void +create_calendar_sources (EShellModule *shell_module, + ESourceList *source_list, + ESourceGroup **on_this_computer, + ESource **personal_source, + ESourceGroup **on_the_web, + ESourceGroup **contacts) +{ + GSList *groups; + ESourceGroup *group; + char *base_uri, *base_uri_proto; + const gchar *base_dir; + + *on_this_computer = NULL; + *on_the_web = NULL; + *contacts = NULL; + *personal_source = NULL; + + base_dir = e_shell_module_get_config_dir (shell_module); + base_uri = g_build_filename (base_dir, "local", NULL); + + base_uri_proto = g_filename_to_uri (base_uri, NULL, NULL); + + groups = e_source_list_peek_groups (source_list); + if (groups) { + /* groups are already there, we need to search for things... */ + GSList *g; + + for (g = groups; g; g = g->next) { + + group = E_SOURCE_GROUP (g->data); + + if (!strcmp (BAD_CONTACTS_BASE_URI, e_source_group_peek_base_uri (group))) + e_source_group_set_base_uri (group, CONTACTS_BASE_URI); + + if (!strcmp (base_uri, e_source_group_peek_base_uri (group))) + e_source_group_set_base_uri (group, base_uri_proto); + + if (!*on_this_computer && !strcmp (base_uri_proto, e_source_group_peek_base_uri (group))) + *on_this_computer = g_object_ref (group); + else if (!*on_the_web && !strcmp (WEBCAL_BASE_URI, e_source_group_peek_base_uri (group))) + *on_the_web = g_object_ref (group); + else if (!*contacts && !strcmp (CONTACTS_BASE_URI, e_source_group_peek_base_uri (group))) + *contacts = g_object_ref (group); + } + } + + if (*on_this_computer) { + /* make sure "Personal" shows up as a source under + this group */ + GSList *sources = e_source_group_peek_sources (*on_this_computer); + GSList *s; + for (s = sources; s; s = s->next) { + ESource *source = E_SOURCE (s->data); + const gchar *relative_uri; + + relative_uri = e_source_peek_relative_uri (source); + if (relative_uri == NULL) + continue; + if (!strcmp (PERSONAL_RELATIVE_URI, relative_uri)) { + *personal_source = g_object_ref (source); + break; + } + } + } else { + /* create the local source group */ + group = e_source_group_new (_("On This Computer"), base_uri_proto); + e_source_list_add_group (source_list, group, -1); + + *on_this_computer = group; + } + + if (!*personal_source) { + char *primary_calendar = calendar_config_get_primary_calendar (); + + /* Create the default Person calendar */ + ESource *source = e_source_new (_("Personal"), PERSONAL_RELATIVE_URI); + e_source_group_add_source (*on_this_computer, source, -1); + + if (!primary_calendar && !calendar_config_get_calendars_selected ()) { + GSList selected; + + calendar_config_set_primary_calendar (e_source_peek_uid (source)); + + selected.data = (gpointer)e_source_peek_uid (source); + selected.next = NULL; + calendar_config_set_calendars_selected (&selected); + } + + g_free (primary_calendar); + e_source_set_color_spec (source, "#BECEDD"); + *personal_source = source; + } + + if (!*on_the_web) { + /* Create the Webcal source group */ + group = e_source_group_new (_("On The Web"), WEBCAL_BASE_URI); + e_source_list_add_group (source_list, group, -1); + + *on_the_web = group; + } + + if (!*contacts) { + group = create_calendar_contact_source (source_list); + + *contacts = group; + } + + g_free (base_uri_proto); + g_free (base_uri); +} + +gboolean +e_cal_shell_module_migrate (EShellModule *shell_module, + gint major, + gint minor, + gint micro, + GError **error) +{ + ESourceGroup *on_this_computer = NULL, *on_the_web = NULL, *contacts = NULL; + ESource *personal_source = NULL; + ESourceList *source_list; + ECalEvent *ece; + ECalEventTargetModule *target; + gboolean retval = FALSE; + + source_list = g_object_get_data ( + G_OBJECT (shell_module), "source-list"); + + /* we call this unconditionally now - create_groups either + creates the groups/sources or it finds the necessary + groups/sources. */ + create_calendar_sources ( + shell_module, source_list, &on_this_computer, + &personal_source, &on_the_web, &contacts); + +#ifndef G_OS_WIN32 + if (major == 1) { + xmlDocPtr config_doc = NULL; + char *conf_file; + struct stat st; + + conf_file = g_build_filename (g_get_home_dir (), "evolution", "config.xmldb", NULL); + if (lstat (conf_file, &st) == 0 && S_ISREG (st.st_mode)) + config_doc = xmlParseFile (conf_file); + g_free (conf_file); + + if (config_doc && minor <= 2) { + GConfClient *gconf; + int res = 0; + + /* move bonobo config to gconf */ + gconf = gconf_client_get_default (); + + res = e_bconf_import (gconf, config_doc, calendar_remap_list); + + g_object_unref (gconf); + + xmlFreeDoc(config_doc); + + if (res != 0) { + /* FIXME: set proper domain/code */ + g_set_error(error, 0, 0, _("Unable to migrate old settings from evolution/config.xmldb")); + goto fail; + } + } + + if (minor <= 4) { + GSList *migration_dirs, *l; + char *path, *local_cal_folder; + + setup_progress_dialog (); + + path = g_build_filename (g_get_home_dir (), "evolution", "local", NULL); + migration_dirs = e_folder_map_local_folders (path, "calendar"); + local_cal_folder = g_build_filename (path, "Calendar", NULL); + g_free (path); + + if (personal_source) + migrate_ical_folder_to_source (local_cal_folder, personal_source, E_CAL_SOURCE_TYPE_EVENT); + + for (l = migration_dirs; l; l = l->next) { + char *source_name; + + if (personal_source && !strcmp ((char*)l->data, local_cal_folder)) + continue; + + source_name = get_source_name (on_this_computer, (char*)l->data); + + if (!migrate_ical_folder (l->data, on_this_computer, source_name, E_CAL_SOURCE_TYPE_EVENT)) { + /* FIXME: domain/code */ + g_set_error(error, 0, 0, _("Unable to migrate calendar `%s'"), source_name); + g_free(source_name); + goto fail; + } + + g_free (source_name); + } + + g_free (local_cal_folder); + + dialog_close (); + } + + if (minor <= 4 || (minor == 5 && micro < 5)) { + GConfClient *gconf; + GConfValue *gconf_val; + int i; + const char *keys[] = { + CALENDAR_CONFIG_HPANE_POS, + CALENDAR_CONFIG_VPANE_POS, + CALENDAR_CONFIG_MONTH_HPANE_POS, + CALENDAR_CONFIG_MONTH_VPANE_POS, + NULL + }; + + gconf = gconf_client_get_default (); + + for (i = 0; keys[i]; i++) { + gconf_val = gconf_client_get (gconf, keys[i], NULL); + if (gconf_val) { + if (gconf_val->type != GCONF_VALUE_INT) + gconf_client_unset (gconf, keys[i], NULL); + gconf_value_free (gconf_val); + } + } + + g_object_unref (gconf); + } + + if (minor < 5 || (minor == 5 && micro <= 10)) { + char *old_path, *new_path; + + old_path = g_build_filename (g_get_home_dir (), "evolution", "local", "Calendar", NULL); + new_path = g_build_filename (e_shell_module_get_config_dir (shell_module), + "local", "system", NULL); + migrate_pilot_data ("calendar", "calendar", old_path, new_path); + g_free (new_path); + g_free (old_path); + } + + /* we only need to do this next step if people ran + older versions of 1.5. We need to clear out the + absolute URI's that were assigned to ESources + during one phase of development, as they take + precedent over relative uris (but aren't updated + when editing an ESource). */ + if (minor == 5 && micro <= 11) { + GSList *g; + for (g = e_source_list_peek_groups (source_list); g; g = g->next) { + ESourceGroup *group = g->data; + GSList *s; + + for (s = e_source_group_peek_sources (group); s; s = s->next) { + ESource *source = s->data; + e_source_set_absolute_uri (source, NULL); + } + } + } + + } +#endif /* !G_OS_WIN32 */ + + e_source_list_sync (source_list, NULL); + + /** @Event: component.migration + * @Title: Migration step in component initialization + * @Target: ECalEventTargetComponent + * + * component.migration is emitted during the calendar component + * initialization process. This allows new calendar backend types + * to be distributed as an e-d-s backend and a plugin without + * reaching their grubby little fingers into migration.c + */ + /* Fire off migration event */ + ece = e_cal_event_peek (); + target = e_cal_event_target_new_module (ece, shell_module, 0); + e_event_emit ((EEvent *) ece, "module.migration", (EEventTarget *) target); + + retval = TRUE; +fail: + if (on_this_computer) + g_object_unref (on_this_computer); + if (on_the_web) + g_object_unref (on_the_web); + if (contacts) + g_object_unref (contacts); + if (personal_source) + g_object_unref (personal_source); + + return retval; +} + diff --git a/calendar/modules/e-cal-shell-module-migrate.h b/calendar/modules/e-cal-shell-module-migrate.h new file mode 100644 index 0000000000..afef469df8 --- /dev/null +++ b/calendar/modules/e-cal-shell-module-migrate.h @@ -0,0 +1,38 @@ +/* + * e-cal-shell-module-migrate.h + * + * 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 + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef E_CAL_SHELL_MODULE_MIGRATE_H +#define E_CAL_SHELL_MODULE_MIGRATE_H + +#include +#include + +G_BEGIN_DECLS + +gboolean e_cal_shell_module_migrate (EShellModule *shell_module, + gint major, + gint minor, + gint micro, + GError **error); + +G_END_DECLS + +#endif /* E_CAL_SHELL_MODULE_MIGRATE_H */ diff --git a/calendar/modules/e-cal-shell-module.c b/calendar/modules/e-cal-shell-module.c index f5cdbe0f48..1dff363a0f 100644 --- a/calendar/modules/e-cal-shell-module.c +++ b/calendar/modules/e-cal-shell-module.c @@ -33,6 +33,7 @@ #include "calendar/gui/calendar-config.h" #include "e-cal-shell-view.h" +#include "e-cal-shell-module-migrate.h" #define MODULE_NAME "calendar" #define MODULE_ALIASES "" @@ -370,7 +371,11 @@ static EShellModuleInfo module_info = { MODULE_NAME, MODULE_ALIASES, MODULE_SCHEMES, - MODULE_SORT_ORDER + MODULE_SORT_ORDER, + + /* is_busy */ NULL, + /* shutdown */ NULL, + e_cal_shell_module_migrate }; void diff --git a/calendar/modules/e-memo-shell-module-migrate.c b/calendar/modules/e-memo-shell-module-migrate.c new file mode 100644 index 0000000000..7fe9442d5e --- /dev/null +++ b/calendar/modules/e-memo-shell-module-migrate.c @@ -0,0 +1,257 @@ +/* + * e-memo-shell-module-migrate.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 + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#include "e-memo-shell-module-migrate.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "calendar/gui/calendar-config.h" +#include "calendar/gui/calendar-config-keys.h" + +#define WEBCAL_BASE_URI "webcal://" +#define PERSONAL_RELATIVE_URI "system" +#define GROUPWISE_BASE_URI "groupwise://" + +static void +create_memo_sources (EShellModule *shell_module, + ESourceList *source_list, + ESourceGroup **on_this_computer, + ESourceGroup **on_the_web, + ESource **personal_source) +{ + GSList *groups; + ESourceGroup *group; + char *base_uri, *base_uri_proto; + const gchar *base_dir; + + *on_this_computer = NULL; + *on_the_web = NULL; + *personal_source = NULL; + + base_dir = e_shell_module_get_config_dir (shell_module); + base_uri = g_build_filename (base_dir, "local", NULL); + + base_uri_proto = g_filename_to_uri (base_uri, NULL, NULL); + + groups = e_source_list_peek_groups (source_list); + if (groups) { + /* groups are already there, we need to search for things... */ + GSList *g; + + for (g = groups; g; g = g->next) { + + group = E_SOURCE_GROUP (g->data); + + if (!*on_this_computer && !strcmp (base_uri_proto, e_source_group_peek_base_uri (group))) + *on_this_computer = g_object_ref (group); + else if (!*on_the_web && !strcmp (WEBCAL_BASE_URI, e_source_group_peek_base_uri (group))) + *on_the_web = g_object_ref (group); + } + } + + if (*on_this_computer) { + /* make sure "Personal" shows up as a source under + this group */ + GSList *sources = e_source_group_peek_sources (*on_this_computer); + GSList *s; + for (s = sources; s; s = s->next) { + ESource *source = E_SOURCE (s->data); + const gchar *relative_uri; + + relative_uri = e_source_peek_relative_uri (source); + if (relative_uri == NULL) + continue; + if (!strcmp (PERSONAL_RELATIVE_URI, relative_uri)) { + *personal_source = g_object_ref (source); + break; + } + } + } else { + /* create the local source group */ + group = e_source_group_new (_("On This Computer"), base_uri_proto); + e_source_list_add_group (source_list, group, -1); + + *on_this_computer = group; + } + + if (!*personal_source) { + /* Create the default Person task list */ + ESource *source = e_source_new (_("Personal"), PERSONAL_RELATIVE_URI); + e_source_group_add_source (*on_this_computer, source, -1); + + if (!calendar_config_get_primary_memos () && !calendar_config_get_memos_selected ()) { + GSList selected; + + calendar_config_set_primary_memos (e_source_peek_uid (source)); + + selected.data = (gpointer)e_source_peek_uid (source); + selected.next = NULL; + calendar_config_set_memos_selected (&selected); + } + + e_source_set_color_spec (source, "#BECEDD"); + *personal_source = source; + } + + if (!*on_the_web) { + /* Create the Webcal source group */ + group = e_source_group_new (_("On The Web"), WEBCAL_BASE_URI); + e_source_list_add_group (source_list, group, -1); + + *on_the_web = group; + } + + g_free (base_uri_proto); + g_free (base_uri); +} + +static gboolean +is_groupwise_account (EAccount *account) +{ + if (account->source->url != NULL) { + return g_str_has_prefix (account->source->url, GROUPWISE_BASE_URI); + } else { + return FALSE; + } +} + +static void +add_gw_esource (ESourceList *source_list, const char *group_name, const char *source_name, CamelURL *url, GConfClient *client) +{ + ESourceGroup *group; + ESource *source; + GSList *ids, *temp ; + GError *error = NULL; + char *relative_uri; + const char *soap_port; + const char * use_ssl; + const char *poa_address; + const char *offline_sync; + + + poa_address = url->host; + if (!poa_address || strlen (poa_address) ==0) + return; + soap_port = camel_url_get_param (url, "soap_port"); + + if (!soap_port || strlen (soap_port) == 0) + soap_port = "7191"; + + use_ssl = camel_url_get_param (url, "use_ssl"); + offline_sync = camel_url_get_param (url, "offline_sync"); + + group = e_source_group_new (group_name, GROUPWISE_BASE_URI); + if (!e_source_list_add_group (source_list, group, -1)) + return; + relative_uri = g_strdup_printf ("%s@%s/", url->user, poa_address); + + source = e_source_new (source_name, relative_uri); + e_source_set_property (source, "auth", "1"); + e_source_set_property (source, "username", url->user); + e_source_set_property (source, "port", camel_url_get_param (url, "soap_port")); + e_source_set_property (source, "auth-domain", "Groupwise"); + e_source_set_property (source, "use_ssl", use_ssl); + e_source_set_property (source, "offline_sync", offline_sync ? "1" : "0" ); + + e_source_set_color_spec (source, "#EEBC60"); + e_source_group_add_source (group, source, -1); + + ids = gconf_client_get_list (client, CALENDAR_CONFIG_MEMOS_SELECTED_MEMOS, GCONF_VALUE_STRING, &error); + if ( error != NULL ) { + g_warning("%s (%s) %s\n", G_STRLOC, G_STRFUNC, error->message); + g_error_free(error); + } + ids = g_slist_append (ids, g_strdup (e_source_peek_uid (source))); + gconf_client_set_list (client, CALENDAR_CONFIG_MEMOS_SELECTED_MEMOS, GCONF_VALUE_STRING, ids, NULL); + temp = ids; + for (; temp != NULL; temp = g_slist_next (temp)) + g_free (temp->data); + + g_slist_free (ids); + g_object_unref (source); + g_object_unref (group); + g_free (relative_uri); +} + +gboolean +e_memo_shell_module_migrate (EShellModule *shell_module, + gint major, + gint minor, + gint revision, + GError **error) +{ + ESourceGroup *on_this_computer = NULL; + ESourceGroup *on_the_web = NULL; + ESource *personal_source = NULL; + ESourceList *source_list = NULL; + gboolean retval = FALSE; + + source_list = g_object_get_data ( + G_OBJECT (shell_module), "source-list"); + + /* we call this unconditionally now - create_groups either + creates the groups/sources or it finds the necessary + groups/sources. */ + create_memo_sources ( + shell_module, source_list, &on_this_computer, + &on_the_web, &personal_source); + + /* Migration for Gw accounts between versions < 2.8 */ + if (major == 2 && minor < 8) { + EAccountList *al; + EAccount *a; + CamelURL *url; + EIterator *it; + GConfClient *gconf_client = gconf_client_get_default (); + al = e_account_list_new (gconf_client); + for (it = e_list_get_iterator((EList *)al); + e_iterator_is_valid(it); + e_iterator_next(it)) { + a = (EAccount *) e_iterator_get(it); + if (!a->enabled || !is_groupwise_account (a)) + continue; + url = camel_url_new (a->source->url, NULL); + add_gw_esource (source_list, a->name, _("Notes"), url, gconf_client); + camel_url_free (url); + } + g_object_unref (al); + g_object_unref (gconf_client); + } + + e_source_list_sync (source_list, NULL); + retval = TRUE; + + if (on_this_computer) + g_object_unref (on_this_computer); + if (on_the_web) + g_object_unref (on_the_web); + if (personal_source) + g_object_unref (personal_source); + + return retval; +} diff --git a/calendar/modules/e-memo-shell-module-migrate.h b/calendar/modules/e-memo-shell-module-migrate.h new file mode 100644 index 0000000000..e40e2ff00f --- /dev/null +++ b/calendar/modules/e-memo-shell-module-migrate.h @@ -0,0 +1,38 @@ +/* + * e-memo-shell-module-migrate.h + * + * 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 + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef E_MEMO_SHELL_MODULE_MIGRATE_H +#define E_MEMO_SHELL_MODULE_MIGRATE_H + +#include +#include + +G_BEGIN_DECLS + +gboolean e_memo_shell_module_migrate (EShellModule *shell_module, + gint major, + gint minor, + gint micro, + GError **error); + +G_END_DECLS + +#endif /* E_MEMO_SHELL_MODULE_MIGRATE_H */ diff --git a/calendar/modules/e-memo-shell-module.c b/calendar/modules/e-memo-shell-module.c index c63f0fd958..7f6df49691 100644 --- a/calendar/modules/e-memo-shell-module.c +++ b/calendar/modules/e-memo-shell-module.c @@ -38,6 +38,7 @@ #include "calendar/gui/dialogs/memo-editor.h" #include "e-memo-shell-view.h" +#include "e-memo-shell-module-migrate.h" #define MODULE_NAME "memos" #define MODULE_ALIASES "" @@ -463,7 +464,11 @@ static EShellModuleInfo module_info = { MODULE_NAME, MODULE_ALIASES, MODULE_SCHEMES, - MODULE_SORT_ORDER + MODULE_SORT_ORDER, + + /* is_busy */ NULL, + /* shutdown */ NULL, + e_memo_shell_module_migrate }; void diff --git a/calendar/modules/e-task-shell-module-migrate.c b/calendar/modules/e-task-shell-module-migrate.c new file mode 100644 index 0000000000..2bd34e89a9 --- /dev/null +++ b/calendar/modules/e-task-shell-module-migrate.c @@ -0,0 +1,664 @@ +/* + * e-task-shell-module-migrate.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 + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#include "e-task-shell-module-migrate.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "e-util/e-bconf-map.h" +#include "e-util/e-folder-map.h" +#include "e-util/e-util-private.h" +#include "calendar/gui/calendar-config.h" +#include "calendar/gui/calendar-config-keys.h" + +#define WEBCAL_BASE_URI "webcal://" +#define PERSONAL_RELATIVE_URI "system" + +static e_gconf_map_t calendar_tasks_map[] = { + /* /Calendar/Tasks */ + { "HideCompletedTasks", "calendar/tasks/hide_completed", E_GCONF_MAP_BOOL }, + { "HideCompletedTasksUnits", "calendar/tasks/hide_completed_units", E_GCONF_MAP_STRING }, + { "HideCompletedTasksValue", "calendar/tasks/hide_completed_value", E_GCONF_MAP_INT }, + { NULL }, +}; + +static e_gconf_map_t calendar_tasks_colours_map[] = { + /* /Calendar/Tasks/Colors */ + { "TasksDueToday", "calendar/tasks/colors/due_today", E_GCONF_MAP_STRING }, + { "TasksOverDue", "calendar/tasks/colors/overdue", E_GCONF_MAP_STRING }, + { "TasksDueToday", "calendar/tasks/colors/due_today", E_GCONF_MAP_STRING }, + { NULL }, +}; + +static e_gconf_map_list_t task_remap_list[] = { + + { "/Calendar/Tasks", calendar_tasks_map }, + { "/Calendar/Tasks/Colors", calendar_tasks_colours_map }, + + { NULL }, +}; + +static GtkWidget *window; +static GtkLabel *label; +static GtkProgressBar *progress; + +#ifndef G_OS_WIN32 + +/* No previous versions have been available on Win32, so don't + * bother with upgrade support from 1.x on Win32. + */ + +static void +setup_progress_dialog (void) +{ + GtkWidget *vbox, *hbox, *w; + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_title ((GtkWindow *) window, _("Migrating...")); + gtk_window_set_modal ((GtkWindow *) window, TRUE); + gtk_container_set_border_width ((GtkContainer *) window, 6); + + vbox = gtk_vbox_new (FALSE, 6); + gtk_widget_show (vbox); + gtk_container_add ((GtkContainer *) window, vbox); + + w = gtk_label_new (_("The location and hierarchy of the Evolution task " + "folders has changed since Evolution 1.x.\n\nPlease be " + "patient while Evolution migrates your folders...")); + + gtk_label_set_line_wrap ((GtkLabel *) w, TRUE); + gtk_widget_show (w); + gtk_box_pack_start_defaults ((GtkBox *) vbox, w); + + hbox = gtk_hbox_new (FALSE, 6); + gtk_widget_show (hbox); + gtk_box_pack_start_defaults ((GtkBox *) vbox, hbox); + + label = (GtkLabel *) gtk_label_new (""); + gtk_widget_show ((GtkWidget *) label); + gtk_box_pack_start_defaults ((GtkBox *) hbox, (GtkWidget *) label); + + progress = (GtkProgressBar *) gtk_progress_bar_new (); + gtk_widget_show ((GtkWidget *) progress); + gtk_box_pack_start_defaults ((GtkBox *) hbox, (GtkWidget *) progress); + + gtk_widget_show (window); +} + +static void +dialog_close (void) +{ + gtk_widget_destroy ((GtkWidget *) window); +} + +static void +dialog_set_folder_name (const char *folder_name) +{ + char *text; + + text = g_strdup_printf (_("Migrating '%s':"), folder_name); + gtk_label_set_text (label, text); + g_free (text); + + gtk_progress_bar_set_fraction (progress, 0.0); + + while (gtk_events_pending ()) + gtk_main_iteration (); +} + +static void +dialog_set_progress (double percent) +{ + char text[5]; + + snprintf (text, sizeof (text), "%d%%", (int) (percent * 100.0f)); + + gtk_progress_bar_set_fraction (progress, percent); + gtk_progress_bar_set_text (progress, text); + + while (gtk_events_pending ()) + gtk_main_iteration (); +} + +static gboolean +check_for_conflict (ESourceGroup *group, char *name) +{ + GSList *sources; + GSList *s; + + sources = e_source_group_peek_sources (group); + + for (s = sources; s; s = s->next) { + ESource *source = E_SOURCE (s->data); + + if (!strcmp (e_source_peek_name (source), name)) + return TRUE; + } + + return FALSE; +} + +static char * +get_source_name (ESourceGroup *group, const char *path) +{ + char **p = g_strsplit (path, "/", 0); + int i, j, starting_index; + int num_elements; + gboolean conflict; + GString *s = g_string_new (NULL); + + for (i = 0; p[i]; i ++) ; + + num_elements = i; + i--; + + /* p[i] is now the last path element */ + + /* check if it conflicts */ + starting_index = i; + do { + for (j = starting_index; j < num_elements; j += 2) { + if (j != starting_index) + g_string_append_c (s, '_'); + g_string_append (s, p[j]); + } + + conflict = check_for_conflict (group, s->str); + + + /* if there was a conflict back up 2 levels (skipping the /subfolder/ element) */ + if (conflict) + starting_index -= 2; + + /* we always break out if we can't go any further, + regardless of whether or not we conflict. */ + if (starting_index < 0) + break; + + } while (conflict); + g_strfreev (p); + + return g_string_free (s, FALSE); +} + +static gboolean +migrate_ical (ECal *old_ecal, ECal *new_ecal) +{ + GList *l, *objects; + int num_added = 0; + int num_objects; + gboolean retval = TRUE; + + /* both ecals are loaded, start the actual migration */ + if (!e_cal_get_object_list (old_ecal, "#t", &objects, NULL)) + return FALSE; + + num_objects = g_list_length (objects); + for (l = objects; l; l = l->next) { + icalcomponent *ical_comp = l->data; + GError *error = NULL; + + if (!e_cal_create_object (new_ecal, ical_comp, NULL, &error)) { + g_warning ("Migration of object failed: %s", error->message); + retval = FALSE; + } + + g_clear_error (&error); + + num_added ++; + dialog_set_progress ((double)num_added / num_objects); + } + + g_list_foreach (objects, (GFunc) icalcomponent_free, NULL); + g_list_free (objects); + + return retval; +} + +static gboolean +migrate_ical_folder_to_source (char *old_path, ESource *new_source, ECalSourceType type) +{ + ECal *old_ecal = NULL, *new_ecal = NULL; + ESource *old_source; + ESourceGroup *group; + char *old_uri = g_strdup_printf ("file://%s", old_path); + GError *error = NULL; + gboolean retval = FALSE; + + group = e_source_group_new ("", old_uri); + old_source = e_source_new ("", ""); + e_source_group_add_source (group, old_source, -1); + + dialog_set_folder_name (e_source_peek_name (new_source)); + + if (!(old_ecal = e_cal_new (old_source, type))) { + g_warning ("could not find a backend for '%s'", e_source_get_uri (old_source)); + goto finish; + } + if (!e_cal_open (old_ecal, FALSE, &error)) { + g_warning ("failed to load source ecal for migration: '%s' (%s)", error->message, + e_source_get_uri (old_source)); + goto finish; + } + + if (!(new_ecal = e_cal_new (new_source, type))) { + g_warning ("could not find a backend for '%s'", e_source_get_uri (new_source)); + goto finish; + } + if (!e_cal_open (new_ecal, FALSE, &error)) { + g_warning ("failed to load destination ecal for migration: '%s' (%s)", error->message, + e_source_get_uri (new_source)); + goto finish; + } + + retval = migrate_ical (old_ecal, new_ecal); + +finish: + g_clear_error (&error); + if (old_ecal) + g_object_unref (old_ecal); + g_object_unref (group); + if (new_ecal) + g_object_unref (new_ecal); + g_free (old_uri); + + return retval; +} + +static gboolean +migrate_ical_folder (char *old_path, ESourceGroup *dest_group, char *source_name, ECalSourceType type) +{ + ESource *new_source; + gboolean retval; + + new_source = e_source_new (source_name, source_name); + e_source_set_relative_uri (new_source, e_source_peek_uid (new_source)); + e_source_group_add_source (dest_group, new_source, -1); + + retval = migrate_ical_folder_to_source (old_path, new_source, type); + + g_object_unref (new_source); + + return retval; +} + +#endif /* !G_OS_WIN32 */ + +#ifndef G_OS_WIN32 + +static void +migrate_pilot_db_key (const char *key, gpointer user_data) +{ + EXmlHash *xmlhash = user_data; + + e_xmlhash_add (xmlhash, key, ""); +} + +static void +migrate_pilot_data (const char *component, const char *conduit, const char *old_path, const char *new_path) +{ + char *changelog, *map; + const char *dent; + const char *ext; + char *filename; + GDir *dir; + + if (!(dir = g_dir_open (old_path, 0, NULL))) + return; + + map = g_alloca (12 + strlen (conduit)); + sprintf (map, "pilot-map-%s-", conduit); + + changelog = g_alloca (24 + strlen (conduit)); + sprintf (changelog, "pilot-sync-evolution-%s-", conduit); + + while ((dent = g_dir_read_name (dir))) { + if (!strncmp (dent, map, strlen (map)) && + ((ext = strrchr (dent, '.')) && !strcmp (ext, ".xml"))) { + /* pilot map file - src and dest file formats are identical */ + unsigned char inbuf[4096]; + size_t nread, nwritten; + int fd0, fd1; + ssize_t n; + + filename = g_build_filename (old_path, dent, NULL); + if ((fd0 = g_open (filename, O_RDONLY|O_BINARY, 0)) == -1) { + g_free (filename); + continue; + } + + g_free (filename); + filename = g_build_filename (new_path, dent, NULL); + if ((fd1 = g_open (filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666)) == -1) { + g_free (filename); + close (fd0); + continue; + } + + do { + do { + n = read (fd0, inbuf, sizeof (inbuf)); + } while (n == -1 && errno == EINTR); + + if (n < 1) + break; + + nread = n; + nwritten = 0; + do { + do { + n = write (fd1, inbuf + nwritten, nread - nwritten); + } while (n == -1 && errno == EINTR); + + if (n > 0) + nwritten += n; + } while (nwritten < nread && n != -1); + + if (n == -1) + break; + } while (1); + + if (n != -1) + n = fsync (fd1); + + if (n == -1) { + g_warning ("Failed to migrate %s: %s", dent, strerror (errno)); + g_unlink (filename); + } + + close (fd0); + close (fd1); + g_free (filename); + } else if (!strncmp (dent, changelog, strlen (changelog)) && + ((ext = strrchr (dent, '.')) && !strcmp (ext, ".db"))) { + /* src and dest formats differ, src format is db3 while dest format is xml */ + EXmlHash *xmlhash; + EDbHash *dbhash; + struct stat st; + + filename = g_build_filename (old_path, dent, NULL); + if (g_stat (filename, &st) == -1) { + g_free (filename); + continue; + } + + dbhash = e_dbhash_new (filename); + g_free (filename); + + filename = g_strdup_printf ("%s/%s.ics-%s", new_path, component, dent); + if (g_stat (filename, &st) != -1) + g_unlink (filename); + xmlhash = e_xmlhash_new (filename); + g_free (filename); + + e_dbhash_foreach_key (dbhash, migrate_pilot_db_key, xmlhash); + + e_dbhash_destroy (dbhash); + + e_xmlhash_write (xmlhash); + e_xmlhash_destroy (xmlhash); + } + } + + g_dir_close (dir); +} + +#endif + +static void +create_task_sources (EShellModule *shell_module, + ESourceList *source_list, + ESourceGroup **on_this_computer, + ESourceGroup **on_the_web, + ESource **personal_source) +{ + GSList *groups; + ESourceGroup *group; + char *base_uri, *base_uri_proto; + const gchar *base_dir; + + *on_this_computer = NULL; + *on_the_web = NULL; + *personal_source = NULL; + + base_dir = e_shell_module_get_config_dir (shell_module); + base_uri = g_build_filename (base_dir, "local", NULL); + + base_uri_proto = g_filename_to_uri (base_uri, NULL, NULL); + + groups = e_source_list_peek_groups (source_list); + if (groups) { + /* groups are already there, we need to search for things... */ + GSList *g; + + for (g = groups; g; g = g->next) { + + group = E_SOURCE_GROUP (g->data); + + if (!*on_this_computer && !strcmp (base_uri_proto, e_source_group_peek_base_uri (group))) + *on_this_computer = g_object_ref (group); + else if (!*on_the_web && !strcmp (WEBCAL_BASE_URI, e_source_group_peek_base_uri (group))) + *on_the_web = g_object_ref (group); + } + } + + if (*on_this_computer) { + /* make sure "Personal" shows up as a source under + this group */ + GSList *sources = e_source_group_peek_sources (*on_this_computer); + GSList *s; + for (s = sources; s; s = s->next) { + ESource *source = E_SOURCE (s->data); + const gchar *relative_uri; + + relative_uri = e_source_peek_relative_uri (source); + if (relative_uri == NULL) + continue; + if (!strcmp (PERSONAL_RELATIVE_URI, relative_uri)) { + *personal_source = g_object_ref (source); + break; + } + } + } else { + /* create the local source group */ + group = e_source_group_new (_("On This Computer"), base_uri_proto); + e_source_list_add_group (source_list, group, -1); + + *on_this_computer = group; + } + + if (!*personal_source) { + /* Create the default Person task list */ + ESource *source = e_source_new (_("Personal"), PERSONAL_RELATIVE_URI); + e_source_group_add_source (*on_this_computer, source, -1); + + if (!calendar_config_get_primary_tasks () && !calendar_config_get_tasks_selected ()) { + GSList selected; + + calendar_config_set_primary_tasks (e_source_peek_uid (source)); + + selected.data = (gpointer)e_source_peek_uid (source); + selected.next = NULL; + calendar_config_set_tasks_selected (&selected); + } + + e_source_set_color_spec (source, "#BECEDD"); + *personal_source = source; + } + + if (!*on_the_web) { + /* Create the Webcal source group */ + group = e_source_group_new (_("On The Web"), WEBCAL_BASE_URI); + e_source_list_add_group (source_list, group, -1); + + *on_the_web = group; + } + + g_free (base_uri_proto); + g_free (base_uri); +} + +gboolean +e_task_shell_module_migrate (EShellModule *shell_module, + gint major, + gint minor, + gint micro, + GError **error) +{ + ESourceGroup *on_this_computer = NULL; + ESourceGroup *on_the_web = NULL; + ESource *personal_source = NULL; + ESourceList *source_list; + gboolean retval = FALSE; + + source_list = g_object_get_data ( + G_OBJECT (source_list), "source-list"); + + /* we call this unconditionally now - create_groups either + creates the groups/sources or it finds the necessary + groups/sources. */ + create_task_sources ( + shell_module, source_list, &on_this_computer, + &on_the_web, &personal_source); + +#ifndef G_OS_WIN32 + if (major == 1) { + xmlDocPtr config_doc = NULL; + char *conf_file; + + conf_file = g_build_filename (g_get_home_dir (), "evolution", "config.xmldb", NULL); + if (g_file_test (conf_file, G_FILE_TEST_IS_REGULAR)) + config_doc = e_xml_parse_file (conf_file); + g_free (conf_file); + + if (config_doc && minor <= 2) { + GConfClient *gconf; + int res = 0; + + /* move bonobo config to gconf */ + gconf = gconf_client_get_default (); + + res = e_bconf_import (gconf, config_doc, task_remap_list); + + g_object_unref (gconf); + + xmlFreeDoc(config_doc); + + if (res != 0) { + g_set_error(error, 0, 0, _("Unable to migrate old settings from evolution/config.xmldb")); + goto fail; + } + } + + if (minor <= 4) { + GSList *migration_dirs, *l; + char *path, *local_task_folder; + + setup_progress_dialog (); + + path = g_build_filename (g_get_home_dir (), "evolution", "local", NULL); + migration_dirs = e_folder_map_local_folders (path, "tasks"); + local_task_folder = g_build_filename (path, "Tasks", NULL); + g_free (path); + + if (personal_source) + migrate_ical_folder_to_source (local_task_folder, personal_source, E_CAL_SOURCE_TYPE_TODO); + + for (l = migration_dirs; l; l = l->next) { + char *source_name; + + if (personal_source && !strcmp ((char*)l->data, local_task_folder)) + continue; + + source_name = get_source_name (on_this_computer, (char*)l->data); + + if (!migrate_ical_folder (l->data, on_this_computer, source_name, E_CAL_SOURCE_TYPE_TODO)) { + /* FIXME: domain/code */ + g_set_error(error, 0, 0, _("Unable to migrate tasks `%s'"), source_name); + g_free(source_name); + goto fail; + } + + g_free (source_name); + } + + g_free (local_task_folder); + + dialog_close (); + } + + if (minor < 5 || (minor == 5 && micro <= 10)) { + char *old_path, *new_path; + + old_path = g_build_filename (g_get_home_dir (), "evolution", "local", "Tasks", NULL); + new_path = g_build_filename (e_shell_module_get_config_dir (shell_module), + "local", "system", NULL); + migrate_pilot_data ("tasks", "todo", old_path, new_path); + g_free (new_path); + g_free (old_path); + } + + /* we only need to do this next step if people ran + older versions of 1.5. We need to clear out the + absolute URI's that were assigned to ESources + during one phase of development, as they take + precedent over relative uris (but aren't updated + when editing an ESource). */ + if (minor == 5 && micro <= 11) { + GSList *g; + for (g = e_source_list_peek_groups (source_list); g; g = g->next) { + ESourceGroup *group = g->data; + GSList *s; + + for (s = e_source_group_peek_sources (group); s; s = s->next) { + ESource *source = s->data; + e_source_set_absolute_uri (source, NULL); + } + } + } + } +#endif /* !G_OS_WIN32 */ + e_source_list_sync (source_list, NULL); + retval = TRUE; +fail: + if (on_this_computer) + g_object_unref (on_this_computer); + if (on_the_web) + g_object_unref (on_the_web); + if (personal_source) + g_object_unref (personal_source); + + return retval; +} diff --git a/calendar/modules/e-task-shell-module-migrate.h b/calendar/modules/e-task-shell-module-migrate.h new file mode 100644 index 0000000000..b20a342e0d --- /dev/null +++ b/calendar/modules/e-task-shell-module-migrate.h @@ -0,0 +1,38 @@ +/* + * e-task-shell-module-migrate.h + * + * 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 + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef E_TASK_SHELL_MODULE_MIGRATE_H +#define E_TASK_SHELL_MODULE_MIGRATE_H + +#include +#include + +G_BEGIN_DECLS + +gboolean e_task_shell_module_migrate (EShellModule *shell_module, + gint major, + gint minor, + gint micro, + GError **error); + +G_END_DECLS + +#endif /* E_TASK_SHELL_MODULE_MIGRATE_H */ diff --git a/calendar/modules/e-task-shell-module.c b/calendar/modules/e-task-shell-module.c index eba720ac6b..c5f268d131 100644 --- a/calendar/modules/e-task-shell-module.c +++ b/calendar/modules/e-task-shell-module.c @@ -38,6 +38,7 @@ #include "calendar/gui/dialogs/task-editor.h" #include "e-task-shell-view.h" +#include "e-task-shell-module-migrate.h" #define MODULE_NAME "tasks" #define MODULE_ALIASES "" @@ -469,7 +470,11 @@ static EShellModuleInfo module_info = { MODULE_NAME, MODULE_ALIASES, MODULE_SCHEMES, - MODULE_SORT_ORDER + MODULE_SORT_ORDER, + + /* is_busy */ NULL, + /* shutdown */ NULL, + e_task_shell_module_migrate }; void diff --git a/calendar/modules/e-task-shell-sidebar.c b/calendar/modules/e-task-shell-sidebar.c index 52d28776b4..e36c05b879 100644 --- a/calendar/modules/e-task-shell-sidebar.c +++ b/calendar/modules/e-task-shell-sidebar.c @@ -434,7 +434,7 @@ task_shell_sidebar_constructed (GObject *object) g_signal_connect_swapped ( widget, "primary-selection-changed", - G_CALLBACK (task_shell_sidebar_selection_changed_cb), + G_CALLBACK (task_shell_sidebar_primary_selection_changed_cb), object); } diff --git a/doc/reference/shell/Makefile.am b/doc/reference/shell/Makefile.am index 7a249b01f9..65d12055e5 100644 --- a/doc/reference/shell/Makefile.am +++ b/doc/reference/shell/Makefile.am @@ -76,12 +76,15 @@ GTKDOC_LIBS= \ $(top_builddir)/shell/.libs/e-shell-sidebar.o \ $(top_builddir)/shell/.libs/e-shell-switcher.o \ $(top_builddir)/shell/.libs/e-shell-taskbar.o \ + $(top_builddir)/shell/.libs/e-shell-upgrade.o \ $(top_builddir)/shell/.libs/e-shell-view.o \ $(top_builddir)/shell/.libs/e-shell-window-actions.o \ $(top_builddir)/shell/.libs/e-shell-window.o \ + $(top_builddir)/e-util/.libs/e-bconf-map.o \ $(top_builddir)/e-util/.libs/e-dialog-utils.o \ $(top_builddir)/e-util/.libs/e-error.o \ $(top_builddir)/e-util/.libs/e-event.o \ + $(top_builddir)/e-util/.libs/e-fsutils.o \ $(top_builddir)/e-util/.libs/e-icon-factory.o \ $(top_builddir)/e-util/.libs/e-import.o \ $(top_builddir)/e-util/.libs/e-logger.o \ diff --git a/doc/reference/shell/eshell-sections.txt b/doc/reference/shell/eshell-sections.txt index 4e0474f0c2..e6cfb9cb4b 100644 --- a/doc/reference/shell/eshell-sections.txt +++ b/doc/reference/shell/eshell-sections.txt @@ -19,6 +19,8 @@ e_shell_get_preferences_window e_shell_is_busy e_shell_do_quit e_shell_quit +E_SHELL_UPGRADE_ERROR +e_shell_upgrade_attempt E_SHELL E_IS_SHELL @@ -30,6 +32,7 @@ EShellClass e_shell_get_type EShellPrivate +e_shell_upgrade_error_quirk
@@ -94,6 +97,7 @@ e_shell_module_get_shell e_shell_module_add_activity e_shell_module_is_busy e_shell_module_shutdown +e_shell_module_upgrade e_shell_module_set_info E_SHELL_MODULE diff --git a/doc/reference/shell/tmpl/e-shell-module.sgml b/doc/reference/shell/tmpl/e-shell-module.sgml index cd4fb930f5..decce2d34c 100644 --- a/doc/reference/shell/tmpl/e-shell-module.sgml +++ b/doc/reference/shell/tmpl/e-shell-module.sgml @@ -52,6 +52,7 @@ EShellModule @sort_order: @is_busy: @shutdown: +@upgrade: @@ -136,6 +137,19 @@ EShellModule @Returns: + + + + + +@shell_module: +@major: +@minor: +@micro: +@error: +@Returns: + + diff --git a/doc/reference/shell/tmpl/e-shell.sgml b/doc/reference/shell/tmpl/e-shell.sgml index db92be82ed..e2ed82e2b1 100644 --- a/doc/reference/shell/tmpl/e-shell.sgml +++ b/doc/reference/shell/tmpl/e-shell.sgml @@ -218,3 +218,19 @@ EShell @Returns: + + + + + + + + + + + + +@shell: +@Returns: + + diff --git a/shell/Makefile.am b/shell/Makefile.am index 0603f603fa..f8510f9357 100644 --- a/shell/Makefile.am +++ b/shell/Makefile.am @@ -61,6 +61,7 @@ eshellincludedir = $(privincludedir)/shell eshellinclude_HEADERS = \ e-shell-common.h \ e-shell-content.h \ + e-shell-migrate.h \ e-shell-module.h \ e-shell-sidebar.h \ e-shell-switcher.h \ @@ -73,6 +74,7 @@ eshellinclude_HEADERS = \ libeshell_la_SOURCES = \ $(IDL_GENERATED) \ e-shell-content.c \ + e-shell-migrate.c \ e-shell-module.c \ e-shell-sidebar.c \ e-shell-switcher.c \ diff --git a/shell/e-shell-migrate.c b/shell/e-shell-migrate.c new file mode 100644 index 0000000000..9979899563 --- /dev/null +++ b/shell/e-shell-migrate.c @@ -0,0 +1,339 @@ +/* + * e-shell-migrate.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 + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#include "e-shell-migrate.h" + +#include +#include +#include +#include +#include + +#include "e-util/e-bconf-map.h" +#include "e-util/e-error.h" +#include "e-util/e-fsutils.h" +#include "e-util/e-util.h" + +#define GCONF_VERSION_KEY "/apps/evolution/version" +#define GCONF_LAST_VERSION_KEY "/apps/evolution/last_version" + +static const gchar * +shell_migrate_get_old_data_dir (void) +{ + static gchar *old_data_dir = NULL; + + if (G_UNLIKELY (old_data_dir == NULL)) + old_data_dir = g_build_filename ( + g_get_home_dir (), "evolution", NULL); + + return old_data_dir; +} + +static gboolean +shell_migrate_attempt (EShell *shell, + gint major, + gint minor, + gint micro) +{ + GList *modules; + gboolean success = TRUE; + + modules = e_shell_list_modules (shell); + + while (success && modules != NULL) { + EShellModule *shell_module = modules->data; + GError *error = NULL; + + success = e_shell_module_migrate ( + shell_module, major, minor, micro, &error); + + if (error != NULL) { + gint response; + + response = e_error_run ( + NULL, "shell:upgrade-failed", + error->message, NULL); + + if (response == GTK_RESPONSE_CANCEL) + success = FALSE; + + g_error_free (error); + } + + modules = g_list_next (modules); + } + + return success; +} + +static void +shell_migrate_get_version (gint *major, + gint *minor, + gint *micro) +{ + GConfClient *client; + const gchar *key; + const gchar *old_data_dir; + gchar *string; + + old_data_dir = shell_migrate_get_old_data_dir (); + + key = GCONF_VERSION_KEY; + client = gconf_client_get_default (); + string = gconf_client_get_string (client, key, NULL); + g_object_unref (client); + + if (string != NULL) { + /* Since 1.4.0 we've kept the version key in GConf. */ + sscanf (string, "%d.%d.%d", major, minor, micro); + g_free (string); + + } else if (!g_file_test (old_data_dir, G_FILE_TEST_IS_DIR)) { + /* If the old data directory does not exist, + * it must be a new installation. */ + *major = 0; + *minor = 0; + *micro = 0; + + } else { + xmlDocPtr doc; + xmlNodePtr source; + gchar *filename; + + filename = g_build_filename ( + old_data_dir, "config.xmldb", NULL); + doc = e_xml_parse_file (filename); + g_free (filename); + + if (doc == NULL) + return; + + source = e_bconf_get_path (doc, "/Shell"); + if (source != NULL) { + key = "upgrade_from_1_0_to_1_2_performed"; + string = e_bconf_get_value (source, key); + } + + if (string != NULL && *string == '1') { + *major = 1; + *minor = 2; + *micro = 0; + } else { + *major = 1; + *minor = 0; + *micro = 0; + } + + g_free (string); + + if (doc != NULL) + xmlFreeDoc (doc); + } +} + +static gint +shell_migrate_remove_dir (const gchar *root, + const gchar *path) +{ + GDir *dir; + const gchar *basename; + gchar *filename; + gint result = -1; + + /* Recursively removes a directory and its contents. */ + + dir = g_dir_open (path, 0, NULL); + if (dir == NULL) + return -1; + + while ((basename = g_dir_read_name (dir)) != NULL) { + filename = g_build_filename (path, basename, NULL); + + /* Make sure we haven't strayed from the evolution dir. */ + g_return_val_if_fail (strlen (path) >= strlen (root), -1); + g_return_val_if_fail (g_str_has_prefix (path, root), -1); + + if (g_file_test (filename, G_FILE_TEST_IS_DIR)) { + if (shell_migrate_remove_dir (root, filename) < 0) + goto fail; + } else { + if (g_unlink (filename) < 0) + goto fail; + } + + g_free (filename); + filename = NULL; + } + + result = g_rmdir (path); + +fail: + g_free (filename); + g_dir_close (dir); + + return result; +} + +gboolean +e_shell_migrate_attempt (EShell *shell) +{ + GConfClient *client; + const gchar *key; + const gchar *old_data_dir; + gint major, minor, micro; + gint last_major, last_minor, last_micro; + gint curr_major, curr_minor, curr_micro; + gboolean migrated = FALSE; + gchar *string; + + g_return_val_if_fail (E_IS_SHELL (shell), FALSE); + + old_data_dir = shell_migrate_get_old_data_dir (); + + if (sscanf (BASE_VERSION, "%d.%d", &curr_major, &curr_minor) != 2) { + g_warning ("Could not parse BASE_VERSION (%s)", BASE_VERSION); + return TRUE; + } + + curr_micro = atoi (UPGRADE_REVISION); + + shell_migrate_get_version (&major, &minor, µ); + + if (!(curr_major > major || + (curr_major == major && curr_minor > minor) || + (curr_minor == minor && curr_micro > micro))) + goto check_old; + + /* If upgrading from < 1.5, we need to copy most data from + * ~/evolution to ~/.evolution. Make sure we have the disk + * space for it before proceeding. */ + if (major == 1 && minor < 5) { + glong avail; + glong usage; + + usage = e_fsutils_usage (old_data_dir); + avail = e_fsutils_avail (g_get_home_dir ()); + if (usage >= 0 && avail >= 0 && avail < usage) { + gchar *need; + gchar *have; + + need = g_strdup_printf (_("%ld KB"), usage); + have = g_strdup_printf (_("%ld KB"), avail); + + e_error_run ( + NULL, "shell:upgrade-nospace", + need, have, NULL); + + g_free (need); + g_free (have); + + _exit (EXIT_SUCCESS); + } + } + + if (!shell_migrate_attempt (shell, major, minor, micro)) + _exit (EXIT_SUCCESS); + + /* Record a successful migration. */ + client = gconf_client_get_default (); + string = g_strdup_printf ("%d.%d.%d", major, minor, micro); + gconf_client_set_string (client, GCONF_VERSION_KEY, string, NULL); + g_object_unref (client); + g_free (string); + + migrated = TRUE; + +check_old: + + key = GCONF_LAST_VERSION_KEY; + client = gconf_client_get_default (); + + /* Try to retrieve the last migrated version from GConf. */ + string = gconf_client_get_string (client, key, NULL); + if (migrated || string == NULL || sscanf (string, "%d.%d.%d", + &last_major, &last_minor, &last_micro) != 3) { + last_major = major; + last_minor = minor; + last_micro = micro; + } + g_free (string); + + /* If the last migrated version was old, check for stuff to remove. */ + if (last_major == 1 && last_minor < 5 && + g_file_test (old_data_dir, G_FILE_TEST_IS_DIR)) { + + gint response; + + string = g_strdup_printf ( + "%d.%d.%d", last_major, last_minor, last_micro); + response = e_error_run ( + NULL, "shel:upgrade-remove-1-4", string, NULL); + g_free (string); + + switch (response) { + case GTK_RESPONSE_OK: /* delete */ + response = e_error_run ( + NULL, + "shell:upgrade-remove-1-4-confirm", + NULL); + if (response == GTK_RESPONSE_OK) + shell_migrate_remove_dir ( + old_data_dir, old_data_dir); + else + break; + /* fall through */ + + case GTK_RESPONSE_ACCEPT: /* keep */ + last_major = curr_major; + last_minor = curr_minor; + last_micro = curr_micro; + break; + + default: + break; + } + } else { + last_major = curr_major; + last_minor = curr_minor; + last_micro = curr_micro; + } + + string = g_strdup_printf ( + "%d.%d.%d", last_major, last_minor, last_micro); + gconf_client_set_string (client, key, string, NULL); + g_free (string); + + g_object_unref (client); + + return TRUE; +} + +GQuark +e_shell_migrate_error_quark (void) +{ + static GQuark quark = 0; + + if (G_UNLIKELY (quark == 0)) + quark = g_quark_from_static_string ( + "e-shell-migrate-error-quark"); + + return quark; +} diff --git a/shell/e-shell-migrate.h b/shell/e-shell-migrate.h new file mode 100644 index 0000000000..315a2904c5 --- /dev/null +++ b/shell/e-shell-migrate.h @@ -0,0 +1,45 @@ +/* + * e-shell-migrate.h + * + * 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 + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +/* This is an EShell extension that handles migrating from older versions. */ + +#ifndef E_SHELL_MIGRATE_H +#define E_SHELL_MIGRATE_H + +#include +#include + +#define E_SHELL_MIGRATE_ERROR \ + (e_shell_migrate_error_quark ()) + +G_BEGIN_DECLS + +/* XXX Need more specific error codes? */ +typedef enum { + E_SHELL_MIGRATE_ERROR_FAILED +} EShellMigrateError; + +gboolean e_shell_migrate_attempt (EShell *shell); +GQuark e_shell_migrate_error_quark (void); + +G_END_DECLS + +#endif /* E_SHELL_MIGRATE_H */ diff --git a/shell/e-shell-module.c b/shell/e-shell-module.c index 1eea8188b6..64d5efc41e 100644 --- a/shell/e-shell-module.c +++ b/shell/e-shell-module.c @@ -498,6 +498,40 @@ e_shell_module_shutdown (EShellModule *shell_module) return TRUE; } +/** + * e_shell_migrate: + * @shell_module: an #EShellModule + * @major: major part of version to migrate from + * @minor: minor part of version to migrate from + * @micro: micro part of version to migrate from + * @error: return location for a #GError, or %NULL + * + * Attempts to migrate data and settings from version %major.%minor.%micro. + * Returns %TRUE if the migration was successful or if no action was + * necessary. Returns %FALSE and sets %error if the migration failed. + * + * Returns: %TRUE if successful, %FALSE otherwise + **/ +gboolean +e_shell_module_migrate (EShellModule *shell_module, + gint major, + gint minor, + gint micro, + GError **error) +{ + EShellModuleInfo *module_info; + + g_return_val_if_fail (E_IS_SHELL_MODULE (shell_module), TRUE); + + module_info = &shell_module->priv->info; + + if (module_info->migrate != NULL) + return module_info->migrate ( + shell_module, major, minor, micro, error); + + return TRUE; +} + /** * e_shell_module_set_info: * @shell_module: an #EShellModule @@ -535,6 +569,7 @@ e_shell_module_set_info (EShellModule *shell_module, module_info->is_busy = info->is_busy; module_info->shutdown = info->shutdown; + module_info->migrate = info->migrate; /* Determine the user data directory for this module. */ g_free (shell_module->priv->data_dir); diff --git a/shell/e-shell-module.h b/shell/e-shell-module.h index 8b00660e91..8dff5a1425 100644 --- a/shell/e-shell-module.h +++ b/shell/e-shell-module.h @@ -81,6 +81,10 @@ typedef struct _EShellModulePrivate EShellModulePrivate; * shutting down. Returning %FALSE indicates there * are still unfinished operations and the #EShell * should check back shortly. + * @migrate: Callback for notifying the module to migrate data and + * settings from the given version. Returns %TRUE if the + * migration was successful or if no action was necessary. + * Returns %FALSE and sets a #GError if the migration failed. **/ struct _EShellModuleInfo { const gchar *name; @@ -90,6 +94,11 @@ struct _EShellModuleInfo { gboolean (*is_busy) (EShellModule *shell_module); gboolean (*shutdown) (EShellModule *shell_module); + gboolean (*migrate) (EShellModule *shell_module, + gint major, + gint minor, + gint micro, + GError **error); }; /** @@ -120,6 +129,11 @@ void e_shell_module_add_activity (EShellModule *shell_module, EActivity *activity); gboolean e_shell_module_is_busy (EShellModule *shell_module); gboolean e_shell_module_shutdown (EShellModule *shell_module); +gboolean e_shell_module_migrate (EShellModule *shell_module, + gint major, + gint minor, + gint micro, + GError **error); void e_shell_module_set_info (EShellModule *shell_module, const EShellModuleInfo *info); diff --git a/shell/e-shell.c b/shell/e-shell.c index 625285839b..7461329e1d 100644 --- a/shell/e-shell.c +++ b/shell/e-shell.c @@ -26,6 +26,7 @@ #include #include +#include #include #define SHUTDOWN_TIMEOUT 500 /* milliseconds */ @@ -79,6 +80,31 @@ shell_window_delete_event_cb (EShell *shell, return !e_shell_quit (shell); } +static gboolean +shell_window_focus_in_event_cb (EShell *shell, + GdkEventFocus *event, + EShellWindow *shell_window) +{ + GList *list, *link; + + /* Keep the active windows list sorted by most recently focused, + * so the first item in the list should always be the currently + * focused shell window. */ + + list = shell->priv->active_windows; + link = g_list_find (list, shell_window); + g_return_val_if_fail (link != NULL, FALSE); + + if (link != list) { + list = g_list_remove_link (list, link); + list = g_list_concat (link, list); + } + + shell->priv->active_windows = list; + + return FALSE; +} + static void shell_window_weak_notify_cb (EShell *shell, GObject *where_the_object_was) @@ -300,6 +326,8 @@ shell_constructed (GObject *object) } g_dir_close (dir); + + e_shell_upgrade_attempt (shell); } static void @@ -493,6 +521,10 @@ e_shell_create_window (EShell *shell) shell_window, "delete-event", G_CALLBACK (shell_window_delete_event_cb), shell); + g_signal_connect_swapped ( + shell_window, "focus-in-event", + G_CALLBACK (shell_window_focus_in_event_cb), shell); + g_object_weak_ref ( G_OBJECT (shell_window), (GWeakNotify) shell_window_weak_notify_cb, shell); @@ -504,6 +536,17 @@ e_shell_create_window (EShell *shell) return shell_window; } +GtkWidget * +e_shell_get_focused_window (EShell *shell) +{ + g_return_val_if_fail (E_IS_SHELL (shell), NULL); + + if (shell->priv->active_windows == NULL) + return NULL; + + return GTK_WIDGET (shell->priv->active_windows->data); +} + gboolean e_shell_handle_uri (EShell *shell, const gchar *uri) diff --git a/shell/e-shell.h b/shell/e-shell.h index d3d5b18f77..183028948c 100644 --- a/shell/e-shell.h +++ b/shell/e-shell.h @@ -90,6 +90,7 @@ EShellModule * e_shell_get_module_by_name (EShell *shell, EShellModule * e_shell_get_module_by_scheme (EShell *shell, const gchar *scheme); GtkWidget * e_shell_create_window (EShell *shell); +GtkWidget * e_shell_get_focused_window (EShell *shell); gboolean e_shell_handle_uri (EShell *shell, const gchar *uri); void e_shell_send_receive (EShell *shell, diff --git a/shell/test/e-test-shell-module.c b/shell/test/e-test-shell-module.c index 32de51a650..19b5d3bd96 100644 --- a/shell/test/e-test-shell-module.c +++ b/shell/test/e-test-shell-module.c @@ -86,8 +86,20 @@ test_module_shutdown (EShellModule *shell_module) } static gboolean -test_module_handle_uri (EShellModule *shell_module, - const gchar *uri) +test_module_migrate (EShellModule *shell_module, + gint major, + gint minor, + gint micro, + GError **error) +{ + g_debug ("%s (from %d.%d.%d)", G_STRFUNC, major, minor, micro); + + return TRUE; +} + +static gboolean +test_module_handle_uri_cb (EShellModule *shell_module, + const gchar *uri) { g_debug ("%s (uri=%s)", G_STRFUNC, uri); @@ -95,15 +107,15 @@ test_module_handle_uri (EShellModule *shell_module, } static void -test_module_send_receive (EShellModule *shell_module, - GtkWindow *parent_window) +test_module_send_receive_cb (EShellModule *shell_module, + GtkWindow *parent_window) { g_debug ("%s (window=%p)", G_STRFUNC, parent_window); } static void -test_module_window_created (EShellModule *shell_module, - EShellWindow *shell_window) +test_module_window_created_cb (EShellModule *shell_module, + EShellWindow *shell_window) { const gchar *module_name; @@ -121,8 +133,8 @@ test_module_window_created (EShellModule *shell_module, } static void -test_module_window_destroyed (EShellModule *shell_module, - gboolean last_window) +test_module_window_destroyed_cb (EShellModule *shell_module, + gboolean last_window) { g_debug ("%s (last=%d)", G_STRFUNC, last_window); } @@ -136,7 +148,8 @@ static EShellModuleInfo module_info = { /* Methods */ test_module_is_busy, - test_module_shutdown + test_module_shutdown, + test_module_migrate }; void @@ -153,17 +166,17 @@ e_shell_module_init (GTypeModule *type_module) g_signal_connect_swapped ( shell, "handle-uri", - G_CALLBACK (test_module_handle_uri), shell_module); + G_CALLBACK (test_module_handle_uri_cb), shell_module); g_signal_connect_swapped ( shell, "send-receive", - G_CALLBACK (test_module_send_receive), shell_module); + G_CALLBACK (test_module_send_receive_cb), shell_module); g_signal_connect_swapped ( shell, "window-created", - G_CALLBACK (test_module_window_created), shell_module); + G_CALLBACK (test_module_window_created_cb), shell_module); g_signal_connect_swapped ( shell, "window-destroyed", - G_CALLBACK (test_module_window_destroyed), shell_module); + G_CALLBACK (test_module_window_destroyed_cb), shell_module); } -- cgit