pidgin/pidgin

closing merged branch
port-changes-from-branch-2.x.y-to-default
16 months ago, Gary Kramlich
2f836435c33c
closing merged branch
/* purple
*
* 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
* source distribution.
*
* 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
*/
#include "internal.h"
#include "buddyicon.h"
#include "conversation.h"
#include "debug.h"
#include "image.h"
#include "util.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. */
struct _PurpleBuddyIcon
{
PurpleAccount *account; /* The account the user is on. */
PurpleImage *img; /* The image containing
the icon data. */
char *username; /* The username the icon belongs to. */
char *checksum; /* The protocol checksum. */
unsigned 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 PurpleImages that are
* shared across all accounts.
*
* Key is the filename for this image as constructed by
* purple_image_generate_filename(). So it is the base16 encoded
* sha-1 hash plus an appropriate file extension. For example:
* "0f4972d17d1e70e751c43c90c948e72efbff9796.gif"
*
* The value is a PurpleImage containing the icon data. These images are
* reference counted, and when the count reaches 0 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 reference 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
* time Pidgin starts.
*
* Key is the filename for this image as constructed by
* purple_image_generate_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
* be deleted from disk.
*/
static GHashTable *icon_file_cache = NULL;
/*
* This hash table is used for both custom buddy icons on PurpleBlistNodes and
* account icons.
*/
static GHashTable *pointer_icon_cache = NULL;
static char *cache_dir = NULL;
/* "Should icons be cached to disk?" */
static gboolean icon_caching = TRUE;
static void delete_buddy_icon_settings(PurpleBlistNode *node, const char *setting_name);
/*
* Begin functions for dealing with the on-disk icon cache
*/
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 gchar *
image_get_filename(PurpleImage *img)
{
return g_object_get_data(G_OBJECT(img), "purple-buddyicon-filename");
}
static void
purple_buddy_icon_data_cache(PurpleImage *img)
{
const gchar *dirname, *filename;
gchar *path;
g_return_if_fail(PURPLE_IS_IMAGE(img));
if (!purple_buddy_icons_is_caching())
return;
dirname = purple_buddy_icons_get_cache_dir();
filename = image_get_filename(img);
g_return_if_fail(filename != NULL);
if (!g_file_test(dirname, G_FILE_TEST_IS_DIR))
{
purple_debug_info("buddyicon", "creating icon cache directory");
if (g_mkdir(dirname, S_IRUSR | S_IWUSR | S_IXUSR) < 0)
{
purple_debug_error("buddyicon",
"unable to create directory %s: %s",
dirname, g_strerror(errno));
return;
}
}
path = g_build_filename(dirname, filename, NULL);
if (!purple_image_save(img, path))
purple_debug_error("buddyicon", "failed to save icon %s", path);
g_free(path);
}
static void
purple_buddy_icon_data_uncache_file(const char *filename)
{
const char *dirname;
char *path;
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)))
return;
dirname = purple_buddy_icons_get_cache_dir();
path = g_build_filename(dirname, 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, g_strerror(errno));
}
else
{
purple_debug_info("buddyicon", "Deleted cache file: %s\n", path);
}
}
g_free(path);
}
/*
* End functions for dealing with the on-disk icon cache
*/
/*
* Begin functions for dealing with the in-memory icon cache
*/
static gboolean
value_equals(gpointer key, gpointer value, gpointer user_data)
{
return (value == user_data);
}
static void
image_deleting_cb(gpointer _filename)
{
PurpleImage *img;
gchar *filename = _filename;
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);
g_free(filename);
}
static PurpleImage *
purple_buddy_icon_data_new(guchar *icon_data, size_t icon_len)
{
PurpleImage *newimg, *oldimg;
const gchar *filename;
g_return_val_if_fail(icon_data != NULL, NULL);
g_return_val_if_fail(icon_len > 0, NULL);
newimg = purple_image_new_from_data(icon_data, icon_len);
filename = purple_image_generate_filename(newimg);
/* TODO: Why is this function called for buddies without icons? If this is
* intended, should the filename be null?
*/
if (filename != NULL) {
oldimg = g_hash_table_lookup(icon_data_cache, filename);
if (oldimg) {
g_warn_if_fail(PURPLE_IS_IMAGE(oldimg));
g_object_unref(newimg);
g_object_ref(oldimg);
return oldimg;
}
/* This will take ownership of file and free it as needed */
g_hash_table_insert(icon_data_cache, g_strdup(filename), newimg);
}
g_object_set_data_full(G_OBJECT(newimg), "purple-buddyicon-filename",
g_strdup(filename), image_deleting_cb);
purple_buddy_icon_data_cache(newimg);
return newimg;
}
/*
* End functions for dealing with the in-memory icon cache
*/
static PurpleBuddyIcon *
purple_buddy_icon_create(PurpleAccount *account, const char *username)
{
PurpleBuddyIcon *icon;
GHashTable *icon_cache;
/* This does not zero. See purple_buddy_icon_new() for
* information on which function allocates which member. */
icon = g_slice_new(PurpleBuddyIcon);
icon->account = account;
icon->username = g_strdup(username);
icon->checksum = NULL;
icon->ref_count = 1;
icon_cache = g_hash_table_lookup(account_cache, account);
if (icon_cache == NULL)
{
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);
return icon;
}
PurpleBuddyIcon *
purple_buddy_icon_new(PurpleAccount *account, const char *username,
void *icon_data, size_t icon_len,
const char *checksum)
{
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);
/* 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 */
if (icon == NULL)
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 */
icon->img = NULL;
purple_buddy_icon_set_data(icon, icon_data, icon_len, checksum);
return icon;
}
PurpleBuddyIcon *
purple_buddy_icon_ref(PurpleBuddyIcon *icon)
{
g_return_val_if_fail(icon != NULL, NULL);
icon->ref_count++;
return icon;
}
void
purple_buddy_icon_unref(PurpleBuddyIcon *icon)
{
if (icon == NULL)
return;
g_return_if_fail(icon->ref_count > 0);
icon->ref_count--;
if (icon->ref_count == 0)
{
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);
g_free(icon->checksum);
g_object_unref(icon->img);
g_slice_free(PurpleBuddyIcon, icon);
}
}
void
purple_buddy_icon_update(PurpleBuddyIcon *icon)
{
PurpleIMConversation *im;
PurpleAccount *account;
const char *username;
PurpleBuddyIcon *icon_to_set;
GSList *buddies;
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_blist_find_buddies(account, username);
while (buddies != NULL)
{
PurpleBuddy *buddy = (PurpleBuddy *)buddies->data;
char *old_icon;
purple_buddy_set_icon(buddy, icon_to_set);
old_icon = g_strdup(purple_blist_node_get_string((PurpleBlistNode *)buddy,
"buddy_icon"));
if (icon->img && purple_buddy_icons_is_caching())
{
const char *filename = image_get_filename(icon->img);
g_warn_if_fail(filename != NULL);
purple_blist_node_set_string((PurpleBlistNode *)buddy,
"buddy_icon",
filename);
if (icon->checksum && *icon->checksum)
{
purple_blist_node_set_string((PurpleBlistNode *)buddy,
"icon_checksum",
icon->checksum);
}
else
{
purple_blist_node_remove_setting((PurpleBlistNode *)buddy,
"icon_checksum");
}
ref_filename(filename);
}
else if (!icon->img)
{
purple_blist_node_remove_setting((PurpleBlistNode *)buddy, "buddy_icon");
purple_blist_node_remove_setting((PurpleBlistNode *)buddy, "icon_checksum");
}
unref_filename(old_icon);
g_free(old_icon);
buddies = g_slist_delete_link(buddies, buddies);
}
im = purple_conversations_find_im_with_account(username, account);
if (im != NULL)
purple_im_conversation_set_icon(im, icon_to_set);
/* icon's refcount was incremented above */
purple_buddy_icon_unref(icon);
}
void
purple_buddy_icon_set_data(PurpleBuddyIcon *icon, guchar *data,
size_t len, const char *checksum)
{
PurpleImage *old_img;
g_return_if_fail(icon != NULL);
old_img = icon->img;
icon->img = NULL;
if (data != NULL)
{
if (len > 0)
icon->img = purple_buddy_icon_data_new(data, len);
else
g_free(data);
}
g_free(icon->checksum);
icon->checksum = g_strdup(checksum);
purple_buddy_icon_update(icon);
if (old_img)
g_object_unref(old_img);
}
PurpleAccount *
purple_buddy_icon_get_account(const PurpleBuddyIcon *icon)
{
g_return_val_if_fail(icon != NULL, NULL);
return icon->account;
}
const char *
purple_buddy_icon_get_username(const PurpleBuddyIcon *icon)
{
g_return_val_if_fail(icon != NULL, NULL);
return icon->username;
}
const char *
purple_buddy_icon_get_checksum(const PurpleBuddyIcon *icon)
{
g_return_val_if_fail(icon != NULL, NULL);
return icon->checksum;
}
gconstpointer
purple_buddy_icon_get_data(const PurpleBuddyIcon *icon, size_t *len)
{
g_return_val_if_fail(icon != NULL, NULL);
if (icon->img)
{
if (len != NULL)
*len = purple_image_get_data_size(icon->img);
return purple_image_get_data(icon->img);
}
return NULL;
}
const char *
purple_buddy_icon_get_extension(const PurpleBuddyIcon *icon)
{
if (icon->img != NULL)
return purple_image_get_extension(icon->img);
return NULL;
}
void
purple_buddy_icons_set_for_user(PurpleAccount *account, const char *username,
void *icon_data, size_t icon_len,
const char *checksum)
{
GHashTable *icon_cache;
PurpleBuddyIcon *icon = NULL;
g_return_if_fail(account != NULL);
g_return_if_fail(username != NULL);
icon_cache = g_hash_table_lookup(account_cache, account);
if (icon_cache != NULL)
icon = g_hash_table_lookup(icon_cache, username);
if (icon != NULL)
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);
}
else
{
/* 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_blist_find_buddies(account, username);
while (buddies != NULL)
{
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);
}
}
}
const gchar *
purple_buddy_icon_get_full_path(PurpleBuddyIcon *icon)
{
const gchar *path;
g_return_val_if_fail(icon != NULL, NULL);
if (icon->img == NULL)
return NULL;
path = purple_image_get_path(icon->img);
if (!g_file_test(path, G_FILE_TEST_EXISTS))
{
return NULL;
}
return path;
}
const char *
purple_buddy_icons_get_checksum_for_user(PurpleBuddy *buddy)
{
return purple_blist_node_get_string((PurpleBlistNode*)buddy,
"icon_checksum");
}
static gboolean
read_icon_file(const char *path, guchar **data, size_t *len)
{
GError *err = NULL;
if (!g_file_get_contents(path, (gchar **)data, len, &err))
{
purple_debug_error("buddyicon", "Error reading %s: %s\n",
path, err->message);
g_error_free(err);
return FALSE;
}
return TRUE;
}
PurpleBuddyIcon *
purple_buddy_icons_find(PurpleAccount *account, const char *username)
{
GHashTable *icon_cache;
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))
{
/* The icon is not currently cached in memory--try reading from disk */
PurpleBuddy *b = purple_blist_find_buddy(account, username);
const char *protocol_icon_file;
const char *dirname;
gboolean caching;
gchar *path;
guchar *data;
size_t len;
if (!b)
return NULL;
protocol_icon_file = purple_blist_node_get_string((PurpleBlistNode*)b, "buddy_icon");
if (protocol_icon_file == NULL)
return 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);
path = g_build_filename(dirname, protocol_icon_file, NULL);
if (read_icon_file(path, &data, &len)) {
const char *checksum;
icon = purple_buddy_icon_create(account, username);
icon->img = NULL;
checksum = purple_blist_node_get_string((PurpleBlistNode *)b,
"icon_checksum");
purple_buddy_icon_set_data(icon, data, len, checksum);
} else {
delete_buddy_icon_settings((PurpleBlistNode *)b, "buddy_icon");
}
g_free(path);
purple_buddy_icons_set_caching(caching);
}
return (icon ? purple_buddy_icon_ref(icon) : NULL);
}
PurpleImage *
purple_buddy_icons_find_account_icon(PurpleAccount *account)
{
PurpleImage *img;
const char *account_icon_file;
const char *dirname;
char *path;
guchar *data;
size_t len;
g_return_val_if_fail(account != NULL, NULL);
img = g_hash_table_lookup(pointer_icon_cache, account);
if (img) {
g_object_ref(img);
return img;
}
account_icon_file = purple_account_get_string(account, "buddy_icon", NULL);
if (account_icon_file == NULL)
return NULL;
dirname = purple_buddy_icons_get_cache_dir();
path = g_build_filename(dirname, account_icon_file, NULL);
if (read_icon_file(path, &data, &len)) {
g_free(path);
img = purple_buddy_icons_set_account_icon(account, data, len);
g_object_ref(img);
return img;
}
g_free(path);
return NULL;
}
PurpleImage *
purple_buddy_icons_set_account_icon(PurpleAccount *account,
guchar *icon_data, size_t icon_len)
{
PurpleImage *old_img;
PurpleImage *img = NULL;
char *old_icon;
if (icon_data != NULL && icon_len > 0) {
img = purple_buddy_icon_data_new(icon_data, icon_len);
}
old_icon = g_strdup(purple_account_get_string(account, "buddy_icon", NULL));
if (img && purple_buddy_icons_is_caching())
{
const char *filename = image_get_filename(img);
g_warn_if_fail(filename != NULL);
purple_account_set_string(account, "buddy_icon", filename);
purple_account_set_int(account, "buddy_icon_timestamp", time(NULL));
ref_filename(filename);
}
else
{
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);
if (img)
g_hash_table_insert(pointer_icon_cache, account, img);
else
g_hash_table_remove(pointer_icon_cache, account);
if (!purple_account_is_disconnected(account))
{
PurpleConnection *gc;
PurpleProtocol *protocol;
gc = purple_account_get_connection(account);
protocol = purple_connection_get_protocol(gc);
if (protocol)
purple_protocol_server_iface_set_buddy_icon(protocol, gc, img);
}
if (old_img)
g_object_unref(old_img);
else if (old_icon)
{
/* 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);
}
g_free(old_icon);
return img;
}
time_t
purple_buddy_icons_get_account_icon_timestamp(PurpleAccount *account)
{
time_t ret;
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)
{
ret = time(NULL);
purple_account_set_int(account, "buddy_icon_timestamp", ret);
}
return ret;
}
gboolean
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);
}
PurpleImage *
purple_buddy_icons_node_find_custom_icon(PurpleBlistNode *node)
{
char *path;
size_t len;
guchar *data;
PurpleImage *img;
const char *custom_icon_file, *dirname;
g_return_val_if_fail(node != NULL, NULL);
img = g_hash_table_lookup(pointer_icon_cache, node);
if (img) {
g_object_ref(img);
return img;
}
custom_icon_file = purple_blist_node_get_string(node,
"custom_buddy_icon");
if (custom_icon_file == NULL)
return NULL;
dirname = purple_buddy_icons_get_cache_dir();
path = g_build_filename(dirname, custom_icon_file, NULL);
if (read_icon_file(path, &data, &len)) {
g_free(path);
img = purple_buddy_icons_node_set_custom_icon(node, data, len);
g_object_ref(img);
return img;
}
g_free(path);
return NULL;
}
PurpleImage *
purple_buddy_icons_node_set_custom_icon(PurpleBlistNode *node,
guchar *icon_data, size_t icon_len)
{
char *old_icon;
PurpleImage *old_img;
PurpleImage *img = NULL;
g_return_val_if_fail(node != NULL, NULL);
if (!PURPLE_IS_CONTACT(node) &&
!PURPLE_IS_CHAT(node) &&
!PURPLE_IS_GROUP(node)) {
return NULL;
}
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);
}
old_icon = g_strdup(purple_blist_node_get_string(node,
"custom_buddy_icon"));
if (img && purple_buddy_icons_is_caching()) {
const char *filename = image_get_filename(img);
g_warn_if_fail(filename);
purple_blist_node_set_string(node, "custom_buddy_icon",
filename);
ref_filename(filename);
} else {
purple_blist_node_remove_setting(node, "custom_buddy_icon");
}
unref_filename(old_icon);
if (img)
g_hash_table_insert(pointer_icon_cache, node, img);
else
g_hash_table_remove(pointer_icon_cache, node);
if (PURPLE_IS_CONTACT(node)) {
PurpleBlistNode *child;
for (child = purple_blist_node_get_first_child(node);
child;
child = purple_blist_node_get_sibling_next(child))
{
PurpleBuddy *buddy;
PurpleIMConversation *im;
if (!PURPLE_IS_BUDDY(child))
continue;
buddy = (PurpleBuddy *)child;
im = purple_conversations_find_im_with_account(purple_buddy_get_name(buddy), purple_buddy_get_account(buddy));
if (im)
purple_conversation_update(PURPLE_CONVERSATION(im), PURPLE_CONVERSATION_UPDATE_ICON);
/* Is this call necessary anymore? Can the buddies
* themselves need updating when the custom buddy
* icon changes? */
purple_blist_update_node(purple_blist_get_default(),
PURPLE_BLIST_NODE(buddy));
}
} else if (PURPLE_IS_CHAT(node)) {
PurpleChatConversation *chat = NULL;
chat = purple_conversations_find_chat_with_account(purple_chat_get_name((PurpleChat*)node), purple_chat_get_account((PurpleChat*)node));
if (chat) {
purple_conversation_update(PURPLE_CONVERSATION(chat), PURPLE_CONVERSATION_UPDATE_ICON);
}
}
purple_blist_update_node(purple_blist_get_default(), node);
if (old_img) {
g_object_unref(old_img);
} else if (old_icon) {
/* 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);
}
g_free(old_icon);
return img;
}
PurpleImage *
purple_buddy_icons_node_set_custom_icon_from_file(PurpleBlistNode *node,
const gchar *filename)
{
size_t len = 0;
guchar *data = NULL;
g_return_val_if_fail(node != NULL, NULL);
if (!PURPLE_IS_CONTACT(node) &&
!PURPLE_IS_CHAT(node) &&
!PURPLE_IS_GROUP(node)) {
return NULL;
}
if (filename != NULL) {
if (!read_icon_file(filename, &data, &len)) {
return NULL;
}
}
return purple_buddy_icons_node_set_custom_icon(node, data, len);
}
static void
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");
}
}
void
_purple_buddy_icons_account_loaded_cb()
{
const char *dirname = purple_buddy_icons_get_cache_dir();
GList *cur;
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);
} else {
ref_filename(account_icon_file);
}
g_free(path);
}
}
}
void
_purple_buddy_icons_blist_loaded_cb()
{
PurpleBlistNode *node = purple_blist_get_default_root();
const char *dirname = purple_buddy_icons_get_cache_dir();
while (node != NULL)
{
if (PURPLE_IS_BUDDY(node))
{
const char *filename;
filename = purple_blist_node_get_string(node, "buddy_icon");
if (filename != NULL)
{
char *path = g_build_filename(dirname, filename, NULL);
if (!g_file_test(path, G_FILE_TEST_EXISTS))
{
purple_blist_node_remove_setting(node,
"buddy_icon");
purple_blist_node_remove_setting(node,
"icon_checksum");
}
else
ref_filename(filename);
g_free(path);
}
}
else if (PURPLE_IS_CONTACT(node) ||
PURPLE_IS_CHAT(node) ||
PURPLE_IS_GROUP(node))
{
const char *filename;
filename = purple_blist_node_get_string(node, "custom_buddy_icon");
if (filename != NULL)
{
char *path = g_build_filename(dirname, filename, NULL);
if (!g_file_test(path, G_FILE_TEST_EXISTS))
{
purple_blist_node_remove_setting(node,
"custom_buddy_icon");
}
else
ref_filename(filename);
g_free(path);
}
}
node = purple_blist_node_next(node, TRUE);
}
}
void
purple_buddy_icons_set_caching(gboolean caching)
{
icon_caching = caching;
}
gboolean
purple_buddy_icons_is_caching(void)
{
return icon_caching;
}
void
purple_buddy_icons_set_cache_dir(const char *dir)
{
g_return_if_fail(dir != NULL);
g_free(cache_dir);
cache_dir = g_strdup(dir);
}
const char *
purple_buddy_icons_get_cache_dir(void)
{
return cache_dir;
}
void *
purple_buddy_icons_get_handle()
{
static int handle;
return &handle;
}
void
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,
g_free, NULL);
icon_file_cache = g_hash_table_new_full(g_str_hash, g_str_equal,
g_free, NULL);
pointer_icon_cache = g_hash_table_new(g_direct_hash, g_direct_equal);
if (!cache_dir)
cache_dir = g_build_filename(purple_cache_dir(), "icons", NULL);
}
void
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);
g_free(cache_dir);
cache_dir = NULL;
}
GType
purple_buddy_icon_get_type(void)
{
static GType type = 0;
if (type == 0) {
type = g_boxed_type_register_static("PurpleBuddyIcon",
(GBoxedCopyFunc)purple_buddy_icon_ref,
(GBoxedFreeFunc)purple_buddy_icon_unref);
}
return type;
}
PurpleBuddyIconSpec *
purple_buddy_icon_spec_new(char *format, int min_width, int min_height,
int max_width, int max_height, size_t max_filesize,
PurpleBuddyIconScaleFlags scale_rules)
{
PurpleBuddyIconSpec *icon_spec;
icon_spec = g_new0(PurpleBuddyIconSpec, 1);
icon_spec->format = format;
icon_spec->min_width = min_width;
icon_spec->min_height = min_height;
icon_spec->max_width = max_width;
icon_spec->max_height = max_height;
icon_spec->max_filesize = max_filesize;
icon_spec->scale_rules = scale_rules;
return icon_spec;
}
static PurpleBuddyIconSpec *
purple_buddy_icon_spec_copy(PurpleBuddyIconSpec *icon_spec)
{
PurpleBuddyIconSpec *icon_spec_copy;
g_return_val_if_fail(icon_spec != NULL, NULL);
icon_spec_copy = g_new0(PurpleBuddyIconSpec, 1);
*icon_spec_copy = *icon_spec;
return icon_spec_copy;
}
void purple_buddy_icon_spec_get_scaled_size(PurpleBuddyIconSpec *spec,
int *width, int *height)
{
int new_width, new_height;
new_width = *width;
new_height = *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;
} else {
new_height = 0.5 + (double)*height * (double)new_width / (double)*width;
}
*width = new_width;
*height = new_height;
}
GType
purple_buddy_icon_spec_get_type(void)
{
static GType type = 0;
if (type == 0) {
type = g_boxed_type_register_static("PurpleBuddyIconSpec",
(GBoxedCopyFunc)purple_buddy_icon_spec_copy,
(GBoxedFreeFunc)g_free);
}
return type;
}