/* * Evolution calendar importer component * * 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. * * 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 Lesser General Public License * along with this program; if not, see . * * * Authors: * Rodrigo Moya * * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include "shell/e-shell.h" #include "evolution-calendar-importer.h" #include "gui/calendar-config-keys.h" /* We timeout after 2 minutes, when opening the folders. */ #define IMPORTER_TIMEOUT_SECONDS 120 typedef struct { EImport *import; EImportTarget *target; guint idle_id; ECalClient *cal_client; ECalClientSourceType source_type; icalcomponent *icalcomp; GCancellable *cancellable; } ICalImporter; typedef struct { EImport *ei; EImportTarget *target; GList *tasks; icalcomponent *icalcomp; GCancellable *cancellable; } ICalIntelligentImporter; static const gint import_type_map[] = { E_CAL_CLIENT_SOURCE_TYPE_EVENTS, E_CAL_CLIENT_SOURCE_TYPE_TASKS, -1 }; static const gchar *import_type_strings[] = { N_("Appointments and Meetings"), N_("Tasks"), NULL }; /* * Functions shared by iCalendar & vCalendar importer. */ static GtkWidget *ical_get_preview (icalcomponent *icalcomp); static gboolean is_icalcomp_usable (icalcomponent *icalcomp) { return icalcomp && icalcomponent_is_valid (icalcomp) && ( icalcomponent_get_first_component (icalcomp, ICAL_VEVENT_COMPONENT) != NULL || icalcomponent_get_first_component (icalcomp, ICAL_VTODO_COMPONENT) != NULL); } static void ivcal_import_done (ICalImporter *ici) { if (ici->cal_client) g_object_unref (ici->cal_client); icalcomponent_free (ici->icalcomp); e_import_complete (ici->import, ici->target); g_object_unref (ici->import); g_object_unref (ici->cancellable); g_free (ici); } /* This removes all components except VEVENTs and VTIMEZONEs from the toplevel */ static void prepare_events (icalcomponent *icalcomp, GList **vtodos) { icalcomponent *subcomp; icalcompiter iter; if (vtodos) *vtodos = NULL; 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) *vtodos = g_list_prepend (*vtodos, subcomp); else icalcomponent_free (subcomp); } else { icalcompiter_next (&iter); } } } /* 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); } else { icalcompiter_next (&iter); } } for (elem = vtodos; elem; elem = elem->next) { icalcomponent_add_component (icalcomp, elem->data); } g_list_free (vtodos); } struct UpdateObjectsData { void (*done_cb) (gpointer user_data); gpointer user_data; }; static void receive_objects_ready_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) { ECalClient *cal_client = E_CAL_CLIENT (source_object); struct UpdateObjectsData *uod = user_data; GError *error = NULL; g_return_if_fail (uod != NULL); e_cal_client_receive_objects_finish (cal_client, result, &error); if (error != NULL) { g_warning ( "%s: Failed to receive objects: %s", G_STRFUNC, error->message); g_error_free (error); } if (uod->done_cb) uod->done_cb (uod->user_data); g_free (uod); } static void update_objects (ECalClient *cal_client, icalcomponent *icalcomp, GCancellable *cancellable, void (*done_cb) (gpointer user_data), gpointer user_data) { icalcomponent_kind kind; icalcomponent *vcal; struct UpdateObjectsData *uod; kind = icalcomponent_isa (icalcomp); if (kind == ICAL_VTODO_COMPONENT || kind == ICAL_VEVENT_COMPONENT) { vcal = e_cal_util_new_top_level (); if (icalcomponent_get_method (icalcomp) == ICAL_METHOD_CANCEL) icalcomponent_set_method (vcal, ICAL_METHOD_CANCEL); else icalcomponent_set_method (vcal, ICAL_METHOD_PUBLISH); icalcomponent_add_component (vcal, icalcomponent_new_clone (icalcomp)); } else if (kind == ICAL_VCALENDAR_COMPONENT) { vcal = icalcomponent_new_clone (icalcomp); if (!icalcomponent_get_first_property (vcal, ICAL_METHOD_PROPERTY)) icalcomponent_set_method (vcal, ICAL_METHOD_PUBLISH); } else { if (done_cb) done_cb (user_data); return; } uod = g_new0 (struct UpdateObjectsData, 1); uod->done_cb = done_cb; uod->user_data = user_data; e_cal_client_receive_objects (cal_client, vcal, cancellable, receive_objects_ready_cb, uod); icalcomponent_free (vcal); return; } struct _selector_data { EImportTarget *target; GtkWidget *selector; GtkWidget *notebook; gint page; }; static void button_toggled_cb (GtkWidget *widget, struct _selector_data *sd) { ESourceSelector *selector; ESource *source; GtkNotebook *notebook; selector = E_SOURCE_SELECTOR (sd->selector); source = e_source_selector_ref_primary_selection (selector); g_return_if_fail (source != NULL); g_datalist_set_data_full ( &sd->target->data, "primary-source", source, (GDestroyNotify) g_object_unref); g_datalist_set_data ( &sd->target->data, "primary-type", GINT_TO_POINTER (import_type_map[sd->page])); notebook = GTK_NOTEBOOK (sd->notebook); gtk_notebook_set_current_page (notebook, sd->page); } static void primary_selection_changed_cb (ESourceSelector *selector, EImportTarget *target) { ESource *source; source = e_source_selector_ref_primary_selection (selector); g_return_if_fail (source != NULL); g_datalist_set_data_full ( &target->data, "primary-source", source, (GDestroyNotify) g_object_unref); } static GtkWidget * ivcal_getwidget (EImport *ei, EImportTarget *target, EImportImporter *im) { EShell *shell; ESourceRegistry *registry; GtkWidget *vbox, *hbox, *first = NULL; GSList *group = NULL; gint i; GtkWidget *nb; shell = e_shell_get_default (); registry = e_shell_get_registry (shell); vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, TRUE, 6); nb = gtk_notebook_new (); gtk_notebook_set_show_tabs (GTK_NOTEBOOK (nb), FALSE); gtk_notebook_set_show_border (GTK_NOTEBOOK (nb), FALSE); gtk_box_pack_start (GTK_BOX (vbox), nb, TRUE, TRUE, 6); /* Type of icalendar items */ for (i = 0; import_type_map[i] != -1; i++) { GtkWidget *selector, *rb; ESource *source = NULL; GtkWidget *scrolled; struct _selector_data *sd; const gchar *extension_name; GList *list; switch (import_type_map[i]) { case E_CAL_CLIENT_SOURCE_TYPE_EVENTS: extension_name = E_SOURCE_EXTENSION_CALENDAR; break; case E_CAL_CLIENT_SOURCE_TYPE_TASKS: extension_name = E_SOURCE_EXTENSION_TASK_LIST; break; default: g_warn_if_reached (); continue; } selector = e_source_selector_new (registry, extension_name); e_source_selector_set_show_toggles ( E_SOURCE_SELECTOR (selector), FALSE); scrolled = gtk_scrolled_window_new (NULL, NULL); gtk_scrolled_window_set_policy ((GtkScrolledWindow *) scrolled, GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS); gtk_container_add ((GtkContainer *) scrolled, selector); gtk_notebook_append_page (GTK_NOTEBOOK (nb), scrolled, NULL); list = e_source_registry_list_sources (registry, extension_name); if (list != NULL) { source = E_SOURCE (list->data); e_source_selector_set_primary_selection ( E_SOURCE_SELECTOR (selector), source); } g_list_free_full (list, (GDestroyNotify) g_object_unref); g_signal_connect ( selector, "primary_selection_changed", G_CALLBACK (primary_selection_changed_cb), target); rb = gtk_radio_button_new_with_label (group, _(import_type_strings[i])); gtk_box_pack_start (GTK_BOX (hbox), rb, FALSE, FALSE, 6); sd = g_malloc0 (sizeof (*sd)); sd->target = target; sd->selector = selector; sd->notebook = nb; sd->page = i; g_object_set_data_full ((GObject *) rb, "selector-data", sd, g_free); g_signal_connect ( rb, "toggled", G_CALLBACK (button_toggled_cb), sd); if (!group) group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (rb)); if (first == NULL && source != NULL) { g_datalist_set_data_full (&target->data, "primary-source", g_object_ref (source), g_object_unref); g_datalist_set_data (&target->data, "primary-type", GINT_TO_POINTER (import_type_map[i])); first = rb; } } if (first) gtk_toggle_button_set_active ((GtkToggleButton *) first, TRUE); gtk_widget_show_all (vbox); return vbox; } static void ivcal_call_import_done (gpointer user_data) { ivcal_import_done (user_data); } static gboolean ivcal_import_items (gpointer d) { ICalImporter *ici = d; switch (ici->source_type) { case E_CAL_CLIENT_SOURCE_TYPE_EVENTS: prepare_events (ici->icalcomp, NULL); update_objects (ici->cal_client, ici->icalcomp, ici->cancellable, ivcal_call_import_done, ici); break; case E_CAL_CLIENT_SOURCE_TYPE_TASKS: prepare_tasks (ici->icalcomp, NULL); update_objects (ici->cal_client, ici->icalcomp, ici->cancellable, ivcal_call_import_done, ici); break; default: g_warn_if_reached (); ici->idle_id = 0; ivcal_import_done (ici); return FALSE; } ici->idle_id = 0; return FALSE; } static void ivcal_connect_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) { EClient *client; ICalImporter *ici = user_data; GError *error = NULL; g_return_if_fail (ici != NULL); client = e_cal_client_connect_finish (result, &error); /* Sanity check. */ g_return_if_fail ( ((client != NULL) && (error == NULL)) || ((client == NULL) && (error != NULL))); if (error != NULL) { g_warning ("%s: %s", G_STRFUNC, error->message); g_error_free (error); ivcal_import_done (ici); return; } ici->cal_client = E_CAL_CLIENT (client); e_import_status (ici->import, ici->target, _("Importing..."), 0); ici->idle_id = g_idle_add (ivcal_import_items, ici); } static void ivcal_import (EImport *ei, EImportTarget *target, icalcomponent *icalcomp) { ECalClientSourceType type; ICalImporter *ici = g_malloc0 (sizeof (*ici)); type = GPOINTER_TO_INT (g_datalist_get_data (&target->data, "primary-type")); ici->import = ei; g_datalist_set_data (&target->data, "ivcal-data", ici); g_object_ref (ei); ici->target = target; ici->icalcomp = icalcomp; ici->cal_client = NULL; ici->source_type = type; ici->cancellable = g_cancellable_new (); e_import_status (ei, target, _("Opening calendar"), 0); e_cal_client_connect ( g_datalist_get_data (&target->data, "primary-source"), type, ici->cancellable, ivcal_connect_cb, ici); } static void ivcal_cancel (EImport *ei, EImportTarget *target, EImportImporter *im) { ICalImporter *ici = g_datalist_get_data (&target->data, "ivcal-data"); if (ici) g_cancellable_cancel (ici->cancellable); } /* ********************************************************************** */ /* * iCalendar importer functions. */ static gboolean ical_supported (EImport *ei, EImportTarget *target, EImportImporter *im) { gchar *filename; gchar *contents; gboolean ret = FALSE; EImportTargetURI *s; if (target->type != E_IMPORT_TARGET_URI) return FALSE; s = (EImportTargetURI *) target; if (s->uri_src == NULL) return TRUE; if (strncmp (s->uri_src, "file:///", 8) != 0) return FALSE; filename = g_filename_from_uri (s->uri_src, NULL, NULL); if (!filename) return FALSE; if (g_file_get_contents (filename, &contents, NULL, NULL)) { icalcomponent *icalcomp = NULL; if (g_ascii_strncasecmp (contents, "BEGIN:", 6) == 0) icalcomp = e_cal_util_parse_ics_string (contents); g_free (contents); if (icalcomp) { if (is_icalcomp_usable (icalcomp)) ret = TRUE; else ret = FALSE; icalcomponent_free (icalcomp); } } g_free (filename); return ret; } static void ical_import (EImport *ei, EImportTarget *target, EImportImporter *im) { gchar *filename; gchar *contents; icalcomponent *icalcomp; EImportTargetURI *s = (EImportTargetURI *) target; filename = g_filename_from_uri (s->uri_src, NULL, NULL); if (!filename) { e_import_complete (ei, target); return; } if (!g_file_get_contents (filename, &contents, NULL, NULL)) { g_free (filename); e_import_complete (ei, target); return; } g_free (filename); icalcomp = e_cal_util_parse_ics_string (contents); g_free (contents); if (icalcomp) ivcal_import (ei, target, icalcomp); else e_import_complete (ei, target); } static GtkWidget * ivcal_get_preview (EImport *ei, EImportTarget *target, EImportImporter *im) { GtkWidget *preview; EImportTargetURI *s = (EImportTargetURI *) target; gchar *filename; icalcomponent *icalcomp; gchar *contents; filename = g_filename_from_uri (s->uri_src, NULL, NULL); if (filename == NULL) { g_message (G_STRLOC ": Couldn't get filename from URI '%s'", s->uri_src); return NULL; } if (!g_file_get_contents (filename, &contents, NULL, NULL)) { g_free (filename); return NULL; } g_free (filename); icalcomp = e_cal_util_parse_ics_string (contents); g_free (contents); if (!icalcomp) return NULL; preview = ical_get_preview (icalcomp); icalcomponent_free (icalcomp); return preview; } static EImportImporter ical_importer = { E_IMPORT_TARGET_URI, 0, ical_supported, ivcal_getwidget, ical_import, ivcal_cancel, ivcal_get_preview, }; EImportImporter * ical_importer_peek (void) { ical_importer.name = _("iCalendar files (.ics)"); ical_importer.description = _("Evolution iCalendar importer"); return &ical_importer; } /* ********************************************************************** */ /* * vCalendar importer functions. */ static gboolean vcal_supported (EImport *ei, EImportTarget *target, EImportImporter *im) { gchar *filename; gchar *contents; gboolean ret = FALSE; EImportTargetURI *s; if (target->type != E_IMPORT_TARGET_URI) return FALSE; s = (EImportTargetURI *) target; if (s->uri_src == NULL) return TRUE; if (strncmp (s->uri_src, "file:///", 8) != 0) return FALSE; filename = g_filename_from_uri (s->uri_src, NULL, NULL); if (!filename) return FALSE; /* Z: Wow, this is *efficient* */ if (g_file_get_contents (filename, &contents, NULL, NULL)) { VObject *vcal; icalcomponent *icalcomp; icalcomp = e_cal_util_parse_ics_string (contents); if (icalcomp && is_icalcomp_usable (icalcomp)) { /* If we can create proper iCalendar from the file, then * rather use ics importer, because it knows to read more * information than older version, the vCalendar. */ ret = FALSE; g_free (contents); } else { if (icalcomp) icalcomponent_free (icalcomp); /* parse the file */ vcal = Parse_MIME (contents, strlen (contents)); g_free (contents); if (vcal) { icalcomp = icalvcal_convert (vcal); if (icalcomp) { icalcomponent_free (icalcomp); ret = TRUE; } cleanVObject (vcal); } } } g_free (filename); 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 gchar *filename) { icalvcal_defaults defaults = { NULL }; icalcomponent *icalcomp = NULL; gchar *contents; gchar *default_alarm_filename; default_alarm_filename = g_build_filename ( EVOLUTION_SOUNDDIR, "default_alarm.wav", NULL); defaults.alarm_audio_url = g_filename_to_uri ( default_alarm_filename, NULL, NULL); g_free (default_alarm_filename); defaults.alarm_audio_fmttype = (gchar *) "audio/x-wav"; defaults.alarm_description = (gchar *) _("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 void vcal_import (EImport *ei, EImportTarget *target, EImportImporter *im) { gchar *filename; icalcomponent *icalcomp; EImportTargetURI *s = (EImportTargetURI *) target; filename = g_filename_from_uri (s->uri_src, NULL, NULL); if (!filename) { e_import_complete (ei, target); return; } icalcomp = load_vcalendar_file (filename); g_free (filename); if (icalcomp) ivcal_import (ei, target, icalcomp); else e_import_complete (ei, target); } static GtkWidget * vcal_get_preview (EImport *ei, EImportTarget *target, EImportImporter *im) { GtkWidget *preview; EImportTargetURI *s = (EImportTargetURI *) target; gchar *filename; icalcomponent *icalcomp; filename = g_filename_from_uri (s->uri_src, NULL, NULL); if (filename == NULL) { g_message (G_STRLOC ": Couldn't get filename from URI '%s'", s->uri_src); return NULL; } icalcomp = load_vcalendar_file (filename); g_free (filename); if (!icalcomp) return NULL; preview = ical_get_preview (icalcomp); icalcomponent_free (icalcomp); return preview; } static EImportImporter vcal_importer = { E_IMPORT_TARGET_URI, 0, vcal_supported, ivcal_getwidget, vcal_import, ivcal_cancel, vcal_get_preview, }; EImportImporter * vcal_importer_peek (void) { vcal_importer.name = _("vCalendar files (.vcs)"); vcal_importer.description = _("Evolution vCalendar importer"); return &vcal_importer; } /* ********************************************************************** */ static gboolean gnome_calendar_supported (EImport *ei, EImportTarget *target, EImportImporter *im) { gchar *filename; gboolean res; if (target->type != E_IMPORT_TARGET_HOME) return FALSE; filename = g_build_filename (g_get_home_dir (), "user-cal.vcf", NULL); res = g_file_test (filename, G_FILE_TEST_IS_REGULAR); g_free (filename); return res; } static void free_ici (gpointer ptr) { ICalIntelligentImporter *ici = ptr; if (!ici) return; if (ici->icalcomp) icalcomponent_free (ici->icalcomp); g_object_unref (ici->cancellable); g_free (ici); } struct OpenDefaultSourceData { ICalIntelligentImporter *ici; void (* opened_cb) (ECalClient *cal_client, const GError *error, ICalIntelligentImporter *ici); }; static void default_client_connect_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) { EClient *client; struct OpenDefaultSourceData *odsd = user_data; GError *error = NULL; g_return_if_fail (odsd != NULL); g_return_if_fail (odsd->ici != NULL); g_return_if_fail (odsd->opened_cb != NULL); client = e_cal_client_connect_finish (result, &error); /* Sanity check. */ g_return_if_fail ( ((client != NULL) && (error == NULL)) || ((client == NULL) && (error != NULL))); /* Client may be NULL; don't use a type cast macro. */ odsd->opened_cb ((ECalClient *) client, error, odsd->ici); g_clear_object (&client); if (error != NULL) g_error_free (error); g_free (odsd); } static void open_default_source (ICalIntelligentImporter *ici, ECalClientSourceType source_type, void (* opened_cb) (ECalClient *cal_client, const GError *error, ICalIntelligentImporter *ici)) { EShell *shell; ESource *source; ESourceRegistry *registry; struct OpenDefaultSourceData *odsd; g_return_if_fail (ici != NULL); g_return_if_fail (opened_cb != NULL); shell = e_shell_get_default (); registry = e_shell_get_registry (shell); switch (source_type) { case E_CAL_CLIENT_SOURCE_TYPE_EVENTS: source = e_source_registry_ref_default_calendar (registry); break; case E_CAL_CLIENT_SOURCE_TYPE_TASKS: source = e_source_registry_ref_default_task_list (registry); break; case E_CAL_CLIENT_SOURCE_TYPE_MEMOS: source = e_source_registry_ref_default_memo_list (registry); break; default: g_return_if_reached (); } odsd = g_new0 (struct OpenDefaultSourceData, 1); odsd->ici = ici; odsd->opened_cb = opened_cb; e_import_status (ici->ei, ici->target, _("Opening calendar"), 0); e_cal_client_connect ( source, source_type, ici->cancellable, default_client_connect_cb, odsd); g_object_unref (source); } static void continue_done_cb (gpointer user_data) { ICalIntelligentImporter *ici = user_data; g_return_if_fail (ici != NULL); e_import_complete (ici->ei, ici->target); } static void gc_import_tasks (ECalClient *cal_client, const GError *error, ICalIntelligentImporter *ici) { g_return_if_fail (ici != NULL); if (error != NULL) { g_warning ( "%s: Failed to open tasks: %s", G_STRFUNC, error->message); e_import_complete (ici->ei, ici->target); return; } e_import_status (ici->ei, ici->target, _("Importing..."), 0); prepare_tasks (ici->icalcomp, ici->tasks); update_objects ( cal_client, ici->icalcomp, ici->cancellable, continue_done_cb, ici); } static void continue_tasks_cb (gpointer user_data) { ICalIntelligentImporter *ici = user_data; g_return_if_fail (ici != NULL); open_default_source (ici, E_CAL_CLIENT_SOURCE_TYPE_TASKS, gc_import_tasks); } static void gc_import_events (ECalClient *cal_client, const GError *error, ICalIntelligentImporter *ici) { g_return_if_fail (ici != NULL); if (error != NULL) { g_warning ( "%s: Failed to open events calendar: %s", G_STRFUNC, error->message); if (ici->tasks) open_default_source ( ici, E_CAL_CLIENT_SOURCE_TYPE_TASKS, gc_import_tasks); else e_import_complete (ici->ei, ici->target); return; } e_import_status (ici->ei, ici->target, _("Importing..."), 0); update_objects ( cal_client, ici->icalcomp, ici->cancellable, ici->tasks ? continue_tasks_cb : continue_done_cb, ici); } static void gnome_calendar_import (EImport *ei, EImportTarget *target, EImportImporter *im) { icalcomponent *icalcomp = NULL; gchar *filename; gint do_calendar, do_tasks; ICalIntelligentImporter *ici; /* This is pretty shitty, everything runs in the gui thread and can block * for quite some time */ do_calendar = GPOINTER_TO_INT (g_datalist_get_data (&target->data, "gnomecal-do-cal")); do_tasks = GPOINTER_TO_INT (g_datalist_get_data (&target->data, "gnomecal-do-tasks")); /* If neither is selected, just return. */ if (!do_calendar && !do_tasks) return; /* Load the Gnome Calendar file and convert to iCalendar. */ filename = g_build_filename (g_get_home_dir (), "user-cal.vcf", NULL); icalcomp = load_vcalendar_file (filename); g_free (filename); /* If we couldn't load the file, just return. FIXME: Error message? */ if (icalcomp) { ici = g_malloc0 (sizeof (*ici)); ici->ei = ei; ici->target = target; ici->cancellable = g_cancellable_new (); ici->icalcomp = icalcomp; g_datalist_set_data_full (&target->data, "gnomecal-data", ici, free_ici); prepare_events (ici->icalcomp, &ici->tasks); if (do_calendar) { open_default_source (ici, E_CAL_CLIENT_SOURCE_TYPE_EVENTS, gc_import_events); return; } prepare_tasks (ici->icalcomp, ici->tasks); if (do_tasks) { open_default_source (ici, E_CAL_CLIENT_SOURCE_TYPE_TASKS, gc_import_tasks); return; } } e_import_complete (ei, target); } static void calendar_toggle_cb (GtkToggleButton *tb, EImportTarget *target) { g_datalist_set_data (&target->data, "gnomecal-do-cal", GINT_TO_POINTER (gtk_toggle_button_get_active (tb))); } static void tasks_toggle_cb (GtkToggleButton *tb, EImportTarget *target) { g_datalist_set_data (&target->data, "gnomecal-do-tasks", GINT_TO_POINTER (gtk_toggle_button_get_active (tb))); } static GtkWidget * gnome_calendar_getwidget (EImport *ei, EImportTarget *target, EImportImporter *im) { GtkWidget *hbox, *w; GSettings *settings; gboolean done_cal, done_tasks; settings = g_settings_new ("org.gnome.evolution.importer"); done_cal = g_settings_get_boolean (settings, "gnome-calendar-done-calendar"); done_tasks = g_settings_get_boolean (settings, "gnome-calendar-done-tasks"); g_object_unref (settings); g_datalist_set_data (&target->data, "gnomecal-do-cal", GINT_TO_POINTER (!done_cal)); g_datalist_set_data (&target->data, "gnomecal-do-tasks", GINT_TO_POINTER (!done_tasks)); hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2); w = gtk_check_button_new_with_label (_("Calendar Events")); gtk_toggle_button_set_active ((GtkToggleButton *) w, !done_cal); g_signal_connect ( w, "toggled", G_CALLBACK (calendar_toggle_cb), target); gtk_box_pack_start (GTK_BOX (hbox), w, FALSE, FALSE, 0); w = gtk_check_button_new_with_label (_("Tasks")); gtk_toggle_button_set_active ((GtkToggleButton *) w, !done_tasks); g_signal_connect ( w, "toggled", G_CALLBACK (tasks_toggle_cb), target); gtk_box_pack_start (GTK_BOX (hbox), w, FALSE, FALSE, 0); gtk_widget_show_all (hbox); return hbox; } static void gnome_calendar_cancel (EImport *ei, EImportTarget *target, EImportImporter *im) { ICalIntelligentImporter *ici = g_datalist_get_data (&target->data, "gnomecal-data"); if (ici) g_cancellable_cancel (ici->cancellable); } static EImportImporter gnome_calendar_importer = { E_IMPORT_TARGET_HOME, 0, gnome_calendar_supported, gnome_calendar_getwidget, gnome_calendar_import, gnome_calendar_cancel, NULL, /* get_preview */ }; EImportImporter * gnome_calendar_importer_peek (void) { gnome_calendar_importer.name = _("Gnome Calendar"); gnome_calendar_importer.description = _("Evolution Calendar intelligent importer"); return &gnome_calendar_importer; } /* ********************************************************************** */ static gchar * format_dt (const ECalComponentDateTime *dt, GHashTable *timezones, icaltimezone *users_zone) { struct tm tm; g_return_val_if_fail (dt != NULL, NULL); g_return_val_if_fail (timezones != NULL, NULL); if (!dt->value) return NULL; dt->value->zone = NULL; if (dt->tzid) { dt->value->zone = g_hash_table_lookup (timezones, dt->tzid); if (!dt->value->zone) dt->value->zone = icaltimezone_get_builtin_timezone_from_tzid (dt->tzid); if (!dt->value->zone && g_ascii_strcasecmp (dt->tzid, "UTC") == 0) dt->value->zone = icaltimezone_get_utc_timezone (); } if (dt->value->zone) tm = icaltimetype_to_tm_with_zone (dt->value, (icaltimezone *) dt->value->zone, users_zone); else tm = icaltimetype_to_tm (dt->value); return e_datetime_format_format_tm ("calendar", "table", dt->value->is_date ? DTFormatKindDate : DTFormatKindDateTime, &tm); } static const gchar * strip_mailto (const gchar *str) { if (!str || g_ascii_strncasecmp (str, "mailto:", 7) != 0) return str; return str + 7; } static void preview_comp (EWebViewPreview *preview, ECalComponent *comp) { ECalComponentText text = { 0 }; ECalComponentDateTime dt; ECalComponentClassification classif; const gchar *str; gchar *tmp; gint percent; gboolean have; GHashTable *timezones; icaltimezone *users_zone; GSList *slist, *l; g_return_if_fail (preview != NULL); g_return_if_fail (comp != NULL); timezones = g_object_get_data (G_OBJECT (preview), "iCalImp-timezones"); users_zone = g_object_get_data (G_OBJECT (preview), "iCalImp-userszone"); str = NULL; switch (e_cal_component_get_vtype (comp)) { case E_CAL_COMPONENT_EVENT: str = e_cal_component_has_attendees (comp) ? C_("iCalImp", "Meeting") : C_("iCalImp", "Event"); break; case E_CAL_COMPONENT_TODO: str = C_("iCalImp", "Task"); break; case E_CAL_COMPONENT_JOURNAL: str = C_("iCalImp", "Memo"); break; default: str = "??? Other ???"; break; } have = FALSE; if (e_cal_component_has_recurrences (comp)) { e_web_view_preview_add_section (preview, have ? NULL : str, C_("iCalImp", "has recurrences")); have = TRUE; } if (e_cal_component_is_instance (comp)) { e_web_view_preview_add_section (preview, have ? NULL : str, C_("iCalImp", "is an instance")); have = TRUE; } if (e_cal_component_has_alarms (comp)) { e_web_view_preview_add_section (preview, have ? NULL : str, C_("iCalImp", "has reminders")); have = TRUE; } if (e_cal_component_has_attachments (comp)) { e_web_view_preview_add_section (preview, have ? NULL : str, C_("iCalImp", "has attachments")); have = TRUE; } if (!have) { e_web_view_preview_add_section (preview, str, ""); } str = NULL; classif = E_CAL_COMPONENT_CLASS_NONE; e_cal_component_get_classification (comp, &classif); if (classif == E_CAL_COMPONENT_CLASS_PUBLIC) { /* Translators: Appointment's classification */ str = C_("iCalImp", "Public"); } else if (classif == E_CAL_COMPONENT_CLASS_PRIVATE) { /* Translators: Appointment's classification */ str = C_("iCalImp", "Private"); } else if (classif == E_CAL_COMPONENT_CLASS_CONFIDENTIAL) { /* Translators: Appointment's classification */ str = C_("iCalImp", "Confidential"); } if (str) /* Translators: Appointment's classification section name */ e_web_view_preview_add_section (preview, C_("iCalImp", "Classification"), str); e_cal_component_get_summary (comp, &text); if ((text.value && *text.value) || (text.altrep && *text.altrep)) /* Translators: Appointment's summary */ e_web_view_preview_add_section (preview, C_("iCalImp", "Summary"), (text.value && *text.value) ? text.value : text.altrep); str = NULL; e_cal_component_get_location (comp, &str); if (str && *str) /* Translators: Appointment's location */ e_web_view_preview_add_section (preview, C_("iCalImp", "Location"), str); dt.value = NULL; e_cal_component_get_dtstart (comp, &dt); if (dt.value) { tmp = format_dt (&dt, timezones, users_zone); if (tmp) /* Translators: Appointment's start time */ e_web_view_preview_add_section (preview, C_("iCalImp", "Start"), tmp); g_free (tmp); } e_cal_component_free_datetime (&dt); dt.value = NULL; e_cal_component_get_due (comp, &dt); if (dt.value) { tmp = format_dt (&dt, timezones, users_zone); if (tmp) /* Translators: 'Due' like the time due a task should be finished */ e_web_view_preview_add_section (preview, C_("iCalImp", "Due"), tmp); g_free (tmp); } else { e_cal_component_free_datetime (&dt); dt.value = NULL; e_cal_component_get_dtend (comp, &dt); if (dt.value) { tmp = format_dt (&dt, timezones, users_zone); if (tmp) /* Translators: Appointment's end time */ e_web_view_preview_add_section (preview, C_("iCalImp", "End"), tmp); g_free (tmp); } } e_cal_component_free_datetime (&dt); str = NULL; e_cal_component_get_categories (comp, &str); if (str && *str) /* Translators: Appointment's categories */ e_web_view_preview_add_section (preview, C_("iCalImp", "Categories"), str); percent = e_cal_component_get_percent_as_int (comp); if (percent >= 0) { tmp = NULL; if (percent == 100) { icaltimetype *completed = NULL; e_cal_component_get_completed (comp, &completed); if (completed) { dt.tzid = "UTC"; dt.value = completed; tmp = format_dt (&dt, timezones, users_zone); e_cal_component_free_icaltimetype (completed); } } if (!tmp) tmp = g_strdup_printf ("%d%%", percent); /* Translators: Appointment's complete value (either percentage, or a date/time of a completion) */ e_web_view_preview_add_section (preview, C_("iCalImp", "Completed"), tmp); g_free (tmp); } str = NULL; e_cal_component_get_url (comp, &str); if (str && *str) /* Translators: Appointment's URL */ e_web_view_preview_add_section (preview, C_("iCalImp", "URL"), str); if (e_cal_component_has_organizer (comp)) { ECalComponentOrganizer organizer = { 0 }; e_cal_component_get_organizer (comp, &organizer); if (organizer.value && *organizer.value) { if (organizer.cn && *organizer.cn) { tmp = g_strconcat (organizer.cn, " <", strip_mailto (organizer.value), ">", NULL); /* Translators: Appointment's organizer */ e_web_view_preview_add_section (preview, C_("iCalImp", "Organizer"), tmp); g_free (tmp); } else { e_web_view_preview_add_section (preview, C_("iCalImp", "Organizer"), strip_mailto (organizer.value)); } } } if (e_cal_component_has_attendees (comp)) { GSList *attendees = NULL, *a; have = FALSE; e_cal_component_get_attendee_list (comp, &attendees); for (a = attendees; a; a = a->next) { ECalComponentAttendee *attnd = a->data; if (!attnd || !attnd->value || !*attnd->value) continue; if (attnd->cn && *attnd->cn) { tmp = g_strconcat (attnd->cn, " <", strip_mailto (attnd->value), ">", NULL); /* Translators: Appointment's attendees */ e_web_view_preview_add_section (preview, have ? NULL : C_("iCalImp", "Attendees"), tmp); g_free (tmp); } else { e_web_view_preview_add_section (preview, have ? NULL : C_("iCalImp", "Attendees"), strip_mailto (attnd->value)); } have = TRUE; } e_cal_component_free_attendee_list (attendees); } slist = NULL; e_cal_component_get_description_list (comp, &slist); for (l = slist; l; l = l->next) { ECalComponentText *txt = l->data; e_web_view_preview_add_section (preview, l != slist ? NULL : C_("iCalImp", "Description"), (txt && txt->value) ? txt->value : ""); } e_cal_component_free_text_list (slist); } static void preview_selection_changed_cb (GtkTreeSelection *selection, EWebViewPreview *preview) { GtkTreeIter iter; GtkTreeModel *model = NULL; g_return_if_fail (selection != NULL); g_return_if_fail (preview != NULL); e_web_view_preview_begin_update (preview); if (gtk_tree_selection_get_selected (selection, &model, &iter) && model) { ECalComponent *comp = NULL; gtk_tree_model_get (model, &iter, 3, &comp, -1); if (comp) { preview_comp (preview, comp); g_object_unref (comp); } } e_web_view_preview_end_update (preview); } static icaltimezone * get_users_timezone (void) { /* more or less copy&paste of calendar_config_get_icaltimezone */ GSettings *settings; icaltimezone *zone = NULL; gchar *location; settings = g_settings_new ("org.gnome.evolution.calendar"); if (g_settings_get_boolean (settings, "use-system-timezone")) { location = e_cal_util_get_system_timezone_location (); } else { location = g_settings_get_string (settings, "timezone"); } g_object_unref (settings); if (location) { zone = icaltimezone_get_builtin_timezone (location); g_free (location); } return zone; } static void free_zone_cb (gpointer ptr) { icaltimezone *zone = ptr; if (zone) icaltimezone_free (zone, 1); } static GtkWidget * ical_get_preview (icalcomponent *icalcomp) { GtkWidget *preview; GtkTreeView *tree_view; GtkTreeSelection *selection; GtkListStore *store; GtkTreeIter iter; GHashTable *timezones; icalcomponent *subcomp; icaltimezone *users_zone; if (!icalcomp || !is_icalcomp_usable (icalcomp)) return NULL; store = gtk_list_store_new (4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, E_TYPE_CAL_COMPONENT); timezones = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, free_zone_cb); users_zone = get_users_timezone (); /* get timezones first */ for (subcomp = icalcomponent_get_first_component (icalcomp, ICAL_VTIMEZONE_COMPONENT); subcomp; subcomp = icalcomponent_get_next_component (icalcomp, ICAL_VTIMEZONE_COMPONENT)) { icaltimezone *zone = icaltimezone_new (); if (!icaltimezone_set_component (zone, icalcomponent_new_clone (subcomp)) || !icaltimezone_get_tzid (zone)) { icaltimezone_free (zone, 1); } else { g_hash_table_insert (timezones, (gchar *) icaltimezone_get_tzid (zone), zone); } } /* then each component */ for (subcomp = icalcomponent_get_first_component (icalcomp, ICAL_ANY_COMPONENT); subcomp; subcomp = icalcomponent_get_next_component (icalcomp, ICAL_ANY_COMPONENT)) { icalcomponent_kind kind = icalcomponent_isa (subcomp); if (kind == ICAL_VEVENT_COMPONENT || kind == ICAL_VTODO_COMPONENT || kind == ICAL_VJOURNAL_COMPONENT) { ECalComponent *comp = e_cal_component_new (); ECalComponentText summary = { 0 }; ECalComponentDateTime dt = { 0 }; gchar *formatted_dt; if (!e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (subcomp))) { g_object_unref (comp); continue; } e_cal_component_get_summary (comp, &summary); e_cal_component_get_dtstart (comp, &dt); formatted_dt = format_dt (&dt, timezones, users_zone); gtk_list_store_append (store, &iter); gtk_list_store_set ( store, &iter, 0, kind == ICAL_VEVENT_COMPONENT ? (e_cal_component_has_attendees (comp) ? C_("iCalImp", "Meeting") : C_("iCalImp", "Event")) : kind == ICAL_VTODO_COMPONENT ? C_("iCalImp", "Task") : kind == ICAL_VJOURNAL_COMPONENT ? C_("iCalImp", "Memo") : "??? Other ???", 1, formatted_dt ? formatted_dt : "", 2, summary.value && *summary.value ? summary.value : summary.altrep && *summary.altrep ? summary.altrep : "", 3, comp, -1); g_free (formatted_dt); e_cal_component_free_datetime (&dt); g_object_unref (comp); } } if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter)) { g_object_unref (store); g_hash_table_destroy (timezones); return NULL; } preview = e_web_view_preview_new (); gtk_widget_show (preview); g_object_set_data_full (G_OBJECT (preview), "iCalImp-timezones", timezones, (GDestroyNotify) g_hash_table_destroy); g_object_set_data (G_OBJECT (preview), "iCalImp-userszone", users_zone); tree_view = e_web_view_preview_get_tree_view (E_WEB_VIEW_PREVIEW (preview)); g_return_val_if_fail (tree_view != NULL, NULL); gtk_tree_view_set_model (tree_view, GTK_TREE_MODEL (store)); g_object_unref (store); /* Translators: Column header for a component type; it can be Event, Task or Memo */ gtk_tree_view_insert_column_with_attributes ( tree_view, -1, C_("iCalImp", "Type"), gtk_cell_renderer_text_new (), "text", 0, NULL); /* Translators: Column header for a component start date/time */ gtk_tree_view_insert_column_with_attributes ( tree_view, -1, C_("iCalImp", "Start"), gtk_cell_renderer_text_new (), "text", 1, NULL); /* Translators: Column header for a component summary */ gtk_tree_view_insert_column_with_attributes ( tree_view, -1, C_("iCalImp", "Summary"), gtk_cell_renderer_text_new (), "text", 2, NULL); if (gtk_tree_model_iter_n_children (GTK_TREE_MODEL (store), NULL) > 1) e_web_view_preview_show_tree_view (E_WEB_VIEW_PREVIEW (preview)); selection = gtk_tree_view_get_selection (tree_view); gtk_tree_selection_select_iter (selection, &iter); g_signal_connect ( selection, "changed", G_CALLBACK (preview_selection_changed_cb), preview); preview_selection_changed_cb (selection, E_WEB_VIEW_PREVIEW (preview)); return preview; }