aboutsummaryrefslogblamecommitdiffstats
path: root/plugins/bbdb/gaimbuddies.c
blob: fe974df5022243b0e8ea5041eec31a100b2e0862 (plain) (tree)
1
2
3
4
5
6
7
8
9







                                                                   
                                                      











                                                                    
                                                                             






                                                        
   
 



                    


                             

                    
                       

                   


                     
                          



                 



                            

            
                                         


                                                                     
                                                                      

                                                                     
                                                                  
                                                                     


                                                                     
 











                                          


                         

                                                                 




                                         




                                 


                                                      












                                                                  
 
                      

 


                                 

                              
                   

                             
                                                           
 
                                           
                                              
                                    


                       
                                                            
                                                                                       
                                                                                 






                                                                                     
                                                     



                                    

                                                       
 
                                  
 

                                             
                                                                                     
                                                                                    


                                        

                               
                            
                     

 

                                       
 
                            



                                                  
 

                                                                    
 

                                             




                                                                        
 
                                             










                                


                                              

                               
                           
                             
 









                                                                          
 
                                                                   
 
                                 
 
                                                


                                                                    
                                  

                                          

                            



                                                                 
 
                                                                      

                                                                        

                                                              
                                                      
                                                                       
                                              

                                 
 
                                      
 


                                                                            

                                                                         
                                                     


                                                                         
                                         
                         

                                                       
 
                                                                          


                                                                         
                                         
                         

                                                             
                                                                                           
                                                                                                 

                                                     



                                                                 





                                                                            
                                                                  
                                           

                                 
 
                           
                                                                                      
                                                                                          
                                             
                                  
                 
 

                                   

         
                                                   
 
     
                                                                
 


                                                                     










                           
                        







                                                                                  

                                           
 

                                         
                
                                
 
                               
 


                                                                       


                           

 
               

                                     
 
                 
 
                                               
                                              
 
                                                  



                                    

 
               


                                                 

                            
                   
                               







                                                  
                                               
                                                                      


                                                         
 



                                                   
                                           


                                                           
                                               

                                                          
                                                                   
 


                                                                       


                                                                           
                                                     
                                             

                         
                                                                       
                                                                             
                                     



                      

                                             
 
                     


                    
                                           
 
                                           
                                        
                                           
                                              
                                        
                                        
                                        
                                        
                                          
                                          
                                           
                                           
                                       
                                             



                                
           

                                  









                                                                  
                                                                     
                                                            






                                                                            

                                              
 
                          

                                      
                               
 
                                           


                                              
                         
                                                                              
                       


                                                
                                                            
                                                                               
                                       
                       

         
                                                                          
                                                                       




                                                          

                                                                          
                                                                     




                                      


                                                               
                                       
                       


                                                                           
                                                                   
                                                                        



                               
                                                        
                               

 
              


                                                                        
                                                            

                            
                                                            

 
              

                                                
                           

                                       
                             
                            
 
                                                                                          



                              



                    
                                  
                                   
                               



                                 
                                       

                                                                             
                                                                     





                                      


                                                                         




                                   
                                                                                   
 
                                                                                          
                                                                       
                                            


                                                                      
 
                                                                                 


                                                                               
                                                                           
                                                                 


                                                               
                                                                          
                                                          
 

         
                       

                                    
                                                    


           
                                    
                                       
                                   



                                                                           
                                                                    

                                 
                                                            

         
/*
 *  Routines to copy information from a Gaim buddy list into an
 *  Evolution addressbook.
 *
 *  I currently copy IM account names and buddy icons, provided you
 *  don't already have a buddy icon defined for a person.
 *
 *  This works today (25 October 2004), but is pretty sure to break
 *  later on as the Gaim buddylist file format shifts.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) version 3.
 *
 * 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with the program; if not, see <http://www.gnu.org/licenses/>
 *
 *
 * Authors:
 *      Nat Friedman <nat@novell.com>
 *
 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <libxml/tree.h>
#include <libxml/parser.h>
#include <libxml/xmlmemory.h>

#include <gtk/gtk.h>
#include <glib/gi18n.h>
#include <string.h>

#include <sys/time.h>
#include <sys/stat.h>

#include <e-util/e-util.h>

#include "bbdb.h"

typedef struct {
    gchar *account_name;
    gchar *proto;
    gchar *alias;
    gchar *icon;
} GaimBuddy;

/* Forward declarations for this file. */
static gboolean bbdb_merge_buddy_to_contact (EBookClient *client,
                         GaimBuddy *buddy,
                         EContact *contact);
static void bbdb_get_gaim_buddy_list    (GQueue *out_buddies);
static gchar *  get_node_text           (xmlNodePtr node);
static gchar *  get_buddy_icon_from_setting (xmlNodePtr setting);
static void parse_buddy_group       (xmlNodePtr group,
                         GQueue *out_buddies,
                         GSList *blocked);
static EContactField
        proto_to_contact_field      (const gchar *proto);

static void
free_gaim_body (GaimBuddy *gb)
{
    if (gb != NULL) {
        g_free (gb->icon);
        g_free (gb->alias);
        g_free (gb->account_name);
        g_free (gb->proto);
        g_free (gb);
    }
}

static gchar *
get_buddy_filename (void)
{
    return g_build_filename (
        g_get_home_dir (), ".purple", "blist.xml", NULL);
}

static gchar *
get_md5_as_string (const gchar *filename)
{
    GMappedFile *mapped_file;
    const gchar *contents;
    gchar *digest;
    gsize length;
    GError *error = NULL;

    g_return_val_if_fail (filename != NULL, NULL);

    mapped_file = g_mapped_file_new (filename, FALSE, &error);
    if (mapped_file == NULL) {
        g_warning ("%s", error->message);
        return NULL;
    }

    contents = g_mapped_file_get_contents (mapped_file);
    length = g_mapped_file_get_length (mapped_file);

    digest = g_compute_checksum_for_data (
        G_CHECKSUM_MD5, (guchar *) contents, length);

    g_mapped_file_unref (mapped_file);

    return digest;
}

void
bbdb_sync_buddy_list_check (void)
{
    struct stat statbuf;
    time_t last_sync_time;
    gchar *md5;
    gchar *blist_path;
    gchar *last_sync_str;
    GSettings *settings = g_settings_new (CONF_SCHEMA);

    blist_path = get_buddy_filename ();
    if (stat (blist_path, &statbuf) < 0) {
        g_free (blist_path);
        return;
    }

    /* Reprocess the buddy list if it's been updated. */
    last_sync_str = g_settings_get_string (settings, CONF_KEY_GAIM_LAST_SYNC_TIME);
    if (last_sync_str == NULL || !strcmp ((const gchar *) last_sync_str, ""))
        last_sync_time = (time_t) 0;
    else
        last_sync_time = (time_t) g_ascii_strtoull (last_sync_str, NULL, 10);

    g_free (last_sync_str);

    if (statbuf.st_mtime <= last_sync_time) {
        g_object_unref (G_OBJECT (settings));
        g_free (blist_path);
        return;
    }

    last_sync_str = g_settings_get_string (
        settings, CONF_KEY_GAIM_LAST_SYNC_MD5);

    g_object_unref (settings);

    md5 = get_md5_as_string (blist_path);

    if (!last_sync_str || !*last_sync_str || !g_str_equal (md5, last_sync_str)) {
        fprintf (stderr, "bbdb: Buddy list has changed since last sync.\n");

        bbdb_sync_buddy_list ();
    }

    g_free (last_sync_str);
    g_free (blist_path);
    g_free (md5);
}

static gboolean
store_last_sync_idle_cb (gpointer data)
{
    GSettings *settings;
    gchar *md5;
    gchar *blist_path = get_buddy_filename ();
    time_t last_sync;
    gchar *last_sync_time;

    time (&last_sync);
    last_sync_time = g_strdup_printf ("%ld", (glong) last_sync);

    md5 = get_md5_as_string (blist_path);

    settings = g_settings_new (CONF_SCHEMA);
    g_settings_set_string (
        settings, CONF_KEY_GAIM_LAST_SYNC_TIME, last_sync_time);
    g_settings_set_string (
        settings, CONF_KEY_GAIM_LAST_SYNC_MD5, md5);

    g_object_unref (G_OBJECT (settings));

    g_free (last_sync_time);
    g_free (blist_path);
    g_free (md5);

    return FALSE;
}

static gboolean syncing = FALSE;
G_LOCK_DEFINE_STATIC (syncing);

static gpointer
bbdb_sync_buddy_list_in_thread (gpointer data)
{
    EBookClient *client;
    GQueue *buddies = data;
    GList *head, *link;
    GError *error = NULL;

    g_return_val_if_fail (buddies != NULL, NULL);

    client = bbdb_create_book_client (GAIM_ADDRESSBOOK, NULL, &error);
    if (error != NULL) {
        g_warning (
            "bbdb: Failed to get addressbook: %s",
            error->message);
        g_error_free (error);
        goto exit;
    }

    printf ("bbdb: Synchronizing buddy list to contacts...\n");

    /* Walk the buddy list */

    head = g_queue_peek_head_link (buddies);

    for (link = head; link != NULL; link = g_list_next (link)) {
        GaimBuddy *b = link->data;
        EBookQuery *query;
        gchar *query_string, *uid;
        GSList *contacts = NULL;
        EContact *c;

        if (b->alias == NULL || strlen (b->alias) == 0) {
            g_free (b->alias);
            b->alias = g_strdup (b->account_name);
        }

        /* Look for an exact match full name == buddy alias */
        query = e_book_query_field_test (
            E_CONTACT_FULL_NAME, E_BOOK_QUERY_IS, b->alias);
        query_string = e_book_query_to_string (query);
        e_book_query_unref (query);
        if (!e_book_client_get_contacts_sync (
            client, query_string, &contacts, NULL, NULL)) {
            g_free (query_string);
            continue;
        }

        g_free (query_string);

        if (contacts != NULL) {

            /* FIXME: If there's more than one contact with this
             * name, just give up; we're not smart enough for
             * this. */
            if (contacts->next != NULL) {
                g_slist_free_full (
                    contacts,
                    (GDestroyNotify) g_object_unref);
                continue;
            }

            c = E_CONTACT (contacts->data);

            if (!bbdb_merge_buddy_to_contact (client, b, c)) {
                g_slist_free_full (
                    contacts,
                    (GDestroyNotify) g_object_unref);
                continue;
            }

            /* Write it out to the addressbook */
            if (!e_book_client_modify_contact_sync (client, c, NULL, &error)) {
                g_warning ("bbdb: Could not modify contact: %s", error->message);
                g_error_free (error);
            }

            g_slist_free_full (
                contacts,
                (GDestroyNotify) g_object_unref);
            continue;
        }

        /* Otherwise, create a new contact. */
        c = e_contact_new ();
        e_contact_set (c, E_CONTACT_FULL_NAME, (gpointer) b->alias);
        if (!bbdb_merge_buddy_to_contact (client, b, c)) {
            g_object_unref (c);
            continue;
        }

        uid = NULL;
        if (!e_book_client_add_contact_sync (client, c, &uid, NULL, &error)) {
            g_warning ("bbdb: Failed to add new contact: %s", error->message);
            g_error_free (error);
            goto exit;
        }

        g_object_unref (c);
        g_free (uid);
    }

    g_idle_add (store_last_sync_idle_cb, NULL);

exit:
    printf ("bbdb: Done syncing buddy list to contacts.\n");

    g_clear_object (&client);

    g_queue_free_full (buddies, (GDestroyNotify) free_gaim_body);

    G_LOCK (syncing);
    syncing = FALSE;
    G_UNLOCK (syncing);

    return NULL;
}

void
bbdb_sync_buddy_list (void)
{
    GQueue *buddies;

    G_LOCK (syncing);
    if (syncing) {
        G_UNLOCK (syncing);
        printf ("bbdb: Already syncing buddy list, skipping this call\n");
        return;
    }

    buddies = g_queue_new ();
    bbdb_get_gaim_buddy_list (buddies);

    if (g_queue_is_empty (buddies)) {
        g_queue_free (buddies);
    } else {
        GThread *thread;

        syncing = TRUE;

        thread = g_thread_new (
            NULL, bbdb_sync_buddy_list_in_thread, buddies);
        g_thread_unref (thread);
    }

    G_UNLOCK (syncing);
}

static gboolean
im_list_contains_buddy (GList *ims,
                        GaimBuddy *b)
{
    GList *l;

    for (l = ims; l != NULL; l = l->next) {
        gchar *im = (gchar *) l->data;

        if (!strcmp (im, b->account_name))
            return TRUE;
    }

    return FALSE;
}

static gboolean
bbdb_merge_buddy_to_contact (EBookClient *client,
                             GaimBuddy *b,
                             EContact *c)
{
    EContactField field;
    GList *ims;
    gboolean dirty = FALSE;

    EContactPhoto *photo = NULL;

    GError *error = NULL;

    /* Set the IM account */
    field = proto_to_contact_field (b->proto);
    ims = e_contact_get (c, field);
    if (!im_list_contains_buddy (ims, b)) {
        ims = g_list_append (ims, g_strdup (b->account_name));
        e_contact_set (c, field, (gpointer) ims);
        dirty = TRUE;
    }

    g_list_foreach (ims, (GFunc) g_free, NULL);
    g_list_free (ims);
    ims = NULL;

        /* Set the photo if it's not set */
    if (b->icon != NULL) {
        photo = e_contact_get (c, E_CONTACT_PHOTO);
        if (photo == NULL) {
            gchar *contents = NULL;

            photo = g_new0 (EContactPhoto, 1);
            photo->type = E_CONTACT_PHOTO_TYPE_INLINED;

            if (!g_file_get_contents (
                b->icon, &contents,
                &photo->data.inlined.length, &error)) {
                g_warning (
                    "bbdb: Could not read buddy icon: "
                    "%s\n", error->message);
                g_error_free (error);
                return dirty;
            }

            photo->data.inlined.data = (guchar *) contents;
            e_contact_set (c, E_CONTACT_PHOTO, (gpointer) photo);
            dirty = TRUE;
        }
    }

    /* Clean up */
    if (photo != NULL)
        e_contact_photo_free (photo);

    return dirty;
}

static EContactField
proto_to_contact_field (const gchar *proto)
{
    if (!strcmp (proto,  "prpl-oscar"))
        return E_CONTACT_IM_AIM;
    if (!strcmp (proto, "prpl-novell"))
        return E_CONTACT_IM_GROUPWISE;
    if (!strcmp (proto, "prpl-msn"))
        return E_CONTACT_IM_MSN;
    if (!strcmp (proto, "prpl-icq"))
        return E_CONTACT_IM_ICQ;
    if (!strcmp (proto, "prpl-yahoo"))
        return E_CONTACT_IM_YAHOO;
    if (!strcmp (proto, "prpl-jabber"))
        return E_CONTACT_IM_JABBER;
    if (!strcmp (proto, "prpl-gg"))
        return E_CONTACT_IM_GADUGADU;

    return E_CONTACT_IM_AIM;
}

static void
get_all_blocked (xmlNodePtr node,
                 GSList **blocked)
{
    xmlNodePtr child;

    if (!node || !blocked)
        return;

    for (child = node->children; child; child = child->next) {
        if (child->children)
            get_all_blocked (child, blocked);

        if (!strcmp ((const gchar *) child->name, "block")) {
            gchar *name = get_node_text (child);

            if (name)
                *blocked = g_slist_prepend (*blocked, name);
        }
    }
}

static void
bbdb_get_gaim_buddy_list (GQueue *out_buddies)
{
    gchar *blist_path;
    xmlDocPtr buddy_xml;
    xmlNodePtr root, child, blist;
    GSList *blocked = NULL;

    blist_path = get_buddy_filename ();

    buddy_xml = xmlParseFile (blist_path);
    g_free (blist_path);
    if (!buddy_xml) {
        fprintf (stderr, "bbdb: Could not open Pidgin buddy list.\n");
        return;
    }

    root = xmlDocGetRootElement (buddy_xml);
    if (strcmp ((const gchar *) root->name, "purple")) {
        fprintf (stderr, "bbdb: Could not parse Pidgin buddy list.\n");
        xmlFreeDoc (buddy_xml);
        return;
    }

    for (child = root->children; child != NULL; child = child->next) {
        if (!strcmp ((const gchar *) child->name, "privacy")) {
            get_all_blocked (child, &blocked);
            break;
        }
    }

    blist = NULL;
    for (child = root->children; child != NULL; child = child->next) {
        if (!strcmp ((const gchar *) child->name, "blist")) {
            blist = child;
            break;
        }
    }
    if (blist == NULL) {
        fprintf (
            stderr, "bbdb: Could not find 'blist' "
            "element in Pidgin buddy list.\n");
        xmlFreeDoc (buddy_xml);
        return;
    }

    for (child = blist->children; child != NULL; child = child->next) {
        if (!strcmp ((const gchar *) child->name, "group"))
            parse_buddy_group (child, out_buddies, blocked);
    }

    xmlFreeDoc (buddy_xml);

    g_slist_foreach (blocked, (GFunc) g_free, NULL);
    g_slist_free (blocked);
}

static gchar *
get_node_text (xmlNodePtr node)
{
    if (node->children == NULL || node->children->content == NULL ||
        strcmp ((gchar *) node->children->name, "text"))
        return NULL;

    return g_strdup ((gchar *) node->children->content);
}

static gchar *
get_buddy_icon_from_setting (xmlNodePtr setting)
{
    gchar *icon = NULL;

    icon = get_node_text (setting);
    if (icon[0] != '/') {
        gchar *path;

        path = g_build_path ("/", g_get_home_dir (), ".purple/icons", icon, NULL);
        g_free (icon);
        icon = path;
    }

    return icon;
}

static void
parse_contact (xmlNodePtr contact,
               GQueue *out_buddies,
               GSList *blocked)
{
    xmlNodePtr  child;
    xmlNodePtr  buddy = NULL;
    GaimBuddy  *gb;
    gboolean    is_blocked = FALSE;

    for (child = contact->children; child != NULL; child = child->next) {
        if (!strcmp ((const gchar *) child->name, "buddy")) {
            buddy = child;
            break;
        }
    }

    if (buddy == NULL) {
        fprintf (
            stderr, "bbdb: Could not find buddy in contact. "
            "Malformed Pidgin buddy list file.\n");
        return;
    }

    gb = g_new0 (GaimBuddy, 1);

    gb->proto = e_xml_get_string_prop_by_name (buddy, (const guchar *)"proto");

    for (child = buddy->children; child != NULL && !is_blocked; child = child->next) {
        if (!strcmp ((const gchar *) child->name, "setting")) {
            gchar *setting_type;

            setting_type = e_xml_get_string_prop_by_name (
                child, (const guchar *)"name");

            if (!strcmp ((const gchar *) setting_type, "buddy_icon"))
                gb->icon = get_buddy_icon_from_setting (child);

            g_free (setting_type);
        } else if (!strcmp ((const gchar *) child->name, "name")) {
            gb->account_name = get_node_text (child);
            is_blocked = g_slist_find_custom (
                blocked, gb->account_name,
                (GCompareFunc) strcmp) != NULL;
        } else if (!strcmp ((const gchar *) child->name, "alias"))
            gb->alias = get_node_text (child);

    }

    if (is_blocked)
        free_gaim_body (gb);
    else
        g_queue_push_tail (out_buddies, gb);
}

static void
parse_buddy_group (xmlNodePtr group,
                   GQueue *out_buddies,
                   GSList *blocked)
{
    xmlNodePtr child;

    for (child = group->children; child != NULL; child = child->next) {
        if (strcmp ((const gchar *) child->name, "contact"))
            continue;

        parse_contact (child, out_buddies, blocked);
    }
}