From 593bb2c12c57aa4a70cfad359fa9aef5d253bce4 Mon Sep 17 00:00:00 2001 From: Colin Leroy Date: Wed, 3 Oct 2007 17:45:10 +0000 Subject: [PATCH] 2007-10-03 [colin] 3.0.2cvs5 * src/Makefile.am * src/addrduplicates.c * src/addrduplicates.h * src/addressbook.c Add duplicate deletion. Patch by Holger --- ChangeLog | 8 + PATCHSETS | 1 + configure.ac | 2 +- src/Makefile.am | 2 + src/addrduplicates.c | 420 +++++++++++++++++++++++++++++++++++++++++++ src/addrduplicates.h | 24 +++ src/addressbook.c | 9 + 7 files changed, 465 insertions(+), 1 deletion(-) create mode 100644 src/addrduplicates.c create mode 100644 src/addrduplicates.h diff --git a/ChangeLog b/ChangeLog index aaf7be3e3..a3d2220ba 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,11 @@ +2007-10-03 [colin] 3.0.2cvs5 + + * src/Makefile.am + * src/addrduplicates.c + * src/addrduplicates.h + * src/addressbook.c + Add duplicate deletion. Patch by Holger + 2007-10-03 [colin] 3.0.2cvs4 * src/matcher.c diff --git a/PATCHSETS b/PATCHSETS index 33034bb1b..718bce773 100644 --- a/PATCHSETS +++ b/PATCHSETS @@ -2917,3 +2917,4 @@ ( cvs diff -u -r 1.207.2.185 -r 1.207.2.186 src/folderview.c; ) > 3.0.2cvs2.patchset ( cvs diff -u -r 1.115.2.167 -r 1.115.2.168 src/main.c; cvs diff -u -r 1.36.2.114 -r 1.36.2.115 src/common/utils.c; cvs diff -u -r 1.20.2.51 -r 1.20.2.52 src/common/utils.h; ) > 3.0.2cvs3.patchset ( cvs diff -u -r 1.75.2.48 -r 1.75.2.49 src/matcher.c; cvs diff -u -r 1.1.2.78 -r 1.1.2.79 src/gtk/quicksearch.c; ) > 3.0.2cvs4.patchset +( cvs diff -u -r 1.155.2.75 -r 1.155.2.76 src/Makefile.am; diff -u /dev/null src/addrduplicates.c; diff -u /dev/null src/addrduplicates.h; cvs diff -u -r 1.60.2.98 -r 1.60.2.99 src/addressbook.c; ) > 3.0.2cvs5.patchset diff --git a/configure.ac b/configure.ac index c98f14511..969b9050c 100644 --- a/configure.ac +++ b/configure.ac @@ -11,7 +11,7 @@ MINOR_VERSION=0 MICRO_VERSION=2 INTERFACE_AGE=0 BINARY_AGE=0 -EXTRA_VERSION=4 +EXTRA_VERSION=5 EXTRA_RELEASE= EXTRA_GTK2_VERSION= diff --git a/src/Makefile.am b/src/Makefile.am index 1af608b3b..f2265255b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -30,6 +30,7 @@ claws_mail_SOURCES = \ addritem.c \ addrquery.c \ addrselect.c \ + addrduplicates.c \ alertpanel.c \ browseldap.c \ codeconv.c \ @@ -176,6 +177,7 @@ claws_mailinclude_HEADERS = \ addritem.h \ addrquery.h \ addrselect.h \ + addrduplicates.h \ alertpanel.h \ browseldap.h \ codeconv.h \ diff --git a/src/addrduplicates.c b/src/addrduplicates.c new file mode 100644 index 000000000..3ffd42dc1 --- /dev/null +++ b/src/addrduplicates.c @@ -0,0 +1,420 @@ +/* Claws Mail -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 2007 Holger Berndt + * and the Claws Mail team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * 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, see . + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include + +#include "defs.h" + +#include "addrduplicates.h" +#include "addrindex.h" +#include "alertpanel.h" +#include "gtkutils.h" + +typedef struct +{ + ItemPerson *person; + gchar *book; +} +AddrDupListEntry; + +enum { + COL_BOOK = 0, + COL_NAME, + NUM_COLS +}; + +static gboolean create_dialog(void); +static void create_addr_hash(void); +static void present_finder_results(void); +static void cb_finder_results_dialog_destroy(GtkWindow*, gpointer); +static void destroy_addr_hash_val(gpointer); +static GSList* deep_copy_hash_val(GSList*); +static void fill_hash_table(); +static gint collect_emails(ItemPerson*, const gchar*); +static gboolean is_not_duplicate(gpointer, gpointer, gpointer); +static gint books_compare(gconstpointer, gconstpointer); +static GtkWidget* create_email_view(GtkListStore*); +static GtkWidget* create_detail_view(GtkListStore*); +static void append_to_email_store(gpointer,gpointer,gpointer); +static void email_selection_changed(GtkTreeSelection*,gpointer); + +static GHashTable *addr_hash; +static gboolean include_same_book = TRUE; +static gboolean include_other_books = TRUE; +static GtkListStore *detail_store; + +void addrduplicates_find(void) +{ + if(create_dialog()) { + create_addr_hash(); + present_finder_results(); + } +} + +static gboolean create_dialog(void) +{ + gboolean want_search; + GtkWidget *vbox; + GtkWidget *check_same_book; + GtkWidget *check_other_book; + AlertValue val; + + want_search = FALSE; + + vbox = gtk_vbox_new(FALSE, 0); + check_same_book = gtk_check_button_new_with_label(_("Show duplicates in " + "the same book")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check_same_book), + include_same_book); + gtk_box_pack_start(GTK_BOX(vbox), check_same_book, FALSE, FALSE, 0); + gtk_widget_show(check_same_book); + check_other_book = gtk_check_button_new_with_label(_("Show duplicates in " + "different books")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check_other_book), + include_other_books); + gtk_box_pack_start(GTK_BOX(vbox), check_other_book, FALSE, FALSE, 0); + gtk_widget_show(check_other_book); + + /* prevent checkboxes from being destroyed on dialog close */ + g_object_ref(check_same_book); + g_object_ref(check_other_book); + + val = alertpanel_full(_("Find address book email duplicates"), + _("Claws-Mail will now search for duplicate email " + "addresses in the addressbook."), + _("Cancel"),_("Search"),NULL, FALSE, vbox, ALERT_NOTICE, + G_ALERTALTERNATE); + if(val == G_ALERTALTERNATE) { + want_search = TRUE; + + /* save options */ + include_same_book = + gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(check_same_book)); + include_other_books = + gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(check_other_book)); + + } + + g_object_unref(check_same_book); + g_object_unref(check_other_book); + return want_search; +} + +static void create_addr_hash(void) +{ + if(addr_hash) + g_hash_table_destroy(addr_hash); + addr_hash = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, destroy_addr_hash_val); + fill_hash_table(); +} + +static void destroy_addr_hash_val(gpointer value) +{ + GSList *list = (GSList*) value; + GSList *walk; + + for(walk = list; walk; walk = walk->next) { + AddrDupListEntry *entry = (AddrDupListEntry*) walk->data; + if(entry->book) + g_free(entry->book); + if(entry) + g_free(entry); + } + if(list) + g_slist_free(list); +} + +static GSList* deep_copy_hash_val(GSList *in) +{ + GSList *walk; + GSList *out = NULL; + + out = g_slist_copy(in); + for(walk = out; walk; walk = walk->next) { + AddrDupListEntry *out_entry; + AddrDupListEntry *in_entry = walk->data; + + out_entry = g_new0(AddrDupListEntry,1); + out_entry->person = in_entry->person; + out_entry->book = g_strdup(in_entry->book); + walk->data = out_entry; + } + + return out; +} + +static void fill_hash_table() +{ + addrindex_load_person_attribute(NULL, collect_emails); + g_hash_table_foreach_remove(addr_hash,is_not_duplicate, NULL); +} + +static gboolean is_not_duplicate(gpointer key, gpointer value, + gpointer user_data) +{ + gboolean is_in_same_book; + gboolean is_in_other_books; + GSList *books; + GSList *walk; + gboolean retval; + GSList *list = value; + + /* remove everything that is just in one book */ + if(g_slist_length(list) <= 1) + return TRUE; + + /* work on a shallow copy */ + books = g_slist_copy(list); + + /* sorting the list makes it easier to check for books */ + books = g_slist_sort(books, books_compare); + + /* check if a book appears twice */ + is_in_same_book = FALSE; + for(walk = books; walk && walk->next; walk = walk->next) { + if(books_compare(walk->data, walk->next->data) == 0) { + is_in_same_book = TRUE; + break; + } + } + + /* check is at least two different books appear in the list */ + is_in_other_books = FALSE; + if(books && books->next) { + for(walk = books->next; walk; walk = walk->next) { + if(books_compare(walk->data, books->data) != 0) { + is_in_other_books = TRUE; + break; + } + } + } + + /* delete the shallow copy */ + g_slist_free(books); + + retval = FALSE; + if(is_in_same_book && include_same_book) + retval = TRUE; + if(is_in_other_books && include_other_books) + retval = TRUE; + retval = !retval; + + return retval; +} + +static gint collect_emails(ItemPerson *itemperson, const gchar *book) +{ + gchar *addr; + GList *nodeM; + GSList *old_val; + GSList *new_val; + AddrDupListEntry *entry; + + /* Process each E-Mail address */ + nodeM = itemperson->listEMail; + while(nodeM) { + ItemEMail *email = nodeM->data; + + addr = g_strdup(email->address); + g_strdown(addr); + old_val = g_hash_table_lookup(addr_hash, addr); + if(old_val) + new_val = deep_copy_hash_val(old_val); + else + new_val = NULL; + + entry = g_new0(AddrDupListEntry,1); + entry->person = itemperson; + entry->book = g_strdup(book); + + new_val = g_slist_prepend(new_val, entry); + g_hash_table_insert(addr_hash, addr, new_val); + + nodeM = g_list_next(nodeM); + } + return 0; +} + +static gint books_compare(gconstpointer a, gconstpointer b) +{ + const AddrDupListEntry *entry1; + const AddrDupListEntry *entry2; + entry1 = a; + entry2 = b; + return strcmp(entry1->book,entry2->book); +} + +static void present_finder_results(void) +{ + GtkListStore *email_store; + GtkWidget *email_view; + GtkWidget *detail_view; + GtkWidget *dialog = NULL; + GtkWidget *vbox; + GtkWidget *hbox; + GtkWidget *hpaned; + GtkWidget *close; + GtkTreeSelection *email_select; + GtkTreeSelection *detail_select; + static GdkGeometry geometry; + + if(g_hash_table_size(addr_hash) == 0) { + alertpanel_notice(_("No duplicate email addresses in the addressbook found")); + return; + } + + email_store = gtk_list_store_new(1, G_TYPE_STRING); + g_hash_table_foreach(addr_hash,append_to_email_store,email_store); + email_view = create_email_view(email_store); + email_select = gtk_tree_view_get_selection(GTK_TREE_VIEW(email_view)); + gtk_tree_selection_set_mode(email_select,GTK_SELECTION_SINGLE); + + g_signal_connect(email_select, "changed", + (GCallback)email_selection_changed, NULL); + + detail_store = gtk_list_store_new(NUM_COLS, G_TYPE_STRING, G_TYPE_STRING); + detail_view = create_detail_view(detail_store); + detail_select = gtk_tree_view_get_selection(GTK_TREE_VIEW(detail_view)); + gtk_tree_selection_set_mode(email_select,GTK_SELECTION_SINGLE); + + dialog = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "address_dupes_finder"); + if(!geometry.min_height) { + geometry.min_width = 600; + geometry.min_height = 400; + } + gtk_window_set_geometry_hints(GTK_WINDOW(dialog), NULL, &geometry, + GDK_HINT_MIN_SIZE); + gtk_window_set_title(GTK_WINDOW(dialog), _("Duplicate email addresses")); + + + vbox = gtk_vbox_new(FALSE, 0); + gtk_container_add(GTK_CONTAINER(dialog), vbox); + + hpaned = gtk_hpaned_new(); + gtk_box_pack_start(GTK_BOX(vbox), hpaned, TRUE, TRUE, 0); + + gtk_paned_add1(GTK_PANED(hpaned), email_view); + gtk_paned_add2(GTK_PANED(hpaned), detail_view); + + hbox = gtk_hbox_new (FALSE, 0); + gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); + + close = gtk_button_new_from_stock(GTK_STOCK_CLOSE); + gtk_box_pack_end(GTK_BOX(hbox), close, FALSE, FALSE, 0); + + g_signal_connect(dialog, "destroy", + G_CALLBACK(cb_finder_results_dialog_destroy), NULL); + g_signal_connect_swapped(close, "clicked", + G_CALLBACK(gtk_widget_destroy), dialog); + + gtk_widget_show_all(dialog); +} + +static void cb_finder_results_dialog_destroy(GtkWindow *win, gpointer data) +{ + g_hash_table_destroy(addr_hash); + addr_hash = NULL; + +} + +static GtkWidget* create_email_view(GtkListStore *store) +{ + GtkWidget *view; + GtkCellRenderer *renderer; + + view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store)); + renderer = gtk_cell_renderer_text_new(); + gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), + -1, + _("Address"), + renderer, + "text", 0, + NULL); + g_object_unref(store); + return view; +} + +static GtkWidget* create_detail_view(GtkListStore *store) +{ + GtkWidget *view; + GtkCellRenderer *renderer; + + view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store)); + renderer = gtk_cell_renderer_text_new(); + + /* col 1 */ + gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), + -1, + _("Bookname"), + renderer, + "text", COL_BOOK, + NULL); + /* col 2 */ + gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), + -1, + _("Name"), + renderer, + "text", COL_NAME, + NULL); + + return view; +} + +static void append_to_email_store(gpointer key,gpointer value,gpointer data) +{ + GtkTreeIter iter; + GtkListStore *store = (GtkListStore*) data; + + gtk_list_store_append(store, &iter); + gtk_list_store_set(store, &iter, 0, (gchar*) key, -1); +} + +static void email_selection_changed(GtkTreeSelection *selection, gpointer data) +{ + GtkTreeIter iter; + GtkTreeModel *model; + gchar *email; + + if(gtk_tree_selection_get_selected(selection, &model, &iter)) { + GSList *hashval; + GSList *walk; + + gtk_tree_model_get(model, &iter, 0, &email, -1); + + hashval = g_hash_table_lookup(addr_hash, email); + gtk_list_store_clear(detail_store); + for(walk = hashval; walk; walk = walk->next) { + AddrDupListEntry *entry = walk->data; + if(!entry) + continue; + gtk_list_store_append(detail_store, &iter); + gtk_list_store_set(detail_store, &iter, + COL_BOOK, entry->book, + COL_NAME, ADDRITEM_NAME(entry->person), + -1); + } + g_free(email); + } +} diff --git a/src/addrduplicates.h b/src/addrduplicates.h new file mode 100644 index 000000000..5dd5fff9e --- /dev/null +++ b/src/addrduplicates.h @@ -0,0 +1,24 @@ +/* Claws Mail -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 2007 Holger Berndt + * and the Claws Mail team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * 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, see . + */ + +#ifndef __ADDRDUPLICATES_H__ +#define __ADDRDUPLICATES_H__ + +void addrduplicates_find(void); + +#endif /*__ADDRDUPLICATES_H__*/ diff --git a/src/addressbook.c b/src/addressbook.c index a67411536..e1917c06c 100644 --- a/src/addressbook.c +++ b/src/addressbook.c @@ -69,6 +69,7 @@ #include "addrbook.h" #include "addrindex.h" #include "addressadd.h" +#include "addrduplicates.h" #include "addressbook_foldersel.h" #include "vcard.h" #include "editvcard.h" @@ -366,6 +367,7 @@ static void addressbook_list_select_add ( AddrItemObject *aio, static void addressbook_list_select_remove ( AddrItemObject *aio ); static void addressbook_import_ldif_cb ( void ); +static void addressbook_find_duplicates_cb ( void ); static void addressbook_import_mutt_cb ( void ); static void addressbook_import_pine_cb ( void ); static void addressbook_export_html_cb ( void ); @@ -461,6 +463,8 @@ static GtkItemFactoryEntry addressbook_entries[] = {N_("/_Tools/---"), NULL, NULL, 0, "", NULL}, {N_("/_Tools/Export _HTML..."), NULL, addressbook_export_html_cb, 0, NULL, NULL}, {N_("/_Tools/Export LDI_F..."), NULL, addressbook_export_ldif_cb, 0, NULL, NULL}, + {N_("/_Tools/---"), NULL, NULL, 0, "", NULL}, + {N_("/_Tools/Find duplicates..."), NULL, addressbook_find_duplicates_cb, 0, NULL, NULL}, {N_("/_Help"), NULL, NULL, 0, "", NULL}, {N_("/_Help/_About"), NULL, about_show, 0, NULL, NULL} }; @@ -5284,6 +5288,11 @@ static void addressbook_export_ldif_cb( void ) { addressbook_exp_ldif( cache ); } +static void addressbook_find_duplicates_cb(void) +{ + addrduplicates_find(); +} + static void addressbook_start_drag(GtkWidget *widget, gint button, GdkEvent *event, void *data) -- 2.25.1