diff options
Diffstat (limited to 'addressbook/gui/merging')
-rw-r--r-- | addressbook/gui/merging/.cvsignore | 1 | ||||
-rw-r--r-- | addressbook/gui/merging/Makefile.am | 17 | ||||
-rw-r--r-- | addressbook/gui/merging/e-card-merging.c | 198 | ||||
-rw-r--r-- | addressbook/gui/merging/e-card-merging.h | 29 | ||||
-rw-r--r-- | addressbook/gui/merging/eab-contact-commit-duplicate-detected.glade (renamed from addressbook/gui/merging/e-card-merging-book-commit-duplicate-detected.glade) | 12 | ||||
-rw-r--r-- | addressbook/gui/merging/eab-contact-compare.c | 736 | ||||
-rw-r--r-- | addressbook/gui/merging/eab-contact-compare.h | 73 | ||||
-rw-r--r-- | addressbook/gui/merging/eab-contact-duplicate-detected.glade (renamed from addressbook/gui/merging/e-card-duplicate-detected.glade) | 12 | ||||
-rw-r--r-- | addressbook/gui/merging/eab-contact-merging.c | 197 | ||||
-rw-r--r-- | addressbook/gui/merging/eab-contact-merging.h | 30 |
10 files changed, 1059 insertions, 246 deletions
diff --git a/addressbook/gui/merging/.cvsignore b/addressbook/gui/merging/.cvsignore index d6c55c7345..b6fa83ca4c 100644 --- a/addressbook/gui/merging/.cvsignore +++ b/addressbook/gui/merging/.cvsignore @@ -5,3 +5,4 @@ Makefile Makefile.in *.lo *.la +*.gladep diff --git a/addressbook/gui/merging/Makefile.am b/addressbook/gui/merging/Makefile.am index f30edd5745..4627f30594 100644 --- a/addressbook/gui/merging/Makefile.am +++ b/addressbook/gui/merging/Makefile.am @@ -1,7 +1,8 @@ INCLUDES = \ - -DG_LOG_DOMAIN=\"e-card-gui\" \ + -DG_LOG_DOMAIN=\"eab-contact-merging\" \ -DEVOLUTION_GLADEDIR=\""$(gladedir)"\" \ -I$(top_srcdir) \ + -I$(top_srcdir)/addressbook \ -I$(top_srcdir)/addressbook/backend \ -I$(top_builddir)/addressbook/backend \ -DG_DISABLE_DEPRECATED \ @@ -11,15 +12,17 @@ INCLUDES = \ $(EVOLUTION_ADDRESSBOOK_CFLAGS) noinst_LTLIBRARIES = \ - libecardmerging.la + libeabbookmerging.la -libecardmerging_la_SOURCES = \ - e-card-merging.c \ - e-card-merging.h +libeabbookmerging_la_SOURCES = \ + eab-contact-compare.c \ + eab-contact-compare.h \ + eab-contact-merging.c \ + eab-contact-merging.h -glade_DATA = e-card-duplicate-detected.glade \ - e-card-merging-book-commit-duplicate-detected.glade +glade_DATA = eab-contact-duplicate-detected.glade \ + eab-contact-commit-duplicate-detected.glade EXTRA_DIST = \ $(glade_DATA) diff --git a/addressbook/gui/merging/e-card-merging.c b/addressbook/gui/merging/e-card-merging.c deleted file mode 100644 index f2322e772c..0000000000 --- a/addressbook/gui/merging/e-card-merging.c +++ /dev/null @@ -1,198 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* - * Code for checking for duplicates when doing ECard work. - * - * Author: - * Christopher James Lahey <clahey@ximian.com> - * - * Copyright 2001, Ximian, Inc. - */ - -#include <config.h> - -#include "e-card-merging.h" -#include <ebook/e-card-compare.h> -#include <glade/glade.h> -#include <gtk/gtksignal.h> -#include "addressbook/gui/widgets/e-minicard-widget.h" - -typedef enum { - E_CARD_MERGING_ADD, - E_CARD_MERGING_COMMIT -} ECardMergingOpType; - -typedef struct { - ECardMergingOpType op; - EBook *book; - ECard *card; - EBookIdCallback id_cb; - EBookCallback cb; - gpointer closure; -} ECardMergingLookup; - -static void -free_lookup (ECardMergingLookup *lookup) -{ - g_object_unref (lookup->book); - g_object_unref (lookup->card); - - g_free (lookup); -} - -static void -final_id_cb (EBook *book, EBookStatus status, const char *id, gpointer closure) -{ - ECardMergingLookup *lookup = closure; - - if (lookup->id_cb) - lookup->id_cb (lookup->book, status, id, lookup->closure); - - free_lookup (lookup); -} - -static void -final_cb (EBook *book, EBookStatus status, gpointer closure) -{ - ECardMergingLookup *lookup = closure; - - if (lookup->cb) - lookup->cb (lookup->book, status, lookup->closure); - - free_lookup (lookup); -} - -static void -doit (ECardMergingLookup *lookup) -{ - if (lookup->op == E_CARD_MERGING_ADD) - e_book_add_card (lookup->book, lookup->card, final_id_cb, lookup); - else if (lookup->op == E_CARD_MERGING_COMMIT) - e_book_commit_card (lookup->book, lookup->card, final_cb, lookup); -} - -static void -cancelit (ECardMergingLookup *lookup) -{ - if (lookup->op == E_CARD_MERGING_ADD) { - if (lookup->id_cb) - final_id_cb (lookup->book, E_BOOK_STATUS_CANCELLED, NULL, lookup); - } else if (lookup->op == E_CARD_MERGING_COMMIT) { - if (lookup->cb) - final_cb (lookup->book, E_BOOK_STATUS_CANCELLED, lookup); - } -} - -static void -response (GtkWidget *dialog, int response, ECardMergingLookup *lookup) -{ - gtk_widget_destroy (dialog); - - switch (response) { - case 0: - doit (lookup); - break; - case 1: - cancelit (lookup); - break; - } -} - -static void -match_query_callback (ECard *card, ECard *match, ECardMatchType type, gpointer closure) -{ - ECardMergingLookup *lookup = closure; - - if ((gint) type <= (gint) E_CARD_MATCH_VAGUE) { - doit (lookup); - } else { - GladeXML *ui; - - GtkWidget *widget; - - if (lookup->op == E_CARD_MERGING_ADD) - ui = glade_xml_new (EVOLUTION_GLADEDIR "/e-card-duplicate-detected.glade", NULL, NULL); - else if (lookup->op == E_CARD_MERGING_COMMIT) - ui = glade_xml_new (EVOLUTION_GLADEDIR "/e-card-merging-book-commit-duplicate-detected.glade", NULL, NULL); - else { - doit (lookup); - return; - } - - widget = glade_xml_get_widget (ui, "custom-old-card"); - g_object_set (widget, - "card", match, - NULL); - - widget = glade_xml_get_widget (ui, "custom-new-card"); - g_object_set (widget, - "card", card, - NULL); - - widget = glade_xml_get_widget (ui, "dialog-duplicate-contact"); - - g_signal_connect (widget, "response", - G_CALLBACK (response), lookup); - - gtk_widget_show_all (widget); - } -} - -gboolean -e_card_merging_book_add_card (EBook *book, - ECard *card, - EBookIdCallback cb, - gpointer closure) -{ - ECardMergingLookup *lookup; - - lookup = g_new (ECardMergingLookup, 1); - - lookup->op = E_CARD_MERGING_ADD; - lookup->book = g_object_ref (book); - lookup->card = g_object_ref (card); - lookup->id_cb = cb; - lookup->closure = closure; - - e_card_locate_match_full (book, card, NULL, match_query_callback, lookup); - - return TRUE; -} - -gboolean -e_card_merging_book_commit_card (EBook *book, - ECard *card, - EBookCallback cb, - gpointer closure) -{ - ECardMergingLookup *lookup; - GList *avoid; - - lookup = g_new (ECardMergingLookup, 1); - - lookup->op = E_CARD_MERGING_COMMIT; - lookup->book = g_object_ref (book); - lookup->card = g_object_ref (card); - lookup->cb = cb; - lookup->closure = closure; - - avoid = g_list_append (NULL, card); - - e_card_locate_match_full (book, card, avoid, match_query_callback, lookup); - - g_list_free (avoid); - - return TRUE; -} - -GtkWidget * -e_card_merging_create_old_card(gchar *name, - gchar *string1, gchar *string2, - gint int1, gint int2); - -GtkWidget * -e_card_merging_create_old_card(gchar *name, - gchar *string1, gchar *string2, - gint int1, gint int2) -{ - return e_minicard_widget_new (); -} diff --git a/addressbook/gui/merging/e-card-merging.h b/addressbook/gui/merging/e-card-merging.h deleted file mode 100644 index 8ea1f9f018..0000000000 --- a/addressbook/gui/merging/e-card-merging.h +++ /dev/null @@ -1,29 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* - * The Evolution addressbook client object. - * - * Author: - * Christopher James Lahey <clahey@ximian.com> - * - * Copyright 2001, Ximian, Inc. - */ - -#ifndef __E_CARD_MERGING_H__ -#define __E_CARD_MERGING_H__ - -#include <addressbook/backend/ebook/e-book.h> - -G_BEGIN_DECLS - -gboolean e_card_merging_book_add_card (EBook *book, - ECard *card, - EBookIdCallback cb, - gpointer closure); -gboolean e_card_merging_book_commit_card (EBook *book, - ECard *card, - EBookCallback cb, - gpointer closure); - -G_END_DECLS - -#endif /* ! __E_CARD_MERGING_H__ */ diff --git a/addressbook/gui/merging/e-card-merging-book-commit-duplicate-detected.glade b/addressbook/gui/merging/eab-contact-commit-duplicate-detected.glade index f7cfd259e5..789c465d92 100644 --- a/addressbook/gui/merging/e-card-merging-book-commit-duplicate-detected.glade +++ b/addressbook/gui/merging/eab-contact-commit-duplicate-detected.glade @@ -68,12 +68,12 @@ <property name="column_spacing">6</property> <child> - <widget class="Custom" id="custom-old-card"> + <widget class="Custom" id="custom-old-contact"> <property name="visible">True</property> - <property name="creation_function">e_card_merging_create_old_card</property> + <property name="creation_function">_eab_contact_merging_create_contact_display</property> <property name="int1">0</property> <property name="int2">0</property> - <property name="last_modification_time">Fri, 08 Jun 2001 01:33:22 GMT</property> + <property name="last_modification_time">Sun, 05 Oct 2003 03:54:20 GMT</property> </widget> <packing> <property name="left_attach">1</property> @@ -155,12 +155,12 @@ exists in this folder. Would you like to add it anyway?</property> </child> <child> - <widget class="Custom" id="custom-new-card"> + <widget class="Custom" id="custom-new-contact"> <property name="visible">True</property> - <property name="creation_function">e_card_merging_create_old_card</property> + <property name="creation_function">_eab_contact_merging_create_contact_display</property> <property name="int1">0</property> <property name="int2">0</property> - <property name="last_modification_time">Fri, 08 Jun 2001 01:33:22 GMT</property> + <property name="last_modification_time">Sun, 05 Oct 2003 03:53:42 GMT</property> </widget> <packing> <property name="left_attach">1</property> diff --git a/addressbook/gui/merging/eab-contact-compare.c b/addressbook/gui/merging/eab-contact-compare.c new file mode 100644 index 0000000000..409b1bce81 --- /dev/null +++ b/addressbook/gui/merging/eab-contact-compare.c @@ -0,0 +1,736 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + +/* + * eab-contact-compare.c + * + * Copyright (C) 2001, 2002, 2003 Ximian, Inc. + * + * Authors: Jon Trowbridge <trow@ximian.com> + * Chris Toshok <toshok@ximian.com> + */ + +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA. + */ + +#include <config.h> +#include <ctype.h> +#include <string.h> +#include "util/eab-book-util.h" +#include "eab-contact-compare.h" + +/* This is an "optimistic" combiner: the best of the two outcomes is + selected. */ +static EABContactMatchType +combine_comparisons (EABContactMatchType prev, + EABContactMatchType new_info) +{ + if (new_info == EAB_CONTACT_MATCH_NOT_APPLICABLE) + return prev; + return (EABContactMatchType) MAX ((gint) prev, (gint) new_info); +} + + +/*** Name comparisons ***/ + +/* This *so* doesn't belong here... at least not implemented in a + sucky way like this. But it can be fixed later. */ + +/* This is very Anglocentric. */ +static gchar *name_synonyms[][2] = { + { "jon", "john" }, /* Ah, the hacker's perogative */ + { "joseph", "joe" }, + { "robert", "bob" }, + { "gene", "jean" }, + { "jesse", "jessie" }, + { "ian", "iain" }, + { "richard", "dick" }, + { "william", "bill" }, + { "william", "will" }, + { "anthony", "tony" }, + { "michael", "mike" }, + { "eric", "erik" }, + { "elizabeth", "liz" }, + { "jeff", "geoff" }, + { "jeff", "geoffrey" }, + { "tom", "thomas" }, + { "dave", "david" }, + { "jim", "james" }, + { "abigal", "abby" }, + { "amanda", "amy" }, + { "amanda", "manda" }, + { "jennifer", "jenny" }, + { "christopher", "chris" }, + { "rebecca", "becca" }, + { "rebecca", "becky" }, + { "anderson", "andersen" }, + { "johnson", "johnsen" }, + /* We could go on and on... */ + /* We should add soundex here. */ + { NULL, NULL } +}; + +static gboolean +name_fragment_match (const gchar *a, const gchar *b, gboolean strict) +{ + gint len; + + if (!(a && b && *a && *b)) + return FALSE; + + /* If we are in 'strict' mode, b must match the beginning of a. + So "Robert", "Rob" would match, but "Robert", "Robbie" wouldn't. + + If strict is FALSE, it is sufficient for the strings to share + some leading characters. In this case, "Robert" and "Robbie" + would match, as would "Dave" and "Dan". */ + + if (strict) { + len = g_utf8_strlen (b, -1); + } else { + len = MIN (g_utf8_strlen (a, -1), g_utf8_strlen (b, -1)); + } + + return !e_utf8_casefold_collate_len (a, b, len); +} + +static gboolean +name_fragment_match_with_synonyms (const gchar *a, const gchar *b, gboolean strict) +{ + gint i; + + if (!(a && b && *a && *b)) + return FALSE; + + if (name_fragment_match (a, b, strict)) + return TRUE; + + /* Check for nicknames. Yes, the linear search blows. */ + for (i=0; name_synonyms[i][0]; ++i) { + + if (!e_utf8_casefold_collate (name_synonyms[i][0], a) + && !e_utf8_casefold_collate (name_synonyms[i][1], b)) + return TRUE; + + if (!e_utf8_casefold_collate (name_synonyms[i][0], b) + && !e_utf8_casefold_collate (name_synonyms[i][1], a)) + return TRUE; + } + + return FALSE; +} + +EABContactMatchType +eab_contact_compare_name_to_string (EContact *contact, const gchar *str) +{ + return eab_contact_compare_name_to_string_full (contact, str, FALSE, NULL, NULL, NULL); +} + +EABContactMatchType +eab_contact_compare_name_to_string_full (EContact *contact, const gchar *str, gboolean allow_partial_matches, + gint *matched_parts_out, EABContactMatchPart *first_matched_part_out, gint *matched_character_count_out) +{ + gchar **namev, **givenv = NULL, **addv = NULL, **familyv = NULL; + + gint matched_parts = EAB_CONTACT_MATCH_PART_NONE; + EABContactMatchPart first_matched_part = EAB_CONTACT_MATCH_PART_NONE; + EABContactMatchPart this_part_match = EAB_CONTACT_MATCH_PART_NOT_APPLICABLE; + EABContactMatchType match_type; + EContactName *contact_name; + + gint match_count = 0, matched_character_count = 0, fragment_count; + gint i, j; + gchar *str_cpy, *s; + + g_return_val_if_fail (E_IS_CONTACT (contact), EAB_CONTACT_MATCH_NOT_APPLICABLE); + + if (!e_contact_get_const (contact, E_CONTACT_FULL_NAME)) + return EAB_CONTACT_MATCH_NOT_APPLICABLE; + if (str == NULL) + return EAB_CONTACT_MATCH_NOT_APPLICABLE; + + str_cpy = s = g_strdup (str); + while (*s) { + if (*s == ',' || *s == '"') + *s = ' '; + ++s; + } + namev = g_strsplit (str_cpy, " ", 0); + g_free (str_cpy); + + contact_name = e_contact_get (contact, E_CONTACT_NAME); + + if (contact_name->given) + givenv = g_strsplit (contact_name->given, " ", 0); + if (contact_name->additional) + addv = g_strsplit (contact_name->additional, " ", 0); + if (contact_name->family) + familyv = g_strsplit (contact_name->family, " ", 0); + + e_contact_name_free (contact_name); + + fragment_count = 0; + for (i = 0; givenv && givenv[i]; ++i) + ++fragment_count; + for (i = 0; addv && addv[i]; ++i) + ++fragment_count; + for (i = 0; familyv && familyv[i]; ++i) + ++fragment_count; + + for (i = 0; namev[i] && this_part_match != EAB_CONTACT_MATCH_PART_NONE; ++i) { + + if (*namev[i]) { + + this_part_match = EAB_CONTACT_MATCH_PART_NONE; + + /* When we are allowing partials, we are strict about the matches we allow. + Does this make sense? Not really, but it does the right thing for the purposes + of completion. */ + + if (givenv && this_part_match == EAB_CONTACT_MATCH_PART_NONE) { + for (j = 0; givenv[j]; ++j) { + if (name_fragment_match_with_synonyms (givenv[j], namev[i], allow_partial_matches)) { + + this_part_match = EAB_CONTACT_MATCH_PART_GIVEN_NAME; + + /* We remove a piece of a name once it has been matched against, so + that "john john" won't match "john doe". */ + g_free (givenv[j]); + givenv[j] = g_strdup (""); + break; + } + } + } + + if (addv && this_part_match == EAB_CONTACT_MATCH_PART_NONE) { + for (j = 0; addv[j]; ++j) { + if (name_fragment_match_with_synonyms (addv[j], namev[i], allow_partial_matches)) { + + this_part_match = EAB_CONTACT_MATCH_PART_ADDITIONAL_NAME; + + g_free (addv[j]); + addv[j] = g_strdup (""); + break; + } + } + } + + if (familyv && this_part_match == EAB_CONTACT_MATCH_PART_NONE) { + for (j = 0; familyv[j]; ++j) { + if (allow_partial_matches ? name_fragment_match_with_synonyms (familyv[j], namev[i], allow_partial_matches) + : !e_utf8_casefold_collate (familyv[j], namev[i])) { + + this_part_match = EAB_CONTACT_MATCH_PART_FAMILY_NAME; + + g_free (familyv[j]); + familyv[j] = g_strdup (""); + break; + } + } + } + + if (this_part_match != EAB_CONTACT_MATCH_PART_NONE) { + ++match_count; + matched_character_count += g_utf8_strlen (namev[i], -1); + matched_parts |= this_part_match; + if (first_matched_part == EAB_CONTACT_MATCH_PART_NONE) + first_matched_part = this_part_match; + } + } + } + + match_type = EAB_CONTACT_MATCH_NONE; + + if (this_part_match != EAB_CONTACT_MATCH_PART_NONE) { + + if (match_count > 0) + match_type = EAB_CONTACT_MATCH_VAGUE; + + if (fragment_count == match_count) { + + match_type = EAB_CONTACT_MATCH_EXACT; + + } else if (fragment_count == match_count + 1) { + + match_type = EAB_CONTACT_MATCH_PARTIAL; + + } + } + + if (matched_parts_out) + *matched_parts_out = matched_parts; + if (first_matched_part_out) + *first_matched_part_out = first_matched_part; + if (matched_character_count_out) + *matched_character_count_out = matched_character_count; + + g_strfreev (namev); + g_strfreev (givenv); + g_strfreev (addv); + g_strfreev (familyv); + + return match_type; +} + +EABContactMatchType +eab_contact_compare_name (EContact *contact1, EContact *contact2) +{ + EContactName *a, *b; + gint matches=0, possible=0; + gboolean given_match = FALSE, additional_match = FALSE, family_match = FALSE; + + g_return_val_if_fail (E_IS_CONTACT (contact1), EAB_CONTACT_MATCH_NOT_APPLICABLE); + g_return_val_if_fail (E_IS_CONTACT (contact2), EAB_CONTACT_MATCH_NOT_APPLICABLE); + + a = e_contact_get (contact1, E_CONTACT_NAME); + b = e_contact_get (contact2, E_CONTACT_NAME); + + if (a == NULL || b == NULL) + return EAB_CONTACT_MATCH_NOT_APPLICABLE; + + if (a->given && b->given) { + ++possible; + if (name_fragment_match_with_synonyms (a->given, b->given, FALSE /* both inputs are complete */)) { + ++matches; + given_match = TRUE; + } + } + + if (a->additional && b->additional) { + ++possible; + if (name_fragment_match_with_synonyms (a->additional, b->additional, FALSE /* both inputs are complete */)) { + ++matches; + additional_match = TRUE; + } + } + + if (a->family && b->family) { + ++possible; + /* We don't allow "loose matching" (i.e. John vs. Jon) on family names */ + if (! e_utf8_casefold_collate (a->family, b->family)) { + ++matches; + family_match = TRUE; + } + } + + e_contact_name_free (a); + e_contact_name_free (b); + + /* Now look at the # of matches and try to intelligently map + an EAB_CONTACT_MATCH_* type to it. Special consideration is given + to family-name matches. */ + + if (possible == 0) + return EAB_CONTACT_MATCH_NOT_APPLICABLE; + + if (possible == 1) + return family_match ? EAB_CONTACT_MATCH_VAGUE : EAB_CONTACT_MATCH_NONE; + + if (possible == matches) + return family_match ? EAB_CONTACT_MATCH_EXACT : EAB_CONTACT_MATCH_PARTIAL; + + if (possible == matches+1) + return family_match ? EAB_CONTACT_MATCH_VAGUE : EAB_CONTACT_MATCH_NONE; + + return EAB_CONTACT_MATCH_NONE; +} + + +/*** Nickname Comparisons ***/ + +EABContactMatchType +eab_contact_compare_nickname (EContact *contact1, EContact *contact2) +{ + g_return_val_if_fail (contact1 && E_IS_CONTACT (contact1), EAB_CONTACT_MATCH_NOT_APPLICABLE); + g_return_val_if_fail (contact2 && E_IS_CONTACT (contact2), EAB_CONTACT_MATCH_NOT_APPLICABLE); + + return EAB_CONTACT_MATCH_NOT_APPLICABLE; +} + + + +/*** E-mail Comparisons ***/ + +static gboolean +match_email_username (const gchar *addr1, const gchar *addr2) +{ + gint c1, c2; + if (addr1 == NULL || addr2 == NULL) + return FALSE; + + while (*addr1 && *addr2 && *addr1 != '@' && *addr2 != '@') { + c1 = isupper (*addr1) ? tolower (*addr1) : *addr1; + c2 = isupper (*addr2) ? tolower (*addr2) : *addr2; + if (c1 != c2) + return FALSE; + ++addr1; + ++addr2; + } + + return *addr1 == *addr2; +} + +static gboolean +match_email_hostname (const gchar *addr1, const gchar *addr2) +{ + gint c1, c2; + gboolean seen_at1, seen_at2; + if (addr1 == NULL || addr2 == NULL) + return FALSE; + + /* Walk to the end of each string. */ + seen_at1 = FALSE; + if (*addr1) { + while (*addr1) { + if (*addr1 == '@') + seen_at1 = TRUE; + ++addr1; + } + --addr1; + } + + seen_at2 = FALSE; + if (*addr2) { + while (*addr2) { + if (*addr2 == '@') + seen_at2 = TRUE; + ++addr2; + } + --addr2; + } + + if (!seen_at1 && !seen_at2) + return TRUE; + if (!seen_at1 || !seen_at2) + return FALSE; + + while (*addr1 != '@' && *addr2 != '@') { + c1 = isupper (*addr1) ? tolower (*addr1) : *addr1; + c2 = isupper (*addr2) ? tolower (*addr2) : *addr2; + if (c1 != c2) + return FALSE; + --addr1; + --addr2; + } + + /* This will match bob@foo.ximian.com and bob@ximian.com */ + return *addr1 == '.' || *addr2 == '.'; +} + +static EABContactMatchType +compare_email_addresses (const gchar *addr1, const gchar *addr2) +{ + if (addr1 == NULL || *addr1 == 0 || + addr2 == NULL || *addr2 == 0) + return EAB_CONTACT_MATCH_NOT_APPLICABLE; + + if (match_email_username (addr1, addr2)) + return match_email_hostname (addr1, addr2) ? EAB_CONTACT_MATCH_EXACT : EAB_CONTACT_MATCH_VAGUE; + + return EAB_CONTACT_MATCH_NONE; +} + +EABContactMatchType +eab_contact_compare_email (EContact *contact1, EContact *contact2) +{ + EABContactMatchType match = EAB_CONTACT_MATCH_NOT_APPLICABLE; + GList *contact1_email, *contact2_email; + GList *i1, *i2; + + g_return_val_if_fail (contact1 && E_IS_CONTACT (contact1), EAB_CONTACT_MATCH_NOT_APPLICABLE); + g_return_val_if_fail (contact2 && E_IS_CONTACT (contact2), EAB_CONTACT_MATCH_NOT_APPLICABLE); + + contact1_email = e_contact_get (contact1, E_CONTACT_EMAIL); + contact2_email = e_contact_get (contact2, E_CONTACT_EMAIL); + + if (contact1_email == NULL || contact2_email == NULL) { + g_list_foreach (contact1_email, (GFunc)g_free, NULL); + g_list_free (contact1_email); + + g_list_foreach (contact2_email, (GFunc)g_free, NULL); + g_list_free (contact2_email); + return EAB_CONTACT_MATCH_NOT_APPLICABLE; + } + + i1 = contact1_email; + + /* Do pairwise-comparisons on all of the e-mail addresses. If + we find an exact match, there is no reason to keep + checking. */ + while (i1 && match != EAB_CONTACT_MATCH_EXACT) { + char *addr1 = (char *) i1->data; + + i2 = contact2_email; + while (i2 && match != EAB_CONTACT_MATCH_EXACT) { + char *addr2 = (char *) i2->data; + + match = combine_comparisons (match, compare_email_addresses (addr1, addr2)); + + i2 = i2->next; + } + + i1 = i1->next; + } + + g_list_foreach (contact1_email, (GFunc)g_free, NULL); + g_list_free (contact1_email); + + g_list_foreach (contact2_email, (GFunc)g_free, NULL); + g_list_free (contact2_email); + + return match; +} + +EABContactMatchType +eab_contact_compare_address (EContact *contact1, EContact *contact2) +{ + g_return_val_if_fail (contact1 && E_IS_CONTACT (contact1), EAB_CONTACT_MATCH_NOT_APPLICABLE); + g_return_val_if_fail (contact2 && E_IS_CONTACT (contact2), EAB_CONTACT_MATCH_NOT_APPLICABLE); + + /* Unimplemented */ + + return EAB_CONTACT_MATCH_NOT_APPLICABLE; +} + +EABContactMatchType +eab_contact_compare_telephone (EContact *contact1, EContact *contact2) +{ + g_return_val_if_fail (contact1 && E_IS_CONTACT (contact1), EAB_CONTACT_MATCH_NOT_APPLICABLE); + g_return_val_if_fail (contact2 && E_IS_CONTACT (contact2), EAB_CONTACT_MATCH_NOT_APPLICABLE); + + /* Unimplemented */ + + return EAB_CONTACT_MATCH_NOT_APPLICABLE; +} + +EABContactMatchType +eab_contact_compare (EContact *contact1, EContact *contact2) +{ + EABContactMatchType result; + + g_return_val_if_fail (contact1 && E_IS_CONTACT (contact1), EAB_CONTACT_MATCH_NOT_APPLICABLE); + g_return_val_if_fail (contact2 && E_IS_CONTACT (contact2), EAB_CONTACT_MATCH_NOT_APPLICABLE); + + result = EAB_CONTACT_MATCH_NONE; + result = combine_comparisons (result, eab_contact_compare_name (contact1, contact2)); + result = combine_comparisons (result, eab_contact_compare_nickname (contact1, contact2)); + result = combine_comparisons (result, eab_contact_compare_email (contact1, contact2)); + result = combine_comparisons (result, eab_contact_compare_address (contact1, contact2)); + result = combine_comparisons (result, eab_contact_compare_telephone (contact1, contact2)); + + return result; +} + +typedef struct _MatchSearchInfo MatchSearchInfo; +struct _MatchSearchInfo { + EContact *contact; + GList *avoid; + EABContactMatchQueryCallback cb; + gpointer closure; +}; + +static void +match_search_info_free (MatchSearchInfo *info) +{ + if (info) { + g_object_unref (info->contact); + + /* This should already have been deallocated, but just in case... */ + if (info->avoid) { + g_list_foreach (info->avoid, (GFunc) g_object_unref, NULL); + g_list_free (info->avoid); + info->avoid = NULL; + } + + g_free (info); + } +} + +static void +query_cb (EBook *book, EBookStatus status, GList *contacts, gpointer closure) +{ + /* XXX we need to free contacts */ + MatchSearchInfo *info = (MatchSearchInfo *) closure; + EABContactMatchType best_match = EAB_CONTACT_MATCH_NONE; + EContact *best_contact = NULL; + GList *remaining_contacts = NULL; + const GList *i; + + if (status != E_BOOK_ERROR_OK) { + info->cb (info->contact, NULL, EAB_CONTACT_MATCH_NONE, info->closure); + match_search_info_free (info); + return; + } + + /* remove the contacts we're to avoid from the list, if they're present */ + for (i = contacts; i != NULL; i = g_list_next (i)) { + EContact *this_contact = E_CONTACT (i->data); + GList *iterator; + gboolean avoid = FALSE; + for (iterator = info->avoid; iterator; iterator = iterator->next) { + if (!strcmp (e_contact_get_const (iterator->data, E_CONTACT_UID), + e_contact_get_const (this_contact, E_CONTACT_UID))) { + avoid = TRUE; + break; + } + } + if (!avoid) + remaining_contacts = g_list_prepend (remaining_contacts, this_contact); + } + + remaining_contacts = g_list_reverse (remaining_contacts); + + for (i = remaining_contacts; i != NULL; i = g_list_next (i)) { + EContact *this_contact = E_CONTACT (i->data); + EABContactMatchType this_match = eab_contact_compare (info->contact, this_contact); + if ((gint)this_match > (gint)best_match) { + best_match = this_match; + best_contact = this_contact; + } + } + + g_list_free (remaining_contacts); + + info->cb (info->contact, best_contact, best_match, info->closure); + match_search_info_free (info); +} + +#define MAX_QUERY_PARTS 10 +static void +use_common_book_cb (EBook *book, gpointer closure) +{ + MatchSearchInfo *info = (MatchSearchInfo *) closure; + EContact *contact = info->contact; + EContactName *contact_name; + GList *contact_email; + gchar *query_parts[MAX_QUERY_PARTS]; + gint p=0; + gchar *query, *qj; + int i; + + if (book == NULL) { + info->cb (info->contact, NULL, EAB_CONTACT_MATCH_NONE, info->closure); + match_search_info_free (info); + return; + } + + contact_name = e_contact_get (contact, E_CONTACT_NAME); + if (contact_name) { + if (contact_name->given && *contact_name->given) + query_parts[p++] = g_strdup_printf ("(contains \"full_name\" \"%s\")", contact_name->given); + + if (contact_name->additional && *contact_name->additional) + query_parts[p++] = g_strdup_printf ("(contains \"full_name\" \"%s\")", contact_name->additional); + + if (contact_name->family && *contact_name->family) + query_parts[p++] = g_strdup_printf ("(contains \"full_name\" \"%s\")", contact_name->family); + + e_contact_name_free (contact_name); + } + + contact_email = e_contact_get (contact, E_CONTACT_EMAIL); + if (contact_email) { + GList *iter; + for (iter = contact_email; iter && p < MAX_QUERY_PARTS; iter = iter->next) { + gchar *addr = g_strdup (iter->data); + if (addr && *addr) { + gchar *s = addr; + while (*s) { + if (*s == '@') { + *s = '\0'; + break; + } + ++s; + } + query_parts[p++] = g_strdup_printf ("(beginswith \"email\" \"%s\")", addr); + g_free (addr); + } + } + } + g_list_foreach (contact_email, (GFunc)g_free, NULL); + g_list_free (contact_email); + + + /* Build up our full query from the parts. */ + query_parts[p] = NULL; + qj = g_strjoinv (" ", query_parts); + for(i = 0; query_parts[i] != NULL; i++) + g_free(query_parts[i]); + if (p > 0) { + query = g_strdup_printf ("(or %s)", qj); + g_free (qj); + } else { + query = qj; + } + + if (query && *query) + e_book_async_get_contacts (book, query, query_cb, info); + else + query_cb (book, E_BOOK_ERROR_OK, NULL, info); + + g_free (query); +} + +void +eab_contact_locate_match (EContact *contact, EABContactMatchQueryCallback cb, gpointer closure) +{ + MatchSearchInfo *info; + + g_return_if_fail (contact && E_IS_CONTACT (contact)); + g_return_if_fail (cb != NULL); + + info = g_new (MatchSearchInfo, 1); + info->contact = contact; + g_object_ref (contact); + info->cb = cb; + info->closure = closure; + info->avoid = NULL; + + addressbook_load_default_book (use_common_book_cb, info); +} + +/** + * e_contact_locate_match_full: + * @book: The book to look in. If this is NULL, use the default + * addressbook. + * @contact: The contact to compare to. + * @avoid: A list of contacts to not match. These will not show up in the search. + * @cb: The function to call. + * @closure: The closure to add to the call. + * + * Look for the best match and return it using the EABContactMatchQueryCallback. + **/ +void +eab_contact_locate_match_full (EBook *book, EContact *contact, GList *avoid, EABContactMatchQueryCallback cb, gpointer closure) +{ + MatchSearchInfo *info; + + g_return_if_fail (contact && E_IS_CONTACT (contact)); + g_return_if_fail (cb != NULL); + + info = g_new (MatchSearchInfo, 1); + info->contact = contact; + g_object_ref (contact); + info->cb = cb; + info->closure = closure; + info->avoid = g_list_copy (avoid); + g_list_foreach (info->avoid, (GFunc) g_object_ref, NULL); + + if (book) + use_common_book_cb (book, info); + else + addressbook_load_default_book (use_common_book_cb, info); +} + diff --git a/addressbook/gui/merging/eab-contact-compare.h b/addressbook/gui/merging/eab-contact-compare.h new file mode 100644 index 0000000000..07a9cd7e35 --- /dev/null +++ b/addressbook/gui/merging/eab-contact-compare.h @@ -0,0 +1,73 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + +/* + * eab-contact-compare.h + * + * Copyright (C) 2001,2002,2003 Ximian, Inc. + * + * Authors: Jon Trowbridge <trow@ximian.com> + * Chris Toshok <toshok@ximian.com> + */ + +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA. + */ + +#ifndef __EAB_CONTACT_COMPARE_H__ +#define __EAB_CONTACT_COMPARE_H__ + +#include "ebook/e-book.h" +#include "ebook/e-contact.h" + +typedef enum { + EAB_CONTACT_MATCH_NOT_APPLICABLE = 0, + EAB_CONTACT_MATCH_NONE = 1, + EAB_CONTACT_MATCH_VAGUE = 2, + EAB_CONTACT_MATCH_PARTIAL = 3, + EAB_CONTACT_MATCH_EXACT = 4 +} EABContactMatchType; + +typedef enum { + EAB_CONTACT_MATCH_PART_NOT_APPLICABLE = -1, + EAB_CONTACT_MATCH_PART_NONE = 0, + EAB_CONTACT_MATCH_PART_GIVEN_NAME = 1<<0, + EAB_CONTACT_MATCH_PART_ADDITIONAL_NAME = 1<<2, + EAB_CONTACT_MATCH_PART_FAMILY_NAME = 1<<3 +} EABContactMatchPart; + +typedef void (*EABContactMatchQueryCallback) (EContact *contact, EContact *match, EABContactMatchType type, gpointer closure); + +EABContactMatchType eab_contact_compare_name_to_string (EContact *contact, const gchar *str); + +EABContactMatchType eab_contact_compare_name_to_string_full (EContact *contact, const gchar *str, + gboolean allow_partial_matches, + gint *matched_parts, EABContactMatchPart *first_matched_part, + gint *matched_character_count); + +EABContactMatchType eab_contact_compare_name (EContact *contact1, EContact *contact2); +EABContactMatchType eab_contact_compare_nickname (EContact *contact1, EContact *contact2); +EABContactMatchType eab_contact_compare_email (EContact *contact1, EContact *contact2); +EABContactMatchType eab_contact_compare_address (EContact *contact1, EContact *contact2); +EABContactMatchType eab_contact_compare_telephone (EContact *contact1, EContact *contact2); + +EABContactMatchType eab_contact_compare (EContact *contact1, EContact *contact2); + +void eab_contact_locate_match (EContact *contact, EABContactMatchQueryCallback cb, gpointer closure); +void eab_contact_locate_match_full (EBook *book, EContact *contact, GList *avoid, EABContactMatchQueryCallback cb, gpointer closure); + + + +#endif /* __E_CONTACT_COMPARE_H__ */ + diff --git a/addressbook/gui/merging/e-card-duplicate-detected.glade b/addressbook/gui/merging/eab-contact-duplicate-detected.glade index 5c287cd163..c44547fb92 100644 --- a/addressbook/gui/merging/e-card-duplicate-detected.glade +++ b/addressbook/gui/merging/eab-contact-duplicate-detected.glade @@ -68,12 +68,12 @@ <property name="column_spacing">6</property> <child> - <widget class="Custom" id="custom-old-card"> + <widget class="Custom" id="custom-old-contact"> <property name="visible">True</property> - <property name="creation_function">e_card_merging_create_old_card</property> + <property name="creation_function">_eab_contact_merging_create_contact_display</property> <property name="int1">0</property> <property name="int2">0</property> - <property name="last_modification_time">Fri, 08 Jun 2001 01:33:22 GMT</property> + <property name="last_modification_time">Sun, 05 Oct 2003 03:55:10 GMT</property> </widget> <packing> <property name="left_attach">1</property> @@ -155,12 +155,12 @@ in this folder. Would you like to add it anyway?</property> </child> <child> - <widget class="Custom" id="custom-new-card"> + <widget class="Custom" id="custom-new-contact"> <property name="visible">True</property> - <property name="creation_function">e_card_merging_create_old_card</property> + <property name="creation_function">_eab_contact_merging_create_contact_display</property> <property name="int1">0</property> <property name="int2">0</property> - <property name="last_modification_time">Fri, 08 Jun 2001 01:33:22 GMT</property> + <property name="last_modification_time">Sun, 05 Oct 2003 03:54:50 GMT</property> </widget> <packing> <property name="left_attach">1</property> diff --git a/addressbook/gui/merging/eab-contact-merging.c b/addressbook/gui/merging/eab-contact-merging.c new file mode 100644 index 0000000000..38a3c188b2 --- /dev/null +++ b/addressbook/gui/merging/eab-contact-merging.c @@ -0,0 +1,197 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Code for checking for duplicates when doing EContact work. + * + * Authors: + * Christopher James Lahey <clahey@ximian.com> + * Chris Toshok <toshok@ximian.com> + * + * Copyright (C) 2001, 2002, 2003, Ximian, Inc. + */ + +#include <config.h> + +#include "eab-contact-merging.h" +#include "eab-contact-compare.h" +#include <glade/glade.h> +#include <gtk/gtksignal.h> +#include "addressbook/gui/widgets/eab-contact-display.h" + +typedef enum { + E_CONTACT_MERGING_ADD, + E_CONTACT_MERGING_COMMIT +} EContactMergingOpType; + +typedef struct { + EContactMergingOpType op; + EBook *book; + EContact *contact; + EBookIdCallback id_cb; + EBookCallback cb; + gpointer closure; +} EContactMergingLookup; + +static void +free_lookup (EContactMergingLookup *lookup) +{ + g_object_unref (lookup->book); + g_object_unref (lookup->contact); + + g_free (lookup); +} + +static void +final_id_cb (EBook *book, EBookStatus status, const char *id, gpointer closure) +{ + EContactMergingLookup *lookup = closure; + + if (lookup->id_cb) + lookup->id_cb (lookup->book, status, id, lookup->closure); + + free_lookup (lookup); +} + +static void +final_cb (EBook *book, EBookStatus status, gpointer closure) +{ + EContactMergingLookup *lookup = closure; + + if (lookup->cb) + lookup->cb (lookup->book, status, lookup->closure); + + free_lookup (lookup); +} + +static void +doit (EContactMergingLookup *lookup) +{ + if (lookup->op == E_CONTACT_MERGING_ADD) + e_book_async_add_contact (lookup->book, lookup->contact, final_id_cb, lookup); + else if (lookup->op == E_CONTACT_MERGING_COMMIT) + e_book_async_commit_contact (lookup->book, lookup->contact, final_cb, lookup); +} + +static void +cancelit (EContactMergingLookup *lookup) +{ + if (lookup->op == E_CONTACT_MERGING_ADD) { + if (lookup->id_cb) + final_id_cb (lookup->book, E_BOOK_ERROR_CANCELLED, NULL, lookup); + } else if (lookup->op == E_CONTACT_MERGING_COMMIT) { + if (lookup->cb) + final_cb (lookup->book, E_BOOK_ERROR_CANCELLED, lookup); + } +} + +static void +response (GtkWidget *dialog, int response, EContactMergingLookup *lookup) +{ + gtk_widget_destroy (dialog); + + switch (response) { + case 0: + doit (lookup); + break; + case 1: + cancelit (lookup); + break; + } +} + +static void +match_query_callback (EContact *contact, EContact *match, EABContactMatchType type, gpointer closure) +{ + EContactMergingLookup *lookup = closure; + + if ((gint) type <= (gint) EAB_CONTACT_MATCH_VAGUE) { + doit (lookup); + } else { + GladeXML *ui; + + GtkWidget *widget; + + if (lookup->op == E_CONTACT_MERGING_ADD) + ui = glade_xml_new (EVOLUTION_GLADEDIR "/eab-contact-duplicate-detected.glade", NULL, NULL); + else if (lookup->op == E_CONTACT_MERGING_COMMIT) + ui = glade_xml_new (EVOLUTION_GLADEDIR "/eab-contact-merging-commit-duplicate-detected.glade", NULL, NULL); + else { + doit (lookup); + return; + } + + widget = glade_xml_get_widget (ui, "custom-old-contact"); + eab_contact_display_render (EAB_CONTACT_DISPLAY (widget), + match, EAB_CONTACT_DISPLAY_RENDER_COMPACT); + + widget = glade_xml_get_widget (ui, "custom-new-contact"); + eab_contact_display_render (EAB_CONTACT_DISPLAY (widget), + contact, EAB_CONTACT_DISPLAY_RENDER_COMPACT); + + widget = glade_xml_get_widget (ui, "dialog-duplicate-contact"); + + g_signal_connect (widget, "response", + G_CALLBACK (response), lookup); + + gtk_widget_show_all (widget); + } +} + +gboolean +eab_merging_book_add_contact (EBook *book, + EContact *contact, + EBookIdCallback cb, + gpointer closure) +{ + EContactMergingLookup *lookup; + + lookup = g_new (EContactMergingLookup, 1); + + lookup->op = E_CONTACT_MERGING_ADD; + lookup->book = g_object_ref (book); + lookup->contact = g_object_ref (contact); + lookup->id_cb = cb; + lookup->closure = closure; + + eab_contact_locate_match_full (book, contact, NULL, match_query_callback, lookup); + + return TRUE; +} + +gboolean +eab_merging_book_commit_contact (EBook *book, + EContact *contact, + EBookCallback cb, + gpointer closure) +{ + EContactMergingLookup *lookup; + GList *avoid; + + lookup = g_new (EContactMergingLookup, 1); + + lookup->op = E_CONTACT_MERGING_COMMIT; + lookup->book = g_object_ref (book); + lookup->contact = g_object_ref (contact); + lookup->cb = cb; + lookup->closure = closure; + + avoid = g_list_append (NULL, contact); + + eab_contact_locate_match_full (book, contact, avoid, match_query_callback, lookup); + + g_list_free (avoid); + + return TRUE; +} + +GtkWidget * +_eab_contact_merging_create_contact_display(gchar *name, + gchar *string1, gchar *string2, + gint int1, gint int2); + +GtkWidget * +_eab_contact_merging_create_contact_display(gchar *name, + gchar *string1, gchar *string2, + gint int1, gint int2) +{ + return eab_contact_display_new(); +} diff --git a/addressbook/gui/merging/eab-contact-merging.h b/addressbook/gui/merging/eab-contact-merging.h new file mode 100644 index 0000000000..6011263f31 --- /dev/null +++ b/addressbook/gui/merging/eab-contact-merging.h @@ -0,0 +1,30 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * The Evolution addressbook client object. + * + * Authors: + * Christopher James Lahey <clahey@ximian.com> + * Chris Toshok <toshok@ximian.com> + * + * Copyright (C) 2001, 2002, 2003 Ximian, Inc. + */ + +#ifndef __E_CONTACT_MERGING_H__ +#define __E_CONTACT_MERGING_H__ + +#include <addressbook/backend/ebook/e-book-async.h> + +G_BEGIN_DECLS + +gboolean eab_merging_book_add_contact (EBook *book, + EContact *contact, + EBookIdCallback cb, + gpointer closure); +gboolean eab_merging_book_commit_contact (EBook *book, + EContact *contact, + EBookCallback cb, + gpointer closure); + +G_END_DECLS + +#endif /* ! __EAB_CONTACT_MERGING_H__ */ |