1 /*
2  * Code for checking for duplicates when doing EContact work.
3  *
4  * This program is free software; you can redistribute it and/or modify it
5  * under the terms of the GNU Lesser General Public License as published by
6  * the Free Software Foundation.
7  *
8  * This program is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
11  * for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public License
14  * along with this program; if not, see <http://www.gnu.org/licenses/>.
15  *
16  *
17  * Authors:
18  *		Christopher James Lahey <clahey@ximian.com>
19  *		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 #include "eab-contact-merging.h"
28 #include "eab-contact-compare.h"
29 #include <gtk/gtk.h>
30 #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 #include <camel/camel.h>
38 
39 /* should be kept in synch with e-contact-editor */
40 static EContactField
41 im_fetch_set[] =
42 {
43 	E_CONTACT_IM_AIM,
44 	E_CONTACT_IM_JABBER,
45 	E_CONTACT_IM_YAHOO,
46 	E_CONTACT_IM_GADUGADU,
47 	E_CONTACT_IM_MSN,
48 	E_CONTACT_IM_ICQ,
49 	E_CONTACT_IM_GROUPWISE,
50 	E_CONTACT_IM_SKYPE,
51 	E_CONTACT_IM_TWITTER,
52 	E_CONTACT_IM_GOOGLE_TALK,
53 	E_CONTACT_IM_MATRIX
54 };
55 
56 typedef enum {
57 	E_CONTACT_MERGING_ADD,
58 	E_CONTACT_MERGING_COMMIT,
59 	E_CONTACT_MERGING_FIND
60 } EContactMergingOpType;
61 
62 typedef struct _MergeDialogData {
63 	GtkWidget *dialog;
64 	GList *use_email_attr_list, *contact_email_attr_list, *match_email_attr_list;
65 	GList *use_tel_attr_list, *contact_tel_attr_list, *match_tel_attr_list;
66 	GList *use_im_attr_list, *contact_im_attr_list, *match_im_attr_list;
67 	GList *use_sip_attr_list, *contact_sip_attr_list, *match_sip_attr_list;
68 	gint row;
69 } MergeDialogData;
70 
71 typedef struct {
72 	EContactMergingOpType op;
73 	ESourceRegistry *registry;
74 	EBookClient *book_client;
75 	/*contact is the new contact which the user has tried to add to the addressbook*/
76 	EContact *contact;
77 	/*match is the duplicate contact already existing in the addressbook*/
78 	EContact *match;
79 	GList *avoid;
80 	EABMergingAsyncCallback cb;
81 	EABMergingIdAsyncCallback id_cb;
82 	EABMergingContactAsyncCallback c_cb;
83 	gpointer closure;
84 
85 	MergeDialogData *merge_dialog_data;
86 } EContactMergingLookup;
87 
88 typedef struct _dropdown_data {
89 	EContact *match;
90 	EContactField field;
91 
92 	/* for E_CONTACT_EMAIL field */
93 	GList *email_attr_list_item;
94 	EVCardAttribute *email_attr;
95 } dropdown_data;
96 
97 static void match_query_callback (EContact *contact, EContact *match, EABContactMatchType type, gpointer closure);
98 
99 #define SIMULTANEOUS_MERGING_REQUESTS 20
100 #define EVOLUTION_UI_SLOT_PARAM "X-EVOLUTION-UI-SLOT"
101 
102 static GList *merging_queue = NULL;
103 static gint running_merge_requests = 0;
104 
105 static void
merge_dialog_data_free(MergeDialogData * mdd)106 merge_dialog_data_free (MergeDialogData *mdd)
107 {
108 	if (!mdd)
109 		return;
110 
111 	gtk_widget_destroy (mdd->dialog);
112 
113 	g_list_free_full (mdd->match_email_attr_list, (GDestroyNotify) e_vcard_attribute_free);
114 	g_list_free_full (mdd->contact_email_attr_list, (GDestroyNotify) e_vcard_attribute_free);
115 	g_list_free (mdd->use_email_attr_list);
116 
117 	g_list_free_full (mdd->match_tel_attr_list, (GDestroyNotify) e_vcard_attribute_free);
118 	g_list_free_full (mdd->contact_tel_attr_list, (GDestroyNotify) e_vcard_attribute_free);
119 	g_list_free (mdd->use_tel_attr_list);
120 
121 	g_list_free_full (mdd->match_im_attr_list, (GDestroyNotify) e_vcard_attribute_free);
122 	g_list_free_full (mdd->contact_im_attr_list, (GDestroyNotify) e_vcard_attribute_free);
123 	g_list_free (mdd->use_im_attr_list);
124 
125 	g_list_free_full (mdd->match_sip_attr_list, (GDestroyNotify) e_vcard_attribute_free);
126 	g_list_free_full (mdd->contact_sip_attr_list, (GDestroyNotify) e_vcard_attribute_free);
127 	g_list_free (mdd->use_sip_attr_list);
128 
129 	g_slice_free (MergeDialogData, mdd);
130 }
131 
132 static void
add_lookup(EContactMergingLookup * lookup)133 add_lookup (EContactMergingLookup *lookup)
134 {
135 	if (running_merge_requests < SIMULTANEOUS_MERGING_REQUESTS) {
136 		running_merge_requests++;
137 		eab_contact_locate_match_full (
138 			lookup->registry, lookup->book_client,
139 			lookup->contact, lookup->avoid,
140 			match_query_callback, lookup);
141 	}
142 	else {
143 		merging_queue = g_list_append (merging_queue, lookup);
144 	}
145 }
146 
147 static void
finished_lookup(void)148 finished_lookup (void)
149 {
150 	running_merge_requests--;
151 
152 	while (running_merge_requests < SIMULTANEOUS_MERGING_REQUESTS) {
153 		EContactMergingLookup *lookup;
154 
155 		if (!merging_queue)
156 			break;
157 
158 		lookup = merging_queue->data;
159 
160 		merging_queue = g_list_remove_link (merging_queue, merging_queue);
161 
162 		running_merge_requests++;
163 		eab_contact_locate_match_full (
164 			lookup->registry, lookup->book_client,
165 			lookup->contact, lookup->avoid,
166 			match_query_callback, lookup);
167 	}
168 }
169 
170 static EContactMergingLookup *
new_lookup(void)171 new_lookup (void)
172 {
173 	return g_slice_new0 (EContactMergingLookup);
174 }
175 
176 static void
free_lookup(EContactMergingLookup * lookup)177 free_lookup (EContactMergingLookup *lookup)
178 {
179 	merge_dialog_data_free (lookup->merge_dialog_data);
180 	g_object_unref (lookup->registry);
181 	g_object_unref (lookup->book_client);
182 	g_object_unref (lookup->contact);
183 	g_list_free (lookup->avoid);
184 	if (lookup->match)
185 		g_object_unref (lookup->match);
186 	g_slice_free (EContactMergingLookup, lookup);
187 }
188 
189 static void
final_id_cb(EBookClient * book_client,const GError * error,const gchar * id,gpointer closure)190 final_id_cb (EBookClient *book_client,
191              const GError *error,
192              const gchar *id,
193              gpointer closure)
194 {
195 	EContactMergingLookup *lookup = closure;
196 
197 	if (lookup->id_cb)
198 		lookup->id_cb (
199 			lookup->book_client,
200 			error, id, lookup->closure);
201 
202 	free_lookup (lookup);
203 
204 	finished_lookup ();
205 }
206 
207 static void
final_cb_as_id(EBookClient * book_client,const GError * error,gpointer closure)208 final_cb_as_id (EBookClient *book_client,
209                 const GError *error,
210                 gpointer closure)
211 {
212 	EContactMergingLookup *lookup = closure;
213 
214 	if (lookup->id_cb)
215 		lookup->id_cb (
216 			lookup->book_client,
217 			error, lookup->contact ?
218 				e_contact_get_const (
219 				lookup->contact, E_CONTACT_UID) : NULL,
220 			lookup->closure);
221 
222 	free_lookup (lookup);
223 
224 	finished_lookup ();
225 }
226 
227 static void
final_cb(EBookClient * book_client,const GError * error,gpointer closure)228 final_cb (EBookClient *book_client,
229           const GError *error,
230           gpointer closure)
231 {
232 	EContactMergingLookup *lookup = closure;
233 
234 	if (lookup->cb)
235 		lookup->cb (lookup->book_client, error, lookup->closure);
236 
237 	free_lookup (lookup);
238 
239 	finished_lookup ();
240 }
241 
242 static void
modify_contact_ready_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)243 modify_contact_ready_cb (GObject *source_object,
244                          GAsyncResult *result,
245                          gpointer user_data)
246 {
247 	EBookClient *book_client = E_BOOK_CLIENT (source_object);
248 	EContactMergingLookup *lookup = user_data;
249 	GError *error = NULL;
250 
251 	g_return_if_fail (book_client != NULL);
252 	g_return_if_fail (lookup != NULL);
253 
254 	e_book_client_modify_contact_finish (book_client, result, &error);
255 
256 	if (lookup->op == E_CONTACT_MERGING_ADD)
257 		final_cb_as_id (book_client, error, lookup);
258 	else
259 		final_cb (book_client, error, lookup);
260 
261 	if (error != NULL)
262 		g_error_free (error);
263 }
264 
265 static void
add_contact_ready_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)266 add_contact_ready_cb (GObject *source_object,
267                       GAsyncResult *result,
268                       gpointer user_data)
269 {
270 	EBookClient *book_client = E_BOOK_CLIENT (source_object);
271 	EContactMergingLookup *lookup = user_data;
272 	gchar *uid = NULL;
273 	GError *error = NULL;
274 
275 	g_return_if_fail (book_client != NULL);
276 	g_return_if_fail (lookup != NULL);
277 
278 	e_book_client_add_contact_finish (book_client, result, &uid, &error);
279 
280 	final_id_cb (book_client, error, uid, lookup);
281 
282 	if (error != NULL)
283 		g_error_free (error);
284 	g_free (uid);
285 }
286 
287 static void
doit(EContactMergingLookup * lookup,gboolean force_modify)288 doit (EContactMergingLookup *lookup,
289       gboolean force_modify)
290 {
291 	if (lookup->op == E_CONTACT_MERGING_ADD) {
292 		if (force_modify)
293 			e_book_client_modify_contact (lookup->book_client, lookup->contact, E_BOOK_OPERATION_FLAG_NONE, NULL, modify_contact_ready_cb, lookup);
294 		else
295 			e_book_client_add_contact (lookup->book_client, lookup->contact, E_BOOK_OPERATION_FLAG_NONE, NULL, add_contact_ready_cb, lookup);
296 	} else if (lookup->op == E_CONTACT_MERGING_COMMIT)
297 		e_book_client_modify_contact (lookup->book_client, lookup->contact, E_BOOK_OPERATION_FLAG_NONE, NULL, modify_contact_ready_cb, lookup);
298 }
299 
300 static void
cancelit(EContactMergingLookup * lookup)301 cancelit (EContactMergingLookup *lookup)
302 {
303 	GError *error;
304 
305 	error = g_error_new_literal (
306 		G_IO_ERROR, G_IO_ERROR_CANCELLED, _("Cancelled"));
307 
308 	if (lookup->op == E_CONTACT_MERGING_ADD) {
309 		final_id_cb (lookup->book_client, error, NULL, lookup);
310 	} else if (lookup->op == E_CONTACT_MERGING_COMMIT) {
311 		final_cb (lookup->book_client, error, lookup);
312 	}
313 
314 	g_error_free (error);
315 }
316 
317 static void
dialog_map(GtkWidget * window,GdkEvent * event,GtkWidget * grid)318 dialog_map (GtkWidget *window,
319             GdkEvent *event,
320             GtkWidget *grid)
321 {
322 	GtkAllocation allocation;
323 	gint h, w;
324 
325 	gtk_widget_get_allocation (grid, &allocation);
326 
327 	/* Spacing around the grid */
328 	w = allocation.width + 30;
329 	/* buttons and outer spacing */
330 	h = allocation.height + 60;
331 	if (w > 400)
332 		w = 400;
333 	if (h > 450)
334 		h = 450;
335 
336 	gtk_widget_set_size_request (window, w, h);
337 }
338 
339 static void
dropdown_changed(GtkWidget * dropdown,dropdown_data * data)340 dropdown_changed (GtkWidget *dropdown,
341                   dropdown_data *data)
342 {
343 	gchar *str = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (dropdown));
344 
345 	if (str && *str)
346 		e_contact_set (data->match, data->field, str);
347 	else
348 		e_contact_set (data->match, data->field, NULL);
349 
350 	g_free (str);
351 }
352 
353 static void
attr_dropdown_changed(GtkWidget * dropdown,dropdown_data * data)354 attr_dropdown_changed (GtkWidget *dropdown,
355 			dropdown_data *data)
356 {
357 	gchar *str = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (dropdown));
358 
359 	if (str && *str)
360 		data->email_attr_list_item->data = data->email_attr;
361 	else
362 		data->email_attr_list_item->data = NULL;
363 
364 	g_free (str);
365 }
366 
367 static void
remove_contact_ready_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)368 remove_contact_ready_cb (GObject *source_object,
369                          GAsyncResult *result,
370                          gpointer user_data)
371 {
372 	EBookClient *book_client = E_BOOK_CLIENT (source_object);
373 	EContactMergingLookup *lookup = user_data;
374 	GError *error = NULL;
375 
376 	g_return_if_fail (book_client != NULL);
377 	g_return_if_fail (lookup != NULL);
378 
379 	e_book_client_remove_contact_finish (book_client, result, &error);
380 
381 	if (error != NULL) {
382 		g_warning (
383 			"%s: Failed to remove contact: %s",
384 			G_STRFUNC, error->message);
385 		g_error_free (error);
386 	}
387 
388 	e_book_client_add_contact (
389 		book_client, lookup->contact, E_BOOK_OPERATION_FLAG_NONE, NULL,
390 		add_contact_ready_cb, lookup);
391 }
392 
393 static void
create_dropdowns_for_multival_attr(GList * match_attr_list,GList * contact_attr_list,GList ** use_attr_list,gint * row,GtkGrid * grid,const gchar * (* label_str)(EVCardAttribute *))394 create_dropdowns_for_multival_attr (GList *match_attr_list,
395 				    GList *contact_attr_list,
396 				    GList **use_attr_list,
397 				    gint *row,
398 				    GtkGrid *grid,
399 				    const gchar * (*label_str) (EVCardAttribute*))
400 {
401 	GtkWidget *label, *dropdown;
402 	GList *miter, *citer;
403 	GHashTable *match_attrs; /* attr in the 'match' contact from address book */
404 
405 	match_attrs = g_hash_table_new_full (camel_strcase_hash, camel_strcase_equal, g_free, NULL);
406 
407 	for (miter = match_attr_list; miter; miter = g_list_next (miter)) {
408 		EVCardAttribute *attr = miter->data;
409 		gchar *value;
410 
411 		value = e_vcard_attribute_get_value (attr);
412 		if (value && *value) {
413 			g_hash_table_insert (match_attrs, value, attr);
414 			*use_attr_list = g_list_prepend (*use_attr_list, attr);
415 		} else {
416 			g_free (value);
417 		}
418 	}
419 
420 	*use_attr_list = g_list_reverse (*use_attr_list);
421 
422 	for (citer = contact_attr_list; citer; citer = g_list_next (citer)) {
423 		EVCardAttribute *attr = citer->data;
424 		gchar *value;
425 
426 		value = e_vcard_attribute_get_value (attr);
427 		if (value && *value) {
428 			if (!g_hash_table_lookup (match_attrs, value)) {
429 				dropdown_data *data;
430 
431 				/* the attr is not set in both contacts */
432 				*use_attr_list = g_list_append (*use_attr_list, attr);
433 
434 				/* remove to avoid collisions with match UI_SLOTs */
435 				e_vcard_attribute_remove_param (attr, EVOLUTION_UI_SLOT_PARAM);
436 
437 				(*row)++;
438 				label = gtk_label_new (label_str (attr));
439 				gtk_grid_attach (grid, label, 0, *row, 1, 1);
440 
441 				dropdown = gtk_combo_box_text_new ();
442 				gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (dropdown), value);
443 
444 				data = g_new0 (dropdown_data, 1);
445 
446 				gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (dropdown), "");
447 
448 				gtk_combo_box_set_active (GTK_COMBO_BOX (dropdown), 0);
449 
450 				data->email_attr_list_item = g_list_last (*use_attr_list);
451 				data->email_attr = attr;
452 
453 				g_signal_connect (
454 					dropdown, "changed",
455 					G_CALLBACK (attr_dropdown_changed), data);
456 				g_object_set_data_full (G_OBJECT (dropdown), "eab-contact-merging::dropdown-data", data, g_free);
457 
458 				gtk_grid_attach (grid, dropdown, 1, *row, 1, 1);
459 			}
460 		}
461 		g_free (value);
462 	}
463 	g_hash_table_destroy (match_attrs);
464 }
465 
466 static void
set_attributes(EContact * contact,EContactField field,GList * use_attr_list)467 set_attributes(EContact *contact, EContactField field, GList *use_attr_list)
468 {
469 	GList *miter, *citer;
470 
471 	citer = NULL;
472 	for (miter = use_attr_list; miter; miter = g_list_next (miter)) {
473 		if (miter->data)
474 			citer = g_list_prepend (citer, miter->data);
475 	}
476 	citer = g_list_reverse (citer);
477 	e_contact_set_attributes (contact, field, citer);
478 	g_list_free (citer);
479 }
480 
481 static MergeDialogData *
merge_dialog_data_create(EContactMergingLookup * lookup,GtkWidget * parent)482 merge_dialog_data_create (EContactMergingLookup *lookup,
483 			  GtkWidget *parent)
484 {
485 	GtkWidget *scrolled_window, *label, *dropdown;
486 	GtkWidget *content_area;
487 	GtkGrid *grid;
488 	EContactField field;
489 	gchar *string = NULL, *string1 = NULL;
490 	MergeDialogData *mdd;
491 
492 	mdd = g_slice_new0 (MergeDialogData);
493 	mdd->row = -1;
494 
495 	mdd->dialog = gtk_dialog_new ();
496 	gtk_window_set_title (GTK_WINDOW (mdd->dialog), _("Merge Contact"));
497 	gtk_container_set_border_width (GTK_CONTAINER (mdd->dialog), 5);
498 	if (GTK_IS_WINDOW (parent))
499 		gtk_window_set_transient_for (GTK_WINDOW (mdd->dialog), GTK_WINDOW (parent));
500 
501 	content_area = gtk_dialog_get_content_area (GTK_DIALOG (mdd->dialog));
502 
503 	scrolled_window = gtk_scrolled_window_new (NULL, NULL);
504 	gtk_scrolled_window_set_policy (
505 		GTK_SCROLLED_WINDOW (scrolled_window),
506 		GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
507 
508 	grid = GTK_GRID (gtk_grid_new ());
509 	g_object_set (G_OBJECT (grid),
510 		"border-width", 12,
511 		"row-spacing", 6,
512 		"column-spacing", 2,
513 		NULL);
514 
515 	gtk_dialog_add_buttons (
516 		GTK_DIALOG (mdd->dialog),
517 		_("_Cancel"), GTK_RESPONSE_CANCEL,
518 		_("_Merge"), GTK_RESPONSE_OK,
519 		NULL);
520 
521 	/*we match all the string fields of the already existing contact and the new contact.*/
522 	for (field = E_CONTACT_FULL_NAME; field != (E_CONTACT_LAST_SIMPLE_STRING -1); field++) {
523 		dropdown_data *data = NULL;
524 		string = (gchar *) e_contact_get_const (lookup->contact, field);
525 		string1 = (gchar *) e_contact_get_const (lookup->match, field);
526 
527 		/*the field must exist in the new as well as the duplicate contact*/
528 		if (string && *string) {
529 			if ((field >= E_CONTACT_FIRST_EMAIL_ID && field <= E_CONTACT_LAST_EMAIL_ID) ||
530 			    (field >= E_CONTACT_FIRST_PHONE_ID && field <= E_CONTACT_LAST_PHONE_ID) ||
531 			    (field >= E_CONTACT_IM_AIM_HOME_1 && field <= E_CONTACT_IM_ICQ_WORK_3) ||
532 			    (field >= E_CONTACT_IM_GADUGADU_HOME_1 && field <= E_CONTACT_IM_GADUGADU_WORK_3) ||
533 			    (field >= E_CONTACT_IM_SKYPE_HOME_1 && field <= E_CONTACT_IM_SKYPE_WORK_3) ||
534 			    (field >= E_CONTACT_IM_GOOGLE_TALK_HOME_1 && field <= E_CONTACT_IM_GOOGLE_TALK_WORK_3) ||
535 			    (field >= E_CONTACT_IM_MATRIX_HOME_1 && field <= E_CONTACT_IM_MATRIX_WORK_3)) {
536 				/* ignore multival attributes, they are compared after this for-loop */
537 				continue;
538 			}
539 
540 			if (!(string1 && *string1) || (g_ascii_strcasecmp (string, string1))) {
541 				mdd->row++;
542 				label = gtk_label_new (e_contact_pretty_name (field));
543 				gtk_grid_attach (grid, label, 0, mdd->row, 1, 1);
544 				data = g_new0 (dropdown_data, 1);
545 				dropdown = gtk_combo_box_text_new ();
546 				gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (dropdown), string);
547 
548 				if (string1 && *string1)
549 					gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (dropdown), string1);
550 				else
551 					gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (dropdown), "");
552 
553 				data->field = field;
554 				data->match = lookup->match;
555 
556 				g_signal_connect (
557 					dropdown, "changed",
558 					G_CALLBACK (dropdown_changed), data);
559 				g_object_set_data_full (G_OBJECT (dropdown), "eab-contact-merging::dropdown-data", data, g_free);
560 
561 				/* Only prefer the original value when it's filled */
562 				if (string1 && *string1 && (
563 				    field == E_CONTACT_NICKNAME ||
564 				    field == E_CONTACT_GIVEN_NAME ||
565 				    field == E_CONTACT_FAMILY_NAME ||
566 				    field == E_CONTACT_FULL_NAME))
567 					gtk_combo_box_set_active (GTK_COMBO_BOX (dropdown), 1);
568 				else
569 					gtk_combo_box_set_active (GTK_COMBO_BOX (dropdown), 0);
570 
571 				gtk_grid_attach (grid, dropdown, 1, mdd->row, 1, 1);
572 			}
573 		}
574 	}
575 
576 	mdd->match_email_attr_list = e_contact_get_attributes (lookup->match, E_CONTACT_EMAIL);
577 	mdd->contact_email_attr_list = e_contact_get_attributes (lookup->contact, E_CONTACT_EMAIL);
578 	mdd->use_email_attr_list = NULL;
579 	create_dropdowns_for_multival_attr (mdd->match_email_attr_list, mdd->contact_email_attr_list,
580 	                                   &(mdd->use_email_attr_list), &(mdd->row), grid, eab_get_email_label_text);
581 
582 	mdd->match_tel_attr_list = e_contact_get_attributes (lookup->match, E_CONTACT_TEL);
583 	mdd->contact_tel_attr_list = e_contact_get_attributes (lookup->contact, E_CONTACT_TEL);
584 	mdd->use_tel_attr_list = NULL;
585 	create_dropdowns_for_multival_attr (mdd->match_tel_attr_list, mdd->contact_tel_attr_list,
586 	                                   &(mdd->use_tel_attr_list), &(mdd->row), grid, eab_get_phone_label_text);
587 
588 	mdd->match_sip_attr_list = e_contact_get_attributes (lookup->match, E_CONTACT_SIP);
589 	mdd->contact_sip_attr_list = e_contact_get_attributes (lookup->contact, E_CONTACT_SIP);
590 	mdd->use_sip_attr_list = NULL;
591 	create_dropdowns_for_multival_attr (mdd->match_sip_attr_list, mdd->contact_sip_attr_list,
592 	                                   &(mdd->use_sip_attr_list), &(mdd->row), grid, eab_get_sip_label_text);
593 
594 	mdd->match_im_attr_list = e_contact_get_attributes_set (lookup->match, im_fetch_set, G_N_ELEMENTS (im_fetch_set));
595 	mdd->contact_im_attr_list = e_contact_get_attributes_set (lookup->contact, im_fetch_set, G_N_ELEMENTS (im_fetch_set));
596 	mdd->use_im_attr_list = NULL;
597 	create_dropdowns_for_multival_attr (mdd->match_im_attr_list, mdd->contact_im_attr_list,
598 	                                   &(mdd->use_im_attr_list), &(mdd->row), grid, eab_get_im_label_text);
599 
600 	gtk_window_set_default_size (GTK_WINDOW (mdd->dialog), 420, 300);
601 	gtk_container_add (GTK_CONTAINER (scrolled_window), GTK_WIDGET (grid));
602 	gtk_box_pack_start (GTK_BOX (content_area), GTK_WIDGET (scrolled_window), TRUE, TRUE, 0);
603 	gtk_widget_show (scrolled_window);
604 	g_signal_connect (
605 		mdd->dialog, "map-event",
606 		G_CALLBACK (dialog_map), grid);
607 	gtk_widget_show_all (GTK_WIDGET (grid));
608 
609 	return mdd;
610 }
611 
612 static gint
mergeit(EContactMergingLookup * lookup,GtkWidget * parent)613 mergeit (EContactMergingLookup *lookup,
614 	 GtkWidget *parent)
615 {
616 	GList *ll;
617 	gint value = 0, result, ii;
618 
619 	if (!lookup->merge_dialog_data)
620 		lookup->merge_dialog_data = merge_dialog_data_create (lookup, parent);
621 
622 	if (lookup->merge_dialog_data->row == -1)
623 		result = GTK_RESPONSE_OK;
624 	else
625 		result = gtk_dialog_run (GTK_DIALOG (lookup->merge_dialog_data->dialog));
626 
627 	switch (result) {
628 	case GTK_RESPONSE_OK:
629 		set_attributes (lookup->match, E_CONTACT_EMAIL, lookup->merge_dialog_data->use_email_attr_list);
630 		set_attributes (lookup->match, E_CONTACT_TEL, lookup->merge_dialog_data->use_tel_attr_list);
631 		set_attributes (lookup->match, E_CONTACT_SIP, lookup->merge_dialog_data->use_sip_attr_list);
632 
633 		for (ii = 0; ii < G_N_ELEMENTS (im_fetch_set); ii++) {
634 			e_contact_set_attributes (lookup->match, im_fetch_set[ii], NULL);
635 		}
636 
637 		for (ll = lookup->merge_dialog_data->use_im_attr_list; ll; ll = ll->next) {
638 			EVCard *vcard;
639 			vcard = E_VCARD (lookup->match);
640 			e_vcard_append_attribute (vcard, e_vcard_attribute_copy ((EVCardAttribute *) ll->data));
641 		}
642 
643 		g_object_unref (lookup->contact);
644 		lookup->contact = g_object_ref (lookup->match);
645 		e_book_client_remove_contact (
646 			lookup->book_client,
647 			lookup->match, E_BOOK_OPERATION_FLAG_NONE, NULL,
648 			remove_contact_ready_cb, lookup);
649 		value = 1;
650 		break;
651 	case GTK_RESPONSE_CANCEL:
652 	default:
653 		value = 0;
654 		break;
655 	}
656 	gtk_widget_hide (lookup->merge_dialog_data->dialog);
657 
658 	return value;
659 }
660 
661 static gboolean
check_if_same(EContact * contact,EContact * match)662 check_if_same (EContact *contact,
663                EContact *match)
664 {
665 	EContactField field;
666 	gchar *string = NULL, *string1 = NULL;
667 	gboolean res = TRUE;
668 
669 
670 	for (field = E_CONTACT_FULL_NAME; res && field != (E_CONTACT_LAST_SIMPLE_STRING -1); field++) {
671 
672 		if (field == E_CONTACT_EMAIL_1) {
673 			GList *email_attr_list1, *email_attr_list2, *iter1, *iter2;
674 			gint num_of_email1, num_of_email2;
675 
676 			email_attr_list1 = e_contact_get_attributes (contact, E_CONTACT_EMAIL);
677 			num_of_email1 = g_list_length (email_attr_list1);
678 
679 			email_attr_list2 = e_contact_get_attributes (match, E_CONTACT_EMAIL);
680 			num_of_email2 = g_list_length (email_attr_list2);
681 
682 			if (num_of_email1 != num_of_email2) {
683 				res = FALSE;
684 			} else { /* Do pairwise-comparisons on all of the e-mail addresses. */
685 				iter1 = email_attr_list1;
686 				while (iter1) {
687 					gboolean         matches = FALSE;
688 					EVCardAttribute *attr;
689 					gchar           *email_address1;
690 
691 					attr = iter1->data;
692 					email_address1 = e_vcard_attribute_get_value (attr);
693 
694 					iter2 = email_attr_list2;
695 					while ( iter2 && matches == FALSE) {
696 						gchar *email_address2;
697 
698 						attr = iter2->data;
699 						email_address2 = e_vcard_attribute_get_value (attr);
700 
701 						if (g_ascii_strcasecmp (email_address1, email_address2) == 0) {
702 							matches = TRUE;
703 						}
704 
705 						g_free (email_address2);
706 						iter2 = g_list_next (iter2);
707 					}
708 
709 					g_free (email_address1);
710 					iter1 = g_list_next (iter1);
711 
712 					if (matches == FALSE) {
713 						res = FALSE;
714 						break;
715 					}
716 				}
717 			}
718 
719 			g_list_free_full (email_attr_list1, (GDestroyNotify) e_vcard_attribute_free);
720 			g_list_free_full (email_attr_list2, (GDestroyNotify) e_vcard_attribute_free);
721 		} else if (field > E_CONTACT_FIRST_EMAIL_ID && field <= E_CONTACT_LAST_EMAIL_ID) {
722 			/* nothing to do, all emails are checked above */
723 		}
724 		else {
725 			string = (gchar *) e_contact_get_const (contact, field);
726 			string1 = (gchar *) e_contact_get_const (match, field);
727 			if ((string && *string) && (string1 && *string1) && (g_ascii_strcasecmp (string1, string))) {
728 				res = FALSE;
729 				break;
730 			/*if the field entry exist in either of the contacts,we'll have to give the choice and thus merge button should be sensitive*/
731 			} else if ((string && *string) && !(string1 && *string1)) {
732 				res = FALSE;
733 				break;
734 			}
735 		}
736 	}
737 
738 	return res;
739 }
740 
741 static GtkWidget *
create_duplicate_contact_detected_dialog(EContact * old_contact,EContact * new_contact,gboolean disable_merge,gboolean is_for_commit)742 create_duplicate_contact_detected_dialog (EContact *old_contact,
743                                           EContact *new_contact,
744                                           gboolean disable_merge,
745                                           gboolean is_for_commit)
746 {
747 	GtkWidget *widget, *scrolled;
748 	GtkContainer *container;
749 	GtkDialog *dialog;
750 	const gchar *text;
751 
752 	widget = gtk_dialog_new ();
753 	dialog = GTK_DIALOG (widget);
754 
755 	g_object_set (
756 		G_OBJECT (dialog),
757 		"title", _("Duplicate Contact Detected"),
758 		"default-width", 500,
759 		"default-height", 400,
760 		NULL);
761 
762 	gtk_dialog_add_action_widget (dialog, e_dialog_button_new_with_icon ("process-stop", _("_Cancel")), GTK_RESPONSE_CANCEL);
763 
764 	if (is_for_commit) {
765 		gtk_dialog_add_action_widget (dialog, e_dialog_button_new_with_icon ("document-save", _("_Save")), GTK_RESPONSE_OK);
766 	} else {
767 		gtk_dialog_add_action_widget (dialog, e_dialog_button_new_with_icon ("list-add", _("_Add")), GTK_RESPONSE_OK);
768 		gtk_dialog_add_action_widget (dialog, e_dialog_button_new_with_icon (NULL, _("_Merge")), GTK_RESPONSE_APPLY);
769 	}
770 
771 	if (disable_merge)
772 		gtk_dialog_set_response_sensitive (dialog, GTK_RESPONSE_APPLY, FALSE);
773 
774 	container = GTK_CONTAINER (gtk_dialog_get_content_area (dialog));
775 
776 	widget = gtk_grid_new ();
777 	g_object_set (
778 		G_OBJECT (widget),
779 		"orientation", GTK_ORIENTATION_HORIZONTAL,
780 		"hexpand", TRUE,
781 		"halign", GTK_ALIGN_FILL,
782 		"vexpand", TRUE,
783 		"valign", GTK_ALIGN_FILL,
784 		"margin", 12,
785 		NULL);
786 
787 	gtk_container_add (container, widget);
788 	container = GTK_CONTAINER (widget);
789 
790 	widget = gtk_image_new_from_icon_name ("avatar-default", GTK_ICON_SIZE_BUTTON);
791 	g_object_set (
792 		G_OBJECT (widget),
793 		"hexpand", FALSE,
794 		"halign", GTK_ALIGN_START,
795 		"vexpand", FALSE,
796 		"valign", GTK_ALIGN_START,
797 		"margin-right", 12,
798 		NULL);
799 	gtk_container_add (container, widget);
800 
801 	widget = gtk_grid_new ();
802 	g_object_set (
803 		G_OBJECT (widget),
804 		"orientation", GTK_ORIENTATION_VERTICAL,
805 		"hexpand", TRUE,
806 		"halign", GTK_ALIGN_FILL,
807 		"vexpand", TRUE,
808 		"valign", GTK_ALIGN_FILL,
809 		NULL);
810 
811 	gtk_container_add (container, widget);
812 	container = GTK_CONTAINER (widget);
813 
814 	if (is_for_commit)
815 		text = _("The name or email address of this contact already exists\n"
816 			 "in this folder. Would you like to save the changes anyway?");
817 	else
818 		text = _("The name or email address of this contact already exists\n"
819 			 "in this folder. Would you like to add it anyway?");
820 
821 	widget = gtk_label_new (text);
822 	g_object_set (
823 		G_OBJECT (widget),
824 		"hexpand", FALSE,
825 		"halign", GTK_ALIGN_START,
826 		"vexpand", FALSE,
827 		"valign", GTK_ALIGN_START,
828 		"margin-bottom", 6,
829 		NULL);
830 	gtk_container_add (container, widget);
831 
832 	if (is_for_commit)
833 		text = _("Changed Contact:");
834 	else
835 		text = _("New Contact:");
836 
837 	widget = gtk_label_new (text);
838 	g_object_set (
839 		G_OBJECT (widget),
840 		"hexpand", FALSE,
841 		"halign", GTK_ALIGN_START,
842 		"vexpand", FALSE,
843 		"valign", GTK_ALIGN_START,
844 		"margin-bottom", 6,
845 		NULL);
846 	gtk_container_add (container, widget);
847 
848 	scrolled = gtk_scrolled_window_new (NULL, NULL);
849 	g_object_set (
850 		G_OBJECT (scrolled),
851 		"hexpand", TRUE,
852 		"halign", GTK_ALIGN_FILL,
853 		"hscrollbar-policy", GTK_POLICY_AUTOMATIC,
854 		"vexpand", TRUE,
855 		"valign", GTK_ALIGN_FILL,
856 		"vscrollbar-policy", GTK_POLICY_AUTOMATIC,
857 		"margin-bottom", 6,
858 		NULL);
859 	gtk_container_add (container, scrolled);
860 
861 	widget = eab_contact_display_new ();
862 	g_object_set (
863 		G_OBJECT (widget),
864 		"hexpand", TRUE,
865 		"halign", GTK_ALIGN_FILL,
866 		"vexpand", TRUE,
867 		"valign", GTK_ALIGN_FILL,
868 		"contact", new_contact,
869 		"mode", EAB_CONTACT_DISPLAY_RENDER_COMPACT,
870 		NULL);
871 	gtk_container_add (GTK_CONTAINER (scrolled), widget);
872 
873 	if (is_for_commit)
874 		text = _("Conflicting Contact:");
875 	else
876 		text = _("Old Contact:");
877 
878 	widget = gtk_label_new (text);
879 	g_object_set (
880 		G_OBJECT (widget),
881 		"hexpand", FALSE,
882 		"halign", GTK_ALIGN_START,
883 		"vexpand", FALSE,
884 		"valign", GTK_ALIGN_START,
885 		"margin-bottom", 6,
886 		NULL);
887 	gtk_container_add (container, widget);
888 
889 	scrolled = gtk_scrolled_window_new (NULL, NULL);
890 	g_object_set (
891 		G_OBJECT (scrolled),
892 		"hexpand", TRUE,
893 		"halign", GTK_ALIGN_FILL,
894 		"hscrollbar-policy", GTK_POLICY_AUTOMATIC,
895 		"vexpand", TRUE,
896 		"valign", GTK_ALIGN_FILL,
897 		"vscrollbar-policy", GTK_POLICY_AUTOMATIC,
898 		NULL);
899 	gtk_container_add (container, scrolled);
900 
901 	widget = eab_contact_display_new ();
902 	g_object_set (
903 		G_OBJECT (widget),
904 		"hexpand", TRUE,
905 		"halign", GTK_ALIGN_FILL,
906 		"vexpand", TRUE,
907 		"valign", GTK_ALIGN_FILL,
908 		"contact", old_contact,
909 		"mode", EAB_CONTACT_DISPLAY_RENDER_COMPACT,
910 		NULL);
911 	gtk_container_add (GTK_CONTAINER (scrolled), widget);
912 
913 	gtk_widget_show_all (gtk_dialog_get_content_area (dialog));
914 
915 	return GTK_WIDGET (dialog);
916 }
917 
918 static void
response(GtkWidget * dialog,gint response,EContactMergingLookup * lookup)919 response (GtkWidget *dialog,
920           gint response,
921           EContactMergingLookup *lookup)
922 {
923 	switch (response) {
924 	case GTK_RESPONSE_OK:
925 		doit (lookup, FALSE);
926 		break;
927 	case GTK_RESPONSE_CANCEL:
928 		cancelit (lookup);
929 		break;
930 	case GTK_RESPONSE_APPLY:
931 		if (mergeit (lookup, dialog))
932 			break;
933 		return;
934 	case GTK_RESPONSE_DELETE_EVENT:
935 		cancelit (lookup);
936 		break;
937 	default:
938 		g_warn_if_reached ();
939 		break;
940 	}
941 
942 	gtk_widget_destroy (dialog);
943 }
944 
945 static void
match_query_callback(EContact * contact,EContact * match,EABContactMatchType type,gpointer closure)946 match_query_callback (EContact *contact,
947                       EContact *match,
948                       EABContactMatchType type,
949                       gpointer closure)
950 {
951 	EContactMergingLookup *lookup = closure;
952 	gboolean flag;
953 	gboolean same_uids;
954 
955 	if (lookup->op == E_CONTACT_MERGING_FIND) {
956 		if (lookup->c_cb)
957 			lookup->c_cb (
958 				lookup->book_client, NULL,
959 				(gint) type <= (gint)
960 				EAB_CONTACT_MATCH_VAGUE ? NULL : match,
961 				lookup->closure);
962 
963 		free_lookup (lookup);
964 		finished_lookup ();
965 		return;
966 	}
967 
968 	/* if had same UID, then we are editing old contact, thus force commit change to it */
969 	same_uids = contact && match
970 		&& e_contact_get_const (contact, E_CONTACT_UID)
971 		&& e_contact_get_const (match, E_CONTACT_UID)
972 		&& g_str_equal (e_contact_get_const (contact, E_CONTACT_UID), e_contact_get_const (match, E_CONTACT_UID));
973 
974 	if ((gint) type <= (gint) EAB_CONTACT_MATCH_VAGUE || same_uids) {
975 		doit (lookup, same_uids);
976 	} else {
977 		GtkWidget *dialog;
978 
979 		lookup->match = g_object_ref (match);
980 		if (lookup->op == E_CONTACT_MERGING_ADD) {
981 			/* Compares all the values of contacts and return true, if they match */
982 			flag = check_if_same (contact, match);
983 			dialog = create_duplicate_contact_detected_dialog (match, contact, flag, FALSE);
984 		} else if (lookup->op == E_CONTACT_MERGING_COMMIT) {
985 			dialog = create_duplicate_contact_detected_dialog (match, contact, FALSE, TRUE);
986 		} else {
987 			doit (lookup, FALSE);
988 			return;
989 		}
990 
991 		g_signal_connect (
992 			dialog, "response",
993 			G_CALLBACK (response), lookup);
994 
995 		gtk_widget_show_all (dialog);
996 	}
997 }
998 
999 gboolean
eab_merging_book_add_contact(ESourceRegistry * registry,EBookClient * book_client,EContact * contact,EABMergingIdAsyncCallback cb,gpointer closure)1000 eab_merging_book_add_contact (ESourceRegistry *registry,
1001                               EBookClient *book_client,
1002                               EContact *contact,
1003                               EABMergingIdAsyncCallback cb,
1004                               gpointer closure)
1005 {
1006 	EContactMergingLookup *lookup;
1007 
1008 	g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), FALSE);
1009 
1010 	lookup = new_lookup ();
1011 
1012 	lookup->op = E_CONTACT_MERGING_ADD;
1013 	lookup->registry = g_object_ref (registry);
1014 	lookup->book_client = g_object_ref (book_client);
1015 	lookup->contact = g_object_ref (contact);
1016 	lookup->id_cb = cb;
1017 	lookup->closure = closure;
1018 	lookup->avoid = NULL;
1019 	lookup->match = NULL;
1020 
1021 	add_lookup (lookup);
1022 
1023 	return TRUE;
1024 }
1025 
1026 gboolean
eab_merging_book_modify_contact(ESourceRegistry * registry,EBookClient * book_client,EContact * contact,EABMergingAsyncCallback cb,gpointer closure)1027 eab_merging_book_modify_contact (ESourceRegistry *registry,
1028                                  EBookClient *book_client,
1029                                  EContact *contact,
1030                                  EABMergingAsyncCallback cb,
1031                                  gpointer closure)
1032 {
1033 	EContactMergingLookup *lookup;
1034 
1035 	g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), FALSE);
1036 
1037 	lookup = new_lookup ();
1038 
1039 	lookup->op = E_CONTACT_MERGING_COMMIT;
1040 	lookup->registry = g_object_ref (registry);
1041 	lookup->book_client = g_object_ref (book_client);
1042 	lookup->contact = g_object_ref (contact);
1043 	lookup->cb = cb;
1044 	lookup->closure = closure;
1045 	lookup->avoid = g_list_append (NULL, contact);
1046 	lookup->match = NULL;
1047 
1048 	add_lookup (lookup);
1049 
1050 	return TRUE;
1051 }
1052 
1053 gboolean
eab_merging_book_find_contact(ESourceRegistry * registry,EBookClient * book_client,EContact * contact,EABMergingContactAsyncCallback cb,gpointer closure)1054 eab_merging_book_find_contact (ESourceRegistry *registry,
1055                                EBookClient *book_client,
1056                                EContact *contact,
1057                                EABMergingContactAsyncCallback cb,
1058                                gpointer closure)
1059 {
1060 	EContactMergingLookup *lookup;
1061 
1062 	lookup = new_lookup ();
1063 
1064 	lookup->op = E_CONTACT_MERGING_FIND;
1065 	lookup->registry = g_object_ref (registry);
1066 	lookup->book_client = g_object_ref (book_client);
1067 	lookup->contact = g_object_ref (contact);
1068 	lookup->c_cb = cb;
1069 	lookup->closure = closure;
1070 	lookup->avoid = g_list_append (NULL, contact);
1071 	lookup->match = NULL;
1072 
1073 	add_lookup (lookup);
1074 
1075 	return TRUE;
1076 }
1077