evolution-3.6.4/addressbook/gui/merging/eab-contact-compare.c

No issues found

  1 /*
  2  * This program is free software; you can redistribute it and/or
  3  * modify it under the terms of the GNU Lesser General Public
  4  * License as published by the Free Software Foundation; either
  5  * version 2 of the License, or (at your option) version 3.
  6  *
  7  * This program is distributed in the hope that it will be useful,
  8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 10  * Lesser General Public License for more details.
 11  *
 12  * You should have received a copy of the GNU Lesser General Public
 13  * License along with the program; if not, see <http://www.gnu.org/licenses/>
 14  *
 15  *
 16  * Authors:
 17  *		Jon Trowbridge <trow@ximian.com>
 18  *      Chris Toshok <toshok@ximian.com>
 19  *
 20  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
 21  *
 22  */
 23 
 24 #ifdef HAVE_CONFIG_H
 25 #include <config.h>
 26 #endif
 27 
 28 #include <ctype.h>
 29 #include <string.h>
 30 
 31 #include <libedataserverui/libedataserverui.h>
 32 
 33 #include "addressbook/util/eab-book-util.h"
 34 #include "eab-contact-compare.h"
 35 
 36 /* This is an "optimistic" combiner: the best of the two outcomes is
 37  * selected. */
 38 static EABContactMatchType
 39 combine_comparisons (EABContactMatchType prev,
 40                      EABContactMatchType new_info)
 41 {
 42 	if (new_info == EAB_CONTACT_MATCH_NOT_APPLICABLE)
 43 		return prev;
 44 	return (EABContactMatchType) MAX ((gint) prev, (gint) new_info);
 45 }
 46 
 47 /*** Name comparisons ***/
 48 
 49 /* This *so* doesn't belong here... at least not implemented in a
 50  * sucky way like this.  But it can be fixed later. */
 51 
 52 /* This is very Anglocentric. */
 53 static const gchar *name_synonyms[][2] = {
 54 	{ "jon", "john" },   /* Ah, the hacker's perogative */
 55 	{ "joseph", "joe" },
 56 	{ "robert", "bob" },
 57 	{ "gene", "jean" },
 58 	{ "jesse", "jessie" },
 59 	{ "ian", "iain" },
 60 	{ "richard", "dick" },
 61 	{ "william", "bill" },
 62 	{ "william", "will" },
 63 	{ "anthony", "tony" },
 64 	{ "michael", "mike" },
 65 	{ "eric", "erik" },
 66 	{ "elizabeth", "liz" },
 67 	{ "jeff", "geoff" },
 68 	{ "jeff", "geoffrey" },
 69 	{ "tom", "thomas" },
 70 	{ "dave", "david" },
 71 	{ "jim", "james" },
 72 	{ "abigal", "abby" },
 73 	{ "amanda", "amy" },
 74 	{ "amanda", "manda" },
 75 	{ "jennifer", "jenny" },
 76 	{ "christopher", "chris" },
 77 	{ "rebecca", "becca" },
 78 	{ "rebecca", "becky" },
 79 	{ "anderson", "andersen" },
 80 	{ "johnson", "johnsen" },
 81 	/* We could go on and on... */
 82 	/* We should add soundex here. */
 83 	{ NULL, NULL }
 84 };
 85 
 86 static gboolean
 87 name_fragment_match_with_synonyms (const gchar *a,
 88                                    const gchar *b,
 89                                    gboolean strict)
 90 {
 91 	gint i;
 92 
 93 	if (!(a && b && *a && *b))
 94 		return FALSE;
 95 
 96 	if (!e_utf8_casefold_collate (a, b))
 97 		return TRUE;
 98 
 99 	/* Check for nicknames.  Yes, the linear search blows. */
100 	for (i = 0; name_synonyms[i][0]; ++i) {
101 
102 		if (!e_utf8_casefold_collate (name_synonyms[i][0], a)
103 		    && !e_utf8_casefold_collate (name_synonyms[i][1], b))
104 			return TRUE;
105 
106 		if (!e_utf8_casefold_collate (name_synonyms[i][0], b)
107 		    && !e_utf8_casefold_collate (name_synonyms[i][1], a))
108 			return TRUE;
109 	}
110 
111 	return FALSE;
112 }
113 
114 EABContactMatchType
115 eab_contact_compare_name_to_string (EContact *contact,
116                                     const gchar *str)
117 {
118 	return eab_contact_compare_name_to_string_full (contact, str, FALSE, NULL, NULL, NULL);
119 }
120 
121 EABContactMatchType
122 eab_contact_compare_name_to_string_full (EContact *contact,
123                                          const gchar *str,
124                                          gboolean allow_partial_matches,
125                                          gint *matched_parts_out,
126                                          EABContactMatchPart *first_matched_part_out,
127                                          gint *matched_character_count_out)
128 {
129 	gchar **namev, **givenv = NULL, **addv = NULL, **familyv = NULL;
130 
131 	gint matched_parts = EAB_CONTACT_MATCH_PART_NONE;
132 	EABContactMatchPart first_matched_part = EAB_CONTACT_MATCH_PART_NONE;
133 	EABContactMatchPart this_part_match = EAB_CONTACT_MATCH_PART_NOT_APPLICABLE;
134 	EABContactMatchType match_type;
135 	EContactName *contact_name;
136 
137 	gint match_count = 0, matched_character_count = 0, fragment_count;
138 	gint i, j;
139 	gchar *str_cpy, *s;
140 
141 	g_return_val_if_fail (E_IS_CONTACT (contact), EAB_CONTACT_MATCH_NOT_APPLICABLE);
142 
143 	if (!e_contact_get_const (contact, E_CONTACT_FULL_NAME))
144 		return EAB_CONTACT_MATCH_NOT_APPLICABLE;
145 	if (str == NULL)
146 		return EAB_CONTACT_MATCH_NOT_APPLICABLE;
147 
148 	str_cpy = s = g_strdup (str);
149 	while (*s) {
150 		if (*s == ',' || *s == '"')
151 			*s = ' ';
152 		++s;
153 	}
154 	namev   = g_strsplit (str_cpy, " ", 0);
155 	g_free (str_cpy);
156 
157 	contact_name = e_contact_get (contact, E_CONTACT_NAME);
158 
159 	if (contact_name->given)
160 		givenv = g_strsplit (contact_name->given, " ", 0);
161 	if (contact_name->additional)
162 		addv = g_strsplit (contact_name->additional, " ", 0);
163 	if (contact_name->family)
164 		familyv = g_strsplit (contact_name->family, " ", 0);
165 
166 	e_contact_name_free (contact_name);
167 
168 	fragment_count = 0;
169 	for (i = 0; givenv && givenv[i]; ++i)
170 		++fragment_count;
171 	for (i = 0; addv && addv[i]; ++i)
172 		++fragment_count;
173 	for (i = 0; familyv && familyv[i]; ++i)
174 		++fragment_count;
175 
176 	for (i = 0; namev[i] && this_part_match != EAB_CONTACT_MATCH_PART_NONE; ++i) {
177 
178 		if (*namev[i]) {
179 
180 			this_part_match = EAB_CONTACT_MATCH_PART_NONE;
181 
182 			/* When we are allowing partials, we are strict about the matches we allow.
183 			 * Does this make sense?  Not really, but it does the right thing for the purposes
184 			 * of completion. */
185 
186 			if (givenv && this_part_match == EAB_CONTACT_MATCH_PART_NONE) {
187 				for (j = 0; givenv[j]; ++j) {
188 					if (name_fragment_match_with_synonyms (givenv[j], namev[i], allow_partial_matches)) {
189 
190 						this_part_match = EAB_CONTACT_MATCH_PART_GIVEN_NAME;
191 
192 						/* We remove a piece of a name once it has been matched against, so
193 						 * that "john john" won't match "john doe". */
194 						g_free (givenv[j]);
195 						givenv[j] = g_strdup ("");
196 						break;
197 					}
198 				}
199 			}
200 
201 			if (addv && this_part_match == EAB_CONTACT_MATCH_PART_NONE) {
202 				for (j = 0; addv[j]; ++j) {
203 					if (name_fragment_match_with_synonyms (addv[j], namev[i], allow_partial_matches)) {
204 
205 						this_part_match = EAB_CONTACT_MATCH_PART_ADDITIONAL_NAME;
206 
207 						g_free (addv[j]);
208 						addv[j] = g_strdup ("");
209 						break;
210 					}
211 				}
212 			}
213 
214 			if (familyv && this_part_match == EAB_CONTACT_MATCH_PART_NONE) {
215 				for (j = 0; familyv[j]; ++j) {
216 					if (allow_partial_matches ? name_fragment_match_with_synonyms (familyv[j], namev[i], allow_partial_matches)
217 					    : !e_utf8_casefold_collate (familyv[j], namev[i])) {
218 
219 						this_part_match = EAB_CONTACT_MATCH_PART_FAMILY_NAME;
220 
221 						g_free (familyv[j]);
222 						familyv[j] = g_strdup ("");
223 						break;
224 					}
225 				}
226 			}
227 
228 			if (this_part_match != EAB_CONTACT_MATCH_PART_NONE) {
229 				++match_count;
230 				matched_character_count += g_utf8_strlen (namev[i], -1);
231 				matched_parts |= this_part_match;
232 				if (first_matched_part == EAB_CONTACT_MATCH_PART_NONE)
233 					first_matched_part = this_part_match;
234 			}
235 		}
236 	}
237 
238 	match_type = EAB_CONTACT_MATCH_NONE;
239 
240 	if (this_part_match != EAB_CONTACT_MATCH_PART_NONE) {
241 
242 		if (match_count > 0)
243 			match_type = EAB_CONTACT_MATCH_VAGUE;
244 
245 		if (fragment_count == match_count) {
246 
247 			match_type = EAB_CONTACT_MATCH_EXACT;
248 
249 		} else if (fragment_count == match_count + 1) {
250 
251 			match_type = EAB_CONTACT_MATCH_PARTIAL;
252 
253 		}
254 	}
255 
256 	if (matched_parts_out)
257 		*matched_parts_out = matched_parts;
258 	if (first_matched_part_out)
259 		*first_matched_part_out = first_matched_part;
260 	if (matched_character_count_out)
261 		*matched_character_count_out = matched_character_count;
262 
263 	g_strfreev (namev);
264 	g_strfreev (givenv);
265 	g_strfreev (addv);
266 	g_strfreev (familyv);
267 
268 	return match_type;
269 }
270 
271 EABContactMatchType
272 eab_contact_compare_file_as (EContact *contact1,
273                              EContact *contact2)
274 {
275 	EABContactMatchType match_type;
276 	gchar *a, *b;
277 
278 	g_return_val_if_fail (E_IS_CONTACT (contact1), EAB_CONTACT_MATCH_NOT_APPLICABLE);
279 	g_return_val_if_fail (E_IS_CONTACT (contact2), EAB_CONTACT_MATCH_NOT_APPLICABLE);
280 
281 	a = e_contact_get (contact1, E_CONTACT_FILE_AS);
282 	b = e_contact_get (contact2, E_CONTACT_FILE_AS);
283 
284 	if (a == NULL || b == NULL) {
285 		g_free (a);
286 		g_free (b);
287 		return EAB_CONTACT_MATCH_NOT_APPLICABLE;
288 	}
289 
290 	if (!strcmp (a, b))
291 		match_type = EAB_CONTACT_MATCH_EXACT;
292 	else if (g_utf8_validate (a, -1, NULL) && g_utf8_validate (b, -1, NULL) &&
293 		 !g_utf8_collate (a, b))
294 		match_type = EAB_CONTACT_MATCH_PARTIAL;
295 	else
296 		match_type = EAB_CONTACT_MATCH_NONE;
297 
298 	g_free (a);
299 	g_free (b);
300 	return match_type;
301 }
302 
303 EABContactMatchType
304 eab_contact_compare_name (EContact *contact1,
305                           EContact *contact2)
306 {
307 	EContactName *a, *b;
308 	gint matches = 0, possible = 0;
309 	gboolean family_match = FALSE;
310 
311 	g_return_val_if_fail (E_IS_CONTACT (contact1), EAB_CONTACT_MATCH_NOT_APPLICABLE);
312 	g_return_val_if_fail (E_IS_CONTACT (contact2), EAB_CONTACT_MATCH_NOT_APPLICABLE);
313 
314 	a = e_contact_get (contact1, E_CONTACT_NAME);
315 	b = e_contact_get (contact2, E_CONTACT_NAME);
316 
317 	if (a == NULL || b == NULL) {
318 		g_free (a);
319 		g_free (b);
320 		return EAB_CONTACT_MATCH_NOT_APPLICABLE;
321 	}
322 
323 	if (a->given && b->given && *a->given && *b->given) {
324 		++possible;
325 		if (name_fragment_match_with_synonyms (a->given, b->given, FALSE /* both inputs are complete */)) {
326 			++matches;
327 		}
328 	}
329 
330 	if (a->additional && b->additional && *a->additional && *b->additional) {
331 		++possible;
332 		if (name_fragment_match_with_synonyms (a->additional, b->additional, FALSE /* both inputs are complete */)) {
333 			++matches;
334 		}
335 	}
336 
337 	if (a->family && b->family && *a->family && *b->family) {
338 		++possible;
339 		/* We don't allow "loose matching" (i.e. John vs. Jon) on family names */
340 		if (!e_utf8_casefold_collate (a->family, b->family)) {
341 			++matches;
342 			family_match = TRUE;
343 		}
344 	}
345 
346 	e_contact_name_free (a);
347 	e_contact_name_free (b);
348 
349 	/* Now look at the # of matches and try to intelligently map
350 	 * an EAB_CONTACT_MATCH_* type to it.  Special consideration is given
351 	 * to family-name matches. */
352 
353 	if (possible == 0)
354 		return EAB_CONTACT_MATCH_NOT_APPLICABLE;
355 
356 	if (possible == 1)
357 		return family_match ? EAB_CONTACT_MATCH_VAGUE : EAB_CONTACT_MATCH_NONE;
358 
359 	if (possible == matches)
360 		return family_match ? EAB_CONTACT_MATCH_EXACT : EAB_CONTACT_MATCH_PARTIAL;
361 
362 	if (possible == matches + 1)
363 		return family_match ? EAB_CONTACT_MATCH_VAGUE : EAB_CONTACT_MATCH_NONE;
364 
365 	return EAB_CONTACT_MATCH_NONE;
366 }
367 
368 /*** Nickname Comparisons ***/
369 
370 EABContactMatchType
371 eab_contact_compare_nickname (EContact *contact1,
372                               EContact *contact2)
373 {
374 	g_return_val_if_fail (contact1 && E_IS_CONTACT (contact1), EAB_CONTACT_MATCH_NOT_APPLICABLE);
375 	g_return_val_if_fail (contact2 && E_IS_CONTACT (contact2), EAB_CONTACT_MATCH_NOT_APPLICABLE);
376 
377 	return EAB_CONTACT_MATCH_NOT_APPLICABLE;
378 }
379 
380 /*** E-mail Comparisons ***/
381 
382 static gboolean
383 match_email_username (const gchar *addr1,
384                       const gchar *addr2)
385 {
386 	gint c1, c2;
387 	if (addr1 == NULL || addr2 == NULL)
388 		return FALSE;
389 
390 	while (*addr1 && *addr2 && *addr1 != '@' && *addr2 != '@') {
391 		c1 = isupper (*addr1) ? tolower (*addr1) : *addr1;
392 		c2 = isupper (*addr2) ? tolower (*addr2) : *addr2;
393 		if (c1 != c2)
394 			return FALSE;
395 		++addr1;
396 		++addr2;
397 	}
398 
399 	return *addr1 == *addr2;
400 }
401 
402 static gboolean
403 match_email_hostname (const gchar *addr1,
404                       const gchar *addr2)
405 {
406 	gint c1, c2;
407 	gboolean seen_at1, seen_at2;
408 	if (addr1 == NULL || addr2 == NULL)
409 		return FALSE;
410 
411 	/* Walk to the end of each string. */
412 	seen_at1 = FALSE;
413 	if (*addr1) {
414 		while (*addr1) {
415 			if (*addr1 == '@')
416 				seen_at1 = TRUE;
417 			++addr1;
418 		}
419 		--addr1;
420 	}
421 
422 	seen_at2 = FALSE;
423 	if (*addr2) {
424 		while (*addr2) {
425 			if (*addr2 == '@')
426 				seen_at2 = TRUE;
427 			++addr2;
428 		}
429 		--addr2;
430 	}
431 
432 	if (!seen_at1 && !seen_at2)
433 		return TRUE;
434 	if (!seen_at1 || !seen_at2)
435 		return FALSE;
436 
437 	while (*addr1 != '@' && *addr2 != '@') {
438 		c1 = isupper (*addr1) ? tolower (*addr1) : *addr1;
439 		c2 = isupper (*addr2) ? tolower (*addr2) : *addr2;
440 		if (c1 != c2)
441 			return FALSE;
442 		--addr1;
443 		--addr2;
444 	}
445 	if ((*addr1 == '@' && *addr2 != '@') || (*addr2 == '@' && *addr1 != '@'))
446 	       return FALSE;
447 
448 	return TRUE;
449 }
450 
451 static EABContactMatchType
452 compare_email_addresses (const gchar *addr1,
453                          const gchar *addr2)
454 {
455 	if (addr1 == NULL || *addr1 == 0 ||
456 	    addr2 == NULL || *addr2 == 0)
457 		return EAB_CONTACT_MATCH_NOT_APPLICABLE;
458 
459 	if (match_email_username (addr1, addr2))
460 		return match_email_hostname (addr1, addr2) ? EAB_CONTACT_MATCH_EXACT : EAB_CONTACT_MATCH_VAGUE;
461 
462 	return EAB_CONTACT_MATCH_NONE;
463 }
464 
465 EABContactMatchType
466 eab_contact_compare_email (EContact *contact1,
467                            EContact *contact2)
468 {
469 	EABContactMatchType match = EAB_CONTACT_MATCH_NOT_APPLICABLE;
470 	GList *contact1_email, *contact2_email;
471 	GList *i1, *i2;
472 
473 	g_return_val_if_fail (contact1 && E_IS_CONTACT (contact1), EAB_CONTACT_MATCH_NOT_APPLICABLE);
474 	g_return_val_if_fail (contact2 && E_IS_CONTACT (contact2), EAB_CONTACT_MATCH_NOT_APPLICABLE);
475 
476 	contact1_email = e_contact_get (contact1, E_CONTACT_EMAIL);
477 	contact2_email = e_contact_get (contact2, E_CONTACT_EMAIL);
478 
479 	if (contact1_email == NULL || contact2_email == NULL) {
480 		g_list_foreach (contact1_email, (GFunc) g_free, NULL);
481 		g_list_free (contact1_email);
482 
483 		g_list_foreach (contact2_email, (GFunc) g_free, NULL);
484 		g_list_free (contact2_email);
485 		return EAB_CONTACT_MATCH_NOT_APPLICABLE;
486 	}
487 
488 	i1 = contact1_email;
489 
490 	/* Do pairwise-comparisons on all of the e-mail addresses.  If
491 	 * we find an exact match, there is no reason to keep
492 	 * checking. */
493 	while (i1 && match != EAB_CONTACT_MATCH_EXACT) {
494 		gchar *addr1 = (gchar *) i1->data;
495 
496 		i2 = contact2_email;
497 		while (i2 && match != EAB_CONTACT_MATCH_EXACT) {
498 			gchar *addr2 = (gchar *) i2->data;
499 
500 			match = combine_comparisons (match, compare_email_addresses (addr1, addr2));
501 
502 			i2 = i2->next;
503 		}
504 
505 		i1 = i1->next;
506 	}
507 
508 	g_list_foreach (contact1_email, (GFunc) g_free, NULL);
509 	g_list_free (contact1_email);
510 
511 	g_list_foreach (contact2_email, (GFunc) g_free, NULL);
512 	g_list_free (contact2_email);
513 
514 	return match;
515 }
516 
517 EABContactMatchType
518 eab_contact_compare_address (EContact *contact1,
519                              EContact *contact2)
520 {
521 	g_return_val_if_fail (contact1 && E_IS_CONTACT (contact1), EAB_CONTACT_MATCH_NOT_APPLICABLE);
522 	g_return_val_if_fail (contact2 && E_IS_CONTACT (contact2), EAB_CONTACT_MATCH_NOT_APPLICABLE);
523 
524 	/* Unimplemented */
525 
526 	return EAB_CONTACT_MATCH_NOT_APPLICABLE;
527 }
528 
529 EABContactMatchType
530 eab_contact_compare_telephone (EContact *contact1,
531                                EContact *contact2)
532 {
533 	g_return_val_if_fail (contact1 && E_IS_CONTACT (contact1), EAB_CONTACT_MATCH_NOT_APPLICABLE);
534 	g_return_val_if_fail (contact2 && E_IS_CONTACT (contact2), EAB_CONTACT_MATCH_NOT_APPLICABLE);
535 
536 	/* Unimplemented */
537 
538 	return EAB_CONTACT_MATCH_NOT_APPLICABLE;
539 }
540 
541 EABContactMatchType
542 eab_contact_compare (EContact *contact1,
543                      EContact *contact2)
544 {
545 	EABContactMatchType result;
546 
547 	g_return_val_if_fail (contact1 && E_IS_CONTACT (contact1), EAB_CONTACT_MATCH_NOT_APPLICABLE);
548 	g_return_val_if_fail (contact2 && E_IS_CONTACT (contact2), EAB_CONTACT_MATCH_NOT_APPLICABLE);
549 
550 	result = EAB_CONTACT_MATCH_NONE;
551 	if (!e_contact_get (contact1, E_CONTACT_IS_LIST)) {
552 		result = combine_comparisons (result, eab_contact_compare_name      (contact1, contact2));
553 		result = combine_comparisons (result, eab_contact_compare_nickname  (contact1, contact2));
554 		if (!e_contact_get (contact2, E_CONTACT_IS_LIST))
555 			result = combine_comparisons (result, eab_contact_compare_email (contact1, contact2));
556 		result = combine_comparisons (result, eab_contact_compare_address   (contact1, contact2));
557 		result = combine_comparisons (result, eab_contact_compare_telephone (contact1, contact2));
558 	}
559 	result = combine_comparisons (result, eab_contact_compare_file_as   (contact1, contact2));
560 
561 	return result;
562 }
563 
564 typedef struct _MatchSearchInfo MatchSearchInfo;
565 struct _MatchSearchInfo {
566 	EContact *contact;
567 	GList *avoid;
568 	EABContactMatchQueryCallback cb;
569 	gpointer closure;
570 };
571 
572 static void
573 match_search_info_free (MatchSearchInfo *info)
574 {
575 	if (info) {
576 		g_object_unref (info->contact);
577 
578 		/* This should already have been deallocated, but just in case... */
579 		if (info->avoid) {
580 			g_list_foreach (info->avoid, (GFunc) g_object_unref, NULL);
581 			g_list_free (info->avoid);
582 			info->avoid = NULL;
583 		}
584 
585 		g_free (info);
586 	}
587 }
588 
589 static void
590 query_cb (GObject *source_object,
591           GAsyncResult *result,
592           gpointer user_data)
593 {
594 	MatchSearchInfo *info = (MatchSearchInfo *) user_data;
595 	EABContactMatchType best_match = EAB_CONTACT_MATCH_NONE;
596 	EContact *best_contact = NULL;
597 	EBookClient *book_client = E_BOOK_CLIENT (source_object);
598 	GSList *remaining_contacts = NULL;
599 	GSList *contacts = NULL;
600 	GError *error = NULL;
601 	const GSList *ii;
602 
603 	if (result != NULL)
604 		e_book_client_get_contacts_finish (
605 			book_client, result, &contacts, &error);
606 
607 	if (error != NULL) {
608 		g_warning (
609 			"%s: Failed to get contacts: %s\n",
610 			G_STRFUNC, error->message);
611 		g_error_free (error);
612 
613 		info->cb (
614 			info->contact, NULL,
615 			EAB_CONTACT_MATCH_NONE,
616 			info->closure);
617 
618 		match_search_info_free (info);
619 		g_object_unref (book_client);
620 		return;
621 	}
622 
623 	/* remove the contacts we're to avoid from the list, if they're present */
624 	for (ii = contacts; ii != NULL; ii = g_slist_next (ii)) {
625 		EContact *this_contact = E_CONTACT (ii->data);
626 		const gchar *this_uid;
627 		GList *iterator;
628 		gboolean avoid = FALSE;
629 
630 		this_uid = e_contact_get_const (this_contact, E_CONTACT_UID);
631 		if (!this_uid)
632 			continue;
633 
634 		for (iterator = info->avoid; iterator; iterator = iterator->next) {
635 			const gchar *avoid_uid;
636 
637 			avoid_uid = e_contact_get_const (iterator->data, E_CONTACT_UID);
638 			if (!avoid_uid)
639 				continue;
640 
641 			if (!strcmp (avoid_uid, this_uid)) {
642 				avoid = TRUE;
643 				break;
644 			}
645 		}
646 		if (!avoid)
647 			remaining_contacts = g_slist_prepend (remaining_contacts, g_object_ref (this_contact));
648 	}
649 
650 	remaining_contacts = g_slist_reverse (remaining_contacts);
651 
652 	for (ii = remaining_contacts; ii != NULL; ii = g_slist_next (ii)) {
653 		EContact *this_contact = E_CONTACT (ii->data);
654 		EABContactMatchType this_match = eab_contact_compare (info->contact, this_contact);
655 		if ((gint) this_match > (gint) best_match) {
656 			best_match = this_match;
657 			best_contact = this_contact;
658 		}
659 	}
660 
661 	if (best_contact)
662 		best_contact = g_object_ref (best_contact);
663 
664 	e_client_util_free_object_slist (contacts);
665 	e_client_util_free_object_slist (remaining_contacts);
666 
667 	info->cb (info->contact, best_contact, best_match, info->closure);
668 	match_search_info_free (info);
669 	g_object_unref (book_client);
670 	if (best_contact)
671 		g_object_unref (best_contact);
672 }
673 
674 #define MAX_QUERY_PARTS 10
675 static void
676 use_common_book_client (EBookClient *book_client,
677                         MatchSearchInfo *info)
678 {
679 	EContact *contact = info->contact;
680 	EContactName *contact_name;
681 	GList *contact_email;
682 	gchar *query_parts[MAX_QUERY_PARTS];
683 	gint p = 0;
684 	gchar *contact_file_as, *qj;
685 	EBookQuery *query = NULL;
686 	gint i;
687 
688 	if (book_client == NULL) {
689 		info->cb (info->contact, NULL, EAB_CONTACT_MATCH_NONE, info->closure);
690 		match_search_info_free (info);
691 		return;
692 	}
693 
694 	contact_file_as = e_contact_get (contact, E_CONTACT_FILE_AS);
695 	if (contact_file_as) {
696 		query_parts[p++] = g_strdup_printf ("(contains \"file_as\" \"%s\")", contact_file_as);
697 		g_free (contact_file_as);
698 	}
699 
700 	if (!e_contact_get (contact, E_CONTACT_IS_LIST)) {
701 		contact_name = e_contact_get (contact, E_CONTACT_NAME);
702 		if (contact_name) {
703 			if (contact_name->given && *contact_name->given)
704 				query_parts[p++] = g_strdup_printf ("(contains \"full_name\" \"%s\")", contact_name->given);
705 
706 			if (contact_name->additional && *contact_name->additional)
707 				query_parts[p++] = g_strdup_printf ("(contains \"full_name\" \"%s\")", contact_name->additional);
708 
709 			if (contact_name->family && *contact_name->family)
710 				query_parts[p++] = g_strdup_printf ("(contains \"full_name\" \"%s\")", contact_name->family);
711 
712 			e_contact_name_free (contact_name);
713 		}
714 
715 		contact_email = e_contact_get (contact, E_CONTACT_EMAIL);
716 		if (contact_email) {
717 			GList *iter;
718 			for (iter = contact_email; iter && p < MAX_QUERY_PARTS; iter = iter->next) {
719 				gchar *addr = g_strdup (iter->data);
720 				if (addr && *addr) {
721 					gchar *s = addr;
722 					while (*s) {
723 						if (*s == '@') {
724 							*s = '\0';
725 							break;
726 						}
727 						++s;
728 					}
729 					query_parts[p++] = g_strdup_printf ("(beginswith \"email\" \"%s\")", addr);
730 					g_free (addr);
731 				}
732 			}
733 		}
734 		g_list_foreach (contact_email, (GFunc) g_free, NULL);
735 		g_list_free (contact_email);
736 	}
737 
738 	/* Build up our full query from the parts. */
739 	query_parts[p] = NULL;
740 	qj = g_strjoinv (" ", query_parts);
741 	for (i = 0; query_parts[i] != NULL; i++)
742 		g_free (query_parts[i]);
743 	if (p > 1) {
744 		gchar *s;
745 		s = g_strdup_printf ("(or %s)", qj);
746 		query = e_book_query_from_string (s);
747 		g_free (s);
748 	}
749 	else if (p == 1) {
750 		query = e_book_query_from_string (qj);
751 	}
752 	else {
753 		query = NULL;
754 	}
755 
756 	if (query) {
757 		gchar *query_str = e_book_query_to_string (query);
758 
759 		e_book_client_get_contacts (book_client, query_str, NULL, query_cb, info);
760 
761 		g_free (query_str);
762 	} else
763 		query_cb (G_OBJECT (book_client), NULL, info);
764 
765 	g_free (qj);
766 	if (query)
767 		e_book_query_unref (query);
768 }
769 
770 static void
771 book_loaded_cb (GObject *source_object,
772                 GAsyncResult *result,
773                 gpointer user_data)
774 {
775 	ESource *source = E_SOURCE (source_object);
776 	MatchSearchInfo *info = user_data;
777 	EClient *client = NULL;
778 
779 	e_client_utils_open_new_finish (source, result, &client, NULL);
780 
781 	/* Client may be NULL; don't use a type cast macro. */
782 	use_common_book_client ((EBookClient *) client, info);
783 }
784 
785 void
786 eab_contact_locate_match (ESourceRegistry *registry,
787                           EContact *contact,
788                           EABContactMatchQueryCallback cb,
789                           gpointer closure)
790 {
791 	eab_contact_locate_match_full (
792 		registry, NULL, contact, NULL, cb, closure);
793 }
794 
795 /**
796  * e_contact_locate_match_full:
797  * @registry: an #ESourceRegistry
798  * @book: The book to look in.  If this is NULL, use the default
799  * addressbook.
800  * @contact: The contact to compare to.
801  * @avoid: A list of contacts to not match.  These will not show up in the search.
802  * @cb: The function to call.
803  * @closure: The closure to add to the call.
804  *
805  * Look for the best match and return it using the EABContactMatchQueryCallback.
806  **/
807 void
808 eab_contact_locate_match_full (ESourceRegistry *registry,
809                                EBookClient *book_client,
810                                EContact *contact,
811                                GList *avoid,
812                                EABContactMatchQueryCallback cb,
813                                gpointer closure)
814 {
815 	MatchSearchInfo *info;
816 	ESource *source;
817 
818 	g_return_if_fail (E_IS_SOURCE_REGISTRY (registry));
819 	g_return_if_fail (E_IS_CONTACT (contact));
820 	g_return_if_fail (cb != NULL);
821 
822 	info = g_new0 (MatchSearchInfo, 1);
823 	info->contact = g_object_ref (contact);
824 	info->cb = cb;
825 	info->closure = closure;
826 	info->avoid = g_list_copy (avoid);
827 	g_list_foreach (info->avoid, (GFunc) g_object_ref, NULL);
828 
829 	if (book_client) {
830 		use_common_book_client (g_object_ref (book_client), info);
831 		return;
832 	}
833 
834 	source = e_source_registry_ref_default_address_book (registry);
835 
836 	e_client_utils_open_new (
837 		source, E_CLIENT_SOURCE_TYPE_CONTACTS, FALSE, NULL,
838 		book_loaded_cb, info);
839 
840 	g_object_unref (source);
841 }