evolution-3.6.4/calendar/importers/icalendar-importer.c

No issues found

   1 /*
   2  * Evolution calendar importer component
   3  *
   4  * This program is free software; you can redistribute it and/or
   5  * modify it under the terms of the GNU Lesser General Public
   6  * License as published by the Free Software Foundation; either
   7  * version 2 of the License, or (at your option) version 3.
   8  *
   9  * This program is distributed in the hope that it will be useful,
  10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  12  * Lesser General Public License for more details.
  13  *
  14  * You should have received a copy of the GNU Lesser General Public
  15  * License along with the program; if not, see <http://www.gnu.org/licenses/>
  16  *
  17  *
  18  * Authors:
  19  *		Rodrigo Moya <rodrigo@ximian.com>
  20  *
  21  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
  22  *
  23  */
  24 
  25 #ifdef HAVE_CONFIG_H
  26 #include <config.h>
  27 #endif
  28 
  29 #include <glib/gi18n.h>
  30 
  31 #include <string.h>
  32 #include <unistd.h>
  33 #include <sys/types.h>
  34 #include <fcntl.h>
  35 
  36 #include <gtk/gtk.h>
  37 
  38 #include <libecal/libecal.h>
  39 #include <libedataserverui/libedataserverui.h>
  40 #include <libical/icalvcal.h>
  41 
  42 #include "evolution-calendar-importer.h"
  43 #include "shell/e-shell.h"
  44 #include "gui/calendar-config-keys.h"
  45 
  46 #include "e-util/e-import.h"
  47 #include "e-util/e-util-private.h"
  48 #include "e-util/e-datetime-format.h"
  49 #include "misc/e-web-view-preview.h"
  50 
  51 /* We timeout after 2 minutes, when opening the folders. */
  52 #define IMPORTER_TIMEOUT_SECONDS 120
  53 
  54 typedef struct {
  55 	EImport *import;
  56 	EImportTarget *target;
  57 
  58 	guint idle_id;
  59 
  60 	ECalClient *cal_client;
  61 	EClientSourceType source_type;
  62 
  63 	icalcomponent *icalcomp;
  64 
  65 	GCancellable *cancellable;
  66 } ICalImporter;
  67 
  68 typedef struct {
  69 	EImport *ei;
  70 	EImportTarget *target;
  71 	GList *tasks;
  72 	icalcomponent *icalcomp;
  73 	GCancellable *cancellable;
  74 } ICalIntelligentImporter;
  75 
  76 static const gint import_type_map[] = {
  77 	E_CLIENT_SOURCE_TYPE_EVENTS,
  78 	E_CLIENT_SOURCE_TYPE_TASKS,
  79 	-1
  80 };
  81 
  82 static const gchar *import_type_strings[] = {
  83 	N_("Appointments and Meetings"),
  84 	N_("Tasks"),
  85 	NULL
  86 };
  87 
  88 /*
  89  * Functions shared by iCalendar & vCalendar importer.
  90  */
  91 
  92 static GtkWidget *ical_get_preview (icalcomponent *icalcomp);
  93 
  94 static gboolean
  95 is_icalcomp_usable (icalcomponent *icalcomp)
  96 {
  97 	return icalcomp && icalcomponent_is_valid (icalcomp) && (
  98 		icalcomponent_get_first_component (icalcomp, ICAL_VEVENT_COMPONENT) != NULL ||
  99 		icalcomponent_get_first_component (icalcomp, ICAL_VTODO_COMPONENT) != NULL);
 100 }
 101 
 102 static void
 103 ivcal_import_done (ICalImporter *ici)
 104 {
 105 	if (ici->cal_client)
 106 		g_object_unref (ici->cal_client);
 107 	icalcomponent_free (ici->icalcomp);
 108 
 109 	e_import_complete (ici->import, ici->target);
 110 	g_object_unref (ici->import);
 111 	g_object_unref (ici->cancellable);
 112 	g_free (ici);
 113 }
 114 
 115 /* This removes all components except VEVENTs and VTIMEZONEs from the toplevel */
 116 static void
 117 prepare_events (icalcomponent *icalcomp,
 118                 GList **vtodos)
 119 {
 120 	icalcomponent *subcomp;
 121 	icalcompiter iter;
 122 
 123 	if (vtodos)
 124 		*vtodos = NULL;
 125 
 126 	iter = icalcomponent_begin_component (icalcomp, ICAL_ANY_COMPONENT);
 127 	while ((subcomp = icalcompiter_deref (&iter)) != NULL) {
 128 		icalcomponent_kind child_kind = icalcomponent_isa (subcomp);
 129 		if (child_kind != ICAL_VEVENT_COMPONENT
 130 		    && child_kind != ICAL_VTIMEZONE_COMPONENT) {
 131 
 132 			icalcompiter_next (&iter);
 133 
 134 			icalcomponent_remove_component (icalcomp, subcomp);
 135 			if (child_kind == ICAL_VTODO_COMPONENT && vtodos)
 136 				*vtodos = g_list_prepend (*vtodos, subcomp);
 137 			else
 138 				icalcomponent_free (subcomp);
 139 		} else {
 140 			icalcompiter_next (&iter);
 141 		}
 142 	}
 143 }
 144 
 145 /* This removes all components except VTODOs and VTIMEZONEs from the toplevel
 146  * icalcomponent, and adds the given list of VTODO components. The list is
 147  * freed afterwards. */
 148 static void
 149 prepare_tasks (icalcomponent *icalcomp,
 150                GList *vtodos)
 151 {
 152 	icalcomponent *subcomp;
 153 	GList *elem;
 154 	icalcompiter iter;
 155 
 156 	iter = icalcomponent_begin_component (icalcomp, ICAL_ANY_COMPONENT);
 157 	while ((subcomp = icalcompiter_deref (&iter)) != NULL) {
 158 		icalcomponent_kind child_kind = icalcomponent_isa (subcomp);
 159 		if (child_kind != ICAL_VTODO_COMPONENT
 160 		    && child_kind != ICAL_VTIMEZONE_COMPONENT) {
 161 			icalcompiter_next (&iter);
 162 			icalcomponent_remove_component (icalcomp, subcomp);
 163 			icalcomponent_free (subcomp);
 164 		} else {
 165 			icalcompiter_next (&iter);
 166 		}
 167 	}
 168 
 169 	for (elem = vtodos; elem; elem = elem->next) {
 170 		icalcomponent_add_component (icalcomp, elem->data);
 171 	}
 172 	g_list_free (vtodos);
 173 }
 174 
 175 struct UpdateObjectsData
 176 {
 177 	void (*done_cb) (gpointer user_data);
 178 	gpointer user_data;
 179 };
 180 
 181 static void
 182 receive_objects_ready_cb (GObject *source_object,
 183                           GAsyncResult *result,
 184                           gpointer user_data)
 185 {
 186 	ECalClient *cal_client = E_CAL_CLIENT (source_object);
 187 	struct UpdateObjectsData *uod = user_data;
 188 	GError *error = NULL;
 189 
 190 	g_return_if_fail (uod != NULL);
 191 
 192 	e_cal_client_receive_objects_finish (cal_client, result, &error);
 193 
 194 	if (error != NULL) {
 195 		g_warning (
 196 			"%s: Failed to receive objects: %s",
 197 			G_STRFUNC, error->message);
 198 		g_error_free (error);
 199 	}
 200 
 201 	if (uod->done_cb)
 202 		uod->done_cb (uod->user_data);
 203 	g_free (uod);
 204 }
 205 
 206 static void
 207 update_objects (ECalClient *cal_client,
 208                 icalcomponent *icalcomp,
 209                 GCancellable *cancellable,
 210                 void (*done_cb) (gpointer user_data),
 211                 gpointer user_data)
 212 {
 213 	icalcomponent_kind kind;
 214 	icalcomponent *vcal;
 215 	struct UpdateObjectsData *uod;
 216 
 217 	kind = icalcomponent_isa (icalcomp);
 218 	if (kind == ICAL_VTODO_COMPONENT || kind == ICAL_VEVENT_COMPONENT) {
 219 		vcal = e_cal_util_new_top_level ();
 220 		if (icalcomponent_get_method (icalcomp) == ICAL_METHOD_CANCEL)
 221 			icalcomponent_set_method (vcal, ICAL_METHOD_CANCEL);
 222 		else
 223 			icalcomponent_set_method (vcal, ICAL_METHOD_PUBLISH);
 224 		icalcomponent_add_component (vcal, icalcomponent_new_clone (icalcomp));
 225 	} else if (kind == ICAL_VCALENDAR_COMPONENT) {
 226 		vcal = icalcomponent_new_clone (icalcomp);
 227 		if (!icalcomponent_get_first_property (vcal, ICAL_METHOD_PROPERTY))
 228 			icalcomponent_set_method (vcal, ICAL_METHOD_PUBLISH);
 229 	} else {
 230 		if (done_cb)
 231 			done_cb (user_data);
 232 		return;
 233 	}
 234 
 235 	uod = g_new0 (struct UpdateObjectsData, 1);
 236 	uod->done_cb = done_cb;
 237 	uod->user_data = user_data;
 238 
 239 	e_cal_client_receive_objects (cal_client, vcal, cancellable, receive_objects_ready_cb, uod);
 240 
 241 	icalcomponent_free (vcal);
 242 
 243 	return;
 244 }
 245 
 246 struct _selector_data {
 247 	EImportTarget *target;
 248 	GtkWidget *selector;
 249 	GtkWidget *notebook;
 250 	gint page;
 251 };
 252 
 253 static void
 254 button_toggled_cb (GtkWidget *widget,
 255                    struct _selector_data *sd)
 256 {
 257 	ESourceSelector *selector;
 258 	ESource *source;
 259 	GtkNotebook *notebook;
 260 
 261 	selector = E_SOURCE_SELECTOR (sd->selector);
 262 	source = e_source_selector_ref_primary_selection (selector);
 263 	g_return_if_fail (source != NULL);
 264 
 265 	g_datalist_set_data_full (
 266 		&sd->target->data, "primary-source",
 267 		source, (GDestroyNotify) g_object_unref);
 268 	g_datalist_set_data (
 269 		&sd->target->data, "primary-type",
 270 		GINT_TO_POINTER (import_type_map[sd->page]));
 271 
 272 	notebook = GTK_NOTEBOOK (sd->notebook);
 273 	gtk_notebook_set_current_page (notebook, sd->page);
 274 }
 275 
 276 static void
 277 primary_selection_changed_cb (ESourceSelector *selector,
 278                               EImportTarget *target)
 279 {
 280 	ESource *source;
 281 
 282 	source = e_source_selector_ref_primary_selection (selector);
 283 	g_return_if_fail (source != NULL);
 284 
 285 	g_datalist_set_data_full (
 286 		&target->data, "primary-source",
 287 		source, (GDestroyNotify) g_object_unref);
 288 }
 289 
 290 static GtkWidget *
 291 ivcal_getwidget (EImport *ei,
 292                  EImportTarget *target,
 293                  EImportImporter *im)
 294 {
 295 	EShell *shell;
 296 	ESourceRegistry *registry;
 297 	GtkWidget *vbox, *hbox, *first = NULL;
 298 	GSList *group = NULL;
 299 	gint i;
 300 	GtkWidget *nb;
 301 
 302 	shell = e_shell_get_default ();
 303 	registry = e_shell_get_registry (shell);
 304 
 305 	vbox = gtk_vbox_new (FALSE, FALSE);
 306 
 307 	hbox = gtk_hbox_new (FALSE, FALSE);
 308 	gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, TRUE, 6);
 309 
 310 	nb = gtk_notebook_new ();
 311 	gtk_notebook_set_show_tabs (GTK_NOTEBOOK (nb), FALSE);
 312 	gtk_box_pack_start (GTK_BOX (vbox), nb, TRUE, TRUE, 6);
 313 
 314 	/* Type of icalendar items */
 315 	for (i = 0; import_type_map[i] != -1; i++) {
 316 		GtkWidget *selector, *rb;
 317 		ESource *source = NULL;
 318 		GtkWidget *scrolled;
 319 		struct _selector_data *sd;
 320 		const gchar *extension_name;
 321 		GList *list;
 322 
 323 		switch (import_type_map[i]) {
 324 			case E_CLIENT_SOURCE_TYPE_EVENTS:
 325 				extension_name = E_SOURCE_EXTENSION_CALENDAR;
 326 				break;
 327 			case E_CLIENT_SOURCE_TYPE_TASKS:
 328 				extension_name = E_SOURCE_EXTENSION_TASK_LIST;
 329 				break;
 330 			default:
 331 				g_warn_if_reached ();
 332 				continue;
 333 		}
 334 
 335 		selector = e_source_selector_new (registry, extension_name);
 336 		e_source_selector_set_show_toggles (
 337 			E_SOURCE_SELECTOR (selector), FALSE);
 338 		scrolled = gtk_scrolled_window_new (NULL, NULL);
 339 		gtk_scrolled_window_set_policy ((GtkScrolledWindow *) scrolled, GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
 340 		gtk_container_add ((GtkContainer *) scrolled, selector);
 341 		gtk_notebook_append_page (GTK_NOTEBOOK (nb), scrolled, NULL);
 342 
 343 		list = e_source_registry_list_sources (registry, extension_name);
 344 		if (list != NULL) {
 345 			source = E_SOURCE (list->data);
 346 			e_source_selector_set_primary_selection (
 347 				E_SOURCE_SELECTOR (selector), source);
 348 		}
 349 
 350 		g_list_free_full (list, (GDestroyNotify) g_object_unref);
 351 
 352 		g_signal_connect (
 353 			selector, "primary_selection_changed",
 354 			G_CALLBACK (primary_selection_changed_cb), target);
 355 
 356 		rb = gtk_radio_button_new_with_label (group, _(import_type_strings[i]));
 357 		gtk_box_pack_start (GTK_BOX (hbox), rb, FALSE, FALSE, 6);
 358 
 359 		sd = g_malloc0 (sizeof (*sd));
 360 		sd->target = target;
 361 		sd->selector = selector;
 362 		sd->notebook = nb;
 363 		sd->page = i;
 364 		g_object_set_data_full ((GObject *) rb, "selector-data", sd, g_free);
 365 		g_signal_connect (
 366 			rb, "toggled",
 367 			G_CALLBACK (button_toggled_cb), sd);
 368 
 369 		if (!group)
 370 			group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (rb));
 371 		if (first == NULL) {
 372 			g_datalist_set_data_full (&target->data, "primary-source", g_object_ref (source), g_object_unref);
 373 			g_datalist_set_data (&target->data, "primary-type", GINT_TO_POINTER (import_type_map[i]));
 374 			first = rb;
 375 		}
 376 	}
 377 	if (first)
 378 		gtk_toggle_button_set_active ((GtkToggleButton *) first, TRUE);
 379 
 380 	gtk_widget_show_all (vbox);
 381 
 382 	return vbox;
 383 }
 384 
 385 static void
 386 ivcal_call_import_done (gpointer user_data)
 387 {
 388 	ivcal_import_done (user_data);
 389 }
 390 
 391 static gboolean
 392 ivcal_import_items (gpointer d)
 393 {
 394 	ICalImporter *ici = d;
 395 
 396 	switch (ici->source_type) {
 397 	case E_CLIENT_SOURCE_TYPE_EVENTS:
 398 		prepare_events (ici->icalcomp, NULL);
 399 		update_objects (ici->cal_client, ici->icalcomp, ici->cancellable, ivcal_call_import_done, ici);
 400 		break;
 401 	case E_CLIENT_SOURCE_TYPE_TASKS:
 402 		prepare_tasks (ici->icalcomp, NULL);
 403 		update_objects (ici->cal_client, ici->icalcomp, ici->cancellable, ivcal_call_import_done, ici);
 404 		break;
 405 	default:
 406 		g_warn_if_reached ();
 407 
 408 		ici->idle_id = 0;
 409 		ivcal_import_done (ici);
 410 		return FALSE;
 411 	}
 412 
 413 	ici->idle_id = 0;
 414 
 415 	return FALSE;
 416 }
 417 
 418 static void
 419 ivcal_opened (GObject *source_object,
 420               GAsyncResult *result,
 421               gpointer user_data)
 422 {
 423 	ESource *source = E_SOURCE (source_object);
 424 	EClient *client = NULL;
 425 	ICalImporter *ici = user_data;
 426 	GError *error = NULL;
 427 
 428 	g_return_if_fail (ici != NULL);
 429 
 430 	e_client_utils_open_new_finish (source, result, &client, &error);
 431 
 432 	if (error != NULL) {
 433 		g_warn_if_fail (client == NULL);
 434 		g_warning (
 435 			"%s: Failed to open calendar: %s",
 436 			G_STRFUNC, error->message);
 437 		g_error_free (error);
 438 		ivcal_import_done (ici);
 439 		return;
 440 	}
 441 
 442 	g_return_if_fail (E_IS_CLIENT (client));
 443 
 444 	ici->cal_client = E_CAL_CLIENT (client);
 445 
 446 	e_import_status (ici->import, ici->target, _("Importing..."), 0);
 447 	ici->idle_id = g_idle_add (ivcal_import_items, ici);
 448 }
 449 
 450 static void
 451 ivcal_import (EImport *ei,
 452               EImportTarget *target,
 453               icalcomponent *icalcomp)
 454 {
 455 	EClientSourceType type;
 456 	ICalImporter *ici = g_malloc0 (sizeof (*ici));
 457 
 458 	type = GPOINTER_TO_INT (g_datalist_get_data (&target->data, "primary-type"));
 459 
 460 	ici->import = ei;
 461 	g_datalist_set_data (&target->data, "ivcal-data", ici);
 462 	g_object_ref (ei);
 463 	ici->target = target;
 464 	ici->icalcomp = icalcomp;
 465 	ici->cal_client = NULL;
 466 	ici->source_type = type;
 467 	ici->cancellable = g_cancellable_new ();
 468 	e_import_status (ei, target, _("Opening calendar"), 0);
 469 
 470 	e_client_utils_open_new (
 471 		g_datalist_get_data (&target->data, "primary-source"),
 472 		type, FALSE, ici->cancellable, ivcal_opened, ici);
 473 }
 474 
 475 static void
 476 ivcal_cancel (EImport *ei,
 477               EImportTarget *target,
 478               EImportImporter *im)
 479 {
 480 	ICalImporter *ici = g_datalist_get_data (&target->data, "ivcal-data");
 481 
 482 	if (ici)
 483 		g_cancellable_cancel (ici->cancellable);
 484 }
 485 
 486 /* ********************************************************************** */
 487 /*
 488  * iCalendar importer functions.
 489  */
 490 
 491 static gboolean
 492 ical_supported (EImport *ei,
 493                 EImportTarget *target,
 494                 EImportImporter *im)
 495 {
 496 	gchar *filename;
 497 	gchar *contents;
 498 	gboolean ret = FALSE;
 499 	EImportTargetURI *s;
 500 
 501 	if (target->type != E_IMPORT_TARGET_URI)
 502 		return FALSE;
 503 
 504 	s = (EImportTargetURI *) target;
 505 	if (s->uri_src == NULL)
 506 		return TRUE;
 507 
 508 	if (strncmp (s->uri_src, "file:///", 8) != 0)
 509 		return FALSE;
 510 
 511 	filename = g_filename_from_uri (s->uri_src, NULL, NULL);
 512 	if (!filename)
 513 		return FALSE;
 514 
 515 	if (g_file_get_contents (filename, &contents, NULL, NULL)) {
 516 		icalcomponent *icalcomp = NULL;
 517 
 518 		if (g_ascii_strncasecmp (contents, "BEGIN:", 6) == 0)
 519 			icalcomp = e_cal_util_parse_ics_string (contents);
 520 		g_free (contents);
 521 
 522 		if (icalcomp) {
 523 			if (is_icalcomp_usable (icalcomp))
 524 				ret = TRUE;
 525 			else
 526 				ret = FALSE;
 527 			icalcomponent_free (icalcomp);
 528 		}
 529 	}
 530 	g_free (filename);
 531 
 532 	return ret;
 533 }
 534 
 535 static void
 536 ical_import (EImport *ei,
 537              EImportTarget *target,
 538              EImportImporter *im)
 539 {
 540 	gchar *filename;
 541 	gchar *contents;
 542 	icalcomponent *icalcomp;
 543 	EImportTargetURI *s = (EImportTargetURI *) target;
 544 
 545 	filename = g_filename_from_uri (s->uri_src, NULL, NULL);
 546 	if (!filename) {
 547 		e_import_complete (ei, target);
 548 		return;
 549 	}
 550 
 551 	if (!g_file_get_contents (filename, &contents, NULL, NULL)) {
 552 		g_free (filename);
 553 		e_import_complete (ei, target);
 554 		return;
 555 	}
 556 	g_free (filename);
 557 
 558 	icalcomp = e_cal_util_parse_ics_string (contents);
 559 	g_free (contents);
 560 
 561 	if (icalcomp)
 562 		ivcal_import (ei, target, icalcomp);
 563 	else
 564 		e_import_complete (ei, target);
 565 }
 566 
 567 static GtkWidget *
 568 ivcal_get_preview (EImport *ei,
 569                    EImportTarget *target,
 570                    EImportImporter *im)
 571 {
 572 	GtkWidget *preview;
 573 	EImportTargetURI *s = (EImportTargetURI *) target;
 574 	gchar *filename;
 575 	icalcomponent *icalcomp;
 576 	gchar *contents;
 577 
 578 	filename = g_filename_from_uri (s->uri_src, NULL, NULL);
 579 	if (filename == NULL) {
 580 		g_message (G_STRLOC ": Couldn't get filename from URI '%s'", s->uri_src);
 581 		return NULL;
 582 	}
 583 
 584 	if (!g_file_get_contents (filename, &contents, NULL, NULL)) {
 585 		g_free (filename);
 586 		return NULL;
 587 	}
 588 	g_free (filename);
 589 
 590 	icalcomp = e_cal_util_parse_ics_string (contents);
 591 	g_free (contents);
 592 
 593 	if (!icalcomp)
 594 		return NULL;
 595 
 596 	preview = ical_get_preview (icalcomp);
 597 
 598 	icalcomponent_free (icalcomp);
 599 
 600 	return preview;
 601 }
 602 
 603 static EImportImporter ical_importer = {
 604 	E_IMPORT_TARGET_URI,
 605 	0,
 606 	ical_supported,
 607 	ivcal_getwidget,
 608 	ical_import,
 609 	ivcal_cancel,
 610 	ivcal_get_preview,
 611 };
 612 
 613 EImportImporter *
 614 ical_importer_peek (void)
 615 {
 616 	ical_importer.name = _("iCalendar files (.ics)");
 617 	ical_importer.description = _("Evolution iCalendar importer");
 618 
 619 	return &ical_importer;
 620 }
 621 
 622 /* ********************************************************************** */
 623 /*
 624  * vCalendar importer functions.
 625  */
 626 
 627 static gboolean
 628 vcal_supported (EImport *ei,
 629                 EImportTarget *target,
 630                 EImportImporter *im)
 631 {
 632 	gchar *filename;
 633 	gchar *contents;
 634 	gboolean ret = FALSE;
 635 	EImportTargetURI *s;
 636 
 637 	if (target->type != E_IMPORT_TARGET_URI)
 638 		return FALSE;
 639 
 640 	s = (EImportTargetURI *) target;
 641 	if (s->uri_src == NULL)
 642 		return TRUE;
 643 
 644 	if (strncmp (s->uri_src, "file:///", 8) != 0)
 645 		return FALSE;
 646 
 647 	filename = g_filename_from_uri (s->uri_src, NULL, NULL);
 648 	if (!filename)
 649 		return FALSE;
 650 
 651 	/* Z: Wow, this is *efficient* */
 652 
 653 	if (g_file_get_contents (filename, &contents, NULL, NULL)) {
 654 		VObject *vcal;
 655 		icalcomponent *icalcomp;
 656 
 657 		icalcomp = e_cal_util_parse_ics_string (contents);
 658 
 659 		if (icalcomp && is_icalcomp_usable (icalcomp)) {
 660 			/* If we can create proper iCalendar from the file, then
 661 			 * rather use ics importer, because it knows to read more
 662 			 * information than older version, the vCalendar. */
 663 			ret = FALSE;
 664 			g_free (contents);
 665 		} else {
 666 			if (icalcomp)
 667 				icalcomponent_free (icalcomp);
 668 
 669 			/* parse the file */
 670 			vcal = Parse_MIME (contents, strlen (contents));
 671 			g_free (contents);
 672 
 673 			if (vcal) {
 674 				icalcomp = icalvcal_convert (vcal);
 675 
 676 				if (icalcomp) {
 677 					icalcomponent_free (icalcomp);
 678 					ret = TRUE;
 679 				}
 680 
 681 				cleanVObject (vcal);
 682 			}
 683 		}
 684 	}
 685 	g_free (filename);
 686 
 687 	return ret;
 688 }
 689 
 690 /* This tries to load in a vCalendar file and convert it to an icalcomponent.
 691  * It returns NULL on failure. */
 692 static icalcomponent *
 693 load_vcalendar_file (const gchar *filename)
 694 {
 695 	icalvcal_defaults defaults = { NULL };
 696 	icalcomponent *icalcomp = NULL;
 697 	gchar *contents;
 698 	gchar *default_alarm_filename;
 699 
 700 	default_alarm_filename = g_build_filename (
 701 		EVOLUTION_SOUNDDIR,
 702 		"default_alarm.wav",
 703 		NULL);
 704 	defaults.alarm_audio_url = g_filename_to_uri (
 705 		default_alarm_filename,
 706 		NULL, NULL);
 707 	g_free (default_alarm_filename);
 708 	defaults.alarm_audio_fmttype = (gchar *) "audio/x-wav";
 709 	defaults.alarm_description = (gchar *) _("Reminder!");
 710 
 711 	if (g_file_get_contents (filename, &contents, NULL, NULL)) {
 712 		VObject *vcal;
 713 
 714 		/* parse the file */
 715 		vcal = Parse_MIME (contents, strlen (contents));
 716 		g_free (contents);
 717 
 718 		if (vcal) {
 719 			icalcomp = icalvcal_convert_with_defaults (
 720 				vcal, &defaults);
 721 			cleanVObject (vcal);
 722 		}
 723 	}
 724 
 725 	return icalcomp;
 726 }
 727 
 728 static void
 729 vcal_import (EImport *ei,
 730              EImportTarget *target,
 731              EImportImporter *im)
 732 {
 733 	gchar *filename;
 734 	icalcomponent *icalcomp;
 735 	EImportTargetURI *s = (EImportTargetURI *) target;
 736 
 737 	filename = g_filename_from_uri (s->uri_src, NULL, NULL);
 738 	if (!filename) {
 739 		e_import_complete (ei, target);
 740 		return;
 741 	}
 742 
 743 	icalcomp = load_vcalendar_file (filename);
 744 	g_free (filename);
 745 	if (icalcomp)
 746 		ivcal_import (ei, target, icalcomp);
 747 	else
 748 		e_import_complete (ei, target);
 749 }
 750 
 751 static GtkWidget *
 752 vcal_get_preview (EImport *ei,
 753                   EImportTarget *target,
 754                   EImportImporter *im)
 755 {
 756 	GtkWidget *preview;
 757 	EImportTargetURI *s = (EImportTargetURI *) target;
 758 	gchar *filename;
 759 	icalcomponent *icalcomp;
 760 
 761 	filename = g_filename_from_uri (s->uri_src, NULL, NULL);
 762 	if (filename == NULL) {
 763 		g_message (G_STRLOC ": Couldn't get filename from URI '%s'", s->uri_src);
 764 		return NULL;
 765 	}
 766 
 767 	icalcomp = load_vcalendar_file (filename);
 768 	g_free (filename);
 769 
 770 	if (!icalcomp)
 771 		return NULL;
 772 
 773 	preview = ical_get_preview (icalcomp);
 774 
 775 	icalcomponent_free (icalcomp);
 776 
 777 	return preview;
 778 }
 779 
 780 static EImportImporter vcal_importer = {
 781 	E_IMPORT_TARGET_URI,
 782 	0,
 783 	vcal_supported,
 784 	ivcal_getwidget,
 785 	vcal_import,
 786 	ivcal_cancel,
 787 	vcal_get_preview,
 788 };
 789 
 790 EImportImporter *
 791 vcal_importer_peek (void)
 792 {
 793 	vcal_importer.name = _("vCalendar files (.vcs)");
 794 	vcal_importer.description = _("Evolution vCalendar importer");
 795 
 796 	return &vcal_importer;
 797 }
 798 
 799 /* ********************************************************************** */
 800 
 801 static gboolean
 802 gnome_calendar_supported (EImport *ei,
 803                           EImportTarget *target,
 804                           EImportImporter *im)
 805 {
 806 	gchar *filename;
 807 	gboolean res;
 808 
 809 	if (target->type != E_IMPORT_TARGET_HOME)
 810 		return FALSE;
 811 
 812 	filename = g_build_filename (g_get_home_dir (), "user-cal.vcf", NULL);
 813 	res = g_file_test (filename, G_FILE_TEST_IS_REGULAR);
 814 	g_free (filename);
 815 
 816 	return res;
 817 }
 818 
 819 static void
 820 free_ici (gpointer ptr)
 821 {
 822 	ICalIntelligentImporter *ici = ptr;
 823 
 824 	if (!ici)
 825 		return;
 826 
 827 	if (ici->icalcomp)
 828 		icalcomponent_free (ici->icalcomp);
 829 
 830 	g_object_unref (ici->cancellable);
 831 	g_free (ici);
 832 }
 833 
 834 struct OpenDefaultSourceData
 835 {
 836 	ICalIntelligentImporter *ici;
 837 	void (* opened_cb) (ECalClient *cal_client, const GError *error, ICalIntelligentImporter *ici);
 838 };
 839 
 840 static void
 841 default_source_opened_cb (GObject *source_object,
 842                           GAsyncResult *result,
 843                           gpointer user_data)
 844 {
 845 	ESource *source = E_SOURCE (source_object);
 846 	EClient *client = NULL;
 847 	struct OpenDefaultSourceData *odsd = user_data;
 848 	GError *error = NULL;
 849 
 850 	g_return_if_fail (odsd != NULL);
 851 	g_return_if_fail (odsd->ici != NULL);
 852 	g_return_if_fail (odsd->opened_cb != NULL);
 853 
 854 	e_client_utils_open_new_finish (source, result, &client, &error);
 855 
 856 	/* Client may be NULL; don't use a type cast macro. */
 857 	odsd->opened_cb ((ECalClient *) client, error, odsd->ici);
 858 
 859 	if (client)
 860 		g_object_unref (client);
 861 	if (error)
 862 		g_error_free (error);
 863 
 864 	g_free (odsd);
 865 }
 866 
 867 static void
 868 open_default_source (ICalIntelligentImporter *ici,
 869                      ECalClientSourceType source_type,
 870                      void (* opened_cb) (ECalClient *cal_client,
 871                                          const GError *error,
 872                                          ICalIntelligentImporter *ici))
 873 {
 874 	EShell *shell;
 875 	ESource *source;
 876 	ESourceRegistry *registry;
 877 	EClientSourceType client_source_type;
 878 	struct OpenDefaultSourceData *odsd;
 879 
 880 	g_return_if_fail (ici != NULL);
 881 	g_return_if_fail (opened_cb != NULL);
 882 
 883 	shell = e_shell_get_default ();
 884 	registry = e_shell_get_registry (shell);
 885 
 886 	switch (source_type) {
 887 		case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
 888 			client_source_type = E_CLIENT_SOURCE_TYPE_EVENTS;
 889 			source = e_source_registry_ref_default_calendar (registry);
 890 			break;
 891 		case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
 892 			client_source_type = E_CLIENT_SOURCE_TYPE_TASKS;
 893 			source = e_source_registry_ref_default_task_list (registry);
 894 			break;
 895 		case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
 896 			client_source_type = E_CLIENT_SOURCE_TYPE_MEMOS;
 897 			source = e_source_registry_ref_default_memo_list (registry);
 898 			break;
 899 		default:
 900 			g_return_if_reached ();
 901 	}
 902 
 903 	odsd = g_new0 (struct OpenDefaultSourceData, 1);
 904 	odsd->ici = ici;
 905 	odsd->opened_cb = opened_cb;
 906 
 907 	e_import_status (ici->ei, ici->target, _("Opening calendar"), 0);
 908 
 909 	e_client_utils_open_new (
 910 		source, client_source_type, FALSE, ici->cancellable,
 911 		default_source_opened_cb, odsd);
 912 
 913 	g_object_unref (source);
 914 }
 915 
 916 static void
 917 continue_done_cb (gpointer user_data)
 918 {
 919 	ICalIntelligentImporter *ici = user_data;
 920 
 921 	g_return_if_fail (ici != NULL);
 922 
 923 	e_import_complete (ici->ei, ici->target);
 924 }
 925 
 926 static void
 927 gc_import_tasks (ECalClient *cal_client,
 928                  const GError *error,
 929                  ICalIntelligentImporter *ici)
 930 {
 931 	g_return_if_fail (ici != NULL);
 932 
 933 	if (error != NULL) {
 934 		g_warning (
 935 			"%s: Failed to open tasks: %s",
 936 			G_STRFUNC, error->message);
 937 		e_import_complete (ici->ei, ici->target);
 938 		return;
 939 	}
 940 
 941 	e_import_status (ici->ei, ici->target, _("Importing..."), 0);
 942 
 943 	prepare_tasks (ici->icalcomp, ici->tasks);
 944 
 945 	update_objects (
 946 		cal_client, ici->icalcomp,
 947 		ici->cancellable, continue_done_cb, ici);
 948 }
 949 
 950 static void
 951 continue_tasks_cb (gpointer user_data)
 952 {
 953 	ICalIntelligentImporter *ici = user_data;
 954 
 955 	g_return_if_fail (ici != NULL);
 956 
 957 	open_default_source (ici, E_CAL_CLIENT_SOURCE_TYPE_TASKS, gc_import_tasks);
 958 }
 959 
 960 static void
 961 gc_import_events (ECalClient *cal_client,
 962                   const GError *error,
 963                   ICalIntelligentImporter *ici)
 964 {
 965 	g_return_if_fail (ici != NULL);
 966 
 967 	if (error != NULL) {
 968 		g_warning (
 969 			"%s: Failed to open events calendar: %s",
 970 			G_STRFUNC, error->message);
 971 		if (ici->tasks)
 972 			open_default_source (
 973 				ici, E_CAL_CLIENT_SOURCE_TYPE_TASKS,
 974 				gc_import_tasks);
 975 		else
 976 			e_import_complete (ici->ei, ici->target);
 977 		return;
 978 	}
 979 
 980 	e_import_status (ici->ei, ici->target, _("Importing..."), 0);
 981 
 982 	update_objects (
 983 		cal_client, ici->icalcomp, ici->cancellable,
 984 		ici->tasks ? continue_tasks_cb : continue_done_cb, ici);
 985 }
 986 
 987 static void
 988 gnome_calendar_import (EImport *ei,
 989                        EImportTarget *target,
 990                        EImportImporter *im)
 991 {
 992 	icalcomponent *icalcomp = NULL;
 993 	gchar *filename;
 994 	gint do_calendar, do_tasks;
 995 	ICalIntelligentImporter *ici;
 996 
 997 	/* This is pretty shitty, everything runs in the gui thread and can block
 998 	 * for quite some time */
 999 
1000 	do_calendar = GPOINTER_TO_INT (g_datalist_get_data (&target->data, "gnomecal-do-cal"));
1001 	do_tasks = GPOINTER_TO_INT (g_datalist_get_data (&target->data, "gnomecal-do-tasks"));
1002 
1003 	/* If neither is selected, just return. */
1004 	if (!do_calendar && !do_tasks)
1005 		return;
1006 
1007 	/* Load the Gnome Calendar file and convert to iCalendar. */
1008 	filename = g_build_filename (g_get_home_dir (), "user-cal.vcf", NULL);
1009 	icalcomp = load_vcalendar_file (filename);
1010 	g_free (filename);
1011 
1012 	/* If we couldn't load the file, just return. FIXME: Error message? */
1013 	if (!icalcomp)
1014 		goto out;
1015 
1016 	ici = g_malloc0 (sizeof (*ici));
1017 	ici->ei = ei;
1018 	ici->target = target;
1019 	ici->cancellable = g_cancellable_new ();
1020 	ici->icalcomp = icalcomp;
1021 	icalcomp = NULL;
1022 
1023 	g_datalist_set_data_full (&target->data, "gnomecal-data", ici, free_ici);
1024 
1025 	prepare_events (ici->icalcomp, &ici->tasks);
1026 	if (do_calendar) {
1027 		open_default_source (ici, E_CAL_CLIENT_SOURCE_TYPE_EVENTS, gc_import_events);
1028 		return;
1029 	}
1030 
1031 	prepare_tasks (ici->icalcomp, ici->tasks);
1032 	if (do_tasks) {
1033 		open_default_source (ici, E_CAL_CLIENT_SOURCE_TYPE_TASKS, gc_import_tasks);
1034 		return;
1035 	}
1036 
1037  out:
1038 	if (icalcomp)
1039 		icalcomponent_free (icalcomp);
1040 
1041 	e_import_complete (ei, target);
1042 }
1043 
1044 static void
1045 calendar_toggle_cb (GtkToggleButton *tb,
1046                     EImportTarget *target)
1047 {
1048 	g_datalist_set_data (&target->data, "gnomecal-do-cal", GINT_TO_POINTER (gtk_toggle_button_get_active (tb)));
1049 }
1050 
1051 static void
1052 tasks_toggle_cb (GtkToggleButton *tb,
1053                  EImportTarget *target)
1054 {
1055 	g_datalist_set_data (&target->data, "gnomecal-do-tasks", GINT_TO_POINTER (gtk_toggle_button_get_active (tb)));
1056 }
1057 
1058 static GtkWidget *
1059 gnome_calendar_getwidget (EImport *ei,
1060                           EImportTarget *target,
1061                           EImportImporter *im)
1062 {
1063 	GtkWidget *hbox, *w;
1064 	GSettings *settings;
1065 	gboolean done_cal, done_tasks;
1066 
1067 	settings = g_settings_new ("org.gnome.evolution.importer");
1068 	done_cal = g_settings_get_boolean (settings, "gnome-calendar-done-calendar");
1069 	done_tasks = g_settings_get_boolean (settings, "gnome-calendar-done-tasks");
1070 	g_object_unref (settings);
1071 
1072 	g_datalist_set_data (&target->data, "gnomecal-do-cal", GINT_TO_POINTER (!done_cal));
1073 	g_datalist_set_data (&target->data, "gnomecal-do-tasks", GINT_TO_POINTER (!done_tasks));
1074 
1075 	hbox = gtk_hbox_new (FALSE, 2);
1076 
1077 	w = gtk_check_button_new_with_label (_("Calendar Events"));
1078 	gtk_toggle_button_set_active ((GtkToggleButton *) w, !done_cal);
1079 	g_signal_connect (
1080 		w, "toggled",
1081 		G_CALLBACK (calendar_toggle_cb), target);
1082 	gtk_box_pack_start (GTK_BOX (hbox), w, FALSE, FALSE, 0);
1083 
1084 	w = gtk_check_button_new_with_label (_("Tasks"));
1085 	gtk_toggle_button_set_active ((GtkToggleButton *) w, !done_tasks);
1086 	g_signal_connect (
1087 		w, "toggled",
1088 		G_CALLBACK (tasks_toggle_cb), target);
1089 	gtk_box_pack_start (GTK_BOX (hbox), w, FALSE, FALSE, 0);
1090 
1091 	gtk_widget_show_all (hbox);
1092 
1093 	return hbox;
1094 }
1095 
1096 static void
1097 gnome_calendar_cancel (EImport *ei,
1098                        EImportTarget *target,
1099                        EImportImporter *im)
1100 {
1101 	ICalIntelligentImporter *ici = g_datalist_get_data (&target->data, "gnomecal-data");
1102 
1103 	if (ici)
1104 		g_cancellable_cancel (ici->cancellable);
1105 }
1106 
1107 static EImportImporter gnome_calendar_importer = {
1108 	E_IMPORT_TARGET_HOME,
1109 	0,
1110 	gnome_calendar_supported,
1111 	gnome_calendar_getwidget,
1112 	gnome_calendar_import,
1113 	gnome_calendar_cancel,
1114 	NULL, /* get_preview */
1115 };
1116 
1117 EImportImporter *
1118 gnome_calendar_importer_peek (void)
1119 {
1120 	gnome_calendar_importer.name = _("Gnome Calendar");
1121 	gnome_calendar_importer.description = _("Evolution Calendar intelligent importer");
1122 
1123 	return &gnome_calendar_importer;
1124 }
1125 
1126 /* ********************************************************************** */
1127 
1128 static gchar *
1129 format_dt (const ECalComponentDateTime *dt,
1130            GHashTable *timezones,
1131            icaltimezone *users_zone)
1132 {
1133 	struct tm tm;
1134 
1135 	g_return_val_if_fail (dt != NULL, NULL);
1136 	g_return_val_if_fail (timezones != NULL, NULL);
1137 
1138 	if (!dt->value)
1139 		return NULL;
1140 
1141 	dt->value->zone = NULL;
1142 	if (dt->tzid) {
1143 		dt->value->zone = g_hash_table_lookup (timezones, dt->tzid);
1144 		if (!dt->value->zone)
1145 			dt->value->zone = icaltimezone_get_builtin_timezone_from_tzid (dt->tzid);
1146 
1147 		if (!dt->value->zone && g_ascii_strcasecmp (dt->tzid, "UTC") == 0)
1148 			dt->value->zone = icaltimezone_get_utc_timezone ();
1149 	}
1150 
1151 	if (dt->value->zone)
1152 		tm = icaltimetype_to_tm_with_zone (dt->value, (icaltimezone *) dt->value->zone, users_zone);
1153 	else
1154 		tm = icaltimetype_to_tm (dt->value);
1155 
1156 	return e_datetime_format_format_tm ("calendar", "table", dt->value->is_date ? DTFormatKindDate : DTFormatKindDateTime, &tm);
1157 }
1158 
1159 static const gchar *
1160 strip_mailto (const gchar *str)
1161 {
1162 	if (!str || g_ascii_strncasecmp (str, "mailto:", 7) != 0)
1163 		return str;
1164 
1165 	return str + 7;
1166 }
1167 
1168 static void
1169 preview_comp (EWebViewPreview *preview,
1170               ECalComponent *comp)
1171 {
1172 	ECalComponentText text = { 0 };
1173 	ECalComponentDateTime dt;
1174 	ECalComponentClassification classif;
1175 	const gchar *str;
1176 	gchar *tmp;
1177 	gint percent;
1178 	gboolean have;
1179 	GHashTable *timezones;
1180 	icaltimezone *users_zone;
1181 	GSList *slist, *l;
1182 
1183 	g_return_if_fail (preview != NULL);
1184 	g_return_if_fail (comp != NULL);
1185 
1186 	timezones = g_object_get_data (G_OBJECT (preview), "iCalImp-timezones");
1187 	users_zone = g_object_get_data (G_OBJECT (preview), "iCalImp-userszone");
1188 
1189 	str = NULL;
1190 	switch (e_cal_component_get_vtype (comp)) {
1191 	case E_CAL_COMPONENT_EVENT:
1192 		str = e_cal_component_has_attendees (comp) ? C_("iCalImp", "Meeting") : C_("iCalImp", "Event");
1193 		break;
1194 	case E_CAL_COMPONENT_TODO:
1195 		str = C_("iCalImp", "Task");
1196 		break;
1197 	case E_CAL_COMPONENT_JOURNAL:
1198 		str = C_("iCalImp", "Memo");
1199 		break;
1200 	default:
1201 		str = "??? Other ???";
1202 		break;
1203 	}
1204 
1205 	have = FALSE;
1206 	if (e_cal_component_has_recurrences (comp)) {
1207 		e_web_view_preview_add_section (preview, have ? NULL : str, C_("iCalImp", "has recurrences"));
1208 		have = TRUE;
1209 	}
1210 
1211 	if (e_cal_component_is_instance (comp)) {
1212 		e_web_view_preview_add_section (preview, have ? NULL : str, C_("iCalImp", "is an instance"));
1213 		have = TRUE;
1214 	}
1215 
1216 	if (e_cal_component_has_alarms (comp)) {
1217 		e_web_view_preview_add_section (preview, have ? NULL : str, C_("iCalImp", "has reminders"));
1218 		have = TRUE;
1219 	}
1220 
1221 	if (e_cal_component_has_attachments (comp)) {
1222 		e_web_view_preview_add_section (preview, have ? NULL : str, C_("iCalImp", "has attachments"));
1223 		have = TRUE;
1224 	}
1225 
1226 	if (!have) {
1227 		e_web_view_preview_add_section (preview, str, "");
1228 	}
1229 
1230 	str = NULL;
1231 	classif = E_CAL_COMPONENT_CLASS_NONE;
1232 	e_cal_component_get_classification (comp, &classif);
1233 	if (classif == E_CAL_COMPONENT_CLASS_PUBLIC) {
1234 		/* Translators: Appointment's classification */
1235 		str = C_("iCalImp", "Public");
1236 	} else if (classif == E_CAL_COMPONENT_CLASS_PRIVATE) {
1237 		/* Translators: Appointment's classification */
1238 		str = C_("iCalImp", "Private");
1239 	} else if (classif == E_CAL_COMPONENT_CLASS_CONFIDENTIAL) {
1240 		/* Translators: Appointment's classification */
1241 		str = C_("iCalImp", "Confidential");
1242 	}
1243 	if (str)
1244 		/* Translators: Appointment's classification section name */
1245 		e_web_view_preview_add_section (preview, C_("iCalImp", "Classification"), str);
1246 
1247 	e_cal_component_get_summary (comp, &text);
1248 	if ((text.value && *text.value) || (text.altrep && *text.altrep))
1249 		/* Translators: Appointment's summary */
1250 		e_web_view_preview_add_section (preview, C_("iCalImp", "Summary"), (text.value && *text.value) ? text.value : text.altrep);
1251 
1252 	str = NULL;
1253 	e_cal_component_get_location (comp, &str);
1254 	if (str && *str)
1255 		/* Translators: Appointment's location */
1256 		e_web_view_preview_add_section (preview, C_("iCalImp", "Location"), str);
1257 
1258 	dt.value = NULL;
1259 	e_cal_component_get_dtstart (comp, &dt);
1260 	if (dt.value) {
1261 		tmp = format_dt (&dt, timezones, users_zone);
1262 		if (tmp)
1263 			/* Translators: Appointment's start time */
1264 			e_web_view_preview_add_section (preview, C_("iCalImp", "Start"), tmp);
1265 		g_free (tmp);
1266 	}
1267 	e_cal_component_free_datetime (&dt);
1268 
1269 	dt.value = NULL;
1270 	e_cal_component_get_due (comp, &dt);
1271 	if (dt.value) {
1272 		tmp = format_dt (&dt, timezones, users_zone);
1273 		if (tmp)
1274 			/* Translators: 'Due' like the time due a task should be finished */
1275 			e_web_view_preview_add_section (preview, C_("iCalImp", "Due"), tmp);
1276 		g_free (tmp);
1277 	} else {
1278 		e_cal_component_free_datetime (&dt);
1279 
1280 		dt.value = NULL;
1281 		e_cal_component_get_dtend (comp, &dt);
1282 		if (dt.value) {
1283 			tmp = format_dt (&dt, timezones, users_zone);
1284 
1285 			if (tmp)
1286 				/* Translators: Appointment's end time */
1287 				e_web_view_preview_add_section (preview, C_("iCalImp", "End"), tmp);
1288 			g_free (tmp);
1289 		}
1290 	}
1291 	e_cal_component_free_datetime (&dt);
1292 
1293 	str = NULL;
1294 	e_cal_component_get_categories (comp, &str);
1295 	if (str && *str)
1296 		/* Translators: Appointment's categories */
1297 		e_web_view_preview_add_section (preview, C_("iCalImp", "Categories"), str);
1298 
1299 	percent = e_cal_component_get_percent_as_int (comp);
1300 	if (percent >= 0) {
1301 		tmp = NULL;
1302 		if (percent == 100) {
1303 			icaltimetype *completed = NULL;
1304 
1305 			e_cal_component_get_completed (comp, &completed);
1306 
1307 			if (completed) {
1308 				dt.tzid = "UTC";
1309 				dt.value = completed;
1310 
1311 				tmp = format_dt (&dt, timezones, users_zone);
1312 
1313 				e_cal_component_free_icaltimetype (completed);
1314 			}
1315 		}
1316 
1317 		if (!tmp)
1318 			tmp = g_strdup_printf ("%d%%", percent);
1319 
1320 		/* Translators: Appointment's complete value (either percentage, or a date/time of a completion) */
1321 		e_web_view_preview_add_section (preview, C_("iCalImp", "Completed"), tmp);
1322 		g_free (tmp);
1323 	}
1324 
1325 	str = NULL;
1326 	e_cal_component_get_url (comp, &str);
1327 	if (str && *str)
1328 		/* Translators: Appointment's URL */
1329 		e_web_view_preview_add_section (preview, C_("iCalImp", "URL"), str);
1330 
1331 	if (e_cal_component_has_organizer (comp)) {
1332 		ECalComponentOrganizer organizer = { 0 };
1333 
1334 		e_cal_component_get_organizer (comp, &organizer);
1335 
1336 		if (organizer.value && *organizer.value) {
1337 			if (organizer.cn && *organizer.cn) {
1338 				tmp = g_strconcat (organizer.cn, " <", strip_mailto (organizer.value), ">", NULL);
1339 				/* Translators: Appointment's organizer */
1340 				e_web_view_preview_add_section (preview, C_("iCalImp", "Organizer"), tmp);
1341 				g_free (tmp);
1342 			} else {
1343 				e_web_view_preview_add_section (preview, C_("iCalImp", "Organizer"), strip_mailto (organizer.value));
1344 			}
1345 		}
1346 	}
1347 
1348 	if (e_cal_component_has_attendees (comp)) {
1349 		GSList *attendees = NULL, *a;
1350 		have = FALSE;
1351 
1352 		e_cal_component_get_attendee_list (comp, &attendees);
1353 
1354 		for (a = attendees; a; a = a->next) {
1355 			ECalComponentAttendee *attnd = a->data;
1356 
1357 			if (!attnd || !attnd->value || !*attnd->value)
1358 				continue;
1359 
1360 			if (attnd->cn && *attnd->cn) {
1361 				tmp = g_strconcat (attnd->cn, " <", strip_mailto (attnd->value), ">", NULL);
1362 				/* Translators: Appointment's attendees */
1363 				e_web_view_preview_add_section (preview, have ? NULL : C_("iCalImp", "Attendees"), tmp);
1364 				g_free (tmp);
1365 			} else {
1366 				e_web_view_preview_add_section (preview, have ? NULL : C_("iCalImp", "Attendees"), strip_mailto (attnd->value));
1367 			}
1368 
1369 			have = TRUE;
1370 		}
1371 
1372 		e_cal_component_free_attendee_list (attendees);
1373 	}
1374 
1375 	slist = NULL;
1376 	e_cal_component_get_description_list (comp, &slist);
1377 	for (l = slist; l; l = l->next) {
1378 		ECalComponentText *txt = l->data;
1379 
1380 		e_web_view_preview_add_section (preview, l != slist ? NULL : C_("iCalImp", "Description"), (txt && txt->value) ? txt->value : "");
1381 	}
1382 
1383 	e_cal_component_free_text_list (slist);
1384 }
1385 
1386 static void
1387 preview_selection_changed_cb (GtkTreeSelection *selection,
1388                               EWebViewPreview *preview)
1389 {
1390 	GtkTreeIter iter;
1391 	GtkTreeModel *model = NULL;
1392 
1393 	g_return_if_fail (selection != NULL);
1394 	g_return_if_fail (preview != NULL);
1395 
1396 	e_web_view_preview_begin_update (preview);
1397 
1398 	if (gtk_tree_selection_get_selected (selection, &model, &iter) && model) {
1399 		ECalComponent *comp = NULL;
1400 
1401 		gtk_tree_model_get (model, &iter, 3, &comp, -1);
1402 
1403 		if (comp) {
1404 			preview_comp (preview, comp);
1405 			g_object_unref (comp);
1406 		}
1407 	}
1408 
1409 	e_web_view_preview_end_update (preview);
1410 }
1411 
1412 static icaltimezone *
1413 get_users_timezone (void)
1414 {
1415 	/* more or less copy&paste of calendar_config_get_icaltimezone */
1416 	EShell *shell;
1417 	EShellSettings *shell_settings;
1418 	icaltimezone *zone = NULL;
1419 	gchar *location;
1420 
1421 	/* FIXME Pass this in. */
1422 	shell = e_shell_get_default ();
1423 	shell_settings = e_shell_get_shell_settings (shell);
1424 
1425 	if (e_shell_settings_get_boolean (shell_settings, "cal-use-system-timezone")) {
1426 		location = e_cal_util_get_system_timezone_location ();
1427 	} else {
1428 		GSettings *settings = g_settings_new ("org.gnome.evolution.calendar");
1429 
1430 		location = g_settings_get_string (settings, "timezone");
1431 
1432 		g_object_unref (settings);
1433 	}
1434 
1435 	if (location) {
1436 		zone = icaltimezone_get_builtin_timezone (location);
1437 
1438 		g_free (location);
1439 	}
1440 
1441 	return zone;
1442 }
1443 
1444 static void
1445 free_zone_cb (gpointer ptr)
1446 {
1447 	icaltimezone *zone = ptr;
1448 
1449 	if (zone)
1450 		icaltimezone_free (zone, 1);
1451 }
1452 
1453 static GtkWidget *
1454 ical_get_preview (icalcomponent *icalcomp)
1455 {
1456 	GtkWidget *preview;
1457 	GtkTreeView *tree_view;
1458 	GtkTreeSelection *selection;
1459 	GtkListStore *store;
1460 	GtkTreeIter iter;
1461 	GHashTable *timezones;
1462 	icalcomponent *subcomp;
1463 	icaltimezone *users_zone;
1464 
1465 	if (!icalcomp || !is_icalcomp_usable (icalcomp))
1466 		return NULL;
1467 
1468 	store = gtk_list_store_new (4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, E_TYPE_CAL_COMPONENT);
1469 
1470 	timezones = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, free_zone_cb);
1471 	users_zone = get_users_timezone ();
1472 
1473 	/* get timezones first */
1474 	for (subcomp = icalcomponent_get_first_component (icalcomp, ICAL_VTIMEZONE_COMPONENT);
1475 	     subcomp;
1476 	     subcomp = icalcomponent_get_next_component (icalcomp,  ICAL_VTIMEZONE_COMPONENT)) {
1477 		icaltimezone *zone = icaltimezone_new ();
1478 		if (!icaltimezone_set_component (zone, icalcomponent_new_clone (subcomp)) || !icaltimezone_get_tzid (zone)) {
1479 			icaltimezone_free (zone, 1);
1480 		} else {
1481 			g_hash_table_insert (timezones, (gchar *) icaltimezone_get_tzid (zone), zone);
1482 		}
1483 	}
1484 
1485 	/* then each component */
1486 	for (subcomp = icalcomponent_get_first_component (icalcomp, ICAL_ANY_COMPONENT);
1487 	     subcomp;
1488 	     subcomp = icalcomponent_get_next_component (icalcomp,  ICAL_ANY_COMPONENT)) {
1489 		icalcomponent_kind kind = icalcomponent_isa (subcomp);
1490 
1491 		if (kind == ICAL_VEVENT_COMPONENT ||
1492 		    kind == ICAL_VTODO_COMPONENT ||
1493 		    kind == ICAL_VJOURNAL_COMPONENT) {
1494 			ECalComponent *comp = e_cal_component_new ();
1495 			ECalComponentText summary = { 0 };
1496 			ECalComponentDateTime dt = { 0 };
1497 			gchar *formatted_dt;
1498 
1499 			if (!e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (subcomp))) {
1500 				g_object_unref (comp);
1501 				continue;
1502 			}
1503 
1504 			e_cal_component_get_summary (comp, &summary);
1505 			e_cal_component_get_dtstart (comp, &dt);
1506 			formatted_dt = format_dt (&dt, timezones, users_zone);
1507 
1508 			gtk_list_store_append (store, &iter);
1509 			gtk_list_store_set (
1510 				store, &iter,
1511 				0, kind == ICAL_VEVENT_COMPONENT ? (e_cal_component_has_attendees (comp) ? C_("iCalImp", "Meeting") : C_("iCalImp", "Event")) :
1512 				kind == ICAL_VTODO_COMPONENT ? C_("iCalImp", "Task") :
1513 				kind == ICAL_VJOURNAL_COMPONENT ? C_("iCalImp", "Memo") : "??? Other ???",
1514 				1, formatted_dt ? formatted_dt : "",
1515 				2, summary.value && *summary.value ? summary.value : summary.altrep && *summary.altrep ? summary.altrep : "",
1516 				3, comp,
1517 				-1);
1518 
1519 			g_free (formatted_dt);
1520 			e_cal_component_free_datetime (&dt);
1521 			g_object_unref (comp);
1522 		}
1523 	}
1524 
1525 	if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter)) {
1526 		g_object_unref (store);
1527 		g_hash_table_destroy (timezones);
1528 		return NULL;
1529 	}
1530 
1531 	preview = e_web_view_preview_new ();
1532 	gtk_widget_show (preview);
1533 
1534 	g_object_set_data_full (G_OBJECT (preview), "iCalImp-timezones", timezones, (GDestroyNotify) g_hash_table_destroy);
1535 	g_object_set_data (G_OBJECT (preview), "iCalImp-userszone", users_zone);
1536 
1537 	tree_view = e_web_view_preview_get_tree_view (E_WEB_VIEW_PREVIEW (preview));
1538 	g_return_val_if_fail (tree_view != NULL, NULL);
1539 
1540 	gtk_tree_view_set_model (tree_view, GTK_TREE_MODEL (store));
1541 	g_object_unref (store);
1542 
1543 	/* Translators: Column header for a component type; it can be Event, Task or Memo */
1544 	gtk_tree_view_insert_column_with_attributes (
1545 		tree_view, -1, C_("iCalImp", "Type"),
1546 		gtk_cell_renderer_text_new (), "text", 0, NULL);
1547 
1548 	/* Translators: Column header for a component start date/time */
1549 	gtk_tree_view_insert_column_with_attributes (
1550 		tree_view, -1, C_("iCalImp", "Start"),
1551 		gtk_cell_renderer_text_new (), "text", 1, NULL);
1552 
1553 	/* Translators: Column header for a component summary */
1554 	gtk_tree_view_insert_column_with_attributes (
1555 		tree_view, -1, C_("iCalImp", "Summary"),
1556 		gtk_cell_renderer_text_new (), "text", 2, NULL);
1557 
1558 	if (gtk_tree_model_iter_n_children (GTK_TREE_MODEL (store), NULL) > 1)
1559 		e_web_view_preview_show_tree_view (E_WEB_VIEW_PREVIEW (preview));
1560 
1561 	selection = gtk_tree_view_get_selection (tree_view);
1562 	gtk_tree_selection_select_iter (selection, &iter);
1563 	g_signal_connect (
1564 		selection, "changed",
1565 		G_CALLBACK (preview_selection_changed_cb), preview);
1566 
1567 	preview_selection_changed_cb (selection, E_WEB_VIEW_PREVIEW (preview));
1568 
1569 	return preview;
1570 }