# HG changeset patch # User Richard Laager # Date 1177386976 0 # Node ID 2a19bbc743edb403815f4f562c640f4b0cb7194a # Parent bf353c85959ac49e33b6db4e66f71fa82fd8c4da# Parent c9b4ff42014012b5fd90804f3f012f71b7abad69 propagate from branch 'im.pidgin.pidgin' (head 6096404018084d5ea24f916c8e757d64e237dbd7) to branch 'im.pidgin.rlaager.gaim_migration' (head 9163bb7f709cd6472c0fae9555e2d357b4d2d58e) diff -r bf353c85959a -r 2a19bbc743ed libpurple/blist.c --- a/libpurple/blist.c Tue Apr 24 03:56:03 2007 +0000 +++ b/libpurple/blist.c Tue Apr 24 03:56:16 2007 +0000 @@ -605,6 +605,9 @@ } xmlnode_free(purple); + + /* This tells the buddy icon code to do its thing. */ + purple_buddy_icons_blist_loaded_cb(); } @@ -1141,20 +1144,12 @@ { g_return_if_fail(buddy != NULL); - if (buddy->icon != icon) { - if (buddy->icon != NULL) - purple_buddy_icon_unref(buddy->icon); - + if (buddy->icon != icon) + { + purple_buddy_icon_unref(buddy->icon); buddy->icon = (icon != NULL ? purple_buddy_icon_ref(icon) : NULL); } - if (buddy->icon) - purple_buddy_icon_cache(icon, buddy); - else - purple_buddy_icon_uncache(buddy); - - purple_blist_schedule_save(); - purple_signal_emit(purple_blist_get_handle(), "buddy-icon-changed", buddy); purple_blist_update_buddy_icon(buddy); @@ -1783,9 +1778,6 @@ contact = (PurpleContact *)cnode; group = (PurpleGroup *)gnode; - /* Delete any buddy icon. */ - purple_buddy_icon_uncache(buddy); - /* Remove the node from its parent */ if (node->prev) node->prev->next = node->next; @@ -1831,8 +1823,7 @@ purple_signal_emit(purple_blist_get_handle(), "buddy-removed", buddy); /* Delete the node */ - if (buddy->icon != NULL) - purple_buddy_icon_unref(buddy->icon); + purple_buddy_icon_unref(buddy->icon); g_hash_table_destroy(buddy->node.settings); purple_presence_remove_buddy(buddy->presence, buddy); purple_presence_destroy(buddy->presence); diff -r bf353c85959a -r 2a19bbc743ed libpurple/buddyicon.c --- a/libpurple/buddyicon.c Tue Apr 24 03:56:03 2007 +0000 +++ b/libpurple/buddyicon.c Tue Apr 24 03:56:16 2007 +0000 @@ -24,15 +24,270 @@ */ #include "internal.h" #include "buddyicon.h" +#include "cipher.h" #include "conversation.h" #include "dbus-maybe.h" #include "debug.h" #include "util.h" +typedef struct _PurpleBuddyIconData PurpleBuddyIconData; + +struct _PurpleBuddyIcon +{ + PurpleAccount *account; /**< The account the user is on. */ + char *username; /**< The username the icon belongs to. */ + PurpleBuddyIconData *protocol_icon; /**< The icon data. */ + PurpleBuddyIconData *custom_icon; /**< The data for a user-set custom icon. */ + int ref_count; /**< The buddy icon reference count. */ +}; + +struct _PurpleBuddyIconData +{ + guchar *image_data; /**< The buddy icon data. */ + size_t len; /**< The length of the buddy icon data. */ + char *filename; /**< The filename of the cache file. */ + int ref_count; /**< The buddy icon reference count. */ +}; + static GHashTable *account_cache = NULL; +static GHashTable *icon_data_cache = NULL; +static GHashTable *icon_file_cache = NULL; static char *cache_dir = NULL; static gboolean icon_caching = TRUE; +/* For ~/.gaim to ~/.purple migration. */ +static char *old_icons_dir = NULL; + +static void +ref_filename(const char *filename) +{ + int refs; + + g_return_if_fail(filename != NULL); + + refs = GPOINTER_TO_INT(g_hash_table_lookup(icon_file_cache, filename)); + + g_hash_table_insert(icon_file_cache, g_strdup(filename), + GINT_TO_POINTER(refs + 1)); +} + +static void +unref_filename(const char *filename) +{ + int refs; + + if (filename == NULL) + return; + + refs = GPOINTER_TO_INT(g_hash_table_lookup(icon_file_cache, filename)); + + if (refs == 1) + { + g_hash_table_remove(icon_file_cache, filename); + } + else + { + g_hash_table_insert(icon_file_cache, g_strdup(filename), + GINT_TO_POINTER(refs - 1)); + } +} + +static const char * +get_icon_type(guchar *icon_data, size_t icon_len) +{ + g_return_val_if_fail(icon_data != NULL, NULL); + g_return_val_if_fail(icon_len > 0, NULL); + + if (icon_len >= 4) + { + if (!strncmp((char *)icon_data, "BM", 2)) + return "bmp"; + else if (!strncmp((char *)icon_data, "GIF8", 4)) + return "gif"; + else if (!strncmp((char *)icon_data, "\xff\xd8\xff\xe0", 4)) + return "jpg"; + else if (!strncmp((char *)icon_data, "\x89PNG", 4)) + return "png"; + } + + return "icon"; +} + +static const char * +purple_buddy_icon_data_get_type(PurpleBuddyIconData *data) +{ + return get_icon_type(data->image_data, data->len); +} + +static char * +purple_buddy_icon_data_calculate_filename(guchar *icon_data, size_t icon_len) +{ + PurpleCipherContext *context; + gchar digest[41]; + + context = purple_cipher_context_new_by_name("sha1", NULL); + if (context == NULL) + { + purple_debug_error("buddyicon", "Could not find sha1 cipher\n"); + g_return_val_if_reached(NULL); + } + + /* Hash the icon data */ + purple_cipher_context_append(context, icon_data, icon_len); + if (!purple_cipher_context_digest_to_str(context, sizeof(digest), digest, NULL)) + { + purple_debug_error("buddyicon", "Failed to get SHA-1 digest.\n"); + g_return_val_if_reached(NULL); + } + purple_cipher_context_destroy(context); + + /* Return the filename */ + return g_strdup_printf("%s.%s", digest, + get_icon_type(icon_data, icon_len)); +} + +static void +purple_buddy_icon_data_cache(PurpleBuddyIconData *data) +{ + const char *dirname; + char *path; + FILE *file = NULL; + + if (!purple_buddy_icons_is_caching()) + return; + + dirname = purple_buddy_icons_get_cache_dir(); + path = g_build_filename(dirname, data->filename, NULL); + + if (!g_file_test(dirname, G_FILE_TEST_IS_DIR)) + { + purple_debug_info("buddyicon", "Creating icon cache directory.\n"); + + if (g_mkdir(dirname, S_IRUSR | S_IWUSR | S_IXUSR) < 0) + { + purple_debug_error("buddyicon", + "Unable to create directory %s: %s\n", + dirname, strerror(errno)); + } + } + + if ((file = g_fopen(path, "wb")) != NULL) + { + if (!fwrite(data->image_data, data->len, 1, file)) + { + purple_debug_error("buddyicon", "Error writing %s: %s\n", + path, strerror(errno)); + } + else + purple_debug_info("buddyicon", "Wrote cache file: %s\n", path); + + fclose(file); + } + else + { + purple_debug_error("buddyicon", "Unable to create file %s: %s\n", + path, strerror(errno)); + g_free(path); + return; + } + g_free(path); +} + +static void +purple_buddy_icon_data_uncache(PurpleBuddyIconData *data) +{ + const char *dirname; + char *path; + + g_return_if_fail(data != NULL); + + /* It's possible that there are other references to this icon + * cache file that are not currently loaded into memory. */ + if (g_hash_table_lookup(icon_file_cache, data->filename)) + return; + + dirname = purple_buddy_icons_get_cache_dir(); + path = g_build_filename(dirname, data->filename, NULL); + + if (g_file_test(path, G_FILE_TEST_EXISTS)) + { + if (g_unlink(path)) + { + purple_debug_error("buddyicon", "Failed to delete %s: %s\n", + path, strerror(errno)); + } + else + purple_debug_info("buddyicon", "Deleted cache file: %s\n", path); + } + + g_free(path); +} + +static PurpleBuddyIconData * +purple_buddy_icon_data_ref(PurpleBuddyIconData *data) +{ + g_return_val_if_fail(data != NULL, NULL); + + data->ref_count++; + + return data; +} + +static PurpleBuddyIconData * +purple_buddy_icon_data_unref(PurpleBuddyIconData *data) +{ + if (data == NULL) + return NULL; + + g_return_val_if_fail(data->ref_count > 0, NULL); + + data->ref_count--; + + if (data->ref_count == 0) + { + g_hash_table_remove(icon_data_cache, data->filename); + + purple_buddy_icon_data_uncache(data); + + g_free(data->image_data); + g_free(data->filename); + g_free(data); + + return NULL; + } + + return data; +} + +static PurpleBuddyIconData * +purple_buddy_icon_data_new(guchar *icon_data, size_t icon_len) +{ + PurpleBuddyIconData *data; + char *filename; + + g_return_val_if_fail(icon_data != NULL, NULL); + g_return_val_if_fail(icon_len > 0, NULL); + + filename = purple_buddy_icon_data_calculate_filename(icon_data, icon_len); + if (filename == NULL) + return NULL; + + if ((data = g_hash_table_lookup(icon_data_cache, filename))) + { + g_free(filename); + return purple_buddy_icon_data_ref(data); + } + + data = g_new0(PurpleBuddyIconData, 1); + data->image_data = g_memdup(icon_data, icon_len); + data->len = icon_len; + data->filename = filename; + + purple_buddy_icon_data_cache(data); + + return data; +} + static PurpleBuddyIcon * purple_buddy_icon_create(PurpleAccount *account, const char *username) { @@ -42,8 +297,8 @@ icon = g_new0(PurpleBuddyIcon, 1); PURPLE_DBUS_REGISTER_POINTER(icon, PurpleBuddyIcon); - purple_buddy_icon_set_account(icon, account); - purple_buddy_icon_set_username(icon, username); + icon->account = account; + icon->username = g_strdup(username); icon_cache = g_hash_table_lookup(account_cache, account); @@ -55,91 +310,39 @@ } g_hash_table_insert(icon_cache, - (char *)purple_buddy_icon_get_username(icon), icon); + (char *)purple_buddy_icon_get_username(icon), icon); return icon; } PurpleBuddyIcon * purple_buddy_icon_new(PurpleAccount *account, const char *username, - void *icon_data, size_t icon_len) + void *protocol_icon_data, size_t protocol_icon_len, + void *custom_icon_data, size_t custom_icon_len) { PurpleBuddyIcon *icon; g_return_val_if_fail(account != NULL, NULL); g_return_val_if_fail(username != NULL, NULL); - g_return_val_if_fail(icon_data != NULL, NULL); - g_return_val_if_fail(icon_len > 0, NULL); + g_return_val_if_fail((protocol_icon_data != NULL && protocol_icon_len > 0) || + (custom_icon_data != NULL && custom_icon_len > 0), NULL); icon = purple_buddy_icons_find(account, username); if (icon == NULL) icon = purple_buddy_icon_create(account, username); + /* Take a reference for the caller of this function. */ purple_buddy_icon_ref(icon); - purple_buddy_icon_set_data(icon, icon_data, icon_len); - purple_buddy_icon_set_path(icon, NULL); - /* purple_buddy_icon_set_data() makes blist.c or - * conversation.c, or both, take a reference. - * - * Plus, we leave one for the caller of this function. - */ + if (protocol_icon_data != NULL && protocol_icon_len > 0) + purple_buddy_icon_set_protocol_data(icon, protocol_icon_data, protocol_icon_len); + + if (custom_icon_data != NULL && custom_icon_len > 0) + purple_buddy_icon_set_custom_data(icon, custom_icon_data, custom_icon_len); return icon; } -void -purple_buddy_icon_destroy(PurpleBuddyIcon *icon) -{ - PurpleConversation *conv; - PurpleAccount *account; - GHashTable *icon_cache; - const char *username; - GSList *sl, *list; - - g_return_if_fail(icon != NULL); - - if (icon->ref_count > 0) - { - /* If the ref count is greater than 0, then we weren't called from - * purple_buddy_icon_unref(). So we go through and ask everyone to - * unref us. Then we return, since we know somewhere along the - * line we got called recursively by one of the unrefs, and the - * icon is already destroyed. - */ - account = purple_buddy_icon_get_account(icon); - username = purple_buddy_icon_get_username(icon); - - conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, username, account); - if (conv != NULL) - purple_conv_im_set_icon(PURPLE_CONV_IM(conv), NULL); - - for (list = sl = purple_find_buddies(account, username); sl != NULL; - sl = sl->next) - { - PurpleBuddy *buddy = (PurpleBuddy *)sl->data; - - purple_buddy_set_icon(buddy, NULL); - } - - g_slist_free(list); - - return; - } - - icon_cache = g_hash_table_lookup(account_cache, - purple_buddy_icon_get_account(icon)); - - if (icon_cache != NULL) - g_hash_table_remove(icon_cache, purple_buddy_icon_get_username(icon)); - - g_free(icon->username); - g_free(icon->data); - g_free(icon->path); - PURPLE_DBUS_UNREGISTER_POINTER(icon); - g_free(icon); -} - PurpleBuddyIcon * purple_buddy_icon_ref(PurpleBuddyIcon *icon) { @@ -153,14 +356,26 @@ PurpleBuddyIcon * purple_buddy_icon_unref(PurpleBuddyIcon *icon) { - g_return_val_if_fail(icon != NULL, NULL); + if (icon == NULL) + return NULL; + g_return_val_if_fail(icon->ref_count > 0, NULL); icon->ref_count--; if (icon->ref_count == 0) { - purple_buddy_icon_destroy(icon); + GHashTable *icon_cache = g_hash_table_lookup(account_cache, purple_buddy_icon_get_account(icon)); + + if (icon_cache != NULL) + g_hash_table_remove(icon_cache, purple_buddy_icon_get_username(icon)); + + g_free(icon->username); + purple_buddy_icon_data_unref(icon->protocol_icon); + purple_buddy_icon_data_unref(icon->custom_icon); + + PURPLE_DBUS_UNREGISTER_POINTER(icon); + g_free(icon); return NULL; } @@ -174,6 +389,7 @@ PurpleConversation *conv; PurpleAccount *account; const char *username; + PurpleBuddyIcon *icon_to_set; GSList *sl, *list; g_return_if_fail(icon != NULL); @@ -181,12 +397,58 @@ account = purple_buddy_icon_get_account(icon); username = purple_buddy_icon_get_username(icon); - for (list = sl = purple_find_buddies(account, username); sl != NULL; - sl = sl->next) + /* If neither type of data exists, then call the functions below with + * NULL to unset the icon. They will then unref the icon and it + * should be destroyed. The only way it wouldn't be destroyed is if + * someone else is holding a reference to it, in which case they can + * kill the icon when they realize it has no data any more. */ + icon_to_set = (icon->protocol_icon || icon->custom_icon) ? icon : NULL; + + for (list = sl = purple_find_buddies(account, username); + sl != NULL; + sl = sl->next) { PurpleBuddy *buddy = (PurpleBuddy *)sl->data; + const char *old_icon; - purple_buddy_set_icon(buddy, icon); + purple_buddy_set_icon(buddy, icon_to_set); + + + old_icon = purple_blist_node_get_string((PurpleBlistNode *)buddy, + "buddy_icon"); + if (icon->protocol_icon) + { + old_icon = purple_blist_node_get_string((PurpleBlistNode *)buddy, + "buddy_icon"); + purple_blist_node_set_string((PurpleBlistNode *)buddy, + "buddy_icon", + icon->protocol_icon->filename); + ref_filename(icon->protocol_icon->filename); + } + else + { + purple_blist_node_remove_setting((PurpleBlistNode *)buddy, "buddy_icon"); + } + unref_filename(old_icon); + + + old_icon = purple_blist_node_get_string((PurpleBlistNode *)buddy, + "custom_buddy_icon"); + if (icon->custom_icon) + { + old_icon = purple_blist_node_get_string((PurpleBlistNode *)buddy, + "custom_buddy_icon"); + purple_blist_node_set_string((PurpleBlistNode *)buddy, + "custom_buddy_icon", + icon->custom_icon->filename); + ref_filename(icon->custom_icon->filename); + } + else + { + purple_blist_node_remove_setting((PurpleBlistNode *)buddy, + "custom_buddy_icon"); + } + unref_filename(old_icon); } g_slist_free(list); @@ -194,160 +456,43 @@ conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, username, account); if (conv != NULL) - purple_conv_im_set_icon(PURPLE_CONV_IM(conv), icon); -} - -static void -delete_icon_cache_file(const char *dirname, const char *old_icon) -{ - struct stat st; - - g_return_if_fail(dirname != NULL); - g_return_if_fail(old_icon != NULL); - - if (g_stat(old_icon, &st) == 0) - g_unlink(old_icon); - else - { - char *filename = g_build_filename(dirname, old_icon, NULL); - if (g_stat(filename, &st) == 0) - g_unlink(filename); - g_free(filename); - } - purple_debug_info("buddyicon", "Uncached file %s\n", old_icon); + purple_conv_im_set_icon(PURPLE_CONV_IM(conv), icon_to_set); } void -purple_buddy_icon_cache(PurpleBuddyIcon *icon, PurpleBuddy *buddy) +purple_buddy_icon_set_custom_data(PurpleBuddyIcon *icon, guchar *data, size_t len) { - const guchar *data; - const char *dirname; - char *random; - char *filename; - const char *old_icon; - size_t len = 0; - FILE *file = NULL; - - g_return_if_fail(icon != NULL); - g_return_if_fail(buddy != NULL); + PurpleBuddyIconData *old_data; - if (!purple_buddy_icons_is_caching()) - return; - - data = purple_buddy_icon_get_data(icon, &len); - - random = g_strdup_printf("%x", g_random_int()); - dirname = purple_buddy_icons_get_cache_dir(); - filename = g_build_filename(dirname, random, NULL); - old_icon = purple_blist_node_get_string((PurpleBlistNode*)buddy, "buddy_icon"); - - if (!g_file_test(dirname, G_FILE_TEST_IS_DIR)) - { - purple_debug_info("buddyicon", "Creating icon cache directory.\n"); + g_return_if_fail(icon != NULL); - if (g_mkdir(dirname, S_IRUSR | S_IWUSR | S_IXUSR) < 0) - { - purple_debug_error("buddyicon", - "Unable to create directory %s: %s\n", - dirname, strerror(errno)); - } - } + old_data = icon->custom_icon; + icon->custom_icon = NULL; - if ((file = g_fopen(filename, "wb")) != NULL) - { - fwrite(data, 1, len, file); - fclose(file); - purple_debug_info("buddyicon", "Wrote file %s\n", filename); - } - else - { - purple_debug_error("buddyicon", "Unable to create file %s: %s\n", - filename, strerror(errno)); - g_free(filename); - g_free(random); - return; - } + if (data != NULL && len > 0) + icon->custom_icon = purple_buddy_icon_data_new(data, len); - g_free(filename); + purple_buddy_icon_update(icon); - if (old_icon != NULL) - delete_icon_cache_file(dirname, old_icon); - - purple_blist_node_set_string((PurpleBlistNode *)buddy, "buddy_icon", random); - - g_free(random); + purple_buddy_icon_data_unref(icon->custom_icon); } void -purple_buddy_icon_uncache(PurpleBuddy *buddy) +purple_buddy_icon_set_protocol_data(PurpleBuddyIcon *icon, guchar *data, size_t len) { - const char *old_icon; - - g_return_if_fail(buddy != NULL); - - old_icon = purple_blist_node_get_string((PurpleBlistNode *)buddy, "buddy_icon"); - - if (old_icon != NULL) - delete_icon_cache_file(purple_buddy_icons_get_cache_dir(), old_icon); - - purple_blist_node_remove_setting((PurpleBlistNode *)buddy, "buddy_icon"); - - /* Unset the icon in case this function is called from - * something other than purple_buddy_set_icon(). */ - if (buddy->icon != NULL) - { - purple_buddy_icon_unref(buddy->icon); - buddy->icon = NULL; - } -} + PurpleBuddyIconData *old_data; -void -purple_buddy_icon_set_account(PurpleBuddyIcon *icon, PurpleAccount *account) -{ - g_return_if_fail(icon != NULL); - g_return_if_fail(account != NULL); - - icon->account = account; -} - -void -purple_buddy_icon_set_username(PurpleBuddyIcon *icon, const char *username) -{ - g_return_if_fail(icon != NULL); - g_return_if_fail(username != NULL); - - g_free(icon->username); - icon->username = g_strdup(username); -} - -void -purple_buddy_icon_set_data(PurpleBuddyIcon *icon, void *data, size_t len) -{ g_return_if_fail(icon != NULL); - g_free(icon->data); + old_data = icon->protocol_icon; + icon->protocol_icon = NULL; if (data != NULL && len > 0) - { - icon->data = g_memdup(data, len); - icon->len = len; - } - else - { - icon->data = NULL; - icon->len = 0; - } + icon->protocol_icon = purple_buddy_icon_data_new(data, len); purple_buddy_icon_update(icon); -} -void -purple_buddy_icon_set_path(PurpleBuddyIcon *icon, const gchar *path) -{ - g_return_if_fail(icon != NULL); - - g_free(icon->path); - icon->path = g_strdup(path); + purple_buddy_icon_data_unref(old_data); } PurpleAccount * @@ -371,116 +516,308 @@ { g_return_val_if_fail(icon != NULL, NULL); - if (len != NULL) - *len = icon->len; + if (icon->custom_icon) + { + if (len != NULL) + *len = icon->custom_icon->len; - return icon->data; -} + return icon->custom_icon->image_data; + } -const char * -purple_buddy_icon_get_path(PurpleBuddyIcon *icon) -{ - g_return_val_if_fail(icon != NULL, NULL); + if (icon->protocol_icon) + { + if (len != NULL) + *len = icon->protocol_icon->len; - return icon->path; + return icon->protocol_icon->image_data; + } + + return NULL; } const char * purple_buddy_icon_get_type(const PurpleBuddyIcon *icon) { - const void *data; - size_t len; - - g_return_val_if_fail(icon != NULL, NULL); - - data = purple_buddy_icon_get_data(icon, &len); + if (icon->custom_icon != NULL) + return purple_buddy_icon_data_get_type(icon->custom_icon); - /* TODO: Find a way to do this with GDK */ - if (len >= 4) - { - if (!strncmp(data, "BM", 2)) - return "bmp"; - else if (!strncmp(data, "GIF8", 4)) - return "gif"; - else if (!strncmp(data, "\xff\xd8\xff\xe0", 4)) - return "jpg"; - else if (!strncmp(data, "\x89PNG", 4)) - return "png"; - } + if (icon->protocol_icon != NULL) + return purple_buddy_icon_data_get_type(icon->protocol_icon); return NULL; } void purple_buddy_icons_set_for_user(PurpleAccount *account, const char *username, - void *icon_data, size_t icon_len) + void *icon_data, size_t icon_len) { g_return_if_fail(account != NULL); g_return_if_fail(username != NULL); if (icon_data == NULL || icon_len == 0) { - PurpleBuddyIcon *buddy_icon; + PurpleBuddyIcon *icon; - buddy_icon = purple_buddy_icons_find(account, username); + icon = purple_buddy_icons_find(account, username); - if (buddy_icon != NULL) - purple_buddy_icon_destroy(buddy_icon); + if (icon != NULL) + purple_buddy_icon_set_protocol_data(icon, icon_data, icon_len); } else { - PurpleBuddyIcon *icon = purple_buddy_icon_new(account, username, icon_data, icon_len); + PurpleBuddyIcon *icon = purple_buddy_icon_new(account, username, icon_data, icon_len, NULL, 0); purple_buddy_icon_unref(icon); } } +static gboolean +read_icon_file(const char *path, guchar **data, size_t *len) +{ + struct stat st; + + if (!g_stat(path, &st)) + { + FILE *f = g_fopen(path, "rb"); + if (f) + { + *data = g_malloc(st.st_size); + if (!fread(*data, st.st_size, 1, f)) + { + purple_debug_error("buddyicon", "Error reading %s: %s\n", + path, strerror(errno)); + g_free(*data); + return FALSE; + } + fclose(f); + + *len = st.st_size; + return TRUE; + } + else + { + purple_debug_error("buddyicon", "Unable to open file %s for reading: %s\n", + path, strerror(errno)); + } + } + return FALSE; +} + PurpleBuddyIcon * purple_buddy_icons_find(PurpleAccount *account, const char *username) { GHashTable *icon_cache; - PurpleBuddyIcon *ret = NULL; - char *filename = NULL; + PurpleBuddyIcon *icon = NULL; g_return_val_if_fail(account != NULL, NULL); g_return_val_if_fail(username != NULL, NULL); icon_cache = g_hash_table_lookup(account_cache, account); - if ((icon_cache == NULL) || ((ret = g_hash_table_lookup(icon_cache, username)) == NULL)) { - const char *file; - struct stat st; + if ((icon_cache == NULL) || ((icon = g_hash_table_lookup(icon_cache, username)) == NULL)) + { PurpleBuddy *b = purple_find_buddy(account, username); + const char *protocol_icon_file; + const char *custom_icon_file; + const char *dirname; + gboolean caching; + guchar *data; + size_t len; if (!b) return NULL; - if ((file = purple_blist_node_get_string((PurpleBlistNode*)b, "buddy_icon")) == NULL) + protocol_icon_file = purple_blist_node_get_string((PurpleBlistNode*)b, "buddy_icon"); + custom_icon_file = purple_blist_node_get_string((PurpleBlistNode*)b, "custom_buddy_icon"); + + if (protocol_icon_file == NULL && custom_icon_file == NULL) return NULL; - if (!g_stat(file, &st)) - filename = g_strdup(file); - else - filename = g_build_filename(purple_buddy_icons_get_cache_dir(), file, NULL); + dirname = purple_buddy_icons_get_cache_dir(); + + caching = purple_buddy_icons_is_caching(); + /* By disabling caching temporarily, we avoid a loop + * and don't have to add special code through several + * functions. */ + purple_buddy_icons_set_caching(FALSE); + + if (custom_icon_file != NULL) + { + char *path = g_build_filename(dirname, custom_icon_file, NULL); + if (read_icon_file(path, &data, &len)) + { + icon = purple_buddy_icon_create(account, username); + purple_buddy_icon_set_custom_data(icon, data, len); + } + g_free(path); + } + + if (protocol_icon_file != NULL) + { + char *path = g_build_filename(dirname, protocol_icon_file, NULL); + if (read_icon_file(path, &data, &len)) + { + if (icon == NULL) + icon = purple_buddy_icon_create(account, username); + purple_buddy_icon_set_protocol_data(icon, data, len); + } + g_free(path); + } + + purple_buddy_icons_set_caching(caching); + } + + return icon; +} + +void +purple_buddy_icon_set_old_icons_dir(const char *dirname) +{ + old_icons_dir = g_strdup(dirname); +} + +static void +migrate_buddy_icon(PurpleBlistNode *node, const char *setting_name, + const char *dirname, const char *filename) +{ + char *path; + + if (filename[0] != '/') + { + path = g_build_filename(dirname, filename, NULL); + if (g_file_test(path, G_FILE_TEST_EXISTS)) + { + g_free(path); + return; + } + g_free(path); + + path = g_build_filename(old_icons_dir, filename, NULL); + } + else + path = g_strdup(filename); + + if (g_file_test(path, G_FILE_TEST_EXISTS)) + { + guchar *icon_data; + size_t icon_len; + FILE *file; + char *new_filename; - if (!g_stat(filename, &st)) { - FILE *f = g_fopen(filename, "rb"); - if (f) { - char *data = g_malloc(st.st_size); - fread(data, 1, st.st_size, f); - fclose(f); - ret = purple_buddy_icon_create(account, username); - purple_buddy_icon_ref(ret); - purple_buddy_icon_set_data(ret, data, st.st_size); - purple_buddy_icon_unref(ret); - g_free(data); - g_free(filename); - return ret; + if (!read_icon_file(path, &icon_data, &icon_len)) + { + g_free(path); + return; + } + + g_free(path); + + new_filename = purple_buddy_icon_data_calculate_filename(icon_data, icon_len); + if (new_filename == NULL) + { + return; + } + + path = g_build_filename(dirname, new_filename, NULL); + if ((file = g_fopen(path, "wb")) != NULL) + { + if (!fwrite(icon_data, icon_len, 1, file)) + { + purple_debug_error("buddyicon", "Error writing %s: %s\n", + path, strerror(errno)); + } + else + purple_debug_info("buddyicon", "Wrote migrated cache file: %s\n", path); + + fclose(file); + } + else + { + purple_debug_error("buddyicon", "Unable to create file %s: %s\n", + path, strerror(errno)); + g_free(new_filename); + g_free(path); + return; + } + g_free(path); + + purple_blist_node_set_string(node, + setting_name, + new_filename); + ref_filename(new_filename); + + g_free(new_filename); + } + else + { + /* If the icon is gone, drop the setting... */ + purple_blist_node_remove_setting(node, + setting_name); + g_free(path); + } +} + +void +purple_buddy_icons_blist_loaded_cb() +{ + PurpleBlistNode *node = purple_blist_get_root(); + const char *dirname = purple_buddy_icons_get_cache_dir(); + + /* Doing this once here saves having to check it inside a loop. */ + if (old_icons_dir != NULL) + { + if (!g_file_test(dirname, G_FILE_TEST_IS_DIR)) + { + purple_debug_info("buddyicon", "Creating icon cache directory.\n"); + + if (g_mkdir(dirname, S_IRUSR | S_IWUSR | S_IXUSR) < 0) + { + purple_debug_error("buddyicon", + "Unable to create directory %s: %s\n", + dirname, strerror(errno)); } } - g_free(filename); } - return ret; + while (node != NULL) + { + if (PURPLE_BLIST_NODE_IS_BUDDY(node)) + { + const char *filename; + + filename = purple_blist_node_get_string(node, "buddy_icon"); + if (filename != NULL) + { + if (old_icons_dir != NULL) + { + migrate_buddy_icon(node, + "buddy_icon", + dirname, filename); + } + else + { + // TODO: If filename doesn't exist, drop the setting. + ref_filename(filename); + } + } + + filename = purple_blist_node_get_string(node, "custom_buddy_icon"); + if (filename != NULL) + { + if (old_icons_dir != NULL) + { + migrate_buddy_icon(node, + "custom_buddy_icon", + dirname, filename); + } + else + { + // TODO: If filename doesn't exist, drop the setting. + ref_filename(filename); + } + } + } + node = purple_blist_node_next(node, TRUE); + } } void @@ -510,6 +847,7 @@ return cache_dir; } +// TODO: Deal with this char *purple_buddy_icons_get_full_path(const char *icon) { if (icon == NULL) return NULL; @@ -535,6 +873,10 @@ g_direct_hash, g_direct_equal, NULL, (GFreeFunc)g_hash_table_destroy); + icon_data_cache = g_hash_table_new(g_str_hash, g_str_equal); + icon_file_cache = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, NULL); + cache_dir = g_build_filename(purple_user_dir(), "icons", NULL); } @@ -542,6 +884,9 @@ purple_buddy_icons_uninit() { g_hash_table_destroy(account_cache); + g_hash_table_destroy(icon_data_cache); + g_hash_table_destroy(icon_file_cache); + g_free(old_icons_dir); } void purple_buddy_icon_get_scale_size(PurpleBuddyIconSpec *spec, int *width, int *height) @@ -572,4 +917,3 @@ *width = new_width; *height = new_height; } - diff -r bf353c85959a -r 2a19bbc743ed libpurple/buddyicon.h --- a/libpurple/buddyicon.h Tue Apr 24 03:56:03 2007 +0000 +++ b/libpurple/buddyicon.h Tue Apr 24 03:56:16 2007 +0000 @@ -31,49 +31,38 @@ #include "blist.h" #include "prpl.h" -struct _PurpleBuddyIcon -{ - PurpleAccount *account; /**< The account the user is on. */ - char *username; /**< The username the icon belongs to. */ - - void *data; /**< The buddy icon data. */ - size_t len; /**< The length of the buddy icon data. */ - char *path; /**< The buddy icon's non-cached path. */ - - int ref_count; /**< The buddy icon reference count. */ -}; - #ifdef __cplusplus extern "C" { #endif +// TODO: Deal with this. +char *purple_buddy_icons_get_full_path(const char *icon); + /**************************************************************************/ /** @name Buddy Icon API */ /**************************************************************************/ /*@{*/ /** - * Creates a new buddy icon structure. + * Create a buddy icon structure and populate it. + * + * If the buddy icon already exists, you'll get a reference to that structure, + * which will have been updated with the data supplied. * * @param account The account the user is on. * @param username The username the icon belongs to. - * @param icon_data The buddy icon data. - * @param icon_len The buddy icon length. - * - * @return The buddy icon structure. + * @param protocol_icon_data The buddy icon data received over the wire. + * @param protocol_icon_len The length of the data in @a protocol_icon_data. + * @param custom_icon_data The data for a custom buddy icon set by the user. + * @param custom_icon_len The length of the data in @a custom_icon-data. + * @return The buddy icon structure, referenced for you. If you don't + * want the reference, then call purple_buddy_icon_unref(). However, + * you may want to use purple_buddy_icon_set_for_user() instead. */ -PurpleBuddyIcon *purple_buddy_icon_new(PurpleAccount *account, const char *username, - void *icon_data, size_t icon_len); - -/** - * Destroys a buddy icon structure. - * - * If the buddy icon's reference count is greater than 1, this will - * just decrease the reference count and return. - * - * @param icon The buddy icon structure to destroy. - */ -void purple_buddy_icon_destroy(PurpleBuddyIcon *icon); +PurpleBuddyIcon * +purple_buddy_icon_new(PurpleAccount *account, const char *username, + void *protocol_icon_data, size_t protocol_icon_len, + void *custom_icon_data, size_t custom_icon_len); /** * Increments the reference count on a buddy icon. @@ -103,52 +92,22 @@ void purple_buddy_icon_update(PurpleBuddyIcon *icon); /** - * Caches a buddy icon associated with a specific buddy to disk. - * - * @param icon The buddy icon. - * @param buddy The buddy that this icon belongs to. - */ -void purple_buddy_icon_cache(PurpleBuddyIcon *icon, PurpleBuddy *buddy); - -/** - * Removes cached buddy icon for a specific buddy. + * Sets the buddy icon's data for a custom icon set by the user. * - * @param buddy The buddy for which to remove the cached icon. + * @param icon The buddy icon. + * @param data The buddy icon data for the custom icon set by the user. + * @param len The length of the data in @a data. */ -void purple_buddy_icon_uncache(PurpleBuddy *buddy); - -/** - * Sets the buddy icon's account. - * - * @param icon The buddy icon. - * @param account The account. - */ -void purple_buddy_icon_set_account(PurpleBuddyIcon *icon, PurpleAccount *account); +void purple_buddy_icon_set_custom_data(PurpleBuddyIcon *icon, guchar *data, size_t len); /** - * Sets the buddy icon's username. - * - * @param icon The buddy icon. - * @param username The username. - */ -void purple_buddy_icon_set_username(PurpleBuddyIcon *icon, const char *username); - -/** - * Sets the buddy icon's icon data. + * Sets the buddy icon's data that was received over the wire. * * @param icon The buddy icon. - * @param data The buddy icon data. - * @param len The length of the icon data. + * @param data The buddy icon data received over the wire. + * @param len The length of the data in @a data. */ -void purple_buddy_icon_set_data(PurpleBuddyIcon *icon, void *data, size_t len); - -/** - * Sets the buddy icon's path. - * - * @param icon The buddy icon. - * @param path The buddy icon's non-cached path. - */ -void purple_buddy_icon_set_path(PurpleBuddyIcon *icon, const gchar *path); +void purple_buddy_icon_set_protocol_data(PurpleBuddyIcon *icon, guchar *data, size_t len); /** * Returns the buddy icon's account. @@ -171,28 +130,32 @@ /** * Returns the buddy icon's data. * + * This will return the data for a custom icon, if one has been set by the + * user. Otherwise, it will return the protocol data, if available. If + * no data is available (which can happen if you're holding on to a + * reference to an icon and the protocol and/or custom icon(s) are deleted + * under you), it will return @c NULL. + * * @param icon The buddy icon. - * @param len The returned icon length. + * @param len If not @c NULL, the length of the icon data returned will be + * set in the location pointed to by this. * * @return The icon data. */ const guchar *purple_buddy_icon_get_data(const PurpleBuddyIcon *icon, size_t *len); /** - * Returns the buddy icon's path. + * Returns an extension corresponding to the buddy icon's file type. + * + * This will return the type of a custom icon, if one has been set by the + * user. Otherwise, it will return the type of the protocol icon, if + * available. If no data is available (which can happen if you're holding on + * to a reference to an icon and the protocol and/or custom icon(s) are deleted + * under you), it will return @c NULL. * * @param icon The buddy icon. * - * @return The buddy icon's non-cached path. - */ -const gchar *purple_buddy_icon_get_path(PurpleBuddyIcon *icon); - -/** - * Returns an extension corresponding to the buddy icon's file type. - * - * @param icon The buddy icon. - * - * @return The icon's extension. + * @return The icon's extension, or "icon" if unknown. */ const char *purple_buddy_icon_get_type(const PurpleBuddyIcon *icon); @@ -213,8 +176,9 @@ * * @return The buddy icon set, or NULL if no icon was set. */ -void purple_buddy_icons_set_for_user(PurpleAccount *account, const char *username, - void *icon_data, size_t icon_len); +void +purple_buddy_icons_set_for_user(PurpleAccount *account, const char *username, + void *icon_data, size_t icon_len); /** * Returns the buddy icon information for a user. @@ -224,8 +188,8 @@ * * @return The icon data if found, or @c NULL if not found. */ -PurpleBuddyIcon *purple_buddy_icons_find(PurpleAccount *account, - const char *username); +PurpleBuddyIcon * +purple_buddy_icons_find(PurpleAccount *account, const char *username); /** * Sets whether or not buddy icon caching is enabled. @@ -263,18 +227,6 @@ const char *purple_buddy_icons_get_cache_dir(void); /** - * Takes a buddy icon and returns a full path. - * - * If @a icon is a full path to an existing file, a copy of - * @a icon is returned. Otherwise, a newly allocated string - * consiting of purple_buddy_icons_get_cache_dir() + @a icon is - * returned. - * - * @return The full path for an icon. - */ -char *purple_buddy_icons_get_full_path(const char *icon); - -/** * Returns the buddy icon subsystem handle. * * @return The subsystem handle. diff -r bf353c85959a -r 2a19bbc743ed libpurple/conversation.c --- a/libpurple/conversation.c Tue Apr 24 03:56:03 2007 +0000 +++ b/libpurple/conversation.c Tue Apr 24 03:56:16 2007 +0000 @@ -425,8 +425,7 @@ purple_conv_im_stop_typing_timeout(conv->u.im); purple_conv_im_stop_send_typed_timeout(conv->u.im); - if (conv->u.im->icon != NULL) - purple_buddy_icon_unref(conv->u.im->icon); + purple_buddy_icon_unref(conv->u.im->icon); conv->u.im->icon = NULL; PURPLE_DBUS_UNREGISTER_POINTER(conv->u.im); @@ -941,8 +940,7 @@ if (im->icon != icon) { - if (im->icon != NULL) - purple_buddy_icon_unref(im->icon); + purple_buddy_icon_unref(im->icon); im->icon = (icon == NULL ? NULL : purple_buddy_icon_ref(icon)); } diff -r bf353c85959a -r 2a19bbc743ed libpurple/core.c --- a/libpurple/core.c Tue Apr 24 03:56:03 2007 +0000 +++ b/libpurple/core.c Tue Apr 24 03:56:16 2007 +0000 @@ -44,6 +44,7 @@ #include "sslconn.h" #include "status.h" #include "stun.h" +#include "util.h" #ifdef HAVE_DBUS # include "dbus-server.h" @@ -268,3 +269,376 @@ { return _ops; } + +static gboolean +move_and_symlink_dir(const char *path, const char *basename, const char *old_base, const char *new_base, const char *relative) +{ + char *new_name = g_build_filename(new_base, basename, NULL); +#ifndef _WIN32 + char *old_name; +#endif + if (g_rename(path, new_name)) + { + purple_debug_error("core", "Error renaming %s to %s: %s\n", + path, new_name, strerror(errno)); + g_free(new_name); + return FALSE; + } + g_free(new_name); + +#ifndef _WIN32 + /* NOTE: This new_name is relative. */ + new_name = g_build_filename(relative, basename, NULL); + old_name = g_build_filename(old_base, basename, NULL); + if (symlink(new_name, old_name)) + { + purple_debug_warning("core", "Error symlinking %s to %s: %s\n", + old_name, new_name, strerror(errno)); + } + g_free(old_name); + g_free(new_name); +#endif + + return TRUE; +} + +gboolean +purple_core_migrate(void) +{ + const char *user_dir = purple_user_dir(); + char *old_user_dir = g_strconcat(purple_home_dir(), + G_DIR_SEPARATOR_S ".gaim", NULL); + char *status_file; + FILE *fp; + GDir *dir; + GError *err; + const char *entry; +#ifndef _WIN32 + char *logs_dir; +#endif + char *old_icons_dir; + + if (!g_file_test(old_user_dir, G_FILE_TEST_EXISTS)) + { + /* ~/.gaim doesn't exist, so there's nothing to migrate. */ + g_free(old_user_dir); + return TRUE; + } + + status_file = g_strconcat(user_dir, G_DIR_SEPARATOR_S "migrating", NULL); + + if (g_file_test(user_dir, G_FILE_TEST_EXISTS)) + { + /* If we're here, we have both ~/.gaim and .purple. */ + + if (!g_file_test(status_file, G_FILE_TEST_EXISTS)) + { + /* There's no "migrating" status file, + * so ~/.purple is all up to date. */ + g_free(status_file); + g_free(old_user_dir); + return TRUE; + } + } + + /* If we're here, it's time to migrate from ~/.gaim to ~/.purple. */ + + /* Ensure the user directory exists */ + if (!g_file_test(user_dir, G_FILE_TEST_IS_DIR)) + { + if (g_mkdir(user_dir, S_IRUSR | S_IWUSR | S_IXUSR) == -1) + { + purple_debug_error("core", "Error creating directory %s: %s\n", + user_dir, strerror(errno)); + g_free(status_file); + g_free(old_user_dir); + return FALSE; + } + } + + /* This writes ~/.purple/migrating, which allows us to detect + * incomplete migrations and properly retry. */ + if (!(fp = g_fopen(status_file, "w"))) + { + purple_debug_error("core", "Error opening file %s for writing: %s\n", + status_file, strerror(errno)); + g_free(status_file); + g_free(old_user_dir); + return FALSE; + } + fclose(fp); + + /* Open ~/.gaim so we can loop over its contents. */ + err = NULL; + if (!(dir = g_dir_open(old_user_dir, 0, &err))) + { + purple_debug_error("core", "Error opening directory %s: %s\n", + status_file, + (err ? err->message : "Unknown error")); + if (err) + g_error_free(err); + g_free(status_file); + g_free(old_user_dir); + return FALSE; + } + + /* Loop over the contents of ~/.gaim */ + while ((entry = g_dir_read_name(dir))) + { + char *name = g_build_filename(old_user_dir, entry, NULL); + +#ifndef _WIN32 + /* Deal with symlinks... */ + if (g_file_test(name, G_FILE_TEST_IS_SYMLINK)) + { + /* We're only going to duplicate a logs symlink. */ + if (!strcmp(entry, "logs")) + { + char buf[MAXPATHLEN]; + + if (readlink(name, buf, sizeof(buf) - 1) == -1) + { + purple_debug_error("core", "Error reading symlink %s: %s\n", + name, strerror(errno)); + g_free(name); + g_dir_close(dir); + g_free(status_file); + g_free(old_user_dir); + return FALSE; + } + buf[sizeof(buf) - 1] = '\0'; + + logs_dir = g_strconcat(user_dir, G_DIR_SEPARATOR_S "logs", NULL); + + if (!strcmp(buf, "../.purple/logs") || !strcmp(buf, logs_dir)) + { + /* If the symlink points to the new directory, we're + * likely just trying again after a failed migration, + * so there's no need to fail here. */ + g_free(logs_dir); + continue; + } + + /* In case we are trying again after a failed migration, we need + * to unlink any existing symlink. If it's a directory, this + * will fail, and so will the symlink below, which is good + * because the user should sort things out. */ + g_unlink(logs_dir); + + /* Relative links will most likely still be + * valid from ~/.purple, though not it's not + * guaranteed. Oh well. */ + if (symlink(buf, logs_dir)) + { + purple_debug_error("core", "Error symlinking %s to %s: %s\n", + logs_dir, buf, strerror(errno)); + g_free(name); + g_free(logs_dir); + g_dir_close(dir); + g_free(status_file); + g_free(old_user_dir); + return FALSE; + } + + g_free(logs_dir); + continue; + } + + /* Ignore all other symlinks. */ + continue; + } +#endif + + /* Deal with directories... */ + if (g_file_test(name, G_FILE_TEST_IS_DIR)) + { + if (!strcmp(entry, "icons")) + { + /* This is a special case for the Album plugin, which + * stores data in the icons folder. We're not copying + * the icons directory over because previous bugs + * meant that it filled up with junk for many users. + * This is a great time to purge it. */ + + GDir *icons_dir; + char *new_icons_dir; + const char *icons_entry; + + err = NULL; + if (!(icons_dir = g_dir_open(name, 0, &err))) + { + purple_debug_error("core", "Error opening directory %s: %s\n", + name, + (err ? err->message : "Unknown error")); + if (err) + g_error_free(err); + g_free(name); + g_dir_close(dir); + g_free(status_file); + g_free(old_user_dir); + return FALSE; + } + + new_icons_dir = g_build_filename(user_dir, "icons", NULL); + /* Ensure the new icon directory exists */ + if (!g_file_test(new_icons_dir, G_FILE_TEST_IS_DIR)) + { + if (g_mkdir(new_icons_dir, S_IRUSR | S_IWUSR | S_IXUSR) == -1) + { + purple_debug_error("core", "Error creating directory %s: %s\n", + new_icons_dir, strerror(errno)); + g_free(new_icons_dir); + g_dir_close(icons_dir); + g_free(name); + g_dir_close(dir); + g_free(status_file); + g_free(old_user_dir); + return FALSE; + } + } + + while ((icons_entry = g_dir_read_name(icons_dir))) + { + char *icons_name = g_build_filename(name, icons_entry, NULL); + + if (g_file_test(icons_name, G_FILE_TEST_IS_DIR)) + { + if (!move_and_symlink_dir(icons_name, icons_entry, + name, new_icons_dir, "../../.purple/icons")) + { + g_free(icons_name); + g_free(new_icons_dir); + g_dir_close(icons_dir); + g_free(name); + g_dir_close(dir); + g_free(status_file); + g_free(old_user_dir); + return FALSE; + } + } + g_free(icons_name); + } + + g_dir_close(icons_dir); + } + else if (!strcmp(entry, "plugins")) + { + /* Do nothing, because we broke plugin compatibility. + * This means that the plugins directory gets left behind. */ + } + else + { + /* All other directories are moved and symlinked. */ + if (!move_and_symlink_dir(name, entry, old_user_dir, user_dir, "../.purple")) + { + g_free(name); + g_dir_close(dir); + g_free(status_file); + g_free(old_user_dir); + return FALSE; + } + } + } + else if (g_file_test(name, G_FILE_TEST_IS_REGULAR)) + { + /* Regular files are copied. */ + + char *new_name; + FILE *new_file; + + if (!(fp = g_fopen(name, "rb"))) + { + purple_debug_error("core", "Error opening file %s for reading: %s\n", + name, strerror(errno)); + g_free(name); + g_dir_close(dir); + g_free(status_file); + g_free(old_user_dir); + return FALSE; + } + + new_name = g_build_filename(user_dir, entry, NULL); + if (!(new_file = g_fopen(new_name, "wb"))) + { + purple_debug_error("core", "Error opening file %s for writing: %s\n", + new_name, strerror(errno)); + fclose(fp); + g_free(new_name); + g_free(name); + g_dir_close(dir); + g_free(status_file); + g_free(old_user_dir); + return FALSE; + } + + while (!feof(fp)) + { + unsigned char buf[256]; + size_t size; + + size = fread(buf, 1, sizeof(buf), fp); + if (size != sizeof(buf) && !feof(fp)) + { + purple_debug_error("core", "Error reading %s: %s\n", + name, strerror(errno)); + fclose(new_file); + fclose(fp); + g_free(new_name); + g_free(name); + g_dir_close(dir); + g_free(status_file); + g_free(old_user_dir); + return FALSE; + } + + if (!fwrite(buf, size, 1, new_file)) + { + purple_debug_error("core", "Error writing %s: %s\n", + new_name, strerror(errno)); + fclose(new_file); + fclose(fp); + g_free(new_name); + g_free(name); + g_dir_close(dir); + g_free(status_file); + g_free(old_user_dir); + return FALSE; + } + } + + if (fclose(new_file)) + { + purple_debug_error("core", "Error writing: %s: %s\n", + new_name, strerror(errno)); + } + if (fclose(fp)) + { + purple_debug_warning("core", "Error closing %s: %s\n", + name, strerror(errno)); + } + g_free(new_name); + } + else + purple_debug_warning("core", "Not a regular file or directory: %s\n", name); + + g_free(name); + } + + /* The migration was successful, so delete the status file. */ + if (g_unlink(status_file)) + { + purple_debug_error("core", "Error unlinking file %s: %s\n", + status_file, strerror(errno)); + g_free(status_file); + return FALSE; + } + + old_icons_dir = g_build_filename(old_user_dir, "icons", NULL); + purple_buddy_icon_set_old_icons_dir(old_icons_dir); + g_free(old_icons_dir); + + g_free(old_user_dir); + + g_free(status_file); + return TRUE; +} diff -r bf353c85959a -r 2a19bbc743ed libpurple/core.h --- a/libpurple/core.h Tue Apr 24 03:56:03 2007 +0000 +++ b/libpurple/core.h Tue Apr 24 03:56:16 2007 +0000 @@ -106,6 +106,17 @@ */ PurpleCoreUiOps *purple_core_get_ui_ops(void); +/** + * Migrates from .gaim to .purple. + * + * UIs MUST NOT call this if they have been told to use a custom + * user directory. + * + * @return A boolean indicating success or migration failure. On failure, + * the application must display an error to the user and then exit. + */ +gboolean purple_core_migrate(void); + #ifdef __cplusplus } #endif diff -r bf353c85959a -r 2a19bbc743ed libpurple/gaim-compat.h --- a/libpurple/gaim-compat.h Tue Apr 24 03:56:03 2007 +0000 +++ b/libpurple/gaim-compat.h Tue Apr 24 03:56:16 2007 +0000 @@ -343,18 +343,14 @@ #define gaim_buddy_icon_ref purple_buddy_icon_ref #define gaim_buddy_icon_unref purple_buddy_icon_unref #define gaim_buddy_icon_update purple_buddy_icon_update -#define gaim_buddy_icon_cache purple_buddy_icon_cache -#define gaim_buddy_icon_uncache purple_buddy_icon_uncache #define gaim_buddy_icon_set_account purple_buddy_icon_set_account #define gaim_buddy_icon_set_username purple_buddy_icon_set_username -#define gaim_buddy_icon_set_data purple_buddy_icon_set_data -#define gaim_buddy_icon_set_path purple_buddy_icon_set_path +#define gaim_buddy_icon_set_data purple_buddy_icon_set_protocol_data #define gaim_buddy_icon_get_account purple_buddy_icon_get_account #define gaim_buddy_icon_get_username purple_buddy_icon_get_username #define gaim_buddy_icon_get_data purple_buddy_icon_get_data -#define gaim_buddy_icon_get_path purple_buddy_icon_get_path #define gaim_buddy_icon_get_type purple_buddy_icon_get_type #define gaim_buddy_icons_set_for_user purple_buddy_icons_set_for_user @@ -363,7 +359,6 @@ #define gaim_buddy_icons_is_caching purple_buddy_icons_is_caching #define gaim_buddy_icons_set_cache_dir purple_buddy_icons_set_cache_dir #define gaim_buddy_icons_get_cache_dir purple_buddy_icons_get_cache_dir -#define gaim_buddy_icons_get_full_path purple_buddy_icons_get_full_path #define gaim_buddy_icons_get_handle purple_buddy_icons_get_handle #define gaim_buddy_icons_init purple_buddy_icons_init diff -r bf353c85959a -r 2a19bbc743ed libpurple/internal.h --- a/libpurple/internal.h Tue Apr 24 03:56:03 2007 +0000 +++ b/libpurple/internal.h Tue Apr 24 03:56:16 2007 +0000 @@ -184,4 +184,15 @@ #define PURPLE_WEBSITE "http://pidgin.im/" +/* This is for the buddy list to notify the buddy icon code that + * it's done loading. We may want to replace this with a signal. */ +void +purple_buddy_icons_blist_loaded_cb(void); + +/* This is for the purple_core_migrate() code to tell the buddy + * icon subsystem about the old icons directory so it can + * migrate any icons in use. */ +void +purple_buddy_icon_set_old_icons_dir(const char *dirname); + #endif /* _PURPLE_INTERNAL_H_ */ diff -r bf353c85959a -r 2a19bbc743ed libpurple/plugins/perl/common/BuddyIcon.xs --- a/libpurple/plugins/perl/common/BuddyIcon.xs Tue Apr 24 03:56:03 2007 +0000 +++ b/libpurple/plugins/perl/common/BuddyIcon.xs Tue Apr 24 03:56:16 2007 +0000 @@ -3,10 +3,6 @@ MODULE = Purple::Buddy::Icon PACKAGE = Purple::Buddy::Icon PREFIX = purple_buddy_icon_ PROTOTYPES: ENABLE -void -purple_buddy_icon_destroy(icon) - Purple::Buddy::Icon icon - Purple::Buddy::Icon purple_buddy_icon_ref(icon) Purple::Buddy::Icon icon @@ -20,22 +16,13 @@ Purple::Buddy::Icon icon void -purple_buddy_icon_cache(icon, buddy) +purple_buddy_icon_set_custom_data(icon, data, len) Purple::Buddy::Icon icon - Purple::BuddyList::Buddy buddy + void * data + size_t len void -purple_buddy_icon_set_account(icon, account) - Purple::Buddy::Icon icon - Purple::Account account - -void -purple_buddy_icon_set_username(icon, username) - Purple::Buddy::Icon icon - const char * username - -void -purple_buddy_icon_set_data(icon, data, len) +purple_buddy_icon_set_protocol_data(icon, data, len) Purple::Buddy::Icon icon void * data size_t len diff -r bf353c85959a -r 2a19bbc743ed libpurple/protocols/qq/buddy_info.c --- a/libpurple/protocols/qq/buddy_info.c Tue Apr 24 03:56:03 2007 +0000 +++ b/libpurple/protocols/qq/buddy_info.c Tue Apr 24 03:56:16 2007 +0000 @@ -545,8 +545,7 @@ data_len = fread(data, 1, st.st_size, file); fclose(file); purple_buddy_icons_set_for_user(account, who, data, data_len); - icon = purple_buddy_icons_find(account, who); - purple_buddy_icon_set_path(icon, iconfile); + // TODO: Set a blist setting or something } } @@ -609,7 +608,8 @@ gchar *icon_path; PurpleBuddyIcon *icon = purple_buddy_icons_find(account, name); gchar *icon_num_str = face_to_icon_str(face); - const gchar *old_path = purple_buddy_icon_get_path(icon); + // TODO: This needs to use a blist setting or something. + const gchar *old_path = NULL; const gchar *buddy_icon_dir = qq_buddy_icon_dir(); icon_path = g_strconcat(buddy_icon_dir, G_DIR_SEPARATOR_S, QQ_ICON_PREFIX, diff -r bf353c85959a -r 2a19bbc743ed libpurple/util.c --- a/libpurple/util.c Tue Apr 24 03:56:03 2007 +0000 +++ b/libpurple/util.c Tue Apr 24 03:56:16 2007 +0000 @@ -65,7 +65,7 @@ }; static char custom_home_dir[MAXPATHLEN]; -static char home_dir[MAXPATHLEN]; +static char home_dir[MAXPATHLEN] = ""; PurpleMenuAction * purple_menu_action_new(const char *label, PurpleCallback callback, gpointer data, @@ -2245,25 +2245,17 @@ #endif } -/* Returns the argument passed to -c IFF it was present, or ~/.gaim IFF it - * exists, else ~/.purple. */ +/* Returns the argument passed to -c IFF it was present, or ~/.purple. */ const char * purple_user_dir(void) { - if (custom_home_dir != NULL && strlen(custom_home_dir) > 0) { + if (custom_home_dir != NULL && *custom_home_dir) { strcpy ((char*) &home_dir, (char*) &custom_home_dir); - } else { + } else if (!*home_dir) { const gchar *hd = purple_home_dir(); if (hd) { g_strlcpy((char*) &home_dir, hd, sizeof(home_dir)); - g_strlcat((char*) &home_dir, G_DIR_SEPARATOR_S ".gaim", - sizeof(home_dir)); - - if (g_file_test(home_dir, G_FILE_TEST_EXISTS)) - return home_dir; - - g_strlcpy((char*) &home_dir, hd, sizeof(home_dir)); g_strlcat((char*) &home_dir, G_DIR_SEPARATOR_S ".purple", sizeof(home_dir)); } diff -r bf353c85959a -r 2a19bbc743ed pidgin/gtkconv.c --- a/pidgin/gtkconv.c Tue Apr 24 03:56:03 2007 +0000 +++ b/pidgin/gtkconv.c Tue Apr 24 03:56:16 2007 +0000 @@ -2539,8 +2539,6 @@ g_return_if_fail(conv != NULL); ext = purple_buddy_icon_get_type(purple_conv_im_get_icon(PURPLE_CONV_IM(conv))); - if (ext == NULL) - ext = "icon"; buf = g_strdup_printf("%s.%s", purple_normalize(conv->account, conv->name), ext); diff -r bf353c85959a -r 2a19bbc743ed pidgin/gtkmain.c --- a/pidgin/gtkmain.c Tue Apr 24 03:56:03 2007 +0000 +++ b/pidgin/gtkmain.c Tue Apr 24 03:56:16 2007 +0000 @@ -446,6 +446,7 @@ int opt; gboolean gui_check; gboolean debug_enabled; + gboolean migration_failed = FALSE; struct option long_options[] = { {"config", required_argument, NULL, 'c'}, @@ -639,6 +640,15 @@ purple_debug_set_enabled(debug_enabled); + /* If we're using a custom configuration directory, we + * do NOT want to migrate, or weird things will happen. */ + if (opt_config_dir_arg == NULL) + { + if (!purple_core_migrate()) + { + migration_failed = TRUE; + } + } search_path = g_build_filename(purple_user_dir(), "gtkrc-2.0", NULL); gtk_rc_add_default_file(search_path); @@ -663,6 +673,36 @@ winpidgin_init(hint); #endif + if (migration_failed) + { + char *old = g_strconcat(purple_home_dir(), + G_DIR_SEPARATOR_S ".gaim", NULL); + char *text = _( + "Pidgin encountered errors migrating your settings " + "from %s to %s. Please investigate and complete the " + "migration by hand."); + GtkWidget *dialog; + + dialog = gtk_message_dialog_new(NULL, + 0, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + text, old, purple_user_dir()); + g_free(old); + + g_signal_connect_swapped(dialog, "response", + G_CALLBACK(gtk_main_quit), NULL); + + gtk_widget_show_all(dialog); + + gtk_main(); + +#ifdef HAVE_SIGNAL_H + g_free(segfault_message); +#endif + return 0; + } + purple_core_set_ui_ops(pidgin_core_get_ui_ops()); purple_eventloop_set_ui_ops(pidgin_eventloop_get_ui_ops());