evolution-3.6.4/addressbook/importers/evolution-vcard-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  *		Chris Toshok <toshok@ximian.com>
  20  *      JP Rosevear <jpr@ximian.com>
  21  *	    Michael Zucchi <notzed@ximian.com>
  22  *
  23  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
  24  *
  25  */
  26 
  27 #ifdef HAVE_CONFIG_H
  28 #include <config.h>
  29 #endif
  30 
  31 #include <stdio.h>
  32 #include <string.h>
  33 
  34 #include <gtk/gtk.h>
  35 #include <glib/gi18n.h>
  36 #include <glib/gstdio.h>
  37 
  38 #include <libebook/libebook.h>
  39 #include <libedataserverui/libedataserverui.h>
  40 
  41 #include <util/eab-book-util.h>
  42 
  43 #include <shell/e-shell.h>
  44 #include <e-util/e-import.h>
  45 #include <e-util/e-datetime-format.h>
  46 #include <misc/e-web-view-preview.h>
  47 
  48 #include "evolution-addressbook-importers.h"
  49 
  50 enum _VCardEncoding {
  51 	VCARD_ENCODING_NONE,
  52 	VCARD_ENCODING_UTF8,
  53 	VCARD_ENCODING_UTF16,
  54 	VCARD_ENCODING_LOCALE
  55 };
  56 
  57 typedef enum _VCardEncoding VCardEncoding;
  58 
  59 typedef struct {
  60 	EImport *import;
  61 	EImportTarget *target;
  62 
  63 	guint idle_id;
  64 
  65 	gint state;		/* 0 - importing, 1 - cancelled/complete */
  66 	gint total;
  67 	gint count;
  68 
  69 	ESource *primary;
  70 
  71 	GSList *contactlist;
  72 	GSList *iterator;
  73 	EBookClient *book_client;
  74 
  75 	/* when opening book */
  76 	gchar *contents;
  77 	VCardEncoding encoding;
  78 } VCardImporter;
  79 
  80 static void vcard_import_done (VCardImporter *gci);
  81 
  82 static void
  83 add_to_notes (EContact *contact,
  84               EContactField field)
  85 {
  86 	const gchar *old_text;
  87 	const gchar *field_text;
  88 	gchar       *new_text;
  89 
  90 	old_text = e_contact_get_const (contact, E_CONTACT_NOTE);
  91 	if (old_text && strstr (old_text, e_contact_pretty_name (field)))
  92 		return;
  93 
  94 	field_text = e_contact_get_const (contact, field);
  95 	if (!field_text || !*field_text)
  96 		return;
  97 
  98 	new_text = g_strdup_printf (
  99 		"%s%s%s: %s",
 100 		old_text ? old_text : "",
 101 		old_text && *old_text &&
 102 		*(old_text + strlen (old_text) - 1) != '\n' ? "\n" : "",
 103 		e_contact_pretty_name (field), field_text);
 104 	e_contact_set (contact, E_CONTACT_NOTE, new_text);
 105 	g_free (new_text);
 106 }
 107 
 108 static void
 109 vcard_import_contact (VCardImporter *gci,
 110                       EContact *contact)
 111 {
 112 	EContactPhoto *photo;
 113 	GList *attrs, *attr;
 114 	gchar *uid = NULL;
 115 
 116 	/* Apple's addressbook.app exports PHOTO's without a TYPE
 117 	 * param, so let's figure out the format here if there's a
 118 	 * PHOTO attribute missing a TYPE param.
 119 	 *
 120 	 * this is sort of a hack, as EContact sets the type for us if
 121 	 * we use the setter.  so let's e_contact_get + e_contact_set
 122 	 * on E_CONTACT_PHOTO.
 123 	*/
 124 	photo = e_contact_get (contact, E_CONTACT_PHOTO);
 125 	if (photo) {
 126 		e_contact_set (contact, E_CONTACT_PHOTO, photo);
 127 		e_contact_photo_free (photo);
 128 	}
 129 
 130 	/* Deal with our XML EDestination stuff in EMAIL attributes, if there is any. */
 131 	attrs = e_contact_get_attributes (contact, E_CONTACT_EMAIL);
 132 	for (attr = attrs; attr; attr = attr->next) {
 133 		EVCardAttribute *a = attr->data;
 134 		GList *v = e_vcard_attribute_get_values (a);
 135 
 136 		if (v && v->data) {
 137 			if (!strncmp ((gchar *) v->data, "<?xml", 5)) {
 138 				EDestination *dest = e_destination_import ((gchar *) v->data);
 139 
 140 				e_destination_export_to_vcard_attribute (dest, a);
 141 
 142 				g_object_unref (dest);
 143 
 144 			}
 145 		}
 146 	}
 147 	e_contact_set_attributes (contact, E_CONTACT_EMAIL, attrs);
 148 
 149 	/* Deal with TEL attributes that don't conform to what we need.
 150 	 *
 151 	 * 1. if there's no location (HOME/WORK/OTHER), default to OTHER.
 152 	 * 2. if there's *only* a location specified, default to VOICE.
 153 	 */
 154 	attrs = e_vcard_get_attributes (E_VCARD (contact));
 155 	for (attr = attrs; attr; attr = attr->next) {
 156 		EVCardAttribute *a = attr->data;
 157 		gboolean location_only = TRUE;
 158 		gboolean no_location = TRUE;
 159 		gboolean is_work_home = FALSE;
 160 		GList *params, *param;
 161 
 162 		if (g_ascii_strcasecmp (e_vcard_attribute_get_name (a),
 163 					EVC_TEL))
 164 			continue;
 165 
 166 		params = e_vcard_attribute_get_params (a);
 167 		for (param = params; param; param = param->next) {
 168 			EVCardAttributeParam *p = param->data;
 169 			GList *vs, *v;
 170 
 171 			if (g_ascii_strcasecmp (e_vcard_attribute_param_get_name (p),
 172 						EVC_TYPE))
 173 				continue;
 174 
 175 			vs = e_vcard_attribute_param_get_values (p);
 176 			for (v = vs; v; v = v->next) {
 177 				is_work_home = is_work_home ||
 178 					!g_ascii_strcasecmp ((gchar *) v->data, "WORK") ||
 179 					!g_ascii_strcasecmp ((gchar *) v->data, "HOME");
 180 
 181 				if (!g_ascii_strcasecmp ((gchar *) v->data, "WORK") ||
 182 				    !g_ascii_strcasecmp ((gchar *) v->data, "HOME") ||
 183 				    !g_ascii_strcasecmp ((gchar *) v->data, "OTHER"))
 184 					no_location = FALSE;
 185 				else
 186 					location_only = FALSE;
 187 			}
 188 		}
 189 
 190 		if (is_work_home) {
 191 			/* only WORK and HOME phone numbers require locations,
 192 			 * the rest should be kept as is */
 193 			if (location_only) {
 194 				/* add VOICE */
 195 				e_vcard_attribute_add_param_with_value (
 196 					a,
 197 					e_vcard_attribute_param_new (EVC_TYPE),
 198 					"VOICE");
 199 			}
 200 			if (no_location) {
 201 				/* add OTHER */
 202 				e_vcard_attribute_add_param_with_value (
 203 					a,
 204 					e_vcard_attribute_param_new (EVC_TYPE),
 205 					"OTHER");
 206 			}
 207 		}
 208 	}
 209 
 210 	/* Deal with ADR and EMAIL attributes that don't conform to what
 211 	 * we need.  If HOME or WORK isn't specified, add TYPE=OTHER. */
 212 	attrs = e_vcard_get_attributes (E_VCARD (contact));
 213 	for (attr = attrs; attr; attr = attr->next) {
 214 		EVCardAttribute *a = attr->data;
 215 		gboolean no_location = TRUE;
 216 		GList *params, *param;
 217 
 218 		if (g_ascii_strcasecmp (e_vcard_attribute_get_name (a), EVC_ADR) &&
 219 		    g_ascii_strcasecmp (e_vcard_attribute_get_name (a), EVC_EMAIL))
 220 			continue;
 221 
 222 		params = e_vcard_attribute_get_params (a);
 223 		for (param = params; param; param = param->next) {
 224 			EVCardAttributeParam *p = param->data;
 225 			GList *vs, *v;
 226 
 227 			if (g_ascii_strcasecmp (e_vcard_attribute_param_get_name (p),
 228 						EVC_TYPE))
 229 				continue;
 230 
 231 			vs = e_vcard_attribute_param_get_values (p);
 232 			for (v = vs; v; v = v->next) {
 233 				if (!g_ascii_strcasecmp ((gchar *) v->data, "WORK") ||
 234 				    !g_ascii_strcasecmp ((gchar *) v->data, "HOME"))
 235 					no_location = FALSE;
 236 			}
 237 		}
 238 
 239 		if (no_location) {
 240 			/* add OTHER */
 241 			e_vcard_attribute_add_param_with_value (
 242 				a,
 243 								e_vcard_attribute_param_new (EVC_TYPE),
 244 								"OTHER");
 245 		}
 246 	}
 247 
 248 	/* Work around the fact that these fields no longer show up in the UI */
 249 	add_to_notes (contact, E_CONTACT_OFFICE);
 250 	add_to_notes (contact, E_CONTACT_SPOUSE);
 251 	add_to_notes (contact, E_CONTACT_BLOG_URL);
 252 
 253 	/* FIXME Error checking */
 254 	if (e_book_client_add_contact_sync (gci->book_client, contact, &uid, NULL, NULL) && uid) {
 255 		e_contact_set (contact, E_CONTACT_UID, uid);
 256 		g_free (uid);
 257 	}
 258 }
 259 
 260 static gboolean
 261 vcard_import_contacts (gpointer data)
 262 {
 263 	VCardImporter *gci = data;
 264 	gint count = 0;
 265 	GSList *iterator = gci->iterator;
 266 
 267 	if (gci->state == 0) {
 268 		while (count < 50 && iterator) {
 269 			vcard_import_contact (gci, iterator->data);
 270 			count++;
 271 			iterator = iterator->next;
 272 		}
 273 		gci->count += count;
 274 		gci->iterator = iterator;
 275 		if (iterator == NULL)
 276 			gci->state = 1;
 277 	}
 278 	if (gci->state == 1) {
 279 		vcard_import_done (gci);
 280 		return FALSE;
 281 	} else {
 282 		e_import_status (
 283 			gci->import, gci->target, _("Importing..."),
 284 			gci->count * 100 / gci->total);
 285 		return TRUE;
 286 	}
 287 }
 288 
 289 #define BOM (gunichar2)0xFEFF
 290 #define ANTIBOM (gunichar2)0xFFFE
 291 
 292 static gboolean
 293 has_bom (const gunichar2 *utf16)
 294 {
 295 
 296 	if ((utf16 == NULL) || (*utf16 == '\0')) {
 297 		return FALSE;
 298 	}
 299 
 300 	return ((*utf16 == BOM) || (*utf16 == ANTIBOM));
 301 }
 302 
 303 static void
 304 fix_utf16_endianness (gunichar2 *utf16)
 305 {
 306 	gunichar2 *it;
 307 
 308 	if ((utf16 == NULL) || (*utf16 == '\0')) {
 309 		return;
 310 	}
 311 
 312 	if (*utf16 != ANTIBOM) {
 313 		return;
 314 	}
 315 
 316 	for (it = utf16; *it != '\0'; it++) {
 317 		*it = GUINT16_SWAP_LE_BE (*it);
 318 	}
 319 }
 320 
 321 /* Converts an UTF-16 string to an UTF-8 string removing the BOM character
 322  * WARNING: this may modify the utf16 argument if the function detects the
 323  * string isn't using the local endianness
 324  */
 325 static gchar *
 326 utf16_to_utf8 (gunichar2 *utf16)
 327 {
 328 
 329 	if (utf16 == NULL) {
 330 		return NULL;
 331 	}
 332 
 333 	fix_utf16_endianness (utf16);
 334 
 335 	if (*utf16 == BOM) {
 336 		utf16++;
 337 	}
 338 
 339 	return g_utf16_to_utf8 (utf16, -1, NULL, NULL, NULL);
 340 }
 341 
 342 /* Actually check the contents of this file */
 343 static VCardEncoding
 344 guess_vcard_encoding (const gchar *filename)
 345 {
 346 	FILE *handle;
 347 	gchar line[4096];
 348 	gchar *line_utf8;
 349 	VCardEncoding encoding = VCARD_ENCODING_NONE;
 350 
 351 	handle = g_fopen (filename, "r");
 352 	if (handle == NULL) {
 353 		g_print ("\n");
 354 		return VCARD_ENCODING_NONE;
 355 	}
 356 
 357 	if (fgets (line, 4096, handle) == NULL) {
 358 		fclose (handle);
 359 		g_print ("\n");
 360 		return VCARD_ENCODING_NONE;
 361 	}
 362 	fclose (handle);
 363 
 364 	if (has_bom ((gunichar2 *) line)) {
 365 		gunichar2 *utf16 = (gunichar2 *) line;
 366 		/* Check for a BOM to try to detect UTF-16 encoded vcards
 367 		 * (MacOSX address book creates such vcards for example)
 368 		 */
 369 		line_utf8 = utf16_to_utf8 (utf16);
 370 		if (line_utf8 == NULL) {
 371 			return VCARD_ENCODING_NONE;
 372 		}
 373 		encoding = VCARD_ENCODING_UTF16;
 374 	} else if (g_utf8_validate (line, -1, NULL)) {
 375 		line_utf8 = g_strdup (line);
 376 		encoding = VCARD_ENCODING_UTF8;
 377 	} else {
 378 		line_utf8 = g_locale_to_utf8 (line, -1, NULL, NULL, NULL);
 379 		if (line_utf8 == NULL) {
 380 			return VCARD_ENCODING_NONE;
 381 		}
 382 		encoding = VCARD_ENCODING_LOCALE;
 383 	}
 384 
 385 	if (g_ascii_strncasecmp (line_utf8, "BEGIN:VCARD", 11) != 0) {
 386 		encoding = VCARD_ENCODING_NONE;
 387 	}
 388 
 389 	g_free (line_utf8);
 390 	return encoding;
 391 }
 392 
 393 static void
 394 primary_selection_changed_cb (ESourceSelector *selector,
 395                               EImportTarget *target)
 396 {
 397 	ESource *source;
 398 
 399 	source = e_source_selector_ref_primary_selection (selector);
 400 	g_return_if_fail (source != NULL);
 401 
 402 	g_datalist_set_data_full (
 403 		&target->data, "vcard-source",
 404 		source, (GDestroyNotify) g_object_unref);
 405 }
 406 
 407 static GtkWidget *
 408 vcard_getwidget (EImport *ei,
 409                  EImportTarget *target,
 410                  EImportImporter *im)
 411 {
 412 	EShell *shell;
 413 	GtkWidget *vbox, *selector;
 414 	ESourceRegistry *registry;
 415 	ESource *primary;
 416 	const gchar *extension_name;
 417 
 418 	vbox = gtk_vbox_new (FALSE, FALSE);
 419 
 420 	shell = e_shell_get_default ();
 421 	registry = e_shell_get_registry (shell);
 422 	extension_name = E_SOURCE_EXTENSION_ADDRESS_BOOK;
 423 	selector = e_source_selector_new (registry, extension_name);
 424 	e_source_selector_set_show_toggles (
 425 		E_SOURCE_SELECTOR (selector), FALSE);
 426 	gtk_box_pack_start (GTK_BOX (vbox), selector, FALSE, TRUE, 6);
 427 
 428 	primary = g_datalist_get_data (&target->data, "vcard-source");
 429 	if (primary == NULL) {
 430 		GList *list;
 431 
 432 		list = e_source_registry_list_sources (registry, extension_name);
 433 		if (list != NULL) {
 434 			primary = g_object_ref (list->data);
 435 			g_datalist_set_data_full (
 436 				&target->data, "vcard-source", primary,
 437 				(GDestroyNotify) g_object_unref);
 438 		}
 439 
 440 		g_list_free_full (list, (GDestroyNotify) g_object_unref);
 441 	}
 442 	e_source_selector_set_primary_selection (
 443 		E_SOURCE_SELECTOR (selector), primary);
 444 
 445 	g_signal_connect (
 446 		selector, "primary_selection_changed",
 447 		G_CALLBACK (primary_selection_changed_cb), target);
 448 
 449 	gtk_widget_show_all (vbox);
 450 
 451 	return vbox;
 452 }
 453 
 454 static gboolean
 455 vcard_supported (EImport *ei,
 456                  EImportTarget *target,
 457                  EImportImporter *im)
 458 {
 459 	EImportTargetURI *s;
 460 	gchar *filename;
 461 	gboolean retval;
 462 
 463 	if (target->type != E_IMPORT_TARGET_URI)
 464 		return FALSE;
 465 
 466 	s = (EImportTargetURI *) target;
 467 	if (s->uri_src == NULL)
 468 		return TRUE;
 469 
 470 	if (strncmp (s->uri_src, "file:///", 8) != 0)
 471 		return FALSE;
 472 
 473 	filename = g_filename_from_uri (s->uri_src, NULL, NULL);
 474 	if (filename == NULL)
 475 		return FALSE;
 476 	retval = (guess_vcard_encoding (filename) != VCARD_ENCODING_NONE);
 477 	g_free (filename);
 478 
 479 	return retval;
 480 }
 481 
 482 static void
 483 vcard_import_done (VCardImporter *gci)
 484 {
 485 	if (gci->idle_id)
 486 		g_source_remove (gci->idle_id);
 487 
 488 	g_free (gci->contents);
 489 	g_object_unref (gci->book_client);
 490 	e_client_util_free_object_slist (gci->contactlist);
 491 
 492 	e_import_complete (gci->import, gci->target);
 493 	g_object_unref (gci->import);
 494 	g_free (gci);
 495 }
 496 
 497 static void
 498 book_loaded_cb (GObject *source_object,
 499                 GAsyncResult *result,
 500                 gpointer user_data)
 501 {
 502 	ESource *source = E_SOURCE (source_object);
 503 	VCardImporter *gci = user_data;
 504 	EClient *client = NULL;
 505 
 506 	e_client_utils_open_new_finish (source, result, &client, NULL);
 507 
 508 	if (client == NULL) {
 509 		vcard_import_done (gci);
 510 		return;
 511 	}
 512 
 513 	gci->book_client = E_BOOK_CLIENT (client);
 514 
 515 	if (gci->encoding == VCARD_ENCODING_UTF16) {
 516 		gchar *tmp;
 517 
 518 		gunichar2 *contents_utf16 = (gunichar2 *) gci->contents;
 519 		tmp = utf16_to_utf8 (contents_utf16);
 520 		g_free (gci->contents);
 521 		gci->contents = tmp;
 522 
 523 	} else if (gci->encoding == VCARD_ENCODING_LOCALE) {
 524 		gchar *tmp;
 525 		tmp = g_locale_to_utf8 (gci->contents, -1, NULL, NULL, NULL);
 526 		g_free (gci->contents);
 527 		gci->contents = tmp;
 528 	}
 529 
 530 	gci->contactlist = eab_contact_list_from_string (gci->contents);
 531 	g_free (gci->contents);
 532 	gci->contents = NULL;
 533 	gci->iterator = gci->contactlist;
 534 	gci->total = g_slist_length (gci->contactlist);
 535 
 536 	if (gci->iterator)
 537 		gci->idle_id = g_idle_add (vcard_import_contacts, gci);
 538 	else
 539 		vcard_import_done (gci);
 540 }
 541 
 542 static void
 543 vcard_import (EImport *ei,
 544               EImportTarget *target,
 545               EImportImporter *im)
 546 {
 547 	VCardImporter *gci;
 548 	ESource *source;
 549 	EImportTargetURI *s = (EImportTargetURI *) target;
 550 	gchar *filename;
 551 	gchar *contents;
 552 	VCardEncoding encoding;
 553 
 554 	filename = g_filename_from_uri (s->uri_src, NULL, NULL);
 555 	if (filename == NULL) {
 556 		g_message (G_STRLOC ": Couldn't get filename from URI '%s'", s->uri_src);
 557 		e_import_complete (ei, target);
 558 		return;
 559 	}
 560 	encoding = guess_vcard_encoding (filename);
 561 	if (encoding == VCARD_ENCODING_NONE) {
 562 		g_free (filename);
 563 		/* This check is superfluous, we've already
 564 		 * checked otherwise we can't get here ... */
 565 		e_import_complete (ei, target);
 566 		return;
 567 	}
 568 
 569 	if (!g_file_get_contents (filename, &contents, NULL, NULL)) {
 570 		g_message (G_STRLOC ":Couldn't read file.");
 571 		g_free (filename);
 572 		e_import_complete (ei, target);
 573 		return;
 574 	}
 575 
 576 	g_free (filename);
 577 	gci = g_malloc0 (sizeof (*gci));
 578 	g_datalist_set_data (&target->data, "vcard-data", gci);
 579 	gci->import = g_object_ref (ei);
 580 	gci->target = target;
 581 	gci->encoding = encoding;
 582 	gci->contents = contents;
 583 
 584 	source = g_datalist_get_data (&target->data, "vcard-source");
 585 
 586 	e_client_utils_open_new (
 587 		source, E_CLIENT_SOURCE_TYPE_CONTACTS, FALSE, NULL,
 588 		book_loaded_cb, gci);
 589 }
 590 
 591 static void
 592 vcard_cancel (EImport *ei,
 593               EImportTarget *target,
 594               EImportImporter *im)
 595 {
 596 	VCardImporter *gci = g_datalist_get_data (&target->data, "vcard-data");
 597 
 598 	if (gci)
 599 		gci->state = 1;
 600 }
 601 
 602 static GtkWidget *
 603 vcard_get_preview (EImport *ei,
 604                    EImportTarget *target,
 605                    EImportImporter *im)
 606 {
 607 	GtkWidget *preview;
 608 	GSList *contacts;
 609 	gchar *contents;
 610 	VCardEncoding encoding;
 611 	EImportTargetURI *s = (EImportTargetURI *) target;
 612 	gchar *filename;
 613 
 614 	filename = g_filename_from_uri (s->uri_src, NULL, NULL);
 615 	if (filename == NULL) {
 616 		g_message (G_STRLOC ": Couldn't get filename from URI '%s'", s->uri_src);
 617 		return NULL;
 618 	}
 619 
 620 	encoding = guess_vcard_encoding (filename);
 621 	if (encoding == VCARD_ENCODING_NONE) {
 622 		g_free (filename);
 623 		return NULL;
 624 	}
 625 
 626 	if (!g_file_get_contents (filename, &contents, NULL, NULL)) {
 627 		g_message (G_STRLOC ": Couldn't read file.");
 628 		g_free (filename);
 629 		return NULL;
 630 	}
 631 
 632 	g_free (filename);
 633 
 634 	if (encoding == VCARD_ENCODING_UTF16) {
 635 		gchar *tmp;
 636 
 637 		gunichar2 *contents_utf16 = (gunichar2 *) contents;
 638 		tmp = utf16_to_utf8 (contents_utf16);
 639 		g_free (contents);
 640 		contents = tmp;
 641 	} else if (encoding == VCARD_ENCODING_LOCALE) {
 642 		gchar *tmp;
 643 		tmp = g_locale_to_utf8 (contents, -1, NULL, NULL, NULL);
 644 		g_free (contents);
 645 		contents = tmp;
 646 	}
 647 
 648 	contacts = eab_contact_list_from_string (contents);
 649 	g_free (contents);
 650 
 651 	preview = evolution_contact_importer_get_preview_widget (contacts);
 652 
 653 	e_client_util_free_object_slist (contacts);
 654 
 655 	return preview;
 656 }
 657 
 658 static EImportImporter vcard_importer = {
 659 	E_IMPORT_TARGET_URI,
 660 	0,
 661 	vcard_supported,
 662 	vcard_getwidget,
 663 	vcard_import,
 664 	vcard_cancel,
 665 	vcard_get_preview,
 666 };
 667 
 668 EImportImporter *
 669 evolution_vcard_importer_peek (void)
 670 {
 671 	vcard_importer.name = _("vCard (.vcf, .gcrd)");
 672 	vcard_importer.description = _("Evolution vCard Importer");
 673 
 674 	return &vcard_importer;
 675 }
 676 
 677 /* utility functions shared between all contact importers */
 678 static void
 679 preview_contact (EWebViewPreview *preview,
 680                  EContact *contact)
 681 {
 682 	gint idx;
 683 	gboolean had_value = FALSE;
 684 
 685 	const gint fields[] = {
 686 		E_CONTACT_FILE_AS,
 687 		E_CONTACT_CATEGORIES,
 688 
 689 		E_CONTACT_IS_LIST,
 690 		E_CONTACT_LIST_SHOW_ADDRESSES,
 691 		E_CONTACT_WANTS_HTML,
 692 
 693 		E_CONTACT_FULL_NAME,
 694 		E_CONTACT_GIVEN_NAME,
 695 		E_CONTACT_FAMILY_NAME,
 696 		E_CONTACT_NICKNAME,
 697 		E_CONTACT_SPOUSE,
 698 		E_CONTACT_BIRTH_DATE,
 699 		E_CONTACT_ANNIVERSARY,
 700 		E_CONTACT_MAILER,
 701 		E_CONTACT_EMAIL,
 702 
 703 		-1,
 704 
 705 		E_CONTACT_ORG,
 706 		E_CONTACT_ORG_UNIT,
 707 		E_CONTACT_OFFICE,
 708 		E_CONTACT_TITLE,
 709 		E_CONTACT_ROLE,
 710 		E_CONTACT_MANAGER,
 711 		E_CONTACT_ASSISTANT,
 712 
 713 		-1,
 714 
 715 		E_CONTACT_PHONE_ASSISTANT,
 716 		E_CONTACT_PHONE_BUSINESS,
 717 		E_CONTACT_PHONE_BUSINESS_2,
 718 		E_CONTACT_PHONE_BUSINESS_FAX,
 719 		E_CONTACT_PHONE_CALLBACK,
 720 		E_CONTACT_PHONE_CAR,
 721 		E_CONTACT_PHONE_COMPANY,
 722 		E_CONTACT_PHONE_HOME,
 723 		E_CONTACT_PHONE_HOME_2,
 724 		E_CONTACT_PHONE_HOME_FAX,
 725 		E_CONTACT_PHONE_ISDN,
 726 		E_CONTACT_PHONE_MOBILE,
 727 		E_CONTACT_PHONE_OTHER,
 728 		E_CONTACT_PHONE_OTHER_FAX,
 729 		E_CONTACT_PHONE_PAGER,
 730 		E_CONTACT_PHONE_PRIMARY,
 731 		E_CONTACT_PHONE_RADIO,
 732 		E_CONTACT_PHONE_TELEX,
 733 		E_CONTACT_PHONE_TTYTDD,
 734 
 735 		-1,
 736 
 737 		E_CONTACT_ADDRESS_HOME,
 738 		E_CONTACT_ADDRESS_WORK,
 739 		E_CONTACT_ADDRESS_OTHER,
 740 
 741 		-1,
 742 
 743 		E_CONTACT_HOMEPAGE_URL,
 744 		E_CONTACT_BLOG_URL,
 745 		E_CONTACT_CALENDAR_URI,
 746 		E_CONTACT_FREEBUSY_URL,
 747 		E_CONTACT_ICS_CALENDAR,
 748 		E_CONTACT_VIDEO_URL,
 749 
 750 		-1,
 751 
 752 		E_CONTACT_IM_AIM,
 753 		E_CONTACT_IM_GROUPWISE,
 754 		E_CONTACT_IM_JABBER,
 755 		E_CONTACT_IM_YAHOO,
 756 		E_CONTACT_IM_MSN,
 757 		E_CONTACT_IM_ICQ,
 758 		E_CONTACT_IM_GADUGADU,
 759 		E_CONTACT_IM_SKYPE,
 760 		E_CONTACT_IM_TWITTER,
 761 
 762 		-1,
 763 
 764 		E_CONTACT_NOTE
 765 	};
 766 
 767 	g_return_if_fail (preview != NULL);
 768 	g_return_if_fail (contact != NULL);
 769 
 770 	for (idx = 0; idx < G_N_ELEMENTS (fields); idx++) {
 771 		EContactField field;
 772 
 773 		if (fields[idx] == -1) {
 774 			if (had_value)
 775 				e_web_view_preview_add_empty_line (preview);
 776 			had_value = FALSE;
 777 			continue;
 778 		}
 779 
 780 		field = fields[idx];
 781 
 782 		if (field == E_CONTACT_BIRTH_DATE || field == E_CONTACT_ANNIVERSARY) {
 783 			EContactDate *dt = e_contact_get (contact, field);
 784 			if (dt) {
 785 				GDate gd = { 0 };
 786 				struct tm tm;
 787 				gchar *value;
 788 
 789 				g_date_set_dmy (&gd, dt->day, dt->month, dt->year);
 790 				g_date_to_struct_tm (&gd, &tm);
 791 
 792 				value = e_datetime_format_format_tm (
 793 					"addressbook", "table",
 794 					DTFormatKindDate, &tm);
 795 				if (value) {
 796 					e_web_view_preview_add_section (
 797 						preview,
 798 						e_contact_pretty_name (field),
 799 						value);
 800 					had_value = TRUE;
 801 				}
 802 
 803 				g_free (value);
 804 				e_contact_date_free (dt);
 805 			}
 806 		} else if (field == E_CONTACT_IS_LIST ||
 807 			   field == E_CONTACT_WANTS_HTML ||
 808 			   field == E_CONTACT_LIST_SHOW_ADDRESSES) {
 809 			if (e_contact_get (contact, field)) {
 810 				e_web_view_preview_add_text (
 811 					preview, e_contact_pretty_name (field));
 812 				had_value = TRUE;
 813 			}
 814 		} else if (field == E_CONTACT_ADDRESS_HOME ||
 815 			   field == E_CONTACT_ADDRESS_WORK ||
 816 			   field == E_CONTACT_ADDRESS_OTHER) {
 817 			EContactAddress *addr = e_contact_get (contact, field);
 818 			if (addr) {
 819 				gboolean have = FALSE;
 820 
 821 				#define add_it(_what)	\
 822 					if (addr->_what && *addr->_what) {	\
 823 						e_web_view_preview_add_section ( \
 824 							preview, have ? NULL : \
 825 							e_contact_pretty_name (field), addr->_what);	\
 826 						have = TRUE;	\
 827 						had_value = TRUE;	\
 828 					}
 829 
 830 				add_it (po);
 831 				add_it (ext);
 832 				add_it (street);
 833 				add_it (locality);
 834 				add_it (region);
 835 				add_it (code);
 836 				add_it (country);
 837 
 838 				#undef add_it
 839 
 840 				e_contact_address_free (addr);
 841 			}
 842 		} else if (field == E_CONTACT_IM_AIM ||
 843 			   field == E_CONTACT_IM_GROUPWISE ||
 844 			   field == E_CONTACT_IM_JABBER ||
 845 			   field == E_CONTACT_IM_YAHOO ||
 846 			   field == E_CONTACT_IM_MSN ||
 847 			   field == E_CONTACT_IM_ICQ ||
 848 			   field == E_CONTACT_IM_GADUGADU ||
 849 			   field == E_CONTACT_IM_SKYPE ||
 850 			   field == E_CONTACT_IM_TWITTER ||
 851 			   field == E_CONTACT_EMAIL) {
 852 			GList *attrs, *a;
 853 			gboolean have = FALSE;
 854 			const gchar *pretty_name;
 855 
 856 			pretty_name = e_contact_pretty_name (field);
 857 
 858 			attrs = e_contact_get_attributes (contact, field);
 859 			for (a = attrs; a; a = a->next) {
 860 				EVCardAttribute *attr = a->data;
 861 				GList *value;
 862 
 863 				if (!attr)
 864 					continue;
 865 
 866 				value = e_vcard_attribute_get_values (attr);
 867 
 868 				while (value != NULL) {
 869 					const gchar *str = value->data;
 870 
 871 					if (str && *str) {
 872 						e_web_view_preview_add_section (
 873 							preview, have ? NULL :
 874 							pretty_name, str);
 875 						have = TRUE;
 876 						had_value = TRUE;
 877 					}
 878 
 879 					value = value->next;
 880 				}
 881 
 882 				e_vcard_attribute_free (attr);
 883 			}
 884 
 885 			g_list_free (attrs);
 886 
 887 		} else if (field == E_CONTACT_CATEGORIES) {
 888 			const gchar *pretty_name;
 889 			const gchar *value;
 890 
 891 			pretty_name = e_contact_pretty_name (field);
 892 			value = e_contact_get_const (contact, field);
 893 
 894 			if (value != NULL && *value != '\0') {
 895 				e_web_view_preview_add_section (
 896 					preview, pretty_name, value);
 897 				had_value = TRUE;
 898 			}
 899 
 900 		} else {
 901 			const gchar *pretty_name;
 902 			const gchar *value;
 903 
 904 			pretty_name = e_contact_pretty_name (field);
 905 			value = e_contact_get_const (contact, field);
 906 
 907 			if (value != NULL && *value != '\0') {
 908 				e_web_view_preview_add_section (
 909 					preview, pretty_name, value);
 910 				had_value = TRUE;
 911 			}
 912 		}
 913 	}
 914 }
 915 
 916 static void
 917 preview_selection_changed_cb (GtkTreeSelection *selection,
 918                               EWebViewPreview *preview)
 919 {
 920 	GtkTreeIter iter;
 921 	GtkTreeModel *model = NULL;
 922 
 923 	g_return_if_fail (selection != NULL);
 924 	g_return_if_fail (preview != NULL);
 925 
 926 	e_web_view_preview_begin_update (preview);
 927 
 928 	if (gtk_tree_selection_get_selected (selection, &model, &iter) && model) {
 929 		EContact *contact = NULL;
 930 
 931 		gtk_tree_model_get (model, &iter, 1, &contact, -1);
 932 
 933 		if (contact) {
 934 			preview_contact (preview, contact);
 935 			g_object_unref (contact);
 936 		}
 937 	}
 938 
 939 	e_web_view_preview_end_update (preview);
 940 }
 941 
 942 GtkWidget *
 943 evolution_contact_importer_get_preview_widget (const GSList *contacts)
 944 {
 945 	GtkWidget *preview;
 946 	GtkTreeView *tree_view;
 947 	GtkTreeSelection *selection;
 948 	GtkListStore *store;
 949 	GtkTreeIter iter;
 950 	const GSList *c;
 951 
 952 	if (!contacts)
 953 		return NULL;
 954 
 955 	store = gtk_list_store_new (2, G_TYPE_STRING, E_TYPE_CONTACT);
 956 
 957 	for (c = contacts; c; c = c->next) {
 958 		const gchar *description;
 959 		gchar *free_description = NULL;
 960 		EContact *contact = (EContact *) c->data;
 961 
 962 		if (!contact || !E_IS_CONTACT (contact))
 963 			continue;
 964 
 965 		description = e_contact_get_const (contact, E_CONTACT_FILE_AS);
 966 		if (!description)
 967 			description = e_contact_get_const (contact, E_CONTACT_UID);
 968 		if (!description)
 969 			description = e_contact_get_const (contact, E_CONTACT_FULL_NAME);
 970 		if (!description) {
 971 			description = e_contact_get_const (contact, E_CONTACT_EMAIL_1);
 972 			if (description) {
 973 				const gchar *at = strchr (description, '@');
 974 				if (at) {
 975 					free_description = g_strndup (
 976 						description,
 977 						(gsize) (at - description));
 978 					description = free_description;
 979 				}
 980 			}
 981 		}
 982 
 983 		gtk_list_store_append (store, &iter);
 984 		gtk_list_store_set (
 985 			store, &iter,
 986 			0, description ? description : "",
 987 			1, contact,
 988 			-1);
 989 
 990 		g_free (free_description);
 991 	}
 992 
 993 	if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter)) {
 994 		g_object_unref (store);
 995 		return NULL;
 996 	}
 997 
 998 	preview = e_web_view_preview_new ();
 999 	gtk_widget_show (preview);
1000 
1001 	tree_view = e_web_view_preview_get_tree_view (E_WEB_VIEW_PREVIEW (preview));
1002 	g_return_val_if_fail (tree_view != NULL, NULL);
1003 
1004 	gtk_tree_view_set_model (tree_view, GTK_TREE_MODEL (store));
1005 	g_object_unref (store);
1006 
1007 	gtk_tree_view_insert_column_with_attributes (
1008 		tree_view, -1, _("Contact"),
1009 		gtk_cell_renderer_text_new (), "text", 0, NULL);
1010 
1011 	if (gtk_tree_model_iter_n_children (GTK_TREE_MODEL (store), NULL) > 1)
1012 		e_web_view_preview_show_tree_view (E_WEB_VIEW_PREVIEW (preview));
1013 
1014 	selection = gtk_tree_view_get_selection (tree_view);
1015 	gtk_tree_selection_select_iter (selection, &iter);
1016 	g_signal_connect (
1017 		selection, "changed",
1018 		G_CALLBACK (preview_selection_changed_cb), preview);
1019 
1020 	preview_selection_changed_cb (selection, E_WEB_VIEW_PREVIEW (preview));
1021 
1022 	return preview;
1023 }