aboutsummaryrefslogblamecommitdiffstats
path: root/addressbook/backend/ebook/e-book-util.c
blob: cc00d045f45dd73dd276f628facc1f4b594f22f7 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12











                                                                           
                                                                   
                                                        












                                                                      

                        
                   

                        
                                     
                           
 








                                              
                                         






                                                                     
                                                                                        



                                        
                                                                                           
                                                                          
                                          
                                        

                 
 
                              









                                                                   

                                              




                                               
    

                                                                                                             

                       


                                                 


                                           
                                                                 

                          














                                                                                          
                                                                           

 

                             
 
                                          

                              
                                                     


                         
 
                                         
 
           
                                                                       





                                                                   
                                                    
 
                                          
                                              
                                                   



                                               

                                                   


                 

                                              






                                               
                                                                  





                                      

                                                  







                                          
                                                                   

 

                              

                         

                       
                  
 
                                                      

                                                                               

                                                      
                          









                                                 


           




























                                                                                      
                                          
 
                  
                                   
 
                                                  
                                                                                                           
 




                                                                      

 













                                                                            
                                                                     


                                                                      


                                                             







                                                                                     
                



                                                           
    

                                                                                     
                        
                                                 
 


                                                 
 






                                                             

                                                                         
 

 
            

                              
                              
                                                       
 
                                

 
















                                                
                           




                                                          
                                                                          
                                                 
                                                                   




                                                 
                                                                           











                                                             
                                                                           








                                                                  
                                                                   




                            
                                                                     


                                           
                                                                                


                   






                                                                                                
                            
                                     

                              
                              



                                                                      



                  


                                             
                                                                    



                                   
                                                                             



                                         
                                          




                                

                                       

                                     


                                                             

                           
                     
                                          
 
                                                                 









                                                                                  


                          
                                                                             
                                                                     


           
                                                                                             


                                      


                                                                                          

                                                                                              







                                                                                                   




                                       
                                              
                                             





                                                                                             





                                                                                                     

 









                                                                                                   
                                                                                    













                                                    
                                     
                                                                                           




































                                                                                                    

                                                                                                                  
                                                                              
                 





































                                                                                                  
                                       




















                                                                                                           
                                                      

                                          
                                                     
 

                                                                                                       

                                             










                                                                          
                                  
                                   














                                                                                 













                                                                         

  
































                                                                                              




                                                                 















                                                                             













                                                                                                        
        




























                                                                                                        
                                                 












                                                          
                                                                  
 






















                                                                           
/* -*- 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 <trow@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 "e-book-util.h"

#include <string.h>
#include <glib.h>
#include <glib-object.h>
#include <e-util/e-config-listener.h>
#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);
}