/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* Evolution calendar importer component * * Authors: Rodrigo Moya * * Copyright (C) 2001 Ximian, Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of version 2 of the GNU General Public * License as published by the Free Software Foundation. * * 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 * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "evolution-calendar-importer.h" #include "common/authentication.h" /* We timeout after 2 minutes, when opening the folders. */ #define IMPORTER_TIMEOUT_SECONDS 120 typedef struct { ECal *client; ECal *tasks_client; EvolutionImporter *importer; icalcomponent *icalcomp; gboolean folder_contains_events; gboolean folder_contains_tasks; } ICalImporter; typedef struct { gboolean do_calendar; gboolean do_tasks; } ICalIntelligentImporter; /* * Functions shared by iCalendar & vCalendar importer. */ static void importer_destroy_cb (gpointer user_data) { ICalImporter *ici = (ICalImporter *) user_data; g_return_if_fail (ici != NULL); g_object_unref (ici->client); g_object_unref (ici->tasks_client); if (ici->icalcomp != NULL) { icalcomponent_free (ici->icalcomp); ici->icalcomp = NULL; } g_free (ici); } /* This removes all components except VEVENTs and VTIMEZONEs from the toplevel icalcomponent, and returns a GList of the VTODO components. */ static GList* prepare_events (icalcomponent *icalcomp) { icalcomponent *subcomp; GList *vtodos = NULL; icalcompiter iter; iter = icalcomponent_begin_component (icalcomp, ICAL_ANY_COMPONENT); while ((subcomp = icalcompiter_deref (&iter)) != NULL) { icalcomponent_kind child_kind = icalcomponent_isa (subcomp); if (child_kind != ICAL_VEVENT_COMPONENT && child_kind != ICAL_VTIMEZONE_COMPONENT) { icalcompiter_next (&iter); icalcomponent_remove_component (icalcomp, subcomp); if (child_kind == ICAL_VTODO_COMPONENT) vtodos = g_list_prepend (vtodos, subcomp); else icalcomponent_free (subcomp); continue; } icalcompiter_next (&iter); } return vtodos; } /* This removes all components except VTODOs and VTIMEZONEs from the toplevel icalcomponent, and adds the given list of VTODO components. The list is freed afterwards. */ static void prepare_tasks (icalcomponent *icalcomp, GList *vtodos) { icalcomponent *subcomp; GList *elem; icalcompiter iter; iter = icalcomponent_begin_component (icalcomp, ICAL_ANY_COMPONENT); while ((subcomp = icalcompiter_deref (&iter)) != NULL) { icalcomponent_kind child_kind = icalcomponent_isa (subcomp); if (child_kind != ICAL_VTODO_COMPONENT && child_kind != ICAL_VTIMEZONE_COMPONENT) { icalcompiter_next (&iter); icalcomponent_remove_component (icalcomp, subcomp); icalcomponent_free (subcomp); continue; } icalcompiter_next (&iter); } for (elem = vtodos; elem; elem = elem->next) { icalcomponent_add_component (icalcomp, elem->data); } g_list_free (vtodos); } static gboolean update_single_object (ECal *client, icalcomponent *icalcomp) { char *uid; icalcomponent *tmp_icalcomp; uid = (char *) icalcomponent_get_uid (icalcomp); if (e_cal_get_object (client, uid, NULL, &tmp_icalcomp, NULL)) return e_cal_modify_object (client, icalcomp, CALOBJ_MOD_ALL, NULL); return e_cal_create_object (client, icalcomp, &uid, NULL); } static gboolean update_objects (ECal *client, icalcomponent *icalcomp) { icalcomponent *subcomp; icalcomponent_kind kind; kind = icalcomponent_isa (icalcomp); if (kind == ICAL_VTODO_COMPONENT || kind == ICAL_VEVENT_COMPONENT) return update_single_object (client, icalcomp); else if (kind != ICAL_VCALENDAR_COMPONENT) return FALSE; subcomp = icalcomponent_get_first_component (icalcomp, ICAL_ANY_COMPONENT); while (subcomp) { gboolean success; kind = icalcomponent_isa (subcomp); if (kind == ICAL_VTIMEZONE_COMPONENT) { icaltimezone *zone; zone = icaltimezone_new (); icaltimezone_set_component (zone, subcomp); success = e_cal_add_timezone (client, zone, NULL); icaltimezone_free (zone, 1); if (!success) return success; } else if (kind == ICAL_VTODO_COMPONENT || kind == ICAL_VEVENT_COMPONENT) { success = update_single_object (client, subcomp); if (!success) return success; } subcomp = icalcomponent_get_next_component (icalcomp, ICAL_ANY_COMPONENT); } return TRUE; } static void process_item_fn (EvolutionImporter *importer, CORBA_Object listener, void *closure, CORBA_Environment *ev) { ECalLoadState state, tasks_state; ICalImporter *ici = (ICalImporter *) closure; GNOME_Evolution_ImporterListener_ImporterResult result; result = GNOME_Evolution_ImporterListener_OK; g_return_if_fail (ici != NULL); g_return_if_fail (E_IS_CAL (ici->client)); g_return_if_fail (ici->icalcomp != NULL); state = e_cal_get_load_state (ici->client); tasks_state = e_cal_get_load_state (ici->tasks_client); if (state == E_CAL_LOAD_LOADING || tasks_state == E_CAL_LOAD_LOADING) { GNOME_Evolution_ImporterListener_notifyResult ( listener, GNOME_Evolution_ImporterListener_BUSY, TRUE, ev); return; } else if (state != E_CAL_LOAD_LOADED || tasks_state != E_CAL_LOAD_LOADED) { GNOME_Evolution_ImporterListener_notifyResult ( listener, GNOME_Evolution_ImporterListener_UNSUPPORTED_OPERATION, FALSE, ev); return; } /* If the folder contains events & tasks we can just import everything into it. If it contains just events, we have to strip out the VTODOs and import them into the default tasks folder. If the folder contains just tasks, we strip out the VEVENTs, which do not get imported at all. */ if (ici->folder_contains_events && ici->folder_contains_tasks) { if (!update_objects (ici->client, ici->icalcomp)) result = GNOME_Evolution_ImporterListener_BAD_DATA; } else if (ici->folder_contains_events) { GList *vtodos = prepare_events (ici->icalcomp); if (!update_objects (ici->client, ici->icalcomp)) result = GNOME_Evolution_ImporterListener_BAD_DATA; prepare_tasks (ici->icalcomp, vtodos); if (!update_objects (ici->tasks_client, ici->icalcomp)) result = GNOME_Evolution_ImporterListener_BAD_DATA; } else { prepare_tasks (ici->icalcomp, NULL); if (!update_objects (ici->client, ici->icalcomp)) result = GNOME_Evolution_ImporterListener_BAD_DATA; } GNOME_Evolution_ImporterListener_notifyResult (listener, result, FALSE, ev); } /* * iCalendar importer functions. */ static gboolean support_format_fn (EvolutionImporter *importer, const char *filename, void *closure) { char *contents ; icalcomponent *icalcomp; gboolean ret = FALSE; if (g_file_get_contents (filename, &contents, NULL, NULL)) { /* parse the file */ icalcomp = icalparser_parse_string (contents); g_free (contents); if (icalcomp) { if (icalcomponent_is_valid (icalcomp)) ret = TRUE; else ret = FALSE; icalcomponent_free (icalcomp); } } return ret; } static gboolean load_file_fn (EvolutionImporter *importer, const char *filename, const char *physical_uri, const char *folder_type, void *closure) { char *contents, *f; gboolean ret = FALSE; ICalImporter *ici = (ICalImporter *) closure; g_return_val_if_fail (ici != NULL, FALSE); if (!strcmp (folder_type, "calendar")) { ici->folder_contains_events = TRUE; ici->folder_contains_tasks = FALSE; f = g_strdup ("calendar.ics"); } else { ici->folder_contains_events = FALSE; ici->folder_contains_tasks = TRUE; f = g_strdup ("tasks.ics"); } if (g_file_get_contents (filename, &contents, NULL, NULL)) { icalcomponent *icalcomp; /* parse the file */ icalcomp = icalparser_parse_string (contents); g_free (contents); if (icalcomp) { char *real_uri; if (!g_strncasecmp (physical_uri, "file", 4) && g_strcasecmp (physical_uri + (strlen (physical_uri) - strlen (f)), f)) { real_uri = g_concat_dir_and_file (physical_uri, f); } else real_uri = g_strdup (physical_uri); /* create ECal's */ if (!ici->client) ici->client = auth_new_cal_from_uri (real_uri, E_CAL_SOURCE_TYPE_EVENT); if (!ici->tasks_client) ici->tasks_client = auth_new_cal_from_uri ("", E_CAL_SOURCE_TYPE_TODO); /* FIXME */ if (e_cal_open (ici->client, TRUE, NULL) && e_cal_open (ici->tasks_client, FALSE, NULL)) { ici->icalcomp = icalcomp; ret = TRUE; } g_free (real_uri); } } g_free (f); return ret; } BonoboObject * ical_importer_new (void) { ICalImporter *ici; ici = g_new0 (ICalImporter, 1); ici->client = NULL; ici->tasks_client = NULL; ici->icalcomp = NULL; ici->importer = evolution_importer_new (support_format_fn, load_file_fn, process_item_fn, NULL, ici); g_object_weak_ref (G_OBJECT (ici->importer), (GWeakNotify) importer_destroy_cb, ici); return BONOBO_OBJECT (ici->importer); } /* * vCalendar importer functions. */ static gboolean vcal_support_format_fn (EvolutionImporter *importer, const char *filename, void *closure) { char *contents; gboolean ret = FALSE; if (g_file_get_contents (filename, &contents, NULL, NULL)) { VObject *vcal; /* parse the file */ vcal = Parse_MIME (contents, strlen (contents)); g_free (contents); if (vcal) { icalcomponent *icalcomp; icalcomp = icalvcal_convert (vcal); if (icalcomp) { icalcomponent_free (icalcomp); ret = TRUE; } cleanVObject (vcal); } } return ret; } /* This tries to load in a vCalendar file and convert it to an icalcomponent. It returns NULL on failure. */ static icalcomponent* load_vcalendar_file (const char *filename) { icalvcal_defaults defaults = { 0 }; icalcomponent *icalcomp = NULL; char *contents; defaults.alarm_audio_url = "file://" EVOLUTION_SOUNDDIR "/default_alarm.wav"; defaults.alarm_audio_fmttype = "audio/x-wav"; defaults.alarm_description = (char*) _("Reminder!!"); if (g_file_get_contents (filename, &contents, NULL, NULL)) { VObject *vcal; /* parse the file */ vcal = Parse_MIME (contents, strlen (contents)); g_free (contents); if (vcal) { icalcomp = icalvcal_convert_with_defaults (vcal, &defaults); cleanVObject (vcal); } } return icalcomp; } static gboolean vcal_load_file_fn (EvolutionImporter *importer, const char *filename, const char *physical_uri, const char *folder_type, void *closure) { gboolean ret = FALSE; char *f; ICalImporter *ici = (ICalImporter *) closure; icalcomponent *icalcomp; g_return_val_if_fail (ici != NULL, FALSE); if (!strcmp (folder_type, "calendar")) { ici->folder_contains_events = TRUE; ici->folder_contains_tasks = FALSE; f = g_strdup ("calendar.ics"); } else { ici->folder_contains_events = FALSE; ici->folder_contains_tasks = TRUE; f = g_strdup ("tasks.ics"); } icalcomp = load_vcalendar_file (filename); if (icalcomp) { char *real_uri; if (!g_strncasecmp (physical_uri, "file", 4) && g_strcasecmp (physical_uri + (strlen (physical_uri) - strlen (f)), f)) { real_uri = g_concat_dir_and_file (physical_uri, f); } else real_uri = g_strdup (physical_uri); /* create ECal's */ if (!ici->client) ici->client = auth_new_cal_from_uri (real_uri, E_CAL_SOURCE_TYPE_EVENT); if (!ici->tasks_client) ici->tasks_client = auth_new_cal_from_uri ("", E_CAL_SOURCE_TYPE_TODO); if (e_cal_open (ici->client, TRUE, NULL) && e_cal_open (ici->tasks_client, FALSE, NULL)) { ici->icalcomp = icalcomp; ret = TRUE; } g_free (real_uri); } g_free (f); return ret; } BonoboObject * vcal_importer_new (void) { ICalImporter *ici; ici = g_new0 (ICalImporter, 1); ici->client = NULL; ici->tasks_client = NULL; ici->icalcomp = NULL; ici->importer = evolution_importer_new (vcal_support_format_fn, vcal_load_file_fn, process_item_fn, NULL, ici); g_object_weak_ref (G_OBJECT (ici->importer), (GWeakNotify) importer_destroy_cb, ici); return BONOBO_OBJECT (ici->importer); } static void gnome_calendar_importer_destroy_cb (gpointer user_data) { ICalIntelligentImporter *ici = (ICalIntelligentImporter *) user_data; g_return_if_fail (ici != NULL); g_free (ici); } static gboolean gnome_calendar_can_import_fn (EvolutionIntelligentImporter *ii, void *closure) { char *filename; gboolean gnome_calendar_exists; filename = gnome_util_home_file ("user-cal.vcf"); gnome_calendar_exists = g_file_exists (filename); g_free (filename); return gnome_calendar_exists; } static void gnome_calendar_import_data_fn (EvolutionIntelligentImporter *ii, void *closure) { ICalIntelligentImporter *ici = closure; icalcomponent *icalcomp = NULL; char *filename; GList *vtodos; ECal *calendar_client = NULL, *tasks_client = NULL; int t; /* If neither is selected, just return. */ if (!ici->do_calendar && !ici->do_tasks) { return; } /* Try to open the default calendar & tasks folders. */ if (ici->do_calendar) { calendar_client = auth_new_cal_from_uri ("", E_CAL_SOURCE_TYPE_EVENT); /* FIXME: use default folder */ if (!e_cal_open (calendar_client, FALSE, NULL)) goto out; } if (ici->do_tasks) { tasks_client = auth_new_cal_from_uri ("", E_CAL_SOURCE_TYPE_TODO); /* FIXME: use default folder */ if (!e_cal_open (tasks_client, FALSE, NULL)) goto out; } /* Load the Gnome Calendar file and convert to iCalendar. */ filename = gnome_util_home_file ("user-cal.vcf"); icalcomp = load_vcalendar_file (filename); g_free (filename); /* If we couldn't load the file, just return. FIXME: Error message? */ if (!icalcomp) goto out; /* * Import the calendar events into the default calendar folder. */ vtodos = prepare_events (icalcomp); /* Wait for client to finish opening the calendar & tasks folders. */ for (t = 0; t < IMPORTER_TIMEOUT_SECONDS; t++) { ECalLoadState calendar_state, tasks_state; calendar_state = tasks_state = E_CAL_LOAD_LOADED; /* We need this so the ECal gets notified that the folder is opened, via Corba. */ while (gtk_events_pending ()) gtk_main_iteration (); if (ici->do_calendar) calendar_state = e_cal_get_load_state (calendar_client); if (ici->do_tasks) tasks_state = e_cal_get_load_state (tasks_client); if (calendar_state == E_CAL_LOAD_LOADED && tasks_state == E_CAL_LOAD_LOADED) break; sleep (1); } /* If we timed out, just return. */ if (t == IMPORTER_TIMEOUT_SECONDS) goto out; /* Import the calendar events. */ /* FIXME: What do intelligent importers do about errors? */ if (ici->do_calendar) update_objects (calendar_client, icalcomp); /* * Import the tasks into the default tasks folder. */ prepare_tasks (icalcomp, vtodos); if (ici->do_tasks) update_objects (tasks_client, icalcomp); out: if (icalcomp) icalcomponent_free (icalcomp); if (calendar_client) g_object_unref (calendar_client); if (tasks_client) g_object_unref (tasks_client); } /* Fun with aggregation */ static void checkbox_toggle_cb (GtkToggleButton *tb, gboolean *do_item) { *do_item = gtk_toggle_button_get_active (tb); } static BonoboControl * create_checkboxes_control (ICalIntelligentImporter *ici) { GtkWidget *hbox, *calendar_checkbox, *tasks_checkbox; BonoboControl *control; hbox = gtk_hbox_new (FALSE, 2); calendar_checkbox = gtk_check_button_new_with_label (_("Calendar Events")); g_signal_connect (G_OBJECT (calendar_checkbox), "toggled", G_CALLBACK (checkbox_toggle_cb), &ici->do_calendar); gtk_box_pack_start (GTK_BOX (hbox), calendar_checkbox, FALSE, FALSE, 0); tasks_checkbox = gtk_check_button_new_with_label (_("Tasks")); g_signal_connect (G_OBJECT (tasks_checkbox), "toggled", G_CALLBACK (checkbox_toggle_cb), &ici->do_tasks); gtk_box_pack_start (GTK_BOX (hbox), tasks_checkbox, FALSE, FALSE, 0); gtk_widget_show_all (hbox); control = bonobo_control_new (hbox); return control; } BonoboObject * gnome_calendar_importer_new (void) { EvolutionIntelligentImporter *importer; ICalIntelligentImporter *ici; BonoboControl *control; char *message = N_("Evolution has found Gnome Calendar files.\n" "Would you like to import them into Evolution?"); ici = g_new0 (ICalIntelligentImporter, 1); importer = evolution_intelligent_importer_new (gnome_calendar_can_import_fn, gnome_calendar_import_data_fn, _("Gnome Calendar"), _(message), ici); g_object_weak_ref (G_OBJECT (importer), (GWeakNotify) gnome_calendar_importer_destroy_cb, ici); control = create_checkboxes_control (ici); bonobo_object_add_interface (BONOBO_OBJECT (importer), BONOBO_OBJECT (control)); return BONOBO_OBJECT (importer); }