/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * e-book-util.c * * Copyright (C) 2001 Ximian, Inc. * * Developed by Jon Trowbridge */ /* * 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 #include "e-book-util.h" #include #include #include #include #include "e-card-compare.h" typedef struct _CommonBookInfo CommonBookInfo; struct _CommonBookInfo { EBookCommonCallback cb; gpointer closure; }; char * e_book_expand_uri (const char *uri) { if (!strncmp (uri, "file:", 5)) { int length = strlen (uri); int offset = 5; if (!strncmp (uri, "file://", 7)) offset = 7; if (length < 3 || strcmp (uri + length - 3, ".db")) { /* we assume it's a dir and glom addressbook.db onto the end. */ char *ret_val; char *file_name; file_name = g_build_filename(uri + offset, "addressbook.db", NULL); ret_val = g_strdup_printf("file://%s", file_name); g_free(file_name); return ret_val; } } return g_strdup (uri); } static void got_uri_book_cb (EBook *book, EBookStatus status, gpointer closure) { CommonBookInfo *info = (CommonBookInfo *) closure; if (status == E_BOOK_STATUS_SUCCESS) { info->cb (book, info->closure); } else { if (book) g_object_unref (book); info->cb (NULL, info->closure); } g_free (info); } void e_book_load_address_book_by_uri (EBook *book, const char *uri, EBookCallback open_response, gpointer closure) { char *real_uri; g_return_if_fail (book != NULL); g_return_if_fail (E_IS_BOOK (book)); g_return_if_fail (open_response != NULL); real_uri = e_book_expand_uri (uri); e_book_load_uri (book, real_uri, open_response, closure); g_free (real_uri); } void e_book_use_address_book_by_uri (const char *uri, EBookCommonCallback cb, gpointer closure) { EBook *book; CommonBookInfo *info; g_return_if_fail (cb != NULL); info = g_new0 (CommonBookInfo, 1); info->cb = cb; info->closure = closure; book = e_book_new (); e_book_load_address_book_by_uri (book, uri, got_uri_book_cb, info); } EConfigListener * e_book_get_config_database () { static EConfigListener *config_db; if (config_db == NULL) config_db = e_config_listener_new (); return config_db; } static EBook *common_default_book = NULL; static void got_default_book_cb (EBook *book, EBookStatus status, gpointer closure) { CommonBookInfo *info = (CommonBookInfo *) closure; if (status == E_BOOK_STATUS_SUCCESS) { /* We try not to leak in a race condition where the default book got loaded twice. */ if (common_default_book) { g_object_unref (book); book = common_default_book; } info->cb (book, info->closure); if (common_default_book == NULL) { common_default_book = book; } } else { if (book) g_object_unref (book); info->cb (NULL, info->closure); } g_free (info); } void e_book_use_default_book (EBookCommonCallback cb, gpointer closure) { EBook *book; CommonBookInfo *info; g_return_if_fail (cb != NULL); if (common_default_book != NULL) { cb (common_default_book, closure); return; } info = g_new0 (CommonBookInfo, 1); info->cb = cb; info->closure = closure; book = e_book_new (); e_book_load_default_book (book, got_default_book_cb, info); } static char *default_book_uri; static char* get_local_book_uri (void) { char *filename; char *uri; filename = g_build_filename (g_get_home_dir(), "evolution/local/Contacts/addressbook.db", NULL); uri = g_strdup_printf ("file://%s", filename); g_free (filename); return uri; } static void set_default_book_uri_local (void) { g_free (default_book_uri); default_book_uri = get_local_book_uri (); } static void set_default_book_uri (char *val) { if (default_book_uri) g_free (default_book_uri); if (val) { default_book_uri = e_book_expand_uri (val); g_free (val); } else { set_default_book_uri_local (); } } #define DEFAULT_CONTACTS_URI_PATH "/apps/evolution/shell/default_folders/contacts_uri" static void default_folder_listener (EConfigListener *cl, const char *key, gpointer data) { char *val; if (strcmp (key, DEFAULT_CONTACTS_URI_PATH)) return; val = e_config_listener_get_string (cl, DEFAULT_CONTACTS_URI_PATH); set_default_book_uri (val); } static void set_default_book_uri_from_config_db (void) { char *val; EConfigListener* config_db; config_db = e_book_get_config_database (); val = e_config_listener_get_string_with_default (config_db, DEFAULT_CONTACTS_URI_PATH, NULL, NULL); g_signal_connect (config_db, "key_changed", G_CALLBACK (default_folder_listener), NULL); set_default_book_uri (val); } typedef struct { gpointer closure; EBookCallback open_response; } DefaultBookClosure; static void e_book_default_book_open (EBook *book, EBookStatus status, gpointer closure) { DefaultBookClosure *default_book_closure = closure; gpointer user_closure = default_book_closure->closure; EBookCallback user_response = default_book_closure->open_response; g_free (default_book_closure); /* If there's a transient error, report it to the caller, but * if the old default folder has disappeared, fall back to the * local contacts folder instead, except when the default * folder is also the local folder. */ if (status == E_BOOK_STATUS_PROTOCOL_NOT_SUPPORTED || status == E_BOOK_STATUS_NO_SUCH_BOOK) { char *local_uri = get_local_book_uri(); if (strcmp (local_uri, default_book_uri)) { set_default_book_uri_local (); e_book_load_default_book (book, user_response, user_closure); } else user_response (book, status, user_closure); g_free (local_uri); } else { user_response (book, status, user_closure); } } void e_book_load_default_book (EBook *book, EBookCallback open_response, gpointer closure) { const char *uri; DefaultBookClosure *default_book_closure; g_return_if_fail (book != NULL); g_return_if_fail (E_IS_BOOK (book)); g_return_if_fail (open_response != NULL); uri = e_book_get_default_book_uri (); default_book_closure = g_new (DefaultBookClosure, 1); default_book_closure->closure = closure; default_book_closure->open_response = open_response; e_book_load_uri (book, uri, e_book_default_book_open, default_book_closure); } const char * e_book_get_default_book_uri () { if (!default_book_uri) set_default_book_uri_from_config_db (); return default_book_uri; } /* * * Simple Query Stuff * */ typedef struct _SimpleQueryInfo SimpleQueryInfo; struct _SimpleQueryInfo { guint tag; EBook *book; gchar *query; EBookSimpleQueryCallback cb; gpointer closure; EBookView *view; guint add_tag; guint seq_complete_tag; GList *cards; gboolean cancelled; }; static void book_add_simple_query (EBook *book, SimpleQueryInfo *info) { GList *pending = g_object_get_data (G_OBJECT(book), "sq_pending"); pending = g_list_prepend (pending, info); g_object_set_data (G_OBJECT (book), "sq_pending", pending); } static SimpleQueryInfo * book_lookup_simple_query (EBook *book, guint tag) { GList *pending = g_object_get_data (G_OBJECT (book), "sq_pending"); while (pending) { SimpleQueryInfo *sq = pending->data; if (sq->tag == tag) return sq; pending = g_list_next (pending); } return NULL; } static void book_remove_simple_query (EBook *book, SimpleQueryInfo *info) { GList *pending = g_object_get_data (G_OBJECT (book), "sq_pending"); GList *i; for (i=pending; i != NULL; i = g_list_next (i)) { if (i->data == info) { pending = g_list_remove_link (pending, i); g_list_free_1 (i); break; } } g_object_set_data (G_OBJECT (book), "sq_pending", pending); } static guint book_issue_tag (EBook *book) { gpointer ptr = g_object_get_data (G_OBJECT (book), "sq_tag"); guint tag = GPOINTER_TO_UINT (ptr); if (tag == 0) tag = 1; g_object_set_data (G_OBJECT (book), "sq_tag", GUINT_TO_POINTER (tag+1)); return tag; } static SimpleQueryInfo * simple_query_new (EBook *book, const char *query, EBookSimpleQueryCallback cb, gpointer closure) { SimpleQueryInfo *sq = g_new0 (SimpleQueryInfo, 1); sq->tag = book_issue_tag (book); sq->book = book; g_object_ref (book); sq->query = g_strdup (query); sq->cb = cb; sq->closure = closure; sq->cancelled = FALSE; /* Automatically add ourselves to the EBook's pending list. */ book_add_simple_query (book, sq); return sq; } static void simple_query_disconnect (SimpleQueryInfo *sq) { if (sq->add_tag) { g_signal_handler_disconnect (sq->view, sq->add_tag); sq->add_tag = 0; } if (sq->seq_complete_tag) { g_signal_handler_disconnect (sq->view, sq->seq_complete_tag); sq->seq_complete_tag = 0; } if (sq->view) { g_object_unref (sq->view); sq->view = NULL; } } static void simple_query_free (SimpleQueryInfo *sq) { simple_query_disconnect (sq); /* Remove ourselves from the EBook's pending list. */ book_remove_simple_query (sq->book, sq); g_free (sq->query); if (sq->book) g_object_unref (sq->book); g_list_foreach (sq->cards, (GFunc) g_object_unref, NULL); g_list_free (sq->cards); g_free (sq); } static void simple_query_card_added_cb (EBookView *view, const GList *cards, gpointer closure) { SimpleQueryInfo *sq = closure; if (sq->cancelled) return; sq->cards = g_list_concat (sq->cards, g_list_copy ((GList *) cards)); g_list_foreach ((GList *) cards, (GFunc) g_object_ref, NULL); } static void simple_query_sequence_complete_cb (EBookView *view, EBookViewStatus status, gpointer closure) { SimpleQueryInfo *sq = closure; /* Disconnect signals, so that we don't pick up any changes to the book that occur in our callback */ simple_query_disconnect (sq); if (! sq->cancelled) sq->cb (sq->book, E_BOOK_SIMPLE_QUERY_STATUS_SUCCESS, sq->cards, sq->closure); simple_query_free (sq); } static void simple_query_book_view_cb (EBook *book, EBookStatus status, EBookView *book_view, gpointer closure) { SimpleQueryInfo *sq = closure; if (sq->cancelled) { simple_query_free (sq); return; } if (status != E_BOOK_STATUS_SUCCESS) { simple_query_disconnect (sq); sq->cb (sq->book, E_BOOK_SIMPLE_QUERY_STATUS_OTHER_ERROR, NULL, sq->closure); simple_query_free (sq); return; } sq->view = book_view; g_object_ref (book_view); sq->add_tag = g_signal_connect (sq->view, "card_added", G_CALLBACK (simple_query_card_added_cb), sq); sq->seq_complete_tag = g_signal_connect (sq->view, "sequence_complete", G_CALLBACK (simple_query_sequence_complete_cb), sq); } guint e_book_simple_query (EBook *book, const char *query, EBookSimpleQueryCallback cb, gpointer closure) { SimpleQueryInfo *sq; g_return_val_if_fail (book && E_IS_BOOK (book), 0); g_return_val_if_fail (query, 0); g_return_val_if_fail (cb, 0); sq = simple_query_new (book, query, cb, closure); e_book_get_book_view (book, (gchar *) query, simple_query_book_view_cb, sq); return sq->tag; } void e_book_simple_query_cancel (EBook *book, guint tag) { SimpleQueryInfo *sq; g_return_if_fail (book && E_IS_BOOK (book)); sq = book_lookup_simple_query (book, tag); if (sq) { sq->cancelled = TRUE; sq->cb (sq->book, E_BOOK_SIMPLE_QUERY_STATUS_CANCELLED, NULL, sq->closure); } else { g_warning ("Simple query tag %d is unknown", tag); } } /* * * Specialized Queries * */ typedef struct _NameEmailQueryInfo NameEmailQueryInfo; struct _NameEmailQueryInfo { gchar *name; gchar *email; EBookSimpleQueryCallback cb; gpointer closure; }; static void name_email_query_info_free (NameEmailQueryInfo *info) { if (info) { g_free (info->name); g_free (info->email); g_free (info); } } static void name_and_email_cb (EBook *book, EBookSimpleQueryStatus status, const GList *cards, gpointer closure) { NameEmailQueryInfo *info = closure; GList *filtered_cards = NULL; while (cards) { ECard *card = E_CARD (cards->data); if ((info->name == NULL || e_card_compare_name_to_string (card, info->name) >= E_CARD_MATCH_VAGUE) && (info->email == NULL || e_card_email_match_string (card, info->email))) { filtered_cards = g_list_append (filtered_cards, card); } cards = g_list_next (cards); } info->cb (book, status, filtered_cards, info->closure); g_list_free (filtered_cards); name_email_query_info_free (info); } guint e_book_name_and_email_query (EBook *book, const gchar *name, const gchar *email, EBookSimpleQueryCallback cb, gpointer closure) { NameEmailQueryInfo *info; gchar *email_query=NULL, *name_query=NULL, *query; guint tag; g_return_val_if_fail (book && E_IS_BOOK (book), 0); g_return_val_if_fail (cb != NULL, 0); if (name && !*name) name = NULL; if (email && !*email) email = NULL; if (name == NULL && email == NULL) return 0; /* Build our e-mail query. * We only query against the username part of the address, to avoid not matching * fred@foo.com and fred@mail.foo.com. While their may be namespace collisions * in the usernames of everyone out there, it shouldn't be that bad. (Famous last words.) */ if (email) { const gchar *t = email; while (*t && *t != '@') ++t; if (*t == '@') { email_query = g_strdup_printf ("(beginswith \"email\" \"%.*s@\")", t-email, email); } else { email_query = g_strdup_printf ("(beginswith \"email\" \"%s\")", email); } } /* Build our name query. * We only do name-query stuff if we don't have an e-mail address. Our basic assumption * is that the username part of the email is good enough to keep the amount of stuff returned * in the query relatively small. */ if (name && !email) { gchar *name_cpy = g_strdup (name), *qjoined; gchar **namev; gint i, count=0; g_strstrip (name_cpy); namev = g_strsplit (name_cpy, " ", 0); for (i=0; namev[i]; ++i) { if (*namev[i]) { char *str = namev[i]; namev[i] = g_strdup_printf ("(contains \"file_as\" \"%s\")", namev[i]); ++count; g_free (str); } } qjoined = g_strjoinv (" ", namev); if (count > 1) { name_query = g_strdup_printf ("(or %s)", qjoined); } else { name_query = qjoined; qjoined = NULL; } g_free (name_cpy); g_strfreev (namev); g_free (qjoined); } /* Assemble our e-mail & name queries */ if (email_query && name_query) { query = g_strdup_printf ("(and %s %s)", email_query, name_query); } else if (email_query) { query = email_query; email_query = NULL; } else if (name_query) { query = name_query; name_query = NULL; } else return 0; info = g_new0 (NameEmailQueryInfo, 1); info->name = g_strdup (name); info->email = g_strdup (email); info->cb = cb; info->closure = closure; tag = e_book_simple_query (book, query, name_and_email_cb, info); g_free (email_query); g_free (name_query); g_free (query); return tag; } /* * Simple nickname query */ typedef struct _NicknameQueryInfo NicknameQueryInfo; struct _NicknameQueryInfo { gchar *nickname; EBookSimpleQueryCallback cb; gpointer closure; }; static void nickname_cb (EBook *book, EBookSimpleQueryStatus status, const GList *cards, gpointer closure) { NicknameQueryInfo *info = closure; if (info->cb) info->cb (book, status, cards, info->closure); g_free (info->nickname); g_free (info); } guint e_book_nickname_query (EBook *book, const char *nickname, EBookSimpleQueryCallback cb, gpointer closure) { NicknameQueryInfo *info; gchar *query; guint retval; g_return_val_if_fail (E_IS_BOOK (book), 0); g_return_val_if_fail (nickname != NULL, 0); /* The empty-string case shouldn't generate a warning. */ if (! *nickname) return 0; info = g_new0 (NicknameQueryInfo, 1); info->nickname = g_strdup (nickname); info->cb = cb; info->closure = closure; query = g_strdup_printf ("(is \"nickname\" \"%s\")", info->nickname); retval = e_book_simple_query (book, query, nickname_cb, info); g_free (query); return retval; } /* * Convenience routine to check for addresses in the local address book. */ typedef struct _HaveAddressInfo HaveAddressInfo; struct _HaveAddressInfo { gchar *email; EBookHaveAddressCallback cb; gpointer closure; }; static void have_address_query_cb (EBook *book, EBookSimpleQueryStatus status, const GList *cards, gpointer closure) { HaveAddressInfo *info = (HaveAddressInfo *) closure; info->cb (book, info->email, cards && (status == E_BOOK_SIMPLE_QUERY_STATUS_SUCCESS) ? E_CARD (cards->data) : NULL, info->closure); g_free (info->email); g_free (info); } static void have_address_book_open_cb (EBook *book, gpointer closure) { HaveAddressInfo *info = (HaveAddressInfo *) closure; if (book) { e_book_name_and_email_query (book, NULL, info->email, have_address_query_cb, info); } else { info->cb (NULL, info->email, NULL, info->closure); g_free (info->email); g_free (info); } } void e_book_query_address_default (const gchar *email, EBookHaveAddressCallback cb, gpointer closure) { HaveAddressInfo *info; g_return_if_fail (email != NULL); g_return_if_fail (cb != NULL); info = g_new0 (HaveAddressInfo, 1); info->email = g_strdup (email); info->cb = cb; info->closure = closure; e_book_use_default_book (have_address_book_open_cb, info); } /* bad place for this i know. */ int e_utf8_casefold_collate_len (const gchar *str1, const gchar *str2, int len) { gchar *s1 = g_utf8_casefold(str1, len); gchar *s2 = g_utf8_casefold(str2, len); int rv; rv = g_utf8_collate (s1, s2); g_free (s1); g_free (s2); return rv; } int e_utf8_casefold_collate (const gchar *str1, const gchar *str2) { return e_utf8_casefold_collate_len (str1, str2, -1); }