* @file buddyicon.c Buddy Icon API * Purple is the legal property of its developers, whose names are too numerous * to list here. Please refer to the COPYRIGHT file distributed with this * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA #define _PURPLE_BUDDYICON_C_ #include "conversation.h" /* NOTE: Instances of this struct are allocated without zeroing the memory, so * NOTE: be sure to update purple_buddy_icon_new() if you add members. */ PurpleAccount *account; /**< The account the user is on. */ PurpleStoredImage *img; /**< The stored image containing char *username; /**< The username the icon belongs to. */ char *checksum; /**< The protocol checksum. */ int ref_count; /**< The buddy icon reference count. */ * This is the big grand daddy hash table that contains references to * everybody's buddy icons. * Key is a PurpleAccount. * Value is another hash table, usually referred to as "icon_cache." * For this inner hash table: * Key is the username of the buddy whose icon is being stored. * Value is the PurpleBuddyIcon for this buddy. static GHashTable *account_cache = NULL; * This hash table contains a bunch of PurpleStoredImages that are * shared across all accounts. * Key is the filename for this image as constructed by * purple_util_get_image_filename(). So it is the base16 encoded * sha-1 hash plus an appropriate file extension. For example: * "0f4972d17d1e70e751c43c90c948e72efbff9796.gif" * The value is a PurpleStoredImage containing the icon data. These * images are reference counted, and when the count reaches 0 * imgstore.c emits the image-deleting signal and we remove the image * from the hash table (but it might still be saved on disk, if the * icon is being used by offline accounts or some such). static GHashTable *icon_data_cache = NULL; * This hash table contains references counts for how many times each * icon in the ~/.purple/icons/ directory is being used. It's pretty * crazy. It maintains the reference count across sessions, too, so * if you exit Pidgin then this hash table is reconstructed the next * Key is the filename for this image as constructed by * purple_util_get_image_filename(). So it is the base16 encoded * sha-1 hash plus an appropriate file extension. For example: * "0f4972d17d1e70e751c43c90c948e72efbff9796.gif" * The value is a GINT_TO_POINTER count of the number of times this * icon is used. So if four of your buddies are using an icon, and * you have the icon set for two of your accounts, then this number * will be six. When this reference count reaches 0 the icon will static GHashTable *icon_file_cache = NULL; * This hash table is used for both custom buddy icons on PurpleBlistNodes and static GHashTable *pointer_icon_cache = NULL; static char *cache_dir = NULL; /** "Should icons be cached to disk?" */ static gboolean icon_caching = TRUE; /* For ~/.gaim to ~/.purple migration. */ static char *old_icons_dir = NULL; static void delete_buddy_icon_settings(PurpleBlistNode *node, const char *setting_name); * Begin functions for dealing with the on-disk icon cache ref_filename(const char *filename) 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)); unref_filename(const char *filename) refs = GPOINTER_TO_INT(g_hash_table_lookup(icon_file_cache, filename)); g_hash_table_remove(icon_file_cache, filename); g_hash_table_insert(icon_file_cache, g_strdup(filename), GINT_TO_POINTER(refs - 1)); purple_buddy_icon_data_cache(PurpleStoredImage *img) g_return_if_fail(img != NULL); if (!purple_buddy_icons_is_caching()) dirname = purple_buddy_icons_get_cache_dir(); path = g_build_filename(dirname, purple_imgstore_get_filename(img), 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, g_strerror(errno)); purple_util_write_data_to_file_absolute(path, purple_imgstore_get_data(img), purple_imgstore_get_size(img)); purple_buddy_icon_data_uncache_file(const char *filename) g_return_if_fail(filename != NULL); /* It's possible that there are other references to this icon * cache file that are not currently loaded into memory. */ if (GPOINTER_TO_INT(g_hash_table_lookup(icon_file_cache, filename))) dirname = purple_buddy_icons_get_cache_dir(); path = g_build_filename(dirname, filename, NULL); if (g_file_test(path, G_FILE_TEST_EXISTS)) purple_debug_error("buddyicon", "Failed to delete %s: %s\n", path, g_strerror(errno)); purple_debug_info("buddyicon", "Deleted cache file: %s\n", path); * End functions for dealing with the on-disk icon cache * Begin functions for dealing with the in-memory icon cache value_equals(gpointer key, gpointer value, gpointer user_data) return (value == user_data); image_deleting_cb(const PurpleStoredImage *img, gpointer data) const char *filename = purple_imgstore_get_filename(img); /* If there's no filename, it can't be one of our images. */ if (img == g_hash_table_lookup(icon_data_cache, filename)) purple_buddy_icon_data_uncache_file(filename); g_hash_table_remove(icon_data_cache, filename); /* We could make this O(1) by using another hash table, but * this is probably good enough. */ g_hash_table_foreach_remove(pointer_icon_cache, value_equals, (gpointer)img); static PurpleStoredImage * purple_buddy_icon_data_new(guchar *icon_data, size_t icon_len, const char *filename) g_return_val_if_fail(icon_data != NULL, NULL); g_return_val_if_fail(icon_len > 0, NULL); file = purple_util_get_image_filename(icon_data, icon_len); file = g_strdup(filename); if ((img = g_hash_table_lookup(icon_data_cache, file))) return purple_imgstore_ref(img); img = purple_imgstore_add(icon_data, icon_len, file); /* This will take ownership of file and g_free it either now or later. */ g_hash_table_insert(icon_data_cache, file, img); purple_buddy_icon_data_cache(img); * End functions for dealing with the in-memory icon cache purple_buddy_icon_create(PurpleAccount *account, const char *username) /* This does not zero. See purple_buddy_icon_new() for * information on which function allocates which member. */ icon = g_slice_new(PurpleBuddyIcon); PURPLE_DBUS_REGISTER_POINTER(icon, PurpleBuddyIcon); icon->username = g_strdup(username); icon_cache = g_hash_table_lookup(account_cache, account); icon_cache = g_hash_table_new(g_str_hash, g_str_equal); g_hash_table_insert(account_cache, account, icon_cache); g_hash_table_insert(icon_cache, (char *)purple_buddy_icon_get_username(icon), icon); purple_buddy_icon_new(PurpleAccount *account, const char *username, void *icon_data, size_t icon_len, 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); /* purple_buddy_icons_find() does allocation, so be * sure to update it as well when members are added. */ icon = purple_buddy_icons_find(account, username); /* purple_buddy_icon_create() sets account & username */ icon = purple_buddy_icon_create(account, username); /* purple_buddy_icon_set_data() sets img, but it * references img first, so we need to initialize it */ purple_buddy_icon_set_data(icon, icon_data, icon_len, checksum); purple_buddy_icon_ref(PurpleBuddyIcon *icon) g_return_val_if_fail(icon != NULL, NULL); purple_buddy_icon_unref(PurpleBuddyIcon *icon) g_return_val_if_fail(icon->ref_count > 0, NULL); if (icon->ref_count == 0) GHashTable *icon_cache = g_hash_table_lookup(account_cache, purple_buddy_icon_get_account(icon)); g_hash_table_remove(icon_cache, purple_buddy_icon_get_username(icon)); purple_imgstore_unref(icon->img); PURPLE_DBUS_UNREGISTER_POINTER(icon); g_slice_free(PurpleBuddyIcon, icon); purple_buddy_icon_update(PurpleBuddyIcon *icon) PurpleConversation *conv; PurpleBuddyIcon *icon_to_set; g_return_if_fail(icon != NULL); account = purple_buddy_icon_get_account(icon); username = purple_buddy_icon_get_username(icon); /* If no data exists (icon->img == NULL), 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. */ icon_to_set = icon->img ? icon : NULL; /* Ensure that icon remains valid throughout */ purple_buddy_icon_ref(icon); buddies = purple_find_buddies(account, username); PurpleBuddy *buddy = (PurpleBuddy *)buddies->data; purple_buddy_set_icon(buddy, icon_to_set); old_icon = g_strdup(purple_blist_node_get_string((PurpleBlistNode *)buddy, if (icon->img && purple_buddy_icons_is_caching()) const char *filename = purple_imgstore_get_filename(icon->img); purple_blist_node_set_string((PurpleBlistNode *)buddy, if (icon->checksum && *icon->checksum) purple_blist_node_set_string((PurpleBlistNode *)buddy, purple_blist_node_remove_setting((PurpleBlistNode *)buddy, purple_blist_node_remove_setting((PurpleBlistNode *)buddy, "buddy_icon"); purple_blist_node_remove_setting((PurpleBlistNode *)buddy, "icon_checksum"); unref_filename(old_icon); buddies = g_slist_delete_link(buddies, buddies); conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, username, account); purple_conv_im_set_icon(PURPLE_CONV_IM(conv), icon_to_set); /* icon's refcount was incremented above */ purple_buddy_icon_unref(icon); purple_buddy_icon_set_data(PurpleBuddyIcon *icon, guchar *data, size_t len, const char *checksum) PurpleStoredImage *old_img; g_return_if_fail(icon != NULL); icon->img = purple_buddy_icon_data_new(data, len, NULL); icon->checksum = g_strdup(checksum); purple_buddy_icon_update(icon); purple_imgstore_unref(old_img); purple_buddy_icon_get_account(const PurpleBuddyIcon *icon) g_return_val_if_fail(icon != NULL, NULL); purple_buddy_icon_get_username(const PurpleBuddyIcon *icon) g_return_val_if_fail(icon != NULL, NULL); purple_buddy_icon_get_checksum(const PurpleBuddyIcon *icon) g_return_val_if_fail(icon != NULL, NULL); purple_buddy_icon_get_data(const PurpleBuddyIcon *icon, size_t *len) g_return_val_if_fail(icon != NULL, NULL); *len = purple_imgstore_get_size(icon->img); return purple_imgstore_get_data(icon->img); purple_buddy_icon_get_extension(const PurpleBuddyIcon *icon) return purple_imgstore_get_extension(icon->img); purple_buddy_icons_set_for_user(PurpleAccount *account, const char *username, void *icon_data, size_t icon_len, PurpleBuddyIcon *icon = NULL; g_return_if_fail(account != NULL); g_return_if_fail(username != NULL); icon_cache = g_hash_table_lookup(account_cache, account); icon = g_hash_table_lookup(icon_cache, username); purple_buddy_icon_set_data(icon, icon_data, icon_len, checksum); else if (icon_data && icon_len > 0) PurpleBuddyIcon *icon = purple_buddy_icon_new(account, username, icon_data, icon_len, checksum); /* purple_buddy_icon_new() calls * purple_buddy_icon_set_data(), which calls * purple_buddy_icon_update(), which has the buddy list * and conversations take references as appropriate. * This function doesn't return icon, so we can't * leave a reference dangling. */ purple_buddy_icon_unref(icon); /* If the buddy list or a conversation was holding a * reference, we'd have found the icon in the cache. * Since we know we're deleting the icon, we only * need a subset of purple_buddy_icon_update(). */ GSList *buddies = purple_find_buddies(account, username); PurpleBuddy *buddy = (PurpleBuddy *)buddies->data; unref_filename(purple_blist_node_get_string((PurpleBlistNode *)buddy, "buddy_icon")); purple_blist_node_remove_setting((PurpleBlistNode *)buddy, "buddy_icon"); purple_blist_node_remove_setting((PurpleBlistNode *)buddy, "icon_checksum"); buddies = g_slist_delete_link(buddies, buddies); char *purple_buddy_icon_get_full_path(PurpleBuddyIcon *icon) g_return_val_if_fail(icon != NULL, NULL); path = g_build_filename(purple_buddy_icons_get_cache_dir(), purple_imgstore_get_filename(icon->img), NULL); if (!g_file_test(path, G_FILE_TEST_EXISTS)) purple_buddy_icons_get_checksum_for_user(PurpleBuddy *buddy) return purple_blist_node_get_string((PurpleBlistNode*)buddy, read_icon_file(const char *path, guchar **data, size_t *len) if (!g_file_get_contents(path, (gchar **)data, len, &err)) purple_debug_error("buddyicon", "Error reading %s: %s\n", purple_buddy_icons_find(PurpleAccount *account, const char *username) 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) || ((icon = g_hash_table_lookup(icon_cache, username)) == NULL)) PurpleBuddy *b = purple_find_buddy(account, username); const char *protocol_icon_file; protocol_icon_file = purple_blist_node_get_string((PurpleBlistNode*)b, "buddy_icon"); if (protocol_icon_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 purple_buddy_icons_set_caching(FALSE); if (protocol_icon_file != NULL) char *path = g_build_filename(dirname, protocol_icon_file, NULL); if (read_icon_file(path, &data, &len)) icon = purple_buddy_icon_create(account, username); checksum = purple_blist_node_get_string((PurpleBlistNode*)b, "icon_checksum"); purple_buddy_icon_set_data(icon, data, len, checksum); delete_buddy_icon_settings((PurpleBlistNode*)b, "buddy_icon"); purple_buddy_icons_set_caching(caching); return (icon ? purple_buddy_icon_ref(icon) : NULL); purple_buddy_icons_find_account_icon(PurpleAccount *account) const char *account_icon_file; g_return_val_if_fail(account != NULL, NULL); if ((img = g_hash_table_lookup(pointer_icon_cache, account))) return purple_imgstore_ref(img); account_icon_file = purple_account_get_string(account, "buddy_icon", NULL); if (account_icon_file == NULL) dirname = purple_buddy_icons_get_cache_dir(); path = g_build_filename(dirname, account_icon_file, NULL); if (read_icon_file(path, &data, &len)) img = purple_buddy_icons_set_account_icon(account, data, len); return purple_imgstore_ref(img); purple_buddy_icons_set_account_icon(PurpleAccount *account, guchar *icon_data, size_t icon_len) PurpleStoredImage *old_img; PurpleStoredImage *img = NULL; if (icon_data != NULL && icon_len > 0) img = purple_buddy_icon_data_new(icon_data, icon_len, NULL); old_icon = g_strdup(purple_account_get_string(account, "buddy_icon", NULL)); if (img && purple_buddy_icons_is_caching()) const char *filename = purple_imgstore_get_filename(img); purple_account_set_string(account, "buddy_icon", filename); purple_account_set_int(account, "buddy_icon_timestamp", time(NULL)); purple_account_set_string(account, "buddy_icon", NULL); purple_account_set_int(account, "buddy_icon_timestamp", 0); unref_filename(old_icon); old_img = g_hash_table_lookup(pointer_icon_cache, account); g_hash_table_insert(pointer_icon_cache, account, img); g_hash_table_remove(pointer_icon_cache, account); if (purple_account_is_connected(account)) PurplePluginProtocolInfo *prpl_info; gc = purple_account_get_connection(account); prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(gc)); if (prpl_info && prpl_info->set_buddy_icon) prpl_info->set_buddy_icon(gc, img); purple_imgstore_unref(old_img); /* The old icon may not have been loaded into memory. In that * case, we'll need to uncache the filename. The filenames * are ref-counted, so this is safe. */ purple_buddy_icon_data_uncache_file(old_icon); purple_buddy_icons_get_account_icon_timestamp(PurpleAccount *account) g_return_val_if_fail(account != NULL, 0); ret = purple_account_get_int(account, "buddy_icon_timestamp", 0); /* This deals with migration cases. */ if (ret == 0 && purple_account_get_string(account, "buddy_icon", NULL) != NULL) purple_account_set_int(account, "buddy_icon_timestamp", ret); purple_buddy_icons_node_has_custom_icon(PurpleBlistNode *node) g_return_val_if_fail(node != NULL, FALSE); return (purple_blist_node_get_string(node, "custom_buddy_icon") != NULL); purple_buddy_icons_node_find_custom_icon(PurpleBlistNode *node) const char *custom_icon_file, *dirname; g_return_val_if_fail(node != NULL, NULL); if ((img = g_hash_table_lookup(pointer_icon_cache, node))) return purple_imgstore_ref(img); custom_icon_file = purple_blist_node_get_string(node, if (custom_icon_file == NULL) dirname = purple_buddy_icons_get_cache_dir(); path = g_build_filename(dirname, custom_icon_file, NULL); if (read_icon_file(path, &data, &len)) img = purple_buddy_icons_node_set_custom_icon(node, data, len); return purple_imgstore_ref(img); purple_buddy_icons_node_set_custom_icon(PurpleBlistNode *node, guchar *icon_data, size_t icon_len) PurpleStoredImage *old_img; PurpleStoredImage *img = NULL; g_return_val_if_fail(node != NULL, NULL); if (!PURPLE_BLIST_NODE_IS_CONTACT(node) && !PURPLE_BLIST_NODE_IS_CHAT(node) && !PURPLE_BLIST_NODE_IS_GROUP(node)) { old_img = g_hash_table_lookup(pointer_icon_cache, node); if (icon_data != NULL && icon_len > 0) { img = purple_buddy_icon_data_new(icon_data, icon_len, NULL); old_icon = g_strdup(purple_blist_node_get_string(node, if (img && purple_buddy_icons_is_caching()) { const char *filename = purple_imgstore_get_filename(img); purple_blist_node_set_string(node, "custom_buddy_icon", purple_blist_node_remove_setting(node, "custom_buddy_icon"); unref_filename(old_icon); g_hash_table_insert(pointer_icon_cache, node, img); g_hash_table_remove(pointer_icon_cache, node); if (PURPLE_BLIST_NODE_IS_CONTACT(node)) { for (child = purple_blist_node_get_first_child(node); child = purple_blist_node_get_sibling_next(child)) PurpleConversation *conv; if (!PURPLE_BLIST_NODE_IS_BUDDY(child)) buddy = (PurpleBuddy *)child; conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, purple_buddy_get_name(buddy), purple_buddy_get_account(buddy)); purple_conversation_update(conv, PURPLE_CONV_UPDATE_ICON); /* Is this call necessary anymore? Can the buddies * themselves need updating when the custom buddy purple_blist_update_node_icon((PurpleBlistNode*)buddy); } else if (PURPLE_BLIST_NODE_IS_CHAT(node)) { PurpleConversation *conv = NULL; conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, purple_chat_get_name((PurpleChat*)node), purple_chat_get_account((PurpleChat*)node)); purple_conversation_update(conv, PURPLE_CONV_UPDATE_ICON); purple_blist_update_node_icon(node); purple_imgstore_unref(old_img); /* The old icon may not have been loaded into memory. In that * case, we'll need to uncache the filename. The filenames * are ref-counted, so this is safe. */ purple_buddy_icon_data_uncache_file(old_icon); purple_buddy_icons_node_set_custom_icon_from_file(PurpleBlistNode *node, g_return_val_if_fail(node != NULL, NULL); if (!PURPLE_BLIST_NODE_IS_CONTACT(node) && !PURPLE_BLIST_NODE_IS_CHAT(node) && !PURPLE_BLIST_NODE_IS_GROUP(node)) { if (!read_icon_file(filename, &data, &len)) { return purple_buddy_icons_node_set_custom_icon(node, data, len); purple_buddy_icons_has_custom_icon(PurpleContact *contact) return purple_buddy_icons_node_has_custom_icon((PurpleBlistNode*)contact); purple_buddy_icons_find_custom_icon(PurpleContact *contact) return purple_buddy_icons_node_find_custom_icon((PurpleBlistNode*)contact); purple_buddy_icons_set_custom_icon(PurpleContact *contact, guchar *icon_data, return purple_buddy_icons_node_set_custom_icon((PurpleBlistNode*)contact, icon_data, icon_len); _purple_buddy_icon_set_old_icons_dir(const char *dirname) old_icons_dir = g_strdup(dirname); delete_buddy_icon_settings(PurpleBlistNode *node, const char *setting_name) purple_blist_node_remove_setting(node, setting_name); if (purple_strequal(setting_name, "buddy_icon")) purple_blist_node_remove_setting(node, "avatar_hash"); purple_blist_node_remove_setting(node, "icon_checksum"); migrate_buddy_icon(PurpleBlistNode *node, const char *setting_name, const char *dirname, const char *filename) path = g_build_filename(dirname, filename, NULL); if (g_file_test(path, G_FILE_TEST_EXISTS)) path = g_build_filename(old_icons_dir, filename, NULL); path = g_strdup(filename); if (g_file_test(path, G_FILE_TEST_EXISTS)) if (!read_icon_file(path, &icon_data, &icon_len)) delete_buddy_icon_settings(node, setting_name); if (icon_data == NULL || icon_len <= 0) /* This really applies to the icon_len check. * icon_data should never be NULL if * read_icon_file() returns TRUE. */ purple_debug_error("buddyicon", "Empty buddy icon file: %s\n", path); delete_buddy_icon_settings(node, setting_name); new_filename = purple_util_get_image_filename(icon_data, icon_len); if (new_filename == NULL) purple_debug_error("buddyicon", "New icon filename is NULL. This should never happen! " "The old filename was: %s\n", path); delete_buddy_icon_settings(node, setting_name); 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, g_strerror(errno)); purple_debug_info("buddyicon", "Wrote migrated cache file: %s\n", path); purple_debug_error("buddyicon", "Unable to create file %s: %s\n", path, g_strerror(errno)); delete_buddy_icon_settings(node, setting_name); purple_blist_node_set_string(node, ref_filename(new_filename); if (purple_strequal(setting_name, "buddy_icon")) hash = purple_blist_node_get_string(node, "avatar_hash"); purple_blist_node_set_string(node, "icon_checksum", hash); purple_blist_node_remove_setting(node, "avatar_hash"); PurpleAccount *account = purple_buddy_get_account((PurpleBuddy *)node); const char *prpl_id = purple_account_get_protocol_id(account); if (g_str_equal(prpl_id, "prpl-yahoo") || g_str_equal(prpl_id, "prpl-yahoojp")) int checksum = purple_blist_node_get_int(node, "icon_checksum"); char *checksum_str = g_strdup_printf("%i", checksum); purple_blist_node_remove_setting(node, "icon_checksum"); purple_blist_node_set_string(node, "icon_checksum", checksum_str); purple_debug_error("buddyicon", "Old icon file doesn't exist: %s\n", path); delete_buddy_icon_settings(node, setting_name); _purple_buddy_icons_account_loaded_cb() const char *dirname = purple_buddy_icons_get_cache_dir(); for (cur = purple_accounts_get_all(); cur != NULL; cur = cur->next) PurpleAccount *account = cur->data; const char *account_icon_file = purple_account_get_string(account, "buddy_icon", NULL); if (account_icon_file != NULL) char *path = g_build_filename(dirname, account_icon_file, NULL); if (!g_file_test(path, G_FILE_TEST_EXISTS)) purple_account_set_string(account, "buddy_icon", NULL); ref_filename(account_icon_file); _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, g_strerror(errno)); if (PURPLE_BLIST_NODE_IS_BUDDY(node)) filename = purple_blist_node_get_string(node, "buddy_icon"); if (old_icons_dir != NULL) char *path = g_build_filename(dirname, filename, NULL); if (!g_file_test(path, G_FILE_TEST_EXISTS)) purple_blist_node_remove_setting(node, purple_blist_node_remove_setting(node, else if (PURPLE_BLIST_NODE_IS_CONTACT(node) || PURPLE_BLIST_NODE_IS_CHAT(node) || PURPLE_BLIST_NODE_IS_GROUP(node)) filename = purple_blist_node_get_string(node, "custom_buddy_icon"); if (old_icons_dir != NULL) char *path = g_build_filename(dirname, filename, NULL); if (!g_file_test(path, G_FILE_TEST_EXISTS)) purple_blist_node_remove_setting(node, node = purple_blist_node_next(node, TRUE); purple_buddy_icons_set_caching(gboolean caching) purple_buddy_icons_is_caching(void) purple_buddy_icons_set_cache_dir(const char *dir) g_return_if_fail(dir != NULL); cache_dir = g_strdup(dir); purple_buddy_icons_get_cache_dir(void) purple_buddy_icons_get_handle() purple_buddy_icons_init() account_cache = g_hash_table_new_full( g_direct_hash, g_direct_equal, NULL, (GFreeFunc)g_hash_table_destroy); icon_data_cache = g_hash_table_new_full(g_str_hash, g_str_equal, icon_file_cache = g_hash_table_new_full(g_str_hash, g_str_equal, pointer_icon_cache = g_hash_table_new(g_direct_hash, g_direct_equal); cache_dir = g_build_filename(purple_user_dir(), "icons", NULL); purple_signal_connect(purple_imgstore_get_handle(), "image-deleting", purple_buddy_icons_get_handle(), G_CALLBACK(image_deleting_cb), NULL); purple_buddy_icons_uninit() purple_signals_disconnect_by_handle(purple_buddy_icons_get_handle()); g_hash_table_destroy(account_cache); g_hash_table_destroy(icon_data_cache); g_hash_table_destroy(icon_file_cache); g_hash_table_destroy(pointer_icon_cache); void purple_buddy_icon_get_scale_size(PurpleBuddyIconSpec *spec, int *width, int *height) int new_width, new_height; if (*width < spec->min_width) new_width = spec->min_width; else if (*width > spec->max_width) new_width = spec->max_width; if (*height < spec->min_height) new_height = spec->min_height; else if (*height > spec->max_height) new_height = spec->max_height; /* preserve aspect ratio */ if ((double)*height * (double)new_width > (double)*width * (double)new_height) { new_width = 0.5 + (double)*width * (double)new_height / (double)*height; new_height = 0.5 + (double)*height * (double)new_width / (double)*width;