2 * @file buddyicon.c Buddy Icon API
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_
29 #include "buddyicon.h"
30 #include "conversation.h"
31 #include "dbus-maybe.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
43 char *username
; /**< The username the icon belongs to. */
44 char *checksum
; /**< The protocol checksum. */
45 int ref_count
; /**< The buddy icon reference count. */
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
;
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
;
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
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
;
98 * This hash table is used for both custom buddy icons on PurpleBlistNodes and
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
118 ref_filename(const char *filename
)
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));
131 unref_filename(const char *filename
)
135 if (filename
== NULL
)
138 refs
= GPOINTER_TO_INT(g_hash_table_lookup(icon_file_cache
, filename
));
142 g_hash_table_remove(icon_file_cache
, filename
);
146 g_hash_table_insert(icon_file_cache
, g_strdup(filename
),
147 GINT_TO_POINTER(refs
- 1));
152 purple_buddy_icon_data_cache(PurpleStoredImage
*img
)
157 g_return_if_fail(img
!= NULL
);
159 if (!purple_buddy_icons_is_caching())
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
));
183 purple_buddy_icon_data_uncache_file(const char *filename
)
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
)))
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
))
202 purple_debug_error("buddyicon", "Failed to delete %s: %s\n",
203 path
, g_strerror(errno
));
207 purple_debug_info("buddyicon", "Deleted cache file: %s\n", path
);
215 * End functions for dealing with the on-disk icon cache
219 * Begin functions for dealing with the in-memory icon cache
223 value_equals(gpointer key
, gpointer value
, gpointer user_data
)
225 return (value
== user_data
);
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
)
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
)
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
);
267 file
= g_strdup(filename
);
269 if ((img
= g_hash_table_lookup(icon_data_cache
, file
)))
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
);
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
;
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
);
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 */
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 */
343 purple_buddy_icon_set_data(icon
, icon_data
, icon_len
, checksum
);
349 purple_buddy_icon_ref(PurpleBuddyIcon
*icon
)
351 g_return_val_if_fail(icon
!= NULL
, NULL
);
359 purple_buddy_icon_unref(PurpleBuddyIcon
*icon
)
364 g_return_val_if_fail(icon
->ref_count
> 0, NULL
);
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
);
389 purple_buddy_icon_update(PurpleBuddyIcon
*icon
)
391 PurpleConversation
*conv
;
392 PurpleAccount
*account
;
393 const char *username
;
394 PurpleBuddyIcon
*icon_to_set
;
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
;
418 purple_buddy_set_icon(buddy
, icon_to_set
);
419 old_icon
= g_strdup(purple_blist_node_get_string((PurpleBlistNode
*)buddy
,
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
,
428 if (icon
->checksum
&& *icon
->checksum
)
430 purple_blist_node_set_string((PurpleBlistNode
*)buddy
,
436 purple_blist_node_remove_setting((PurpleBlistNode
*)buddy
,
439 ref_filename(filename
);
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
);
449 buddies
= g_slist_delete_link(buddies
, buddies
);
452 conv
= purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM
, username
, account
);
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
);
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
);
475 icon
->img
= purple_buddy_icon_data_new(data
, len
, NULL
);
480 g_free(icon
->checksum
);
481 icon
->checksum
= g_strdup(checksum
);
483 purple_buddy_icon_update(icon
);
485 purple_imgstore_unref(old_img
);
489 purple_buddy_icon_get_account(const PurpleBuddyIcon
*icon
)
491 g_return_val_if_fail(icon
!= NULL
, NULL
);
493 return icon
->account
;
497 purple_buddy_icon_get_username(const PurpleBuddyIcon
*icon
)
499 g_return_val_if_fail(icon
!= NULL
, NULL
);
501 return icon
->username
;
505 purple_buddy_icon_get_checksum(const PurpleBuddyIcon
*icon
)
507 g_return_val_if_fail(icon
!= NULL
, NULL
);
509 return icon
->checksum
;
513 purple_buddy_icon_get_data(const PurpleBuddyIcon
*icon
, size_t *len
)
515 g_return_val_if_fail(icon
!= NULL
, NULL
);
520 *len
= purple_imgstore_get_size(icon
->img
);
522 return purple_imgstore_get_data(icon
->img
);
529 purple_buddy_icon_get_extension(const PurpleBuddyIcon
*icon
)
531 if (icon
->img
!= NULL
)
532 return purple_imgstore_get_extension(icon
->img
);
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
);
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
);
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
)
592 g_return_val_if_fail(icon
!= NULL
, NULL
);
594 if (icon
->img
== 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
))
608 purple_buddy_icons_get_checksum_for_user(PurpleBuddy
*buddy
)
610 return purple_blist_node_get_string((PurpleBlistNode
*)buddy
,
615 read_icon_file(const char *path
, guchar
**data
, size_t *len
)
619 if (!g_file_get_contents(path
, (gchar
**)data
, len
, &err
))
621 purple_debug_error("buddyicon", "Error reading %s: %s\n",
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
;
654 protocol_icon_file
= purple_blist_node_get_string((PurpleBlistNode
*)b
, "buddy_icon");
656 if (protocol_icon_file
== 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
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
);
676 checksum
= purple_blist_node_get_string((PurpleBlistNode
*)b
, "icon_checksum");
677 purple_buddy_icon_set_data(icon
, data
, len
, checksum
);
680 delete_buddy_icon_settings((PurpleBlistNode
*)b
, "buddy_icon");
685 purple_buddy_icons_set_caching(caching
);
688 return (icon
? purple_buddy_icon_ref(icon
) : NULL
);
692 purple_buddy_icons_find_account_icon(PurpleAccount
*account
)
694 PurpleStoredImage
*img
;
695 const char *account_icon_file
;
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
)
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
))
719 img
= purple_buddy_icon_data_new(data
, len
, account_icon_file
);
720 g_hash_table_insert(pointer_icon_cache
, account
, img
);
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
;
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
);
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
);
759 g_hash_table_insert(pointer_icon_cache
, account
, img
);
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
);
776 purple_imgstore_unref(old_img
);
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
);
790 purple_buddy_icons_get_account_icon_timestamp(PurpleAccount
*account
)
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
)
802 purple_account_set_int(account
, "buddy_icon_timestamp", ret
);
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
);
817 purple_buddy_icons_node_find_custom_icon(PurpleBlistNode
*node
)
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
)
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
))
844 img
= purple_buddy_icon_data_new(data
, len
, custom_icon_file
);
845 g_hash_table_insert(pointer_icon_cache
, node
, img
);
854 purple_buddy_icons_node_set_custom_icon(PurpleBlistNode
*node
,
855 guchar
*icon_data
, size_t icon_len
)
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
)) {
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",
881 ref_filename(filename
);
883 purple_blist_node_remove_setting(node
, "custom_buddy_icon");
885 unref_filename(old_icon
);
888 g_hash_table_insert(pointer_icon_cache
, node
, img
);
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
)
897 PurpleConversation
*conv
;
899 if (!PURPLE_BLIST_NODE_IS_BUDDY(child
))
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
));
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
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
));
918 purple_conversation_update(conv
, PURPLE_CONV_UPDATE_ICON
);
922 purple_blist_update_node_icon(node
);
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
);
938 purple_buddy_icons_node_set_custom_icon_from_file(PurpleBlistNode
*node
,
939 const gchar
*filename
)
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
)) {
952 if (filename
!= NULL
) {
953 if (!read_icon_file(filename
, &data
, &len
)) {
958 return purple_buddy_icons_node_set_custom_icon(node
, data
, len
);
962 purple_buddy_icons_has_custom_icon(PurpleContact
*contact
)
964 return purple_buddy_icons_node_has_custom_icon((PurpleBlistNode
*)contact
);
968 purple_buddy_icons_find_custom_icon(PurpleContact
*contact
)
970 return purple_buddy_icons_node_find_custom_icon((PurpleBlistNode
*)contact
);
974 purple_buddy_icons_set_custom_icon(PurpleContact
*contact
, guchar
*icon_data
,
977 return purple_buddy_icons_node_set_custom_icon((PurpleBlistNode
*)contact
, icon_data
, icon_len
);
981 _purple_buddy_icon_set_old_icons_dir(const char *dirname
)
983 old_icons_dir
= g_strdup(dirname
);
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");
999 migrate_buddy_icon(PurpleBlistNode
*node
, const char *setting_name
,
1000 const char *dirname
, const char *filename
)
1004 if (filename
[0] != '/')
1006 path
= g_build_filename(dirname
, filename
, NULL
);
1007 if (g_file_test(path
, G_FILE_TEST_EXISTS
))
1014 path
= g_build_filename(old_icons_dir
, filename
, NULL
);
1017 path
= g_strdup(filename
);
1019 if (g_file_test(path
, G_FILE_TEST_EXISTS
))
1026 if (!read_icon_file(path
, &icon_data
, &icon_len
))
1029 delete_buddy_icon_settings(node
, setting_name
);
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
);
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
));
1065 purple_debug_info("buddyicon", "Wrote migrated cache file: %s\n", path
);
1071 purple_debug_error("buddyicon", "Unable to create file %s: %s\n",
1072 path
, g_strerror(errno
));
1073 g_free(new_filename
);
1076 delete_buddy_icon_settings(node
, setting_name
);
1081 purple_blist_node_set_string(node
,
1084 ref_filename(new_filename
);
1086 g_free(new_filename
);
1088 if (!strcmp(setting_name
, "buddy_icon"))
1092 hash
= purple_blist_node_get_string(node
, "avatar_hash");
1095 purple_blist_node_set_string(node
, "icon_checksum", hash
);
1096 purple_blist_node_remove_setting(node
, "avatar_hash");
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");
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
);
1119 purple_debug_error("buddyicon", "Old icon file doesn't exist: %s\n", path
);
1120 delete_buddy_icon_settings(node
, setting_name
);
1126 _purple_buddy_icons_account_loaded_cb()
1128 const char *dirname
= purple_buddy_icons_get_cache_dir();
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
);
1143 ref_filename(account_icon_file
);
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
,
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
,
1194 purple_blist_node_remove_setting(node
,
1198 ref_filename(filename
);
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",
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");
1227 ref_filename(filename
);
1232 node
= purple_blist_node_next(node
, TRUE
);
1237 purple_buddy_icons_set_caching(gboolean caching
)
1239 icon_caching
= caching
;
1243 purple_buddy_icons_is_caching(void)
1245 return icon_caching
;
1249 purple_buddy_icons_set_cache_dir(const char *dir
)
1251 g_return_if_fail(dir
!= NULL
);
1254 cache_dir
= g_strdup(dir
);
1258 purple_buddy_icons_get_cache_dir(void)
1264 purple_buddy_icons_get_handle()
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
,
1280 icon_file_cache
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
1282 pointer_icon_cache
= g_hash_table_new(g_direct_hash
, g_direct_equal
);
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
);
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
;
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
;
1326 new_height
= 0.5 + (double)*height
* (double)new_width
/ (double)*width
;
1330 *height
= new_height
;