3 * Purple is the legal property of its developers, whose names are too numerous
4 * to list here. Please refer to the COPYRIGHT file distributed with this
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
21 #define _PURPLE_BUDDYICON_C_
24 #include "buddyicon.h"
25 #include "conversation.h"
26 #include "dbus-maybe.h"
31 /* NOTE: Instances of this struct are allocated without zeroing the memory, so
32 * NOTE: be sure to update purple_buddy_icon_new() if you add members. */
33 struct _PurpleBuddyIcon
35 PurpleAccount
*account
; /* The account the user is on. */
36 PurpleImage
*img
; /* The image containing
38 char *username
; /* The username the icon belongs to. */
39 char *checksum
; /* The protocol checksum. */
40 unsigned int ref_count
; /* The buddy icon reference count. */
44 * This is the big grand daddy hash table that contains references to
45 * everybody's buddy icons.
47 * Key is a PurpleAccount.
48 * Value is another hash table, usually referred to as "icon_cache."
49 * For this inner hash table:
50 * Key is the username of the buddy whose icon is being stored.
51 * Value is the PurpleBuddyIcon for this buddy.
53 static GHashTable
*account_cache
= NULL
;
56 * This hash table contains a bunch of PurpleImages that are
57 * shared across all accounts.
59 * Key is the filename for this image as constructed by
60 * purple_image_generate_filename(). So it is the base16 encoded
61 * sha-1 hash plus an appropriate file extension. For example:
62 * "0f4972d17d1e70e751c43c90c948e72efbff9796.gif"
64 * The value is a PurpleImage containing the icon data. These images are
65 * reference counted, and when the count reaches 0 we remove the image from
66 * the hash table (but it might still be saved on disk, if the icon is being
67 * used by offline accounts or some such).
69 static GHashTable
*icon_data_cache
= NULL
;
72 * This hash table contains reference counts for how many times each
73 * icon in the ~/.purple/icons/ directory is being used. It's pretty
74 * crazy. It maintains the reference count across sessions, too, so
75 * if you exit Pidgin then this hash table is reconstructed the next
78 * Key is the filename for this image as constructed by
79 * purple_image_generate_filename(). So it is the base16 encoded
80 * sha-1 hash plus an appropriate file extension. For example:
81 * "0f4972d17d1e70e751c43c90c948e72efbff9796.gif"
83 * The value is a GINT_TO_POINTER count of the number of times this
84 * icon is used. So if four of your buddies are using an icon, and
85 * you have the icon set for two of your accounts, then this number
86 * will be six. When this reference count reaches 0 the icon will
87 * be deleted from disk.
89 static GHashTable
*icon_file_cache
= NULL
;
92 * This hash table is used for both custom buddy icons on PurpleBlistNodes and
95 static GHashTable
*pointer_icon_cache
= NULL
;
97 static char *cache_dir
= NULL
;
99 /* "Should icons be cached to disk?" */
100 static gboolean icon_caching
= TRUE
;
102 static void delete_buddy_icon_settings(PurpleBlistNode
*node
, const char *setting_name
);
105 * Begin functions for dealing with the on-disk icon cache
109 ref_filename(const char *filename
)
113 g_return_if_fail(filename
!= NULL
);
115 refs
= GPOINTER_TO_INT(g_hash_table_lookup(icon_file_cache
, filename
));
117 g_hash_table_insert(icon_file_cache
, g_strdup(filename
),
118 GINT_TO_POINTER(refs
+ 1));
122 unref_filename(const char *filename
)
126 if (filename
== NULL
)
129 refs
= GPOINTER_TO_INT(g_hash_table_lookup(icon_file_cache
, filename
));
133 g_hash_table_remove(icon_file_cache
, filename
);
137 g_hash_table_insert(icon_file_cache
, g_strdup(filename
),
138 GINT_TO_POINTER(refs
- 1));
143 image_get_filename(PurpleImage
*img
)
145 return g_object_get_data(G_OBJECT(img
), "purple-buddyicon-filename");
149 purple_buddy_icon_data_cache(PurpleImage
*img
)
151 const gchar
*dirname
, *filename
;
154 g_return_if_fail(PURPLE_IS_IMAGE(img
));
156 if (!purple_buddy_icons_is_caching())
159 dirname
= purple_buddy_icons_get_cache_dir();
160 filename
= image_get_filename(img
);
161 g_return_if_fail(filename
!= NULL
);
162 path
= g_build_filename(dirname
, filename
, NULL
);
164 if (!g_file_test(dirname
, G_FILE_TEST_IS_DIR
))
166 purple_debug_info("buddyicon", "creating icon cache directory");
168 if (g_mkdir(dirname
, S_IRUSR
| S_IWUSR
| S_IXUSR
) < 0)
170 purple_debug_error("buddyicon",
171 "unable to create directory %s: %s",
172 dirname
, g_strerror(errno
));
177 if (!purple_image_save(img
, path
))
178 purple_debug_error("buddyicon", "failed to save icon %s", path
);
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(gpointer _filename
)
232 gchar
*filename
= _filename
;
234 img
= g_hash_table_lookup(icon_data_cache
, filename
);
235 purple_buddy_icon_data_uncache_file(filename
);
236 g_hash_table_remove(icon_data_cache
, filename
);
238 /* We could make this O(1) by using another hash table, but
239 * this is probably good enough. */
240 g_hash_table_foreach_remove(pointer_icon_cache
, value_equals
, (gpointer
)img
);
246 purple_buddy_icon_data_new(guchar
*icon_data
, size_t icon_len
)
248 PurpleImage
*newimg
, *oldimg
;
249 const gchar
*filename
;
251 g_return_val_if_fail(icon_data
!= NULL
, NULL
);
252 g_return_val_if_fail(icon_len
> 0, NULL
);
254 newimg
= purple_image_new_from_data(icon_data
, icon_len
);
255 filename
= purple_image_generate_filename(newimg
);
257 /* TODO: Why is this function called for buddies without icons? If this is
258 * intended, should the filename be null?
260 if (filename
!= NULL
) {
261 oldimg
= g_hash_table_lookup(icon_data_cache
, filename
);
263 g_warn_if_fail(PURPLE_IS_IMAGE(oldimg
));
264 g_object_unref(newimg
);
265 g_object_ref(oldimg
);
269 /* This will take ownership of file and free it as needed */
270 g_hash_table_insert(icon_data_cache
, g_strdup(filename
), newimg
);
273 g_object_set_data_full(G_OBJECT(newimg
), "purple-buddyicon-filename",
274 g_strdup(filename
), image_deleting_cb
);
276 purple_buddy_icon_data_cache(newimg
);
282 * End functions for dealing with the in-memory icon cache
285 static PurpleBuddyIcon
*
286 purple_buddy_icon_create(PurpleAccount
*account
, const char *username
)
288 PurpleBuddyIcon
*icon
;
289 GHashTable
*icon_cache
;
291 /* This does not zero. See purple_buddy_icon_new() for
292 * information on which function allocates which member. */
293 icon
= g_slice_new(PurpleBuddyIcon
);
294 PURPLE_DBUS_REGISTER_POINTER(icon
, PurpleBuddyIcon
);
296 icon
->account
= account
;
297 icon
->username
= g_strdup(username
);
298 icon
->checksum
= NULL
;
301 icon_cache
= g_hash_table_lookup(account_cache
, account
);
303 if (icon_cache
== NULL
)
305 icon_cache
= g_hash_table_new(g_str_hash
, g_str_equal
);
307 g_hash_table_insert(account_cache
, account
, icon_cache
);
310 g_hash_table_insert(icon_cache
,
311 (char *)purple_buddy_icon_get_username(icon
), icon
);
316 purple_buddy_icon_new(PurpleAccount
*account
, const char *username
,
317 void *icon_data
, size_t icon_len
,
318 const char *checksum
)
320 PurpleBuddyIcon
*icon
;
322 g_return_val_if_fail(account
!= NULL
, NULL
);
323 g_return_val_if_fail(username
!= NULL
, NULL
);
324 g_return_val_if_fail(icon_data
!= NULL
, NULL
);
325 g_return_val_if_fail(icon_len
> 0, NULL
);
327 /* purple_buddy_icons_find() does allocation, so be
328 * sure to update it as well when members are added. */
329 icon
= purple_buddy_icons_find(account
, username
);
331 /* purple_buddy_icon_create() sets account & username */
333 icon
= purple_buddy_icon_create(account
, username
);
335 /* purple_buddy_icon_set_data() sets img, but it
336 * references img first, so we need to initialize it */
338 purple_buddy_icon_set_data(icon
, icon_data
, icon_len
, checksum
);
344 purple_buddy_icon_ref(PurpleBuddyIcon
*icon
)
346 g_return_val_if_fail(icon
!= NULL
, NULL
);
354 purple_buddy_icon_unref(PurpleBuddyIcon
*icon
)
359 g_return_if_fail(icon
->ref_count
> 0);
363 if (icon
->ref_count
== 0)
365 GHashTable
*icon_cache
= g_hash_table_lookup(account_cache
, purple_buddy_icon_get_account(icon
));
367 if (icon_cache
!= NULL
)
368 g_hash_table_remove(icon_cache
, purple_buddy_icon_get_username(icon
));
370 g_free(icon
->username
);
371 g_free(icon
->checksum
);
372 g_object_unref(icon
->img
);
374 PURPLE_DBUS_UNREGISTER_POINTER(icon
);
375 g_slice_free(PurpleBuddyIcon
, icon
);
380 purple_buddy_icon_update(PurpleBuddyIcon
*icon
)
382 PurpleIMConversation
*im
;
383 PurpleAccount
*account
;
384 const char *username
;
385 PurpleBuddyIcon
*icon_to_set
;
388 g_return_if_fail(icon
!= NULL
);
390 account
= purple_buddy_icon_get_account(icon
);
391 username
= purple_buddy_icon_get_username(icon
);
393 /* If no data exists (icon->img == NULL), then call the functions below
394 * with NULL to unset the icon. They will then unref the icon and it should
395 * be destroyed. The only way it wouldn't be destroyed is if someone
396 * else is holding a reference to it, in which case they can kill
397 * the icon when they realize it has no data. */
398 icon_to_set
= icon
->img
? icon
: NULL
;
400 /* Ensure that icon remains valid throughout */
401 purple_buddy_icon_ref(icon
);
403 buddies
= purple_blist_find_buddies(account
, username
);
404 while (buddies
!= NULL
)
406 PurpleBuddy
*buddy
= (PurpleBuddy
*)buddies
->data
;
409 purple_buddy_set_icon(buddy
, icon_to_set
);
410 old_icon
= g_strdup(purple_blist_node_get_string((PurpleBlistNode
*)buddy
,
412 if (icon
->img
&& purple_buddy_icons_is_caching())
414 const char *filename
= image_get_filename(icon
->img
);
415 g_warn_if_fail(filename
!= NULL
);
416 purple_blist_node_set_string((PurpleBlistNode
*)buddy
,
420 if (icon
->checksum
&& *icon
->checksum
)
422 purple_blist_node_set_string((PurpleBlistNode
*)buddy
,
428 purple_blist_node_remove_setting((PurpleBlistNode
*)buddy
,
431 ref_filename(filename
);
435 purple_blist_node_remove_setting((PurpleBlistNode
*)buddy
, "buddy_icon");
436 purple_blist_node_remove_setting((PurpleBlistNode
*)buddy
, "icon_checksum");
438 unref_filename(old_icon
);
441 buddies
= g_slist_delete_link(buddies
, buddies
);
444 im
= purple_conversations_find_im_with_account(username
, account
);
447 purple_im_conversation_set_icon(im
, icon_to_set
);
449 /* icon's refcount was incremented above */
450 purple_buddy_icon_unref(icon
);
454 purple_buddy_icon_set_data(PurpleBuddyIcon
*icon
, guchar
*data
,
455 size_t len
, const char *checksum
)
457 PurpleImage
*old_img
;
459 g_return_if_fail(icon
!= NULL
);
467 icon
->img
= purple_buddy_icon_data_new(data
, len
);
472 g_free(icon
->checksum
);
473 icon
->checksum
= g_strdup(checksum
);
475 purple_buddy_icon_update(icon
);
478 g_object_unref(old_img
);
482 purple_buddy_icon_get_account(const PurpleBuddyIcon
*icon
)
484 g_return_val_if_fail(icon
!= NULL
, NULL
);
486 return icon
->account
;
490 purple_buddy_icon_get_username(const PurpleBuddyIcon
*icon
)
492 g_return_val_if_fail(icon
!= NULL
, NULL
);
494 return icon
->username
;
498 purple_buddy_icon_get_checksum(const PurpleBuddyIcon
*icon
)
500 g_return_val_if_fail(icon
!= NULL
, NULL
);
502 return icon
->checksum
;
506 purple_buddy_icon_get_data(const PurpleBuddyIcon
*icon
, size_t *len
)
508 g_return_val_if_fail(icon
!= NULL
, NULL
);
513 *len
= purple_image_get_size(icon
->img
);
515 return purple_image_get_data(icon
->img
);
522 purple_buddy_icon_get_extension(const PurpleBuddyIcon
*icon
)
524 if (icon
->img
!= NULL
)
525 return purple_image_get_extension(icon
->img
);
531 purple_buddy_icons_set_for_user(PurpleAccount
*account
, const char *username
,
532 void *icon_data
, size_t icon_len
,
533 const char *checksum
)
535 GHashTable
*icon_cache
;
536 PurpleBuddyIcon
*icon
= NULL
;
538 g_return_if_fail(account
!= NULL
);
539 g_return_if_fail(username
!= NULL
);
541 icon_cache
= g_hash_table_lookup(account_cache
, account
);
543 if (icon_cache
!= NULL
)
544 icon
= g_hash_table_lookup(icon_cache
, username
);
547 purple_buddy_icon_set_data(icon
, icon_data
, icon_len
, checksum
);
548 else if (icon_data
&& icon_len
> 0)
550 PurpleBuddyIcon
*icon
= purple_buddy_icon_new(account
, username
, icon_data
, icon_len
, checksum
);
552 /* purple_buddy_icon_new() calls
553 * purple_buddy_icon_set_data(), which calls
554 * purple_buddy_icon_update(), which has the buddy list
555 * and conversations take references as appropriate.
556 * This function doesn't return icon, so we can't
557 * leave a reference dangling. */
558 purple_buddy_icon_unref(icon
);
562 /* If the buddy list or a conversation was holding a
563 * reference, we'd have found the icon in the cache.
564 * Since we know we're deleting the icon, we only
565 * need a subset of purple_buddy_icon_update(). */
567 GSList
*buddies
= purple_blist_find_buddies(account
, username
);
568 while (buddies
!= NULL
)
570 PurpleBuddy
*buddy
= (PurpleBuddy
*)buddies
->data
;
572 unref_filename(purple_blist_node_get_string((PurpleBlistNode
*)buddy
, "buddy_icon"));
573 purple_blist_node_remove_setting((PurpleBlistNode
*)buddy
, "buddy_icon");
574 purple_blist_node_remove_setting((PurpleBlistNode
*)buddy
, "icon_checksum");
576 buddies
= g_slist_delete_link(buddies
, buddies
);
582 purple_buddy_icon_get_full_path(PurpleBuddyIcon
*icon
)
586 g_return_val_if_fail(icon
!= NULL
, NULL
);
588 if (icon
->img
== NULL
)
591 path
= purple_image_get_path(icon
->img
);
592 if (!g_file_test(path
, G_FILE_TEST_EXISTS
))
600 purple_buddy_icons_get_checksum_for_user(PurpleBuddy
*buddy
)
602 return purple_blist_node_get_string((PurpleBlistNode
*)buddy
,
607 read_icon_file(const char *path
, guchar
**data
, size_t *len
)
611 if (!g_file_get_contents(path
, (gchar
**)data
, len
, &err
))
613 purple_debug_error("buddyicon", "Error reading %s: %s\n",
624 purple_buddy_icons_find(PurpleAccount
*account
, const char *username
)
626 GHashTable
*icon_cache
;
627 PurpleBuddyIcon
*icon
= NULL
;
629 g_return_val_if_fail(account
!= NULL
, NULL
);
630 g_return_val_if_fail(username
!= NULL
, NULL
);
632 icon_cache
= g_hash_table_lookup(account_cache
, account
);
634 if ((icon_cache
== NULL
) || ((icon
= g_hash_table_lookup(icon_cache
, username
)) == NULL
))
636 /* The icon is not currently cached in memory--try reading from disk */
637 PurpleBuddy
*b
= purple_blist_find_buddy(account
, username
);
638 const char *protocol_icon_file
;
647 protocol_icon_file
= purple_blist_node_get_string((PurpleBlistNode
*)b
, "buddy_icon");
649 if (protocol_icon_file
== NULL
)
652 dirname
= purple_buddy_icons_get_cache_dir();
654 caching
= purple_buddy_icons_is_caching();
655 /* By disabling caching temporarily, we avoid a loop
656 * and don't have to add special code through several
658 purple_buddy_icons_set_caching(FALSE
);
660 if (protocol_icon_file
!= NULL
)
662 char *path
= g_build_filename(dirname
, protocol_icon_file
, NULL
);
663 if (read_icon_file(path
, &data
, &len
))
665 const char *checksum
;
667 icon
= purple_buddy_icon_create(account
, username
);
669 checksum
= purple_blist_node_get_string((PurpleBlistNode
*)b
, "icon_checksum");
670 purple_buddy_icon_set_data(icon
, data
, len
, checksum
);
673 delete_buddy_icon_settings((PurpleBlistNode
*)b
, "buddy_icon");
678 purple_buddy_icons_set_caching(caching
);
681 return (icon
? purple_buddy_icon_ref(icon
) : NULL
);
685 purple_buddy_icons_find_account_icon(PurpleAccount
*account
)
688 const char *account_icon_file
;
694 g_return_val_if_fail(account
!= NULL
, NULL
);
696 img
= g_hash_table_lookup(pointer_icon_cache
, account
);
702 account_icon_file
= purple_account_get_string(account
, "buddy_icon", NULL
);
704 if (account_icon_file
== NULL
)
707 dirname
= purple_buddy_icons_get_cache_dir();
708 path
= g_build_filename(dirname
, account_icon_file
, NULL
);
710 if (read_icon_file(path
, &data
, &len
)) {
712 img
= purple_buddy_icons_set_account_icon(account
, data
, len
);
722 purple_buddy_icons_set_account_icon(PurpleAccount
*account
,
723 guchar
*icon_data
, size_t icon_len
)
725 PurpleImage
*old_img
;
726 PurpleImage
*img
= NULL
;
729 if (icon_data
!= NULL
&& icon_len
> 0) {
730 img
= purple_buddy_icon_data_new(icon_data
, icon_len
);
733 old_icon
= g_strdup(purple_account_get_string(account
, "buddy_icon", NULL
));
734 if (img
&& purple_buddy_icons_is_caching())
736 const char *filename
= image_get_filename(img
);
737 g_warn_if_fail(filename
!= NULL
);
738 purple_account_set_string(account
, "buddy_icon", filename
);
739 purple_account_set_int(account
, "buddy_icon_timestamp", time(NULL
));
740 ref_filename(filename
);
744 purple_account_set_string(account
, "buddy_icon", NULL
);
745 purple_account_set_int(account
, "buddy_icon_timestamp", 0);
747 unref_filename(old_icon
);
749 old_img
= g_hash_table_lookup(pointer_icon_cache
, account
);
752 g_hash_table_insert(pointer_icon_cache
, account
, img
);
754 g_hash_table_remove(pointer_icon_cache
, account
);
756 if (!purple_account_is_disconnected(account
))
758 PurpleConnection
*gc
;
759 PurpleProtocol
*protocol
;
761 gc
= purple_account_get_connection(account
);
762 protocol
= purple_connection_get_protocol(gc
);
765 purple_protocol_server_iface_set_buddy_icon(protocol
, gc
, img
);
769 g_object_unref(old_img
);
772 /* The old icon may not have been loaded into memory. In that
773 * case, we'll need to uncache the filename. The filenames
774 * are ref-counted, so this is safe. */
775 purple_buddy_icon_data_uncache_file(old_icon
);
783 purple_buddy_icons_get_account_icon_timestamp(PurpleAccount
*account
)
787 g_return_val_if_fail(account
!= NULL
, 0);
789 ret
= purple_account_get_int(account
, "buddy_icon_timestamp", 0);
791 /* This deals with migration cases. */
792 if (ret
== 0 && purple_account_get_string(account
, "buddy_icon", NULL
) != NULL
)
795 purple_account_set_int(account
, "buddy_icon_timestamp", ret
);
802 purple_buddy_icons_node_has_custom_icon(PurpleBlistNode
*node
)
804 g_return_val_if_fail(node
!= NULL
, FALSE
);
806 return (purple_blist_node_get_string(node
, "custom_buddy_icon") != NULL
);
810 purple_buddy_icons_node_find_custom_icon(PurpleBlistNode
*node
)
816 const char *custom_icon_file
, *dirname
;
818 g_return_val_if_fail(node
!= NULL
, NULL
);
820 img
= g_hash_table_lookup(pointer_icon_cache
, node
);
826 custom_icon_file
= purple_blist_node_get_string(node
,
827 "custom_buddy_icon");
829 if (custom_icon_file
== NULL
)
832 dirname
= purple_buddy_icons_get_cache_dir();
833 path
= g_build_filename(dirname
, custom_icon_file
, NULL
);
835 if (read_icon_file(path
, &data
, &len
)) {
837 img
= purple_buddy_icons_node_set_custom_icon(node
, data
, len
);
847 purple_buddy_icons_node_set_custom_icon(PurpleBlistNode
*node
,
848 guchar
*icon_data
, size_t icon_len
)
851 PurpleImage
*old_img
;
852 PurpleImage
*img
= NULL
;
853 PurpleBlistUiOps
*ops
= purple_blist_get_ui_ops();
855 g_return_val_if_fail(node
!= NULL
, NULL
);
857 if (!PURPLE_IS_CONTACT(node
) &&
858 !PURPLE_IS_CHAT(node
) &&
859 !PURPLE_IS_GROUP(node
)) {
863 old_img
= g_hash_table_lookup(pointer_icon_cache
, node
);
865 if (icon_data
!= NULL
&& icon_len
> 0) {
866 img
= purple_buddy_icon_data_new(icon_data
, icon_len
);
869 old_icon
= g_strdup(purple_blist_node_get_string(node
,
870 "custom_buddy_icon"));
871 if (img
&& purple_buddy_icons_is_caching()) {
872 const char *filename
= image_get_filename(img
);
873 g_warn_if_fail(filename
);
874 purple_blist_node_set_string(node
, "custom_buddy_icon",
876 ref_filename(filename
);
878 purple_blist_node_remove_setting(node
, "custom_buddy_icon");
880 unref_filename(old_icon
);
883 g_hash_table_insert(pointer_icon_cache
, node
, img
);
885 g_hash_table_remove(pointer_icon_cache
, node
);
887 if (PURPLE_IS_CONTACT(node
)) {
888 PurpleBlistNode
*child
;
889 for (child
= purple_blist_node_get_first_child(node
);
891 child
= purple_blist_node_get_sibling_next(child
))
894 PurpleIMConversation
*im
;
896 if (!PURPLE_IS_BUDDY(child
))
899 buddy
= (PurpleBuddy
*)child
;
901 im
= purple_conversations_find_im_with_account(purple_buddy_get_name(buddy
), purple_buddy_get_account(buddy
));
903 purple_conversation_update(PURPLE_CONVERSATION(im
), PURPLE_CONVERSATION_UPDATE_ICON
);
905 /* Is this call necessary anymore? Can the buddies
906 * themselves need updating when the custom buddy
908 if (ops
&& ops
->update
)
909 ops
->update(purple_blist_get_buddy_list(), PURPLE_BLIST_NODE(buddy
));
911 } else if (PURPLE_IS_CHAT(node
)) {
912 PurpleChatConversation
*chat
= NULL
;
914 chat
= purple_conversations_find_chat_with_account(purple_chat_get_name((PurpleChat
*)node
), purple_chat_get_account((PurpleChat
*)node
));
916 purple_conversation_update(PURPLE_CONVERSATION(chat
), PURPLE_CONVERSATION_UPDATE_ICON
);
920 if (ops
&& ops
->update
)
921 ops
->update(purple_blist_get_buddy_list(), node
);
924 g_object_unref(old_img
);
925 } else if (old_icon
) {
926 /* The old icon may not have been loaded into memory. In that
927 * case, we'll need to uncache the filename. The filenames
928 * are ref-counted, so this is safe. */
929 purple_buddy_icon_data_uncache_file(old_icon
);
937 purple_buddy_icons_node_set_custom_icon_from_file(PurpleBlistNode
*node
,
938 const gchar
*filename
)
943 g_return_val_if_fail(node
!= NULL
, NULL
);
945 if (!PURPLE_IS_CONTACT(node
) &&
946 !PURPLE_IS_CHAT(node
) &&
947 !PURPLE_IS_GROUP(node
)) {
951 if (filename
!= NULL
) {
952 if (!read_icon_file(filename
, &data
, &len
)) {
957 return purple_buddy_icons_node_set_custom_icon(node
, data
, len
);
961 delete_buddy_icon_settings(PurpleBlistNode
*node
, const char *setting_name
)
963 purple_blist_node_remove_setting(node
, setting_name
);
965 if (purple_strequal(setting_name
, "buddy_icon"))
967 purple_blist_node_remove_setting(node
, "avatar_hash");
968 purple_blist_node_remove_setting(node
, "icon_checksum");
973 _purple_buddy_icons_account_loaded_cb()
975 const char *dirname
= purple_buddy_icons_get_cache_dir();
978 for (cur
= purple_accounts_get_all(); cur
!= NULL
; cur
= cur
->next
)
980 PurpleAccount
*account
= cur
->data
;
981 const char *account_icon_file
= purple_account_get_string(account
, "buddy_icon", NULL
);
983 if (account_icon_file
!= NULL
)
985 char *path
= g_build_filename(dirname
, account_icon_file
, NULL
);
986 if (!g_file_test(path
, G_FILE_TEST_EXISTS
))
988 purple_account_set_string(account
, "buddy_icon", NULL
);
990 ref_filename(account_icon_file
);
998 _purple_buddy_icons_blist_loaded_cb()
1000 PurpleBlistNode
*node
= purple_blist_get_root();
1001 const char *dirname
= purple_buddy_icons_get_cache_dir();
1003 while (node
!= NULL
)
1005 if (PURPLE_IS_BUDDY(node
))
1007 const char *filename
;
1009 filename
= purple_blist_node_get_string(node
, "buddy_icon");
1010 if (filename
!= NULL
)
1012 char *path
= g_build_filename(dirname
, filename
, NULL
);
1013 if (!g_file_test(path
, G_FILE_TEST_EXISTS
))
1015 purple_blist_node_remove_setting(node
,
1017 purple_blist_node_remove_setting(node
,
1021 ref_filename(filename
);
1025 else if (PURPLE_IS_CONTACT(node
) ||
1026 PURPLE_IS_CHAT(node
) ||
1027 PURPLE_IS_GROUP(node
))
1029 const char *filename
;
1031 filename
= purple_blist_node_get_string(node
, "custom_buddy_icon");
1032 if (filename
!= NULL
)
1034 char *path
= g_build_filename(dirname
, filename
, NULL
);
1035 if (!g_file_test(path
, G_FILE_TEST_EXISTS
))
1037 purple_blist_node_remove_setting(node
,
1038 "custom_buddy_icon");
1041 ref_filename(filename
);
1045 node
= purple_blist_node_next(node
, TRUE
);
1050 purple_buddy_icons_set_caching(gboolean caching
)
1052 icon_caching
= caching
;
1056 purple_buddy_icons_is_caching(void)
1058 return icon_caching
;
1062 purple_buddy_icons_set_cache_dir(const char *dir
)
1064 g_return_if_fail(dir
!= NULL
);
1067 cache_dir
= g_strdup(dir
);
1071 purple_buddy_icons_get_cache_dir(void)
1077 purple_buddy_icons_get_handle()
1085 purple_buddy_icons_init()
1087 account_cache
= g_hash_table_new_full(
1088 g_direct_hash
, g_direct_equal
,
1089 NULL
, (GFreeFunc
)g_hash_table_destroy
);
1091 icon_data_cache
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
1093 icon_file_cache
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
1095 pointer_icon_cache
= g_hash_table_new(g_direct_hash
, g_direct_equal
);
1098 cache_dir
= g_build_filename(purple_cache_dir(), "icons", NULL
);
1102 purple_buddy_icons_uninit()
1104 purple_signals_disconnect_by_handle(purple_buddy_icons_get_handle());
1106 g_hash_table_destroy(account_cache
);
1107 g_hash_table_destroy(icon_data_cache
);
1108 g_hash_table_destroy(icon_file_cache
);
1109 g_hash_table_destroy(pointer_icon_cache
);
1116 purple_buddy_icon_get_type(void)
1118 static GType type
= 0;
1121 type
= g_boxed_type_register_static("PurpleBuddyIcon",
1122 (GBoxedCopyFunc
)purple_buddy_icon_ref
,
1123 (GBoxedFreeFunc
)purple_buddy_icon_unref
);
1129 PurpleBuddyIconSpec
*
1130 purple_buddy_icon_spec_new(char *format
, int min_width
, int min_height
,
1131 int max_width
, int max_height
, size_t max_filesize
,
1132 PurpleBuddyIconScaleFlags scale_rules
)
1134 PurpleBuddyIconSpec
*icon_spec
;
1136 icon_spec
= g_new0(PurpleBuddyIconSpec
, 1);
1138 icon_spec
->format
= format
;
1139 icon_spec
->min_width
= min_width
;
1140 icon_spec
->min_height
= min_height
;
1141 icon_spec
->max_width
= max_width
;
1142 icon_spec
->max_height
= max_height
;
1143 icon_spec
->max_filesize
= max_filesize
;
1144 icon_spec
->scale_rules
= scale_rules
;
1149 static PurpleBuddyIconSpec
*
1150 purple_buddy_icon_spec_copy(PurpleBuddyIconSpec
*icon_spec
)
1152 PurpleBuddyIconSpec
*icon_spec_copy
;
1154 g_return_val_if_fail(icon_spec
!= NULL
, NULL
);
1156 icon_spec_copy
= g_new0(PurpleBuddyIconSpec
, 1);
1157 *icon_spec_copy
= *icon_spec
;
1159 return icon_spec_copy
;
1162 void purple_buddy_icon_spec_get_scaled_size(PurpleBuddyIconSpec
*spec
,
1163 int *width
, int *height
)
1165 int new_width
, new_height
;
1168 new_height
= *height
;
1170 if (*width
< spec
->min_width
)
1171 new_width
= spec
->min_width
;
1172 else if (*width
> spec
->max_width
)
1173 new_width
= spec
->max_width
;
1175 if (*height
< spec
->min_height
)
1176 new_height
= spec
->min_height
;
1177 else if (*height
> spec
->max_height
)
1178 new_height
= spec
->max_height
;
1180 /* preserve aspect ratio */
1181 if ((double)*height
* (double)new_width
>
1182 (double)*width
* (double)new_height
) {
1183 new_width
= 0.5 + (double)*width
* (double)new_height
/ (double)*height
;
1185 new_height
= 0.5 + (double)*height
* (double)new_width
/ (double)*width
;
1189 *height
= new_height
;
1193 purple_buddy_icon_spec_get_type(void)
1195 static GType type
= 0;
1198 type
= g_boxed_type_register_static("PurpleBuddyIconSpec",
1199 (GBoxedCopyFunc
)purple_buddy_icon_spec_copy
,
1200 (GBoxedFreeFunc
)g_free
);