Fix crashes when filenames end up being NULL in some prpls.
[pidgin-git.git] / libpurple / buddyicon.c
blob4f636c08c970957b81b240ea7b9c1ff8387ff181
1 /**
2 * @file buddyicon.c Buddy Icon API
3 * @ingroup core
4 */
6 /* purple
8 * Purple is the legal property of its developers, whose names are too numerous
9 * to list here. Please refer to the COPYRIGHT file distributed with this
10 * source distribution.
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
26 #define _PURPLE_BUDDYICON_C_
28 #include "internal.h"
29 #include "buddyicon.h"
30 #include "conversation.h"
31 #include "dbus-maybe.h"
32 #include "debug.h"
33 #include "imgstore.h"
34 #include "util.h"
36 /* NOTE: Instances of this struct are allocated without zeroing the memory, so
37 * NOTE: be sure to update purple_buddy_icon_new() if you add members. */
38 struct _PurpleBuddyIcon
40 PurpleAccount *account; /**< The account the user is on. */
41 PurpleStoredImage *img; /**< The stored image containing
42 the icon data. */
43 char *username; /**< The username the icon belongs to. */
44 char *checksum; /**< The protocol checksum. */
45 int ref_count; /**< The buddy icon reference count. */
48 /**
49 * This is the big grand daddy hash table that contains references to
50 * everybody's buddy icons.
52 * Key is a PurpleAccount.
53 * Value is another hash table, usually referred to as "icon_cache."
54 * For this inner hash table:
55 * Key is the username of the buddy whose icon is being stored.
56 * Value is the PurpleBuddyIcon for this buddy.
58 static GHashTable *account_cache = NULL;
60 /**
61 * This hash table contains a bunch of PurpleStoredImages that are
62 * shared across all accounts.
64 * Key is the filename for this image as constructed by
65 * purple_util_get_image_filename(). So it is the base16 encoded
66 * sha-1 hash plus an appropriate file extension. For example:
67 * "0f4972d17d1e70e751c43c90c948e72efbff9796.gif"
69 * The value is a PurpleStoredImage containing the icon data. These
70 * images are reference counted, and when the count reaches 0
71 * imgstore.c emits the image-deleting signal and we remove the image
72 * from the hash table (but it might still be saved on disk, if the
73 * icon is being used by offline accounts or some such).
75 static GHashTable *icon_data_cache = NULL;
77 /**
78 * This hash table contains references counts for how many times each
79 * icon in the ~/.purple/icons/ directory is being used. It's pretty
80 * crazy. It maintains the reference count across sessions, too, so
81 * if you exit Pidgin then this hash table is reconstructed the next
82 * time Pidgin starts.
84 * Key is the filename for this image as constructed by
85 * purple_util_get_image_filename(). So it is the base16 encoded
86 * sha-1 hash plus an appropriate file extension. For example:
87 * "0f4972d17d1e70e751c43c90c948e72efbff9796.gif"
89 * The value is a GINT_TO_POINTER count of the number of times this
90 * icon is used. So if four of your buddies are using an icon, and
91 * you have the icon set for two of your accounts, then this number
92 * will be six. When this reference count reaches 0 the icon will
93 * be deleted from disk.
95 static GHashTable *icon_file_cache = NULL;
97 /**
98 * This hash table is used for both custom buddy icons on PurpleBlistNodes and
99 * account icons.
101 static GHashTable *pointer_icon_cache = NULL;
103 static char *cache_dir = NULL;
105 /** "Should icons be cached to disk?" */
106 static gboolean icon_caching = TRUE;
108 /* For ~/.gaim to ~/.purple migration. */
109 static char *old_icons_dir = NULL;
111 static void delete_buddy_icon_settings(PurpleBlistNode *node, const char *setting_name);
114 * Begin functions for dealing with the on-disk icon cache
117 static void
118 ref_filename(const char *filename)
120 int refs;
122 g_return_if_fail(filename != NULL);
124 refs = GPOINTER_TO_INT(g_hash_table_lookup(icon_file_cache, filename));
126 g_hash_table_insert(icon_file_cache, g_strdup(filename),
127 GINT_TO_POINTER(refs + 1));
130 static void
131 unref_filename(const char *filename)
133 int refs;
135 if (filename == NULL)
136 return;
138 refs = GPOINTER_TO_INT(g_hash_table_lookup(icon_file_cache, filename));
140 if (refs == 1)
142 g_hash_table_remove(icon_file_cache, filename);
144 else
146 g_hash_table_insert(icon_file_cache, g_strdup(filename),
147 GINT_TO_POINTER(refs - 1));
151 static void
152 purple_buddy_icon_data_cache(PurpleStoredImage *img)
154 const char *dirname;
155 char *path;
157 g_return_if_fail(img != NULL);
159 if (!purple_buddy_icons_is_caching())
160 return;
162 dirname = purple_buddy_icons_get_cache_dir();
163 path = g_build_filename(dirname, purple_imgstore_get_filename(img), NULL);
165 if (!g_file_test(dirname, G_FILE_TEST_IS_DIR))
167 purple_debug_info("buddyicon", "Creating icon cache directory.\n");
169 if (g_mkdir(dirname, S_IRUSR | S_IWUSR | S_IXUSR) < 0)
171 purple_debug_error("buddyicon",
172 "Unable to create directory %s: %s\n",
173 dirname, g_strerror(errno));
177 purple_util_write_data_to_file_absolute(path, purple_imgstore_get_data(img),
178 purple_imgstore_get_size(img));
179 g_free(path);
182 static void
183 purple_buddy_icon_data_uncache_file(const char *filename)
185 const char *dirname;
186 char *path;
188 g_return_if_fail(filename != NULL);
190 /* It's possible that there are other references to this icon
191 * cache file that are not currently loaded into memory. */
192 if (GPOINTER_TO_INT(g_hash_table_lookup(icon_file_cache, filename)))
193 return;
195 dirname = purple_buddy_icons_get_cache_dir();
196 path = g_build_filename(dirname, filename, NULL);
198 if (g_file_test(path, G_FILE_TEST_EXISTS))
200 if (g_unlink(path))
202 purple_debug_error("buddyicon", "Failed to delete %s: %s\n",
203 path, g_strerror(errno));
205 else
207 purple_debug_info("buddyicon", "Deleted cache file: %s\n", path);
211 g_free(path);
215 * End functions for dealing with the on-disk icon cache
219 * Begin functions for dealing with the in-memory icon cache
222 static gboolean
223 value_equals(gpointer key, gpointer value, gpointer user_data)
225 return (value == user_data);
228 static void
229 image_deleting_cb(const PurpleStoredImage *img, gpointer data)
231 const char *filename = purple_imgstore_get_filename(img);
233 /* If there's no filename, it can't be one of our images. */
234 if (filename == NULL)
235 return;
237 if (img == g_hash_table_lookup(icon_data_cache, filename))
239 purple_buddy_icon_data_uncache_file(filename);
240 g_hash_table_remove(icon_data_cache, filename);
242 /* We could make this O(1) by using another hash table, but
243 * this is probably good enough. */
244 g_hash_table_foreach_remove(pointer_icon_cache, value_equals, (gpointer)img);
248 static PurpleStoredImage *
249 purple_buddy_icon_data_new(guchar *icon_data, size_t icon_len, const char *filename)
251 char *file;
252 PurpleStoredImage *img;
254 g_return_val_if_fail(icon_data != NULL, NULL);
255 g_return_val_if_fail(icon_len > 0, NULL);
257 if (filename == NULL)
259 file = purple_util_get_image_filename(icon_data, icon_len);
260 if (file == NULL)
262 g_free(icon_data);
263 return NULL;
266 else
267 file = g_strdup(filename);
269 if ((img = g_hash_table_lookup(icon_data_cache, file)))
271 g_free(file);
272 g_free(icon_data);
273 return purple_imgstore_ref(img);
276 img = purple_imgstore_add(icon_data, icon_len, file);
278 /* This will take ownership of file and g_free it either now or later. */
279 g_hash_table_insert(icon_data_cache, file, img);
281 purple_buddy_icon_data_cache(img);
283 return img;
287 * End functions for dealing with the in-memory icon cache
290 static PurpleBuddyIcon *
291 purple_buddy_icon_create(PurpleAccount *account, const char *username)
293 PurpleBuddyIcon *icon;
294 GHashTable *icon_cache;
296 /* This does not zero. See purple_buddy_icon_new() for
297 * information on which function allocates which member. */
298 icon = g_slice_new(PurpleBuddyIcon);
299 PURPLE_DBUS_REGISTER_POINTER(icon, PurpleBuddyIcon);
301 icon->account = account;
302 icon->username = g_strdup(username);
303 icon->checksum = NULL;
304 icon->ref_count = 1;
306 icon_cache = g_hash_table_lookup(account_cache, account);
308 if (icon_cache == NULL)
310 icon_cache = g_hash_table_new(g_str_hash, g_str_equal);
312 g_hash_table_insert(account_cache, account, icon_cache);
315 g_hash_table_insert(icon_cache,
316 (char *)purple_buddy_icon_get_username(icon), icon);
317 return icon;
320 PurpleBuddyIcon *
321 purple_buddy_icon_new(PurpleAccount *account, const char *username,
322 void *icon_data, size_t icon_len,
323 const char *checksum)
325 PurpleBuddyIcon *icon;
327 g_return_val_if_fail(account != NULL, NULL);
328 g_return_val_if_fail(username != NULL, NULL);
329 g_return_val_if_fail(icon_data != NULL, NULL);
330 g_return_val_if_fail(icon_len > 0, NULL);
332 /* purple_buddy_icons_find() does allocation, so be
333 * sure to update it as well when members are added. */
334 icon = purple_buddy_icons_find(account, username);
336 /* purple_buddy_icon_create() sets account & username */
337 if (icon == NULL)
338 icon = purple_buddy_icon_create(account, username);
340 /* purple_buddy_icon_set_data() sets img, but it
341 * references img first, so we need to initialize it */
342 icon->img = NULL;
343 purple_buddy_icon_set_data(icon, icon_data, icon_len, checksum);
345 return icon;
348 PurpleBuddyIcon *
349 purple_buddy_icon_ref(PurpleBuddyIcon *icon)
351 g_return_val_if_fail(icon != NULL, NULL);
353 icon->ref_count++;
355 return icon;
358 PurpleBuddyIcon *
359 purple_buddy_icon_unref(PurpleBuddyIcon *icon)
361 if (icon == NULL)
362 return NULL;
364 g_return_val_if_fail(icon->ref_count > 0, NULL);
366 icon->ref_count--;
368 if (icon->ref_count == 0)
370 GHashTable *icon_cache = g_hash_table_lookup(account_cache, purple_buddy_icon_get_account(icon));
372 if (icon_cache != NULL)
373 g_hash_table_remove(icon_cache, purple_buddy_icon_get_username(icon));
375 g_free(icon->username);
376 g_free(icon->checksum);
377 purple_imgstore_unref(icon->img);
379 PURPLE_DBUS_UNREGISTER_POINTER(icon);
380 g_slice_free(PurpleBuddyIcon, icon);
382 return NULL;
385 return icon;
388 void
389 purple_buddy_icon_update(PurpleBuddyIcon *icon)
391 PurpleConversation *conv;
392 PurpleAccount *account;
393 const char *username;
394 PurpleBuddyIcon *icon_to_set;
395 GSList *buddies;
397 g_return_if_fail(icon != NULL);
399 account = purple_buddy_icon_get_account(icon);
400 username = purple_buddy_icon_get_username(icon);
402 /* If no data exists (icon->img == NULL), then call the functions below
403 * with NULL to unset the icon. They will then unref the icon and it should
404 * be destroyed. The only way it wouldn't be destroyed is if someone
405 * else is holding a reference to it, in which case they can kill
406 * the icon when they realize it has no data. */
407 icon_to_set = icon->img ? icon : NULL;
409 /* Ensure that icon remains valid throughout */
410 if (icon) purple_buddy_icon_ref(icon);
412 buddies = purple_find_buddies(account, username);
413 while (buddies != NULL)
415 PurpleBuddy *buddy = (PurpleBuddy *)buddies->data;
416 char *old_icon;
418 purple_buddy_set_icon(buddy, icon_to_set);
419 old_icon = g_strdup(purple_blist_node_get_string((PurpleBlistNode *)buddy,
420 "buddy_icon"));
421 if (icon->img && purple_buddy_icons_is_caching())
423 const char *filename = purple_imgstore_get_filename(icon->img);
424 purple_blist_node_set_string((PurpleBlistNode *)buddy,
425 "buddy_icon",
426 filename);
428 if (icon->checksum && *icon->checksum)
430 purple_blist_node_set_string((PurpleBlistNode *)buddy,
431 "icon_checksum",
432 icon->checksum);
434 else
436 purple_blist_node_remove_setting((PurpleBlistNode *)buddy,
437 "icon_checksum");
439 ref_filename(filename);
441 else if (!icon->img)
443 purple_blist_node_remove_setting((PurpleBlistNode *)buddy, "buddy_icon");
444 purple_blist_node_remove_setting((PurpleBlistNode *)buddy, "icon_checksum");
446 unref_filename(old_icon);
447 g_free(old_icon);
449 buddies = g_slist_delete_link(buddies, buddies);
452 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, username, account);
454 if (conv != NULL)
455 purple_conv_im_set_icon(PURPLE_CONV_IM(conv), icon_to_set);
457 /* icon's refcount was incremented above */
458 if (icon) purple_buddy_icon_unref(icon);
461 void
462 purple_buddy_icon_set_data(PurpleBuddyIcon *icon, guchar *data,
463 size_t len, const char *checksum)
465 PurpleStoredImage *old_img;
467 g_return_if_fail(icon != NULL);
469 old_img = icon->img;
470 icon->img = NULL;
472 if (data != NULL)
474 if (len > 0)
475 icon->img = purple_buddy_icon_data_new(data, len, NULL);
476 else
477 g_free(data);
480 g_free(icon->checksum);
481 icon->checksum = g_strdup(checksum);
483 purple_buddy_icon_update(icon);
485 purple_imgstore_unref(old_img);
488 PurpleAccount *
489 purple_buddy_icon_get_account(const PurpleBuddyIcon *icon)
491 g_return_val_if_fail(icon != NULL, NULL);
493 return icon->account;
496 const char *
497 purple_buddy_icon_get_username(const PurpleBuddyIcon *icon)
499 g_return_val_if_fail(icon != NULL, NULL);
501 return icon->username;
504 const char *
505 purple_buddy_icon_get_checksum(const PurpleBuddyIcon *icon)
507 g_return_val_if_fail(icon != NULL, NULL);
509 return icon->checksum;
512 gconstpointer
513 purple_buddy_icon_get_data(const PurpleBuddyIcon *icon, size_t *len)
515 g_return_val_if_fail(icon != NULL, NULL);
517 if (icon->img)
519 if (len != NULL)
520 *len = purple_imgstore_get_size(icon->img);
522 return purple_imgstore_get_data(icon->img);
525 return NULL;
528 const char *
529 purple_buddy_icon_get_extension(const PurpleBuddyIcon *icon)
531 if (icon->img != NULL)
532 return purple_imgstore_get_extension(icon->img);
534 return NULL;
537 void
538 purple_buddy_icons_set_for_user(PurpleAccount *account, const char *username,
539 void *icon_data, size_t icon_len,
540 const char *checksum)
542 GHashTable *icon_cache;
543 PurpleBuddyIcon *icon = NULL;
545 g_return_if_fail(account != NULL);
546 g_return_if_fail(username != NULL);
548 icon_cache = g_hash_table_lookup(account_cache, account);
550 if (icon_cache != NULL)
551 icon = g_hash_table_lookup(icon_cache, username);
553 if (icon != NULL)
554 purple_buddy_icon_set_data(icon, icon_data, icon_len, checksum);
555 else if (icon_data && icon_len > 0)
557 PurpleBuddyIcon *icon = purple_buddy_icon_new(account, username, icon_data, icon_len, checksum);
559 /* purple_buddy_icon_new() calls
560 * purple_buddy_icon_set_data(), which calls
561 * purple_buddy_icon_update(), which has the buddy list
562 * and conversations take references as appropriate.
563 * This function doesn't return icon, so we can't
564 * leave a reference dangling. */
565 purple_buddy_icon_unref(icon);
567 else
569 /* If the buddy list or a conversation was holding a
570 * reference, we'd have found the icon in the cache.
571 * Since we know we're deleting the icon, we only
572 * need a subset of purple_buddy_icon_update(). */
574 GSList *buddies = purple_find_buddies(account, username);
575 while (buddies != NULL)
577 PurpleBuddy *buddy = (PurpleBuddy *)buddies->data;
579 unref_filename(purple_blist_node_get_string((PurpleBlistNode *)buddy, "buddy_icon"));
580 purple_blist_node_remove_setting((PurpleBlistNode *)buddy, "buddy_icon");
581 purple_blist_node_remove_setting((PurpleBlistNode *)buddy, "icon_checksum");
583 buddies = g_slist_delete_link(buddies, buddies);
588 char *purple_buddy_icon_get_full_path(PurpleBuddyIcon *icon)
590 char *path;
592 g_return_val_if_fail(icon != NULL, NULL);
594 if (icon->img == NULL)
595 return NULL;
597 path = g_build_filename(purple_buddy_icons_get_cache_dir(),
598 purple_imgstore_get_filename(icon->img), NULL);
599 if (!g_file_test(path, G_FILE_TEST_EXISTS))
601 g_free(path);
602 return NULL;
604 return path;
607 const char *
608 purple_buddy_icons_get_checksum_for_user(PurpleBuddy *buddy)
610 return purple_blist_node_get_string((PurpleBlistNode*)buddy,
611 "icon_checksum");
614 static gboolean
615 read_icon_file(const char *path, guchar **data, size_t *len)
617 GError *err = NULL;
619 if (!g_file_get_contents(path, (gchar **)data, len, &err))
621 purple_debug_error("buddyicon", "Error reading %s: %s\n",
622 path, err->message);
623 g_error_free(err);
625 return FALSE;
628 return TRUE;
631 PurpleBuddyIcon *
632 purple_buddy_icons_find(PurpleAccount *account, const char *username)
634 GHashTable *icon_cache;
635 PurpleBuddyIcon *icon = NULL;
637 g_return_val_if_fail(account != NULL, NULL);
638 g_return_val_if_fail(username != NULL, NULL);
640 icon_cache = g_hash_table_lookup(account_cache, account);
642 if ((icon_cache == NULL) || ((icon = g_hash_table_lookup(icon_cache, username)) == NULL))
644 PurpleBuddy *b = purple_find_buddy(account, username);
645 const char *protocol_icon_file;
646 const char *dirname;
647 gboolean caching;
648 guchar *data;
649 size_t len;
651 if (!b)
652 return NULL;
654 protocol_icon_file = purple_blist_node_get_string((PurpleBlistNode*)b, "buddy_icon");
656 if (protocol_icon_file == NULL)
657 return NULL;
659 dirname = purple_buddy_icons_get_cache_dir();
661 caching = purple_buddy_icons_is_caching();
662 /* By disabling caching temporarily, we avoid a loop
663 * and don't have to add special code through several
664 * functions. */
665 purple_buddy_icons_set_caching(FALSE);
667 if (protocol_icon_file != NULL)
669 char *path = g_build_filename(dirname, protocol_icon_file, NULL);
670 if (read_icon_file(path, &data, &len))
672 const char *checksum;
674 icon = purple_buddy_icon_create(account, username);
675 icon->img = NULL;
676 checksum = purple_blist_node_get_string((PurpleBlistNode*)b, "icon_checksum");
677 purple_buddy_icon_set_data(icon, data, len, checksum);
679 else
680 delete_buddy_icon_settings((PurpleBlistNode*)b, "buddy_icon");
682 g_free(path);
685 purple_buddy_icons_set_caching(caching);
688 return (icon ? purple_buddy_icon_ref(icon) : NULL);
691 PurpleStoredImage *
692 purple_buddy_icons_find_account_icon(PurpleAccount *account)
694 PurpleStoredImage *img;
695 const char *account_icon_file;
696 const char *dirname;
697 char *path;
698 guchar *data;
699 size_t len;
701 g_return_val_if_fail(account != NULL, NULL);
703 if ((img = g_hash_table_lookup(pointer_icon_cache, account)))
705 return purple_imgstore_ref(img);
708 account_icon_file = purple_account_get_string(account, "buddy_icon", NULL);
710 if (account_icon_file == NULL)
711 return NULL;
713 dirname = purple_buddy_icons_get_cache_dir();
714 path = g_build_filename(dirname, account_icon_file, NULL);
716 if (read_icon_file(path, &data, &len))
718 g_free(path);
719 img = purple_buddy_icon_data_new(data, len, account_icon_file);
720 g_hash_table_insert(pointer_icon_cache, account, img);
721 return img;
723 g_free(path);
725 return NULL;
728 PurpleStoredImage *
729 purple_buddy_icons_set_account_icon(PurpleAccount *account,
730 guchar *icon_data, size_t icon_len)
732 PurpleStoredImage *old_img;
733 PurpleStoredImage *img = NULL;
734 char *old_icon;
736 if (icon_data != NULL && icon_len > 0)
738 img = purple_buddy_icon_data_new(icon_data, icon_len, NULL);
741 old_icon = g_strdup(purple_account_get_string(account, "buddy_icon", NULL));
742 if (img && purple_buddy_icons_is_caching())
744 const char *filename = purple_imgstore_get_filename(img);
745 purple_account_set_string(account, "buddy_icon", filename);
746 purple_account_set_int(account, "buddy_icon_timestamp", time(NULL));
747 ref_filename(filename);
749 else
751 purple_account_set_string(account, "buddy_icon", NULL);
752 purple_account_set_int(account, "buddy_icon_timestamp", 0);
754 unref_filename(old_icon);
756 old_img = g_hash_table_lookup(pointer_icon_cache, account);
758 if (img)
759 g_hash_table_insert(pointer_icon_cache, account, img);
760 else
761 g_hash_table_remove(pointer_icon_cache, account);
763 if (purple_account_is_connected(account))
765 PurpleConnection *gc;
766 PurplePluginProtocolInfo *prpl_info;
768 gc = purple_account_get_connection(account);
769 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(gc));
771 if (prpl_info && prpl_info->set_buddy_icon)
772 prpl_info->set_buddy_icon(gc, img);
775 if (old_img)
776 purple_imgstore_unref(old_img);
777 else if (old_icon)
779 /* The old icon may not have been loaded into memory. In that
780 * case, we'll need to uncache the filename. The filenames
781 * are ref-counted, so this is safe. */
782 purple_buddy_icon_data_uncache_file(old_icon);
784 g_free(old_icon);
786 return img;
789 time_t
790 purple_buddy_icons_get_account_icon_timestamp(PurpleAccount *account)
792 time_t ret;
794 g_return_val_if_fail(account != NULL, 0);
796 ret = purple_account_get_int(account, "buddy_icon_timestamp", 0);
798 /* This deals with migration cases. */
799 if (ret == 0 && purple_account_get_string(account, "buddy_icon", NULL) != NULL)
801 ret = time(NULL);
802 purple_account_set_int(account, "buddy_icon_timestamp", ret);
805 return ret;
808 gboolean
809 purple_buddy_icons_node_has_custom_icon(PurpleBlistNode *node)
811 g_return_val_if_fail(node != NULL, FALSE);
813 return (purple_blist_node_get_string(node, "custom_buddy_icon") != NULL);
816 PurpleStoredImage *
817 purple_buddy_icons_node_find_custom_icon(PurpleBlistNode *node)
819 char *path;
820 size_t len;
821 guchar *data;
822 PurpleStoredImage *img;
823 const char *custom_icon_file, *dirname;
825 g_return_val_if_fail(node != NULL, NULL);
827 if ((img = g_hash_table_lookup(pointer_icon_cache, node)))
829 return purple_imgstore_ref(img);
832 custom_icon_file = purple_blist_node_get_string(node,
833 "custom_buddy_icon");
835 if (custom_icon_file == NULL)
836 return NULL;
838 dirname = purple_buddy_icons_get_cache_dir();
839 path = g_build_filename(dirname, custom_icon_file, NULL);
841 if (read_icon_file(path, &data, &len))
843 g_free(path);
844 img = purple_buddy_icon_data_new(data, len, custom_icon_file);
845 g_hash_table_insert(pointer_icon_cache, node, img);
846 return img;
848 g_free(path);
850 return NULL;
853 PurpleStoredImage *
854 purple_buddy_icons_node_set_custom_icon(PurpleBlistNode *node,
855 guchar *icon_data, size_t icon_len)
857 char *old_icon;
858 PurpleStoredImage *old_img;
859 PurpleStoredImage *img = NULL;
861 g_return_val_if_fail(node != NULL, NULL);
863 if (!PURPLE_BLIST_NODE_IS_CONTACT(node) &&
864 !PURPLE_BLIST_NODE_IS_CHAT(node) &&
865 !PURPLE_BLIST_NODE_IS_GROUP(node)) {
866 return NULL;
869 old_img = g_hash_table_lookup(pointer_icon_cache, node);
871 if (icon_data != NULL && icon_len > 0) {
872 img = purple_buddy_icon_data_new(icon_data, icon_len, NULL);
875 old_icon = g_strdup(purple_blist_node_get_string(node,
876 "custom_buddy_icon"));
877 if (img && purple_buddy_icons_is_caching()) {
878 const char *filename = purple_imgstore_get_filename(img);
879 purple_blist_node_set_string(node, "custom_buddy_icon",
880 filename);
881 ref_filename(filename);
882 } else {
883 purple_blist_node_remove_setting(node, "custom_buddy_icon");
885 unref_filename(old_icon);
887 if (img)
888 g_hash_table_insert(pointer_icon_cache, node, img);
889 else
890 g_hash_table_remove(pointer_icon_cache, node);
892 if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
893 PurpleBlistNode *child;
894 for (child = node->child ; child ; child = child->next)
896 PurpleBuddy *buddy;
897 PurpleConversation *conv;
899 if (!PURPLE_BLIST_NODE_IS_BUDDY(child))
900 continue;
902 buddy = (PurpleBuddy *)child;
904 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, purple_buddy_get_name(buddy), purple_buddy_get_account(buddy));
905 if (conv)
906 purple_conversation_update(conv, PURPLE_CONV_UPDATE_ICON);
908 /* Is this call necessary anymore? Can the buddies
909 * themselves need updating when the custom buddy
910 * icon changes? */
911 purple_blist_update_node_icon((PurpleBlistNode*)buddy);
913 } else if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
914 PurpleConversation *conv = NULL;
916 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, purple_chat_get_name((PurpleChat*)node), purple_chat_get_account((PurpleChat*)node));
917 if (conv) {
918 purple_conversation_update(conv, PURPLE_CONV_UPDATE_ICON);
922 purple_blist_update_node_icon(node);
924 if (old_img) {
925 purple_imgstore_unref(old_img);
926 } else if (old_icon) {
927 /* The old icon may not have been loaded into memory. In that
928 * case, we'll need to uncache the filename. The filenames
929 * are ref-counted, so this is safe. */
930 purple_buddy_icon_data_uncache_file(old_icon);
932 g_free(old_icon);
934 return img;
937 PurpleStoredImage *
938 purple_buddy_icons_node_set_custom_icon_from_file(PurpleBlistNode *node,
939 const gchar *filename)
941 size_t len = 0;
942 guchar *data = NULL;
944 g_return_val_if_fail(node != NULL, NULL);
946 if (!PURPLE_BLIST_NODE_IS_CONTACT(node) &&
947 !PURPLE_BLIST_NODE_IS_CHAT(node) &&
948 !PURPLE_BLIST_NODE_IS_GROUP(node)) {
949 return NULL;
952 if (filename != NULL) {
953 if (!read_icon_file(filename, &data, &len)) {
954 return NULL;
958 return purple_buddy_icons_node_set_custom_icon(node, data, len);
961 gboolean
962 purple_buddy_icons_has_custom_icon(PurpleContact *contact)
964 return purple_buddy_icons_node_has_custom_icon((PurpleBlistNode*)contact);
967 PurpleStoredImage *
968 purple_buddy_icons_find_custom_icon(PurpleContact *contact)
970 return purple_buddy_icons_node_find_custom_icon((PurpleBlistNode*)contact);
973 PurpleStoredImage *
974 purple_buddy_icons_set_custom_icon(PurpleContact *contact, guchar *icon_data,
975 size_t icon_len)
977 return purple_buddy_icons_node_set_custom_icon((PurpleBlistNode*)contact, icon_data, icon_len);
980 void
981 _purple_buddy_icon_set_old_icons_dir(const char *dirname)
983 old_icons_dir = g_strdup(dirname);
986 static void
987 delete_buddy_icon_settings(PurpleBlistNode *node, const char *setting_name)
989 purple_blist_node_remove_setting(node, setting_name);
991 if (!strcmp(setting_name, "buddy_icon"))
993 purple_blist_node_remove_setting(node, "avatar_hash");
994 purple_blist_node_remove_setting(node, "icon_checksum");
998 static void
999 migrate_buddy_icon(PurpleBlistNode *node, const char *setting_name,
1000 const char *dirname, const char *filename)
1002 char *path;
1004 if (filename[0] != '/')
1006 path = g_build_filename(dirname, filename, NULL);
1007 if (g_file_test(path, G_FILE_TEST_EXISTS))
1009 g_free(path);
1010 return;
1012 g_free(path);
1014 path = g_build_filename(old_icons_dir, filename, NULL);
1016 else
1017 path = g_strdup(filename);
1019 if (g_file_test(path, G_FILE_TEST_EXISTS))
1021 guchar *icon_data;
1022 size_t icon_len;
1023 FILE *file;
1024 char *new_filename;
1026 if (!read_icon_file(path, &icon_data, &icon_len))
1028 g_free(path);
1029 delete_buddy_icon_settings(node, setting_name);
1030 return;
1033 if (icon_data == NULL || icon_len <= 0)
1035 /* This really applies to the icon_len check.
1036 * icon_data should never be NULL if
1037 * read_icon_file() returns TRUE. */
1038 purple_debug_error("buddyicon", "Empty buddy icon file: %s\n", path);
1039 delete_buddy_icon_settings(node, setting_name);
1040 g_free(path);
1041 return;
1044 g_free(path);
1046 new_filename = purple_util_get_image_filename(icon_data, icon_len);
1047 if (new_filename == NULL)
1049 purple_debug_error("buddyicon",
1050 "New icon filename is NULL. This should never happen! "
1051 "The old filename was: %s\n", path);
1052 delete_buddy_icon_settings(node, setting_name);
1053 g_return_if_reached();
1056 path = g_build_filename(dirname, new_filename, NULL);
1057 if ((file = g_fopen(path, "wb")) != NULL)
1059 if (!fwrite(icon_data, icon_len, 1, file))
1061 purple_debug_error("buddyicon", "Error writing %s: %s\n",
1062 path, g_strerror(errno));
1064 else
1065 purple_debug_info("buddyicon", "Wrote migrated cache file: %s\n", path);
1067 fclose(file);
1069 else
1071 purple_debug_error("buddyicon", "Unable to create file %s: %s\n",
1072 path, g_strerror(errno));
1073 g_free(new_filename);
1074 g_free(path);
1076 delete_buddy_icon_settings(node, setting_name);
1077 return;
1079 g_free(path);
1081 purple_blist_node_set_string(node,
1082 setting_name,
1083 new_filename);
1084 ref_filename(new_filename);
1086 g_free(new_filename);
1088 if (!strcmp(setting_name, "buddy_icon"))
1090 const char *hash;
1092 hash = purple_blist_node_get_string(node, "avatar_hash");
1093 if (hash != NULL)
1095 purple_blist_node_set_string(node, "icon_checksum", hash);
1096 purple_blist_node_remove_setting(node, "avatar_hash");
1098 else
1100 PurpleAccount *account = purple_buddy_get_account((PurpleBuddy *)node);
1101 const char *prpl_id = purple_account_get_protocol_id(account);
1103 if (!strcmp(prpl_id, "prpl-yahoo"))
1105 int checksum = purple_blist_node_get_int(node, "icon_checksum");
1106 if (checksum != 0)
1108 char *checksum_str = g_strdup_printf("%i", checksum);
1109 purple_blist_node_remove_setting(node, "icon_checksum");
1110 purple_blist_node_set_string(node, "icon_checksum", checksum_str);
1111 g_free(checksum_str);
1117 else
1119 purple_debug_error("buddyicon", "Old icon file doesn't exist: %s\n", path);
1120 delete_buddy_icon_settings(node, setting_name);
1121 g_free(path);
1125 void
1126 _purple_buddy_icons_account_loaded_cb()
1128 const char *dirname = purple_buddy_icons_get_cache_dir();
1129 GList *cur;
1131 for (cur = purple_accounts_get_all(); cur != NULL; cur = cur->next)
1133 PurpleAccount *account = cur->data;
1134 const char *account_icon_file = purple_account_get_string(account, "buddy_icon", NULL);
1136 if (account_icon_file != NULL)
1138 char *path = g_build_filename(dirname, account_icon_file, NULL);
1139 if (!g_file_test(path, G_FILE_TEST_EXISTS))
1141 purple_account_set_string(account, "buddy_icon", NULL);
1142 } else {
1143 ref_filename(account_icon_file);
1145 g_free(path);
1150 void
1151 _purple_buddy_icons_blist_loaded_cb()
1153 PurpleBlistNode *node = purple_blist_get_root();
1154 const char *dirname = purple_buddy_icons_get_cache_dir();
1156 /* Doing this once here saves having to check it inside a loop. */
1157 if (old_icons_dir != NULL)
1159 if (!g_file_test(dirname, G_FILE_TEST_IS_DIR))
1161 purple_debug_info("buddyicon", "Creating icon cache directory.\n");
1163 if (g_mkdir(dirname, S_IRUSR | S_IWUSR | S_IXUSR) < 0)
1165 purple_debug_error("buddyicon",
1166 "Unable to create directory %s: %s\n",
1167 dirname, g_strerror(errno));
1172 while (node != NULL)
1174 if (PURPLE_BLIST_NODE_IS_BUDDY(node))
1176 const char *filename;
1178 filename = purple_blist_node_get_string(node, "buddy_icon");
1179 if (filename != NULL)
1181 if (old_icons_dir != NULL)
1183 migrate_buddy_icon(node,
1184 "buddy_icon",
1185 dirname, filename);
1187 else
1189 char *path = g_build_filename(dirname, filename, NULL);
1190 if (!g_file_test(path, G_FILE_TEST_EXISTS))
1192 purple_blist_node_remove_setting(node,
1193 "buddy_icon");
1194 purple_blist_node_remove_setting(node,
1195 "icon_checksum");
1197 else
1198 ref_filename(filename);
1199 g_free(path);
1203 else if (PURPLE_BLIST_NODE_IS_CONTACT(node) ||
1204 PURPLE_BLIST_NODE_IS_CHAT(node) ||
1205 PURPLE_BLIST_NODE_IS_GROUP(node))
1207 const char *filename;
1209 filename = purple_blist_node_get_string(node, "custom_buddy_icon");
1210 if (filename != NULL)
1212 if (old_icons_dir != NULL)
1214 migrate_buddy_icon(node,
1215 "custom_buddy_icon",
1216 dirname, filename);
1218 else
1220 char *path = g_build_filename(dirname, filename, NULL);
1221 if (!g_file_test(path, G_FILE_TEST_EXISTS))
1223 purple_blist_node_remove_setting(node,
1224 "custom_buddy_icon");
1226 else
1227 ref_filename(filename);
1228 g_free(path);
1232 node = purple_blist_node_next(node, TRUE);
1236 void
1237 purple_buddy_icons_set_caching(gboolean caching)
1239 icon_caching = caching;
1242 gboolean
1243 purple_buddy_icons_is_caching(void)
1245 return icon_caching;
1248 void
1249 purple_buddy_icons_set_cache_dir(const char *dir)
1251 g_return_if_fail(dir != NULL);
1253 g_free(cache_dir);
1254 cache_dir = g_strdup(dir);
1257 const char *
1258 purple_buddy_icons_get_cache_dir(void)
1260 return cache_dir;
1263 void *
1264 purple_buddy_icons_get_handle()
1266 static int handle;
1268 return &handle;
1271 void
1272 purple_buddy_icons_init()
1274 account_cache = g_hash_table_new_full(
1275 g_direct_hash, g_direct_equal,
1276 NULL, (GFreeFunc)g_hash_table_destroy);
1278 icon_data_cache = g_hash_table_new_full(g_str_hash, g_str_equal,
1279 g_free, NULL);
1280 icon_file_cache = g_hash_table_new_full(g_str_hash, g_str_equal,
1281 g_free, NULL);
1282 pointer_icon_cache = g_hash_table_new(g_direct_hash, g_direct_equal);
1284 if (!cache_dir)
1285 cache_dir = g_build_filename(purple_user_dir(), "icons", NULL);
1287 purple_signal_connect(purple_imgstore_get_handle(), "image-deleting",
1288 purple_buddy_icons_get_handle(),
1289 G_CALLBACK(image_deleting_cb), NULL);
1292 void
1293 purple_buddy_icons_uninit()
1295 purple_signals_disconnect_by_handle(purple_buddy_icons_get_handle());
1297 g_hash_table_destroy(account_cache);
1298 g_hash_table_destroy(icon_data_cache);
1299 g_hash_table_destroy(icon_file_cache);
1300 g_hash_table_destroy(pointer_icon_cache);
1301 g_free(old_icons_dir);
1304 void purple_buddy_icon_get_scale_size(PurpleBuddyIconSpec *spec, int *width, int *height)
1306 int new_width, new_height;
1308 new_width = *width;
1309 new_height = *height;
1311 if (*width < spec->min_width)
1312 new_width = spec->min_width;
1313 else if (*width > spec->max_width)
1314 new_width = spec->max_width;
1316 if (*height < spec->min_height)
1317 new_height = spec->min_height;
1318 else if (*height > spec->max_height)
1319 new_height = spec->max_height;
1321 /* preserve aspect ratio */
1322 if ((double)*height * (double)new_width >
1323 (double)*width * (double)new_height) {
1324 new_width = 0.5 + (double)*width * (double)new_height / (double)*height;
1325 } else {
1326 new_height = 0.5 + (double)*height * (double)new_width / (double)*width;
1329 *width = new_width;
1330 *height = new_height;