eab-contact-merging.c 29.7 KB
Newer Older
1 2 3
/*
 * Code for checking for duplicates when doing EContact work.
 *
4 5 6
 * 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.
7
 *
8 9 10 11
 * 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.
12
 *
13 14
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, see <http://www.gnu.org/licenses/>.
15 16
 *
 *
17
 * Authors:
18 19
 *		Christopher James Lahey <clahey@ximian.com>
 *		Chris Toshok <toshok@ximian.com>
20
 *
21
 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
22
 *
23 24
 */

25
#include "evolution-config.h"
26 27 28

#include "eab-contact-merging.h"
#include "eab-contact-compare.h"
29 30
#include <gtk/gtk.h>
#include <string.h>
31
#include "addressbook/gui/widgets/eab-contact-display.h"
32
#include "addressbook/util/eab-book-util.h"
33
#include "e-util/e-util.h"
34
#include "e-util/e-util-private.h"
35
#include <glib/gi18n.h>
36

37 38
#include <camel/camel.h>

39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
/* should be kept in synch with e-contact-editor */
static EContactField
im_fetch_set[] =
{
	E_CONTACT_IM_AIM,
	E_CONTACT_IM_JABBER,
	E_CONTACT_IM_YAHOO,
	E_CONTACT_IM_GADUGADU,
	E_CONTACT_IM_MSN,
	E_CONTACT_IM_ICQ,
	E_CONTACT_IM_GROUPWISE,
	E_CONTACT_IM_SKYPE,
	E_CONTACT_IM_TWITTER,
	E_CONTACT_IM_GOOGLE_TALK
};

55
typedef struct dropdown_data dropdown_data;
56 57
typedef enum {
	E_CONTACT_MERGING_ADD,
58 59
	E_CONTACT_MERGING_COMMIT,
	E_CONTACT_MERGING_FIND
60 61 62 63
} EContactMergingOpType;

typedef struct {
	EContactMergingOpType op;
64
	ESourceRegistry *registry;
65
	EBookClient *book_client;
66
	/*contact is the new contact which the user has tried to add to the addressbook*/
67
	EContact *contact;
68 69
	/*match is the duplicate contact already existing in the addressbook*/
	EContact *match;
70
	GList *avoid;
71 72 73
	EABMergingAsyncCallback cb;
	EABMergingIdAsyncCallback id_cb;
	EABMergingContactAsyncCallback c_cb;
74 75 76
	gpointer closure;
} EContactMergingLookup;

77 78 79
struct dropdown_data {
	EContact *match;
	EContactField field;
80 81 82 83

	/* for E_CONTACT_EMAIL field */
	GList *email_attr_list_item;
	EVCardAttribute *email_attr;
84
};
85 86 87
static void match_query_callback (EContact *contact, EContact *match, EABContactMatchType type, gpointer closure);

#define SIMULTANEOUS_MERGING_REQUESTS 20
88
#define EVOLUTION_UI_SLOT_PARAM "X-EVOLUTION-UI-SLOT"
89 90

static GList *merging_queue = NULL;
91
static gint running_merge_requests = 0;
92 93 94 95 96 97

static void
add_lookup (EContactMergingLookup *lookup)
{
	if (running_merge_requests < SIMULTANEOUS_MERGING_REQUESTS) {
		running_merge_requests++;
98
		eab_contact_locate_match_full (
99
			lookup->registry, lookup->book_client,
100 101
			lookup->contact, lookup->avoid,
			match_query_callback, lookup);
102 103 104 105 106 107 108 109 110 111 112 113 114
	}
	else {
		merging_queue = g_list_append (merging_queue, lookup);
	}
}

static void
finished_lookup (void)
{
	running_merge_requests--;

	while (running_merge_requests < SIMULTANEOUS_MERGING_REQUESTS) {
		EContactMergingLookup *lookup;
115

116 117 118 119 120 121 122 123
		if (!merging_queue)
			break;

		lookup = merging_queue->data;

		merging_queue = g_list_remove_link (merging_queue, merging_queue);

		running_merge_requests++;
124
		eab_contact_locate_match_full (
125
			lookup->registry, lookup->book_client,
126 127
			lookup->contact, lookup->avoid,
			match_query_callback, lookup);
128 129 130
	}
}

131 132 133
static void
free_lookup (EContactMergingLookup *lookup)
{
134
	g_object_unref (lookup->registry);
135
	g_object_unref (lookup->book_client);
136
	g_object_unref (lookup->contact);
137
	g_list_free (lookup->avoid);
138
	if (lookup->match)
139
		g_object_unref (lookup->match);
140 141 142 143
	g_free (lookup);
}

static void
144 145 146 147
final_id_cb (EBookClient *book_client,
             const GError *error,
             const gchar *id,
             gpointer closure)
148 149 150 151
{
	EContactMergingLookup *lookup = closure;

	if (lookup->id_cb)
152 153 154
		lookup->id_cb (
			lookup->book_client,
			error, id, lookup->closure);
155 156

	free_lookup (lookup);
157 158

	finished_lookup ();
159 160
}

161
static void
162 163 164
final_cb_as_id (EBookClient *book_client,
                const GError *error,
                gpointer closure)
165 166 167 168
{
	EContactMergingLookup *lookup = closure;

	if (lookup->id_cb)
169
		lookup->id_cb (
170 171
			lookup->book_client,
			error, lookup->contact ?
172 173 174
				e_contact_get_const (
				lookup->contact, E_CONTACT_UID) : NULL,
			lookup->closure);
175 176 177 178 179 180

	free_lookup (lookup);

	finished_lookup ();
}

181
static void
182 183 184
final_cb (EBookClient *book_client,
          const GError *error,
          gpointer closure)
185 186 187 188
{
	EContactMergingLookup *lookup = closure;

	if (lookup->cb)
189
		lookup->cb (lookup->book_client, error, lookup->closure);
190 191

	free_lookup (lookup);
192 193

	finished_lookup ();
194 195 196
}

static void
197 198 199
modify_contact_ready_cb (GObject *source_object,
                         GAsyncResult *result,
                         gpointer user_data)
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
{
	EBookClient *book_client = E_BOOK_CLIENT (source_object);
	EContactMergingLookup *lookup = user_data;
	GError *error = NULL;

	g_return_if_fail (book_client != NULL);
	g_return_if_fail (lookup != NULL);

	e_book_client_modify_contact_finish (book_client, result, &error);

	if (lookup->op == E_CONTACT_MERGING_ADD)
		final_cb_as_id (book_client, error, lookup);
	else
		final_cb (book_client, error, lookup);

Matthew Barnes's avatar
Matthew Barnes committed
215
	if (error != NULL)
216 217 218 219
		g_error_free (error);
}

static void
220 221 222
add_contact_ready_cb (GObject *source_object,
                      GAsyncResult *result,
                      gpointer user_data)
223 224 225 226 227 228 229 230 231
{
	EBookClient *book_client = E_BOOK_CLIENT (source_object);
	EContactMergingLookup *lookup = user_data;
	gchar *uid = NULL;
	GError *error = NULL;

	g_return_if_fail (book_client != NULL);
	g_return_if_fail (lookup != NULL);

Matthew Barnes's avatar
Matthew Barnes committed
232
	e_book_client_add_contact_finish (book_client, result, &uid, &error);
233 234 235

	final_id_cb (book_client, error, uid, lookup);

Matthew Barnes's avatar
Matthew Barnes committed
236
	if (error != NULL)
237 238 239 240
		g_error_free (error);
}

static void
241 242
doit (EContactMergingLookup *lookup,
      gboolean force_modify)
243
{
244
	if (lookup->op == E_CONTACT_MERGING_ADD) {
245
		if (force_modify)
246
			e_book_client_modify_contact (lookup->book_client, lookup->contact, E_BOOK_OPERATION_FLAG_NONE, NULL, modify_contact_ready_cb, lookup);
247
		else
248
			e_book_client_add_contact (lookup->book_client, lookup->contact, E_BOOK_OPERATION_FLAG_NONE, NULL, add_contact_ready_cb, lookup);
249
	} else if (lookup->op == E_CONTACT_MERGING_COMMIT)
250
		e_book_client_modify_contact (lookup->book_client, lookup->contact, E_BOOK_OPERATION_FLAG_NONE, NULL, modify_contact_ready_cb, lookup);
251 252 253 254 255
}

static void
cancelit (EContactMergingLookup *lookup)
{
Matthew Barnes's avatar
Matthew Barnes committed
256 257 258 259
	GError *error;

	error = g_error_new_literal (
		G_IO_ERROR, G_IO_ERROR_CANCELLED, _("Cancelled"));
260

261
	if (lookup->op == E_CONTACT_MERGING_ADD) {
262
		final_id_cb (lookup->book_client, error, NULL, lookup);
263
	} else if (lookup->op == E_CONTACT_MERGING_COMMIT) {
264
		final_cb (lookup->book_client, error, lookup);
265
	}
266 267

	g_error_free (error);
268 269
}

270
static void
271 272 273
dialog_map (GtkWidget *window,
            GdkEvent *event,
            GtkWidget *table)
274
{
275
	GtkAllocation allocation;
276
	gint h, w;
277

278 279
	gtk_widget_get_allocation (table, &allocation);

280
	/* Spacing around the table */
281
	w = allocation.width + 30;
282
	/* buttons and outer spacing */
283
	h = allocation.height + 60;
284 285 286 287
	if (w > 400)
		w = 400;
	if (h > 450)
		h = 450;
288 289

	gtk_widget_set_size_request (window, w, h);
290 291
}

292
static void
293 294
dropdown_changed (GtkWidget *dropdown,
                  dropdown_data *data)
295
{
296
	gchar *str = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (dropdown));
297

298
	if (str && *str)
299 300 301
		e_contact_set (data->match, data->field, str);
	else
		e_contact_set (data->match, data->field, NULL);
302 303 304 305 306

	g_free (str);
}

static void
307
attr_dropdown_changed (GtkWidget *dropdown,
Matthew Barnes's avatar
Matthew Barnes committed
308
                        dropdown_data *data)
309 310 311 312 313 314 315 316 317
{
	gchar *str = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (dropdown));

	if (str && *str)
		data->email_attr_list_item->data = data->email_attr;
	else
		data->email_attr_list_item->data = NULL;

	g_free (str);
318 319
}

320
static void
321 322 323
remove_contact_ready_cb (GObject *source_object,
                         GAsyncResult *result,
                         gpointer user_data)
324 325 326 327 328 329 330 331 332 333
{
	EBookClient *book_client = E_BOOK_CLIENT (source_object);
	EContactMergingLookup *lookup = user_data;
	GError *error = NULL;

	g_return_if_fail (book_client != NULL);
	g_return_if_fail (lookup != NULL);

	e_book_client_remove_contact_finish (book_client, result, &error);

334 335 336 337
	if (error != NULL) {
		g_warning (
			"%s: Failed to remove contact: %s",
			G_STRFUNC, error->message);
338 339 340
		g_error_free (error);
	}

341
	e_book_client_add_contact (
342
		book_client, lookup->contact, E_BOOK_OPERATION_FLAG_NONE, NULL,
343
		add_contact_ready_cb, lookup);
344 345
}

346 347 348 349 350 351
static void
create_dropdowns_for_multival_attr(GList *match_attr_list,
                                   GList *contact_attr_list,
                                   GList **use_attr_list,
                                   gint *row,
                                   GtkTable *table,
352
                                   const gchar * (*label_str) (EVCardAttribute*) )
353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390
{
	GtkWidget *label, *hbox, *dropdown;
	GList *miter, *citer;
	GHashTable *match_attrs; /* attr in the 'match' contact from address book */

	match_attrs = g_hash_table_new_full (camel_strcase_hash, camel_strcase_equal, g_free, NULL);

	for (miter = match_attr_list; miter; miter = g_list_next (miter)) {
		EVCardAttribute *attr = miter->data;
		gchar *value;

		value = e_vcard_attribute_get_value (attr);
		if (value && *value) {
			g_hash_table_insert (match_attrs, value, attr);
			*use_attr_list = g_list_prepend (*use_attr_list, attr);
		} else {
			g_free (value);
		}
	}

	*use_attr_list = g_list_reverse (*use_attr_list);

	for (citer = contact_attr_list; citer; citer = g_list_next (citer)) {
		EVCardAttribute *attr = citer->data;
		gchar *value;

		value = e_vcard_attribute_get_value (attr);
		if (value && *value) {
			if (!g_hash_table_lookup (match_attrs, value)) {
				dropdown_data *data;

				/* the attr is not set in both contacts */
				*use_attr_list = g_list_append (*use_attr_list, attr);

				/* remove to avoid collisions with match UI_SLOTs */
				e_vcard_attribute_remove_param (attr, EVOLUTION_UI_SLOT_PARAM);

				(*row)++;
391
				label = gtk_label_new (label_str (attr));
392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437
				hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
				gtk_box_pack_start (GTK_BOX (hbox), (GtkWidget *) label, FALSE, FALSE, 0);
				gtk_table_attach_defaults (table, (GtkWidget *) hbox, 0, 1, *row, *row + 1);

				dropdown = gtk_combo_box_text_new ();
				gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (dropdown), value);

				data = g_new0 (dropdown_data, 1);

				gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (dropdown), "");

				gtk_combo_box_set_active (GTK_COMBO_BOX (dropdown), 0);
				data->email_attr_list_item = g_list_last (*use_attr_list);
				data->email_attr = attr;

				g_signal_connect (
					dropdown, "changed",
					G_CALLBACK (attr_dropdown_changed), data);
				g_object_set_data_full (G_OBJECT (dropdown), "eab-contact-merging::dropdown-data", data, g_free);

				hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
				gtk_box_pack_start (GTK_BOX (hbox), (GtkWidget *) dropdown, FALSE, FALSE, 0);
				gtk_table_attach_defaults (table, (GtkWidget *) hbox, 1, 2, *row, *row + 1);
				gtk_widget_show ((GtkWidget *) dropdown);
			}
		}
		g_free (value);
	}
	g_hash_table_destroy (match_attrs);
}

static void
set_attributes(EContact *contact, EContactField field, GList *use_attr_list)
{
	GList *miter, *citer;

	citer = NULL;
	for (miter = use_attr_list; miter; miter = g_list_next (miter)) {
		if (miter->data)
			citer = g_list_prepend (citer, miter->data);
	}
	citer = g_list_reverse (citer);
	e_contact_set_attributes (contact, field, citer);
	g_list_free (citer);
}

Matthew Barnes's avatar
Matthew Barnes committed
438
static gint
439 440 441
mergeit (EContactMergingLookup *lookup)
{
	GtkWidget *scrolled_window, *label, *hbox, *dropdown;
442
	GtkWidget *content_area;
Matthew Barnes's avatar
Matthew Barnes committed
443
	GtkWidget *dialog;
444 445
	GtkTable *table;
	EContactField field;
446
	gchar *string = NULL, *string1 = NULL;
447 448 449 450
	GList *use_email_attr_list, *contact_email_attr_list, *match_email_attr_list;
	GList *use_tel_attr_list, *contact_tel_attr_list, *match_tel_attr_list;
	GList *use_im_attr_list, *contact_im_attr_list, *match_im_attr_list;
	GList *use_sip_attr_list, *contact_sip_attr_list, *match_sip_attr_list;
451 452
	gint row = -1;
	gint value = 0, result;
453

Matthew Barnes's avatar
Matthew Barnes committed
454 455
	dialog = gtk_dialog_new ();
	gtk_window_set_title (GTK_WINDOW (dialog), _("Merge Contact"));
Matthew Barnes's avatar
Matthew Barnes committed
456
	gtk_container_set_border_width (GTK_CONTAINER (dialog), 5);
457

Matthew Barnes's avatar
Matthew Barnes committed
458
	content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
459

460
	scrolled_window = gtk_scrolled_window_new (NULL, NULL);
Matthew Barnes's avatar
Matthew Barnes committed
461 462 463
	gtk_scrolled_window_set_policy (
		GTK_SCROLLED_WINDOW (scrolled_window),
		GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
464 465 466 467 468 469

	table = (GtkTable *) gtk_table_new (20, 2, FALSE);
	gtk_container_set_border_width ((GtkContainer *) table, 12);
	gtk_table_set_row_spacings (table, 6);
	gtk_table_set_col_spacings (table, 2);

Matthew Barnes's avatar
Matthew Barnes committed
470 471
	gtk_dialog_add_buttons (
		GTK_DIALOG (dialog),
472
		_("_Cancel"), GTK_RESPONSE_CANCEL,
Matthew Barnes's avatar
Matthew Barnes committed
473 474
		_("_Merge"), GTK_RESPONSE_OK,
		NULL);
475 476 477


	/*we match all the string fields of the already existing contact and the new contact.*/
478
	for (field = E_CONTACT_FULL_NAME; field != (E_CONTACT_LAST_SIMPLE_STRING -1); field++) {
479
		dropdown_data *data = NULL;
Matthew Barnes's avatar
Matthew Barnes committed
480 481
		string = (gchar *) e_contact_get_const (lookup->contact, field);
		string1 = (gchar *) e_contact_get_const (lookup->match, field);
482 483 484

		/*the field must exist in the new as well as the duplicate contact*/
		if (string && *string) {
485 486 487 488
			if ((field >= E_CONTACT_FIRST_EMAIL_ID && field <= E_CONTACT_LAST_EMAIL_ID) ||
			    (field >= E_CONTACT_FIRST_PHONE_ID && field <= E_CONTACT_LAST_PHONE_ID) ||
			    (field >= E_CONTACT_IM_AIM_HOME_1 && field <= E_CONTACT_IM_ICQ_WORK_3) ) {
				/* ignore multival attributes, they are compared after this for-loop */
489
				continue;
490
			}
491

Matthew Barnes's avatar
Matthew Barnes committed
492
			if (!(string1 && *string1) || (g_ascii_strcasecmp (string, string1))) {
493
				row++;
Matthew Barnes's avatar
Matthew Barnes committed
494
				label = gtk_label_new (e_contact_pretty_name (field));
495
				hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
496
				gtk_box_pack_start (GTK_BOX (hbox), (GtkWidget *) label, FALSE, FALSE, 0);
Matthew Barnes's avatar
Matthew Barnes committed
497
				gtk_table_attach_defaults (table, (GtkWidget *) hbox, 0, 1, row, row + 1);
498
				data = g_new0 (dropdown_data, 1);
499 500
				dropdown = gtk_combo_box_text_new ();
				gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (dropdown), string);
501 502

				if (string1 && *string1)
503
					gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (dropdown), string1);
504
				else
505
					gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (dropdown), "");
506 507 508 509

				data->field = field;
				data->match = lookup->match;

510 511 512
				g_signal_connect (
					dropdown, "changed",
					G_CALLBACK (dropdown_changed), data);
513
				g_object_set_data_full (G_OBJECT (dropdown), "eab-contact-merging::dropdown-data", data, g_free);
514

515 516 517 518 519
				if (field == E_CONTACT_NICKNAME || field == E_CONTACT_GIVEN_NAME || field == E_CONTACT_FAMILY_NAME || field == E_CONTACT_FULL_NAME)
					gtk_combo_box_set_active (GTK_COMBO_BOX (dropdown), 1);
				else
					gtk_combo_box_set_active (GTK_COMBO_BOX (dropdown), 0);

520
				hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
521
				gtk_box_pack_start (GTK_BOX (hbox), (GtkWidget *) dropdown, FALSE, FALSE, 0);
Matthew Barnes's avatar
Matthew Barnes committed
522 523
				gtk_table_attach_defaults (table, (GtkWidget *) hbox, 1, 2, row, row + 1);
				gtk_widget_show_all ((GtkWidget *) dropdown);
524
			}
525 526 527
		}
	}

528 529 530 531
	match_email_attr_list = e_contact_get_attributes (lookup->match, E_CONTACT_EMAIL);
	contact_email_attr_list = e_contact_get_attributes (lookup->contact, E_CONTACT_EMAIL);
	use_email_attr_list = NULL;
	create_dropdowns_for_multival_attr (match_email_attr_list, contact_email_attr_list,
532
	                                   &use_email_attr_list, &row, table, eab_get_email_label_text);
533 534 535 536 537

	match_tel_attr_list = e_contact_get_attributes (lookup->match, E_CONTACT_TEL);
	contact_tel_attr_list = e_contact_get_attributes (lookup->contact, E_CONTACT_TEL);
	use_tel_attr_list = NULL;
	create_dropdowns_for_multival_attr (match_tel_attr_list, contact_tel_attr_list,
538
	                                   &use_tel_attr_list, &row, table, eab_get_phone_label_text);
539 540 541 542 543

	match_sip_attr_list = e_contact_get_attributes (lookup->match, E_CONTACT_SIP);
	contact_sip_attr_list = e_contact_get_attributes (lookup->contact, E_CONTACT_SIP);
	use_sip_attr_list = NULL;
	create_dropdowns_for_multival_attr (match_sip_attr_list, contact_sip_attr_list,
544
	                                   &use_sip_attr_list, &row, table, eab_get_sip_label_text);
545 546 547 548 549

	match_im_attr_list = e_contact_get_attributes_set (lookup->match, im_fetch_set, G_N_ELEMENTS (im_fetch_set));
	contact_im_attr_list = e_contact_get_attributes_set (lookup->contact, im_fetch_set, G_N_ELEMENTS (im_fetch_set));
	use_im_attr_list = NULL;
	create_dropdowns_for_multival_attr (match_im_attr_list, contact_im_attr_list,
550
	                                   &use_im_attr_list, &row, table, eab_get_im_label_text);
551

552 553
	gtk_window_set_default_size (GTK_WINDOW (dialog), 420, 300);
	gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scrolled_window), GTK_WIDGET (table));
554
	gtk_box_pack_start (GTK_BOX (content_area), GTK_WIDGET (scrolled_window), TRUE, TRUE, 0);
555
	gtk_widget_show (scrolled_window);
556 557 558
	g_signal_connect (
		dialog, "map-event",
		G_CALLBACK (dialog_map), table);
Matthew Barnes's avatar
Matthew Barnes committed
559
	gtk_widget_show_all ((GtkWidget *) table);
Matthew Barnes's avatar
Matthew Barnes committed
560
	result = gtk_dialog_run (GTK_DIALOG (dialog));
561

562
	switch (result) {
563 564
		gint ii;
		GList *ll;
565
	case GTK_RESPONSE_OK:
566 567 568 569 570 571 572 573 574 575 576 577
		set_attributes (lookup->match, E_CONTACT_EMAIL, use_email_attr_list);
		set_attributes (lookup->match, E_CONTACT_TEL, use_tel_attr_list);
		set_attributes (lookup->match, E_CONTACT_SIP, use_sip_attr_list);

		for (ii = 0; ii < G_N_ELEMENTS (im_fetch_set); ii++) {
			e_contact_set_attributes (lookup->match, im_fetch_set[ii], NULL);
		}

		for (ll = use_im_attr_list; ll; ll = ll->next) {
			EVCard *vcard;
			vcard = E_VCARD (lookup->match);
			e_vcard_append_attribute (vcard, e_vcard_attribute_copy ((EVCardAttribute *) ll->data));
578 579
		}

Matthew Barnes's avatar
Matthew Barnes committed
580 581 582 583
		g_object_unref (lookup->contact);
		lookup->contact = g_object_ref (lookup->match);
		e_book_client_remove_contact (
			lookup->book_client,
584
			lookup->match, E_BOOK_OPERATION_FLAG_NONE, NULL,
Matthew Barnes's avatar
Matthew Barnes committed
585 586 587
			remove_contact_ready_cb, lookup);
		value = 1;
		break;
588
	case GTK_RESPONSE_CANCEL:
589
	default:
Matthew Barnes's avatar
Matthew Barnes committed
590 591
		value = 0;
		break;
592
	}
Matthew Barnes's avatar
Matthew Barnes committed
593
	gtk_widget_destroy (dialog);
594

595 596 597
	g_list_free_full (match_email_attr_list, (GDestroyNotify) e_vcard_attribute_free);
	g_list_free_full (contact_email_attr_list, (GDestroyNotify) e_vcard_attribute_free);
	g_list_free (use_email_attr_list);
598 599 600 601 602 603 604 605 606 607 608 609

	g_list_free_full (match_tel_attr_list, (GDestroyNotify) e_vcard_attribute_free);
	g_list_free_full (contact_tel_attr_list, (GDestroyNotify) e_vcard_attribute_free);
	g_list_free (use_tel_attr_list);

	g_list_free_full (match_im_attr_list, (GDestroyNotify) e_vcard_attribute_free);
	g_list_free_full (contact_im_attr_list, (GDestroyNotify) e_vcard_attribute_free);
	g_list_free (use_im_attr_list);

	g_list_free_full (match_sip_attr_list, (GDestroyNotify) e_vcard_attribute_free);
	g_list_free_full (contact_sip_attr_list, (GDestroyNotify) e_vcard_attribute_free);
	g_list_free (use_sip_attr_list);
610

611 612 613 614
	return value;
}

static gboolean
615 616
check_if_same (EContact *contact,
               EContact *match)
617
{
618
	EContactField field;
619
	gchar *string = NULL, *string1 = NULL;
620
	gboolean res = TRUE;
621

622 623

	for (field = E_CONTACT_FULL_NAME; res && field != (E_CONTACT_LAST_SIMPLE_STRING -1); field++) {
624

625 626 627 628 629 630 631 632 633 634 635
		if (field == E_CONTACT_EMAIL_1) {
			GList *email_attr_list1, *email_attr_list2, *iter1, *iter2;
			gint num_of_email1, num_of_email2;

			email_attr_list1 = e_contact_get_attributes (contact, E_CONTACT_EMAIL);
			num_of_email1 = g_list_length (email_attr_list1);

			email_attr_list2 = e_contact_get_attributes (match, E_CONTACT_EMAIL);
			num_of_email2 = g_list_length (email_attr_list2);

			if (num_of_email1 != num_of_email2) {
636 637
				res = FALSE;
				break;
638 639 640
			} else { /* Do pairwise-comparisons on all of the e-mail addresses. */
				iter1 = email_attr_list1;
				while (iter1) {
641
					gboolean         matches = FALSE;
642 643 644 645 646 647 648
					EVCardAttribute *attr;
					gchar           *email_address1;

					attr = iter1->data;
					email_address1 = e_vcard_attribute_get_value (attr);

					iter2 = email_attr_list2;
649
					while ( iter2 && matches == FALSE) {
650 651 652 653 654 655
						gchar *email_address2;

						attr = iter2->data;
						email_address2 = e_vcard_attribute_get_value (attr);

						if (g_ascii_strcasecmp (email_address1, email_address2) == 0) {
656
							matches = TRUE;
657 658 659 660 661 662 663 664 665
						}

						g_free (email_address2);
						iter2 = g_list_next (iter2);
					}

					g_free (email_address1);
					iter1 = g_list_next (iter1);

666
					if (matches == FALSE) {
667 668 669 670
						res = FALSE;
						break;
					}
				}
671
			}
672 673 674 675 676

			g_list_free_full (email_attr_list1, (GDestroyNotify) e_vcard_attribute_free);
			g_list_free_full (email_attr_list2, (GDestroyNotify) e_vcard_attribute_free);
		} else if (field > E_CONTACT_FIRST_EMAIL_ID && field <= E_CONTACT_LAST_EMAIL_ID) {
			/* nothing to do, all emails are checked above */
677
		}
678
		else {
Matthew Barnes's avatar
Matthew Barnes committed
679 680
			string = (gchar *) e_contact_get_const (contact, field);
			string1 = (gchar *) e_contact_get_const (match, field);
681 682 683
			if ((string && *string) && (string1 && *string1) && (g_ascii_strcasecmp (string1, string))) {
				res = FALSE;
				break;
684
			/*if the field entry exist in either of the contacts,we'll have to give the choice and thus merge button should be sensitive*/
685 686 687 688
			} else if ((string && *string) && !(string1 && *string1)) {
				res = FALSE;
				break;
			}
689 690
		}
	}
691 692

	return res;
693
}
694

695 696
static GtkWidget *
create_duplicate_contact_detected_dialog (EContact *old_contact,
Matthew Barnes's avatar
Matthew Barnes committed
697 698 699
                                          EContact *new_contact,
                                          gboolean disable_merge,
                                          gboolean is_for_commit)
700 701 702 703 704 705 706 707 708
{
	GtkWidget *widget, *scrolled;
	GtkContainer *container;
	GtkDialog *dialog;
	const gchar *text;

	widget = gtk_dialog_new ();
	dialog = GTK_DIALOG (widget);

Matthew Barnes's avatar
Matthew Barnes committed
709 710
	g_object_set (
		G_OBJECT (dialog),
711 712 713 714 715
		"title", _("Duplicate Contact Detected"),
		"default-width", 500,
		"default-height", 400,
		NULL);

716
	gtk_dialog_add_action_widget (dialog, e_dialog_button_new_with_icon ("process-stop", _("_Cancel")), GTK_RESPONSE_CANCEL);
717 718

	if (is_for_commit) {
719
		gtk_dialog_add_action_widget (dialog, e_dialog_button_new_with_icon ("document-save", _("_Save")), GTK_RESPONSE_OK);
720
	} else {
721 722
		gtk_dialog_add_action_widget (dialog, e_dialog_button_new_with_icon ("list-add", _("_Add")), GTK_RESPONSE_OK);
		gtk_dialog_add_action_widget (dialog, e_dialog_button_new_with_icon (NULL, _("_Merge")), GTK_RESPONSE_APPLY);
723 724 725 726 727 728 729 730
	}

	if (disable_merge)
		gtk_dialog_set_response_sensitive (dialog, GTK_RESPONSE_APPLY, FALSE);

	container = GTK_CONTAINER (gtk_dialog_get_content_area (dialog));

	widget = gtk_grid_new ();
Matthew Barnes's avatar
Matthew Barnes committed
731 732
	g_object_set (
		G_OBJECT (widget),
733 734 735 736 737 738 739 740 741 742 743 744
		"orientation", GTK_ORIENTATION_HORIZONTAL,
		"hexpand", TRUE,
		"halign", GTK_ALIGN_FILL,
		"vexpand", TRUE,
		"valign", GTK_ALIGN_FILL,
		"margin", 12,
		NULL);

	gtk_container_add (container, widget);
	container = GTK_CONTAINER (widget);

	widget = gtk_image_new_from_icon_name ("avatar-default", GTK_ICON_SIZE_BUTTON);
Matthew Barnes's avatar
Matthew Barnes committed
745 746
	g_object_set (
		G_OBJECT (widget),
747 748 749 750 751 752 753 754 755
		"hexpand", FALSE,
		"halign", GTK_ALIGN_START,
		"vexpand", FALSE,
		"valign", GTK_ALIGN_START,
		"margin-right", 12,
		NULL);
	gtk_container_add (container, widget);

	widget = gtk_grid_new ();
Matthew Barnes's avatar
Matthew Barnes committed
756 757
	g_object_set (
		G_OBJECT (widget),
758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775
		"orientation", GTK_ORIENTATION_VERTICAL,
		"hexpand", TRUE,
		"halign", GTK_ALIGN_FILL,
		"vexpand", TRUE,
		"valign", GTK_ALIGN_FILL,
		NULL);

	gtk_container_add (container, widget);
	container = GTK_CONTAINER (widget);

	if (is_for_commit)
		text = _("The name or email address of this contact already exists\n"
			 "in this folder. Would you like to save the changes anyway?");
	else
		text = _("The name or email address of this contact already exists\n"
			 "in this folder. Would you like to add it anyway?");

	widget = gtk_label_new (text);
Matthew Barnes's avatar
Matthew Barnes committed
776 777
	g_object_set (
		G_OBJECT (widget),
778 779 780 781 782 783 784 785 786 787 788 789 790 791
		"hexpand", FALSE,
		"halign", GTK_ALIGN_START,
		"vexpand", FALSE,
		"valign", GTK_ALIGN_START,
		"margin-bottom", 6,
		NULL);
	gtk_container_add (container, widget);

	if (is_for_commit)
		text = _("Changed Contact:");
	else
		text = _("New Contact:");

	widget = gtk_label_new (text);
Matthew Barnes's avatar
Matthew Barnes committed
792 793
	g_object_set (
		G_OBJECT (widget),
794 795 796 797 798 799 800 801 802
		"hexpand", FALSE,
		"halign", GTK_ALIGN_START,
		"vexpand", FALSE,
		"valign", GTK_ALIGN_START,
		"margin-bottom", 6,
		NULL);
	gtk_container_add (container, widget);

	scrolled = gtk_scrolled_window_new (NULL, NULL);
Matthew Barnes's avatar
Matthew Barnes committed
803 804
	g_object_set (
		G_OBJECT (scrolled),
805 806 807 808 809 810 811 812 813 814 815
		"hexpand", TRUE,
		"halign", GTK_ALIGN_FILL,
		"hscrollbar-policy", GTK_POLICY_AUTOMATIC,
		"vexpand", TRUE,
		"valign", GTK_ALIGN_FILL,
		"vscrollbar-policy", GTK_POLICY_AUTOMATIC,
		"margin-bottom", 6,
		NULL);
	gtk_container_add (container, scrolled);

	widget = eab_contact_display_new ();
Matthew Barnes's avatar
Matthew Barnes committed
816 817
	g_object_set (
		G_OBJECT (widget),
818 819 820 821 822 823 824 825 826 827 828 829 830 831 832
		"hexpand", TRUE,
		"halign", GTK_ALIGN_FILL,
		"vexpand", TRUE,
		"valign", GTK_ALIGN_FILL,
		"contact", new_contact,
		"mode", EAB_CONTACT_DISPLAY_RENDER_COMPACT,
		NULL);
	gtk_container_add (GTK_CONTAINER (scrolled), widget);

	if (is_for_commit)
		text = _("Conflicting Contact:");
	else
		text = _("Old Contact:");

	widget = gtk_label_new (text);
Matthew Barnes's avatar
Matthew Barnes committed
833 834
	g_object_set (
		G_OBJECT (widget),
835 836 837 838 839 840 841 842 843
		"hexpand", FALSE,
		"halign", GTK_ALIGN_START,
		"vexpand", FALSE,
		"valign", GTK_ALIGN_START,
		"margin-bottom", 6,
		NULL);
	gtk_container_add (container, widget);

	scrolled = gtk_scrolled_window_new (NULL, NULL);
Matthew Barnes's avatar
Matthew Barnes committed
844 845
	g_object_set (
		G_OBJECT (scrolled),
846 847 848 849 850 851 852 853 854 855
		"hexpand", TRUE,
		"halign", GTK_ALIGN_FILL,
		"hscrollbar-policy", GTK_POLICY_AUTOMATIC,
		"vexpand", TRUE,
		"valign", GTK_ALIGN_FILL,
		"vscrollbar-policy", GTK_POLICY_AUTOMATIC,
		NULL);
	gtk_container_add (container, scrolled);

	widget = eab_contact_display_new ();
Matthew Barnes's avatar
Matthew Barnes committed
856 857
	g_object_set (
		G_OBJECT (widget),
858 859 860 861 862 863 864 865 866 867 868 869 870 871
		"hexpand", TRUE,
		"halign", GTK_ALIGN_FILL,
		"vexpand", TRUE,
		"valign", GTK_ALIGN_FILL,
		"contact", old_contact,
		"mode", EAB_CONTACT_DISPLAY_RENDER_COMPACT,
		NULL);
	gtk_container_add (GTK_CONTAINER (scrolled), widget);

	gtk_widget_show_all (gtk_dialog_get_content_area (dialog));

	return GTK_WIDGET (dialog);
}

872
static void
873 874 875
response (GtkWidget *dialog,
          gint response,
          EContactMergingLookup *lookup)
876
{
877
	switch (response) {
878
	case GTK_RESPONSE_OK:
879
		doit (lookup, FALSE);
880
		break;
881
	case GTK_RESPONSE_CANCEL:
882 883
		cancelit (lookup);
		break;
884 885
	case GTK_RESPONSE_APPLY:
		if (mergeit (lookup))
886 887
			break;
		return;
888 889 890
	case GTK_RESPONSE_DELETE_EVENT:
		cancelit (lookup);
		break;
891 892 893
	default:
		g_warn_if_reached ();
		break;
894
	}
895

896
	gtk_widget_destroy (dialog);
897 898 899
}

static void
900 901 902 903
match_query_callback (EContact *contact,
                      EContact *match,
                      EABContactMatchType type,
                      gpointer closure)
904 905
{
	EContactMergingLookup *lookup = closure;
906
	gboolean flag;
907 908 909 910
	gboolean same_uids;

	if (lookup->op == E_CONTACT_MERGING_FIND) {
		if (lookup->c_cb)
911 912 913 914 915
			lookup->c_cb (
				lookup->book_client, NULL,
				(gint) type <= (gint)
				EAB_CONTACT_MATCH_VAGUE ? NULL : match,
				lookup->closure);
916 917 918 919 920 921 922

		free_lookup (lookup);
		finished_lookup ();
		return;
	}

	/* if had same UID, then we are editing old contact, thus force commit change to it */
923 924 925 926
	same_uids = contact && match
		&& e_contact_get_const (contact, E_CONTACT_UID)
		&& e_contact_get_const (match, E_CONTACT_UID)
		&& g_str_equal (e_contact_get_const (contact, E_CONTACT_UID), e_contact_get_const (match, E_CONTACT_UID));
927

928 929
	if ((gint) type <= (gint) EAB_CONTACT_MATCH_VAGUE || same_uids) {
		doit (lookup, same_uids);
930
	} else {
931
		GtkWidget *dialog;
932

933
		lookup->match = g_object_ref (match);
934
		if (lookup->op == E_CONTACT_MERGING_ADD) {
935 936
			/* Compares all the values of contacts and return true, if they match */
			flag = check_if_same (contact, match);
937
			dialog = create_duplicate_contact_detected_dialog (match, contact, flag, FALSE);
938
		} else if (lookup->op == E_CONTACT_MERGING_COMMIT) {
939
			dialog = create_duplicate_contact_detected_dialog (match, contact, FALSE, TRUE);
940
		} else {
941
			doit (lookup, FALSE);
942 943 944
			return;
		}

945
		g_signal_connect (
946
			dialog, "response",
947
			G_CALLBACK (response), lookup);
948

949
		gtk_widget_show_all (dialog);
950 951 952 953
	}
}

gboolean
954 955
eab_merging_book_add_contact (ESourceRegistry *registry,
                              EBookClient *book_client,
956 957 958
                              EContact *contact,
                              EABMergingIdAsyncCallback cb,
                              gpointer closure)
959 960 961
{
	EContactMergingLookup *lookup;

962 963
	g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), FALSE);

964 965 966
	lookup = g_new (EContactMergingLookup, 1);

	lookup->op = E_CONTACT_MERGING_ADD;
967
	lookup->registry = g_object_ref (registry);
968
	lookup->book_client = g_object_ref (book_client);
969 970 971
	lookup->contact = g_object_ref (contact);
	lookup->id_cb = cb;
	lookup->closure = closure;
972
	lookup->avoid = NULL;
973
	lookup->match = NULL;
974

975
	add_lookup (lookup);
976 977 978 979 980

	return TRUE;
}

gboolean
981 982
eab_merging_book_modify_contact (ESourceRegistry *registry,
                                 EBookClient *book_client,
983 984 985
                                 EContact *contact,
                                 EABMergingAsyncCallback cb,
                                 gpointer closure)
986 987
{
	EContactMergingLookup *lookup;
988

989 990
	g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), FALSE);

991 992 993
	lookup = g_new (EContactMergingLookup, 1);

	lookup->op = E_CONTACT_MERGING_COMMIT;
994
	lookup->registry = g_object_ref (registry);
995
	lookup->book_client = g_object_ref (book_client);
996 997 998
	lookup->contact = g_object_ref (contact);
	lookup->cb = cb;
	lookup->closure = closure;
999
	lookup->avoid = g_list_append (NULL, contact);
1000
	lookup->match = NULL;
1001

1002
	add_lookup (lookup);
1003 1004 1005 1006

	return TRUE;
}

1007
gboolean
1008 1009
eab_merging_book_find_contact (ESourceRegistry *registry,
                               EBookClient *book_client,
1010 1011 1012
                               EContact *contact,
                               EABMergingContactAsyncCallback cb,
                               gpointer closure)
1013 1014 1015 1016 1017 1018
{
	EContactMergingLookup *lookup;

	lookup = g_new (EContactMergingLookup, 1);

	lookup->op = E_CONTACT_MERGING_FIND;
1019
	lookup->registry = g_object_ref (registry);
1020
	lookup->book_client = g_object_ref (book_client);
1021 1022 1023 1024 1025 1026 1027 1028 1029 1030
	lookup->contact = g_object_ref (contact);
	lookup->c_cb = cb;
	lookup->closure = closure;
	lookup->avoid = g_list_append (NULL, contact);
	lookup->match = NULL;

	add_lookup (lookup);

	return TRUE;
}