Convert XMPP console dialogs to popovers.
[pidgin-git.git] / libpurple / buddyicon.c
blobc65d2d5e0f230cc63da591b5d6554520ad9cfe7b
1 /* purple
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
5 * source distribution.
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
22 #include "internal.h"
23 #include "buddyicon.h"
24 #include "conversation.h"
25 #include "debug.h"
26 #include "image.h"
27 #include "util.h"
29 /* NOTE: Instances of this struct are allocated without zeroing the memory, so
30 * NOTE: be sure to update purple_buddy_icon_new() if you add members. */
31 struct _PurpleBuddyIcon
33 PurpleAccount *account; /* The account the user is on. */
34 PurpleImage *img; /* The image containing
35 the icon data. */
36 char *username; /* The username the icon belongs to. */
37 char *checksum; /* The protocol checksum. */
38 unsigned int ref_count; /* The buddy icon reference count. */
42 * This is the big grand daddy hash table that contains references to
43 * everybody's buddy icons.
45 * Key is a PurpleAccount.
46 * Value is another hash table, usually referred to as "icon_cache."
47 * For this inner hash table:
48 * Key is the username of the buddy whose icon is being stored.
49 * Value is the PurpleBuddyIcon for this buddy.
51 static GHashTable *account_cache = NULL;
54 * This hash table contains a bunch of PurpleImages that are
55 * shared across all accounts.
57 * Key is the filename for this image as constructed by
58 * purple_image_generate_filename(). So it is the base16 encoded
59 * sha-1 hash plus an appropriate file extension. For example:
60 * "0f4972d17d1e70e751c43c90c948e72efbff9796.gif"
62 * The value is a PurpleImage containing the icon data. These images are
63 * reference counted, and when the count reaches 0 we remove the image from
64 * the hash table (but it might still be saved on disk, if the icon is being
65 * used by offline accounts or some such).
67 static GHashTable *icon_data_cache = NULL;
70 * This hash table contains reference counts for how many times each
71 * icon in the ~/.purple/icons/ directory is being used. It's pretty
72 * crazy. It maintains the reference count across sessions, too, so
73 * if you exit Pidgin then this hash table is reconstructed the next
74 * time Pidgin starts.
76 * Key is the filename for this image as constructed by
77 * purple_image_generate_filename(). So it is the base16 encoded
78 * sha-1 hash plus an appropriate file extension. For example:
79 * "0f4972d17d1e70e751c43c90c948e72efbff9796.gif"
81 * The value is a GINT_TO_POINTER count of the number of times this
82 * icon is used. So if four of your buddies are using an icon, and
83 * you have the icon set for two of your accounts, then this number
84 * will be six. When this reference count reaches 0 the icon will
85 * be deleted from disk.
87 static GHashTable *icon_file_cache = NULL;
90 * This hash table is used for both custom buddy icons on PurpleBlistNodes and
91 * account icons.
93 static GHashTable *pointer_icon_cache = NULL;
95 static char *cache_dir = NULL;
97 /* "Should icons be cached to disk?" */
98 static gboolean icon_caching = TRUE;
100 static void delete_buddy_icon_settings(PurpleBlistNode *node, const char *setting_name);
103 * Begin functions for dealing with the on-disk icon cache
106 static void
107 ref_filename(const char *filename)
109 int refs;
111 g_return_if_fail(filename != NULL);
113 refs = GPOINTER_TO_INT(g_hash_table_lookup(icon_file_cache, filename));
115 g_hash_table_insert(icon_file_cache, g_strdup(filename),
116 GINT_TO_POINTER(refs + 1));
119 static void
120 unref_filename(const char *filename)
122 int refs;
124 if (filename == NULL)
125 return;
127 refs = GPOINTER_TO_INT(g_hash_table_lookup(icon_file_cache, filename));
129 if (refs == 1)
131 g_hash_table_remove(icon_file_cache, filename);
133 else
135 g_hash_table_insert(icon_file_cache, g_strdup(filename),
136 GINT_TO_POINTER(refs - 1));
140 static const gchar *
141 image_get_filename(PurpleImage *img)
143 return g_object_get_data(G_OBJECT(img), "purple-buddyicon-filename");
146 static void
147 purple_buddy_icon_data_cache(PurpleImage *img)
149 const gchar *dirname, *filename;
150 gchar *path;
152 g_return_if_fail(PURPLE_IS_IMAGE(img));
154 if (!purple_buddy_icons_is_caching())
155 return;
157 dirname = purple_buddy_icons_get_cache_dir();
158 filename = image_get_filename(img);
159 g_return_if_fail(filename != NULL);
161 if (!g_file_test(dirname, G_FILE_TEST_IS_DIR))
163 purple_debug_info("buddyicon", "creating icon cache directory");
165 if (g_mkdir(dirname, S_IRUSR | S_IWUSR | S_IXUSR) < 0)
167 purple_debug_error("buddyicon",
168 "unable to create directory %s: %s",
169 dirname, g_strerror(errno));
170 return;
174 path = g_build_filename(dirname, filename, NULL);
175 if (!purple_image_save(img, path))
176 purple_debug_error("buddyicon", "failed to save icon %s", path);
178 g_free(path);
181 static void
182 purple_buddy_icon_data_uncache_file(const char *filename)
184 const char *dirname;
185 char *path;
187 g_return_if_fail(filename != NULL);
189 /* It's possible that there are other references to this icon
190 * cache file that are not currently loaded into memory. */
191 if (GPOINTER_TO_INT(g_hash_table_lookup(icon_file_cache, filename)))
192 return;
194 dirname = purple_buddy_icons_get_cache_dir();
195 path = g_build_filename(dirname, filename, NULL);
197 if (g_file_test(path, G_FILE_TEST_EXISTS))
199 if (g_unlink(path))
201 purple_debug_error("buddyicon", "Failed to delete %s: %s\n",
202 path, g_strerror(errno));
204 else
206 purple_debug_info("buddyicon", "Deleted cache file: %s\n", path);
210 g_free(path);
214 * End functions for dealing with the on-disk icon cache
218 * Begin functions for dealing with the in-memory icon cache
221 static gboolean
222 value_equals(gpointer key, gpointer value, gpointer user_data)
224 return (value == user_data);
227 static void
228 image_deleting_cb(gpointer _filename)
230 PurpleImage *img;
231 gchar *filename = _filename;
233 img = g_hash_table_lookup(icon_data_cache, filename);
234 purple_buddy_icon_data_uncache_file(filename);
235 g_hash_table_remove(icon_data_cache, filename);
237 /* We could make this O(1) by using another hash table, but
238 * this is probably good enough. */
239 g_hash_table_foreach_remove(pointer_icon_cache, value_equals, (gpointer)img);
241 g_free(filename);
244 static PurpleImage *
245 purple_buddy_icon_data_new(guchar *icon_data, size_t icon_len)
247 PurpleImage *newimg, *oldimg;
248 const gchar *filename;
250 g_return_val_if_fail(icon_data != NULL, NULL);
251 g_return_val_if_fail(icon_len > 0, NULL);
253 newimg = purple_image_new_from_data(icon_data, icon_len);
254 filename = purple_image_generate_filename(newimg);
256 /* TODO: Why is this function called for buddies without icons? If this is
257 * intended, should the filename be null?
259 if (filename != NULL) {
260 oldimg = g_hash_table_lookup(icon_data_cache, filename);
261 if (oldimg) {
262 g_warn_if_fail(PURPLE_IS_IMAGE(oldimg));
263 g_object_unref(newimg);
264 g_object_ref(oldimg);
265 return oldimg;
268 /* This will take ownership of file and free it as needed */
269 g_hash_table_insert(icon_data_cache, g_strdup(filename), newimg);
272 g_object_set_data_full(G_OBJECT(newimg), "purple-buddyicon-filename",
273 g_strdup(filename), image_deleting_cb);
275 purple_buddy_icon_data_cache(newimg);
277 return newimg;
281 * End functions for dealing with the in-memory icon cache
284 static PurpleBuddyIcon *
285 purple_buddy_icon_create(PurpleAccount *account, const char *username)
287 PurpleBuddyIcon *icon;
288 GHashTable *icon_cache;
290 /* This does not zero. See purple_buddy_icon_new() for
291 * information on which function allocates which member. */
292 icon = g_slice_new(PurpleBuddyIcon);
294 icon->account = account;
295 icon->username = g_strdup(username);
296 icon->checksum = NULL;
297 icon->ref_count = 1;
299 icon_cache = g_hash_table_lookup(account_cache, account);
301 if (icon_cache == NULL)
303 icon_cache = g_hash_table_new(g_str_hash, g_str_equal);
305 g_hash_table_insert(account_cache, account, icon_cache);
308 g_hash_table_insert(icon_cache,
309 (char *)purple_buddy_icon_get_username(icon), icon);
310 return icon;
313 PurpleBuddyIcon *
314 purple_buddy_icon_new(PurpleAccount *account, const char *username,
315 void *icon_data, size_t icon_len,
316 const char *checksum)
318 PurpleBuddyIcon *icon;
320 g_return_val_if_fail(account != NULL, NULL);
321 g_return_val_if_fail(username != NULL, NULL);
322 g_return_val_if_fail(icon_data != NULL, NULL);
323 g_return_val_if_fail(icon_len > 0, NULL);
325 /* purple_buddy_icons_find() does allocation, so be
326 * sure to update it as well when members are added. */
327 icon = purple_buddy_icons_find(account, username);
329 /* purple_buddy_icon_create() sets account & username */
330 if (icon == NULL)
331 icon = purple_buddy_icon_create(account, username);
333 /* purple_buddy_icon_set_data() sets img, but it
334 * references img first, so we need to initialize it */
335 icon->img = NULL;
336 purple_buddy_icon_set_data(icon, icon_data, icon_len, checksum);
338 return icon;
341 PurpleBuddyIcon *
342 purple_buddy_icon_ref(PurpleBuddyIcon *icon)
344 g_return_val_if_fail(icon != NULL, NULL);
346 icon->ref_count++;
348 return icon;
351 void
352 purple_buddy_icon_unref(PurpleBuddyIcon *icon)
354 if (icon == NULL)
355 return;
357 g_return_if_fail(icon->ref_count > 0);
359 icon->ref_count--;
361 if (icon->ref_count == 0)
363 GHashTable *icon_cache = g_hash_table_lookup(account_cache, purple_buddy_icon_get_account(icon));
365 if (icon_cache != NULL)
366 g_hash_table_remove(icon_cache, purple_buddy_icon_get_username(icon));
368 g_free(icon->username);
369 g_free(icon->checksum);
370 g_object_unref(icon->img);
372 g_slice_free(PurpleBuddyIcon, icon);
376 void
377 purple_buddy_icon_update(PurpleBuddyIcon *icon)
379 PurpleIMConversation *im;
380 PurpleAccount *account;
381 const char *username;
382 PurpleBuddyIcon *icon_to_set;
383 GSList *buddies;
385 g_return_if_fail(icon != NULL);
387 account = purple_buddy_icon_get_account(icon);
388 username = purple_buddy_icon_get_username(icon);
390 /* If no data exists (icon->img == NULL), then call the functions below
391 * with NULL to unset the icon. They will then unref the icon and it should
392 * be destroyed. The only way it wouldn't be destroyed is if someone
393 * else is holding a reference to it, in which case they can kill
394 * the icon when they realize it has no data. */
395 icon_to_set = icon->img ? icon : NULL;
397 /* Ensure that icon remains valid throughout */
398 purple_buddy_icon_ref(icon);
400 buddies = purple_blist_find_buddies(account, username);
401 while (buddies != NULL)
403 PurpleBuddy *buddy = (PurpleBuddy *)buddies->data;
404 char *old_icon;
406 purple_buddy_set_icon(buddy, icon_to_set);
407 old_icon = g_strdup(purple_blist_node_get_string((PurpleBlistNode *)buddy,
408 "buddy_icon"));
409 if (icon->img && purple_buddy_icons_is_caching())
411 const char *filename = image_get_filename(icon->img);
412 g_warn_if_fail(filename != NULL);
413 purple_blist_node_set_string((PurpleBlistNode *)buddy,
414 "buddy_icon",
415 filename);
417 if (icon->checksum && *icon->checksum)
419 purple_blist_node_set_string((PurpleBlistNode *)buddy,
420 "icon_checksum",
421 icon->checksum);
423 else
425 purple_blist_node_remove_setting((PurpleBlistNode *)buddy,
426 "icon_checksum");
428 ref_filename(filename);
430 else if (!icon->img)
432 purple_blist_node_remove_setting((PurpleBlistNode *)buddy, "buddy_icon");
433 purple_blist_node_remove_setting((PurpleBlistNode *)buddy, "icon_checksum");
435 unref_filename(old_icon);
436 g_free(old_icon);
438 buddies = g_slist_delete_link(buddies, buddies);
441 im = purple_conversations_find_im_with_account(username, account);
443 if (im != NULL)
444 purple_im_conversation_set_icon(im, icon_to_set);
446 /* icon's refcount was incremented above */
447 purple_buddy_icon_unref(icon);
450 void
451 purple_buddy_icon_set_data(PurpleBuddyIcon *icon, guchar *data,
452 size_t len, const char *checksum)
454 PurpleImage *old_img;
456 g_return_if_fail(icon != NULL);
458 old_img = icon->img;
459 icon->img = NULL;
461 if (data != NULL)
463 if (len > 0)
464 icon->img = purple_buddy_icon_data_new(data, len);
465 else
466 g_free(data);
469 g_free(icon->checksum);
470 icon->checksum = g_strdup(checksum);
472 purple_buddy_icon_update(icon);
474 if (old_img)
475 g_object_unref(old_img);
478 PurpleAccount *
479 purple_buddy_icon_get_account(const PurpleBuddyIcon *icon)
481 g_return_val_if_fail(icon != NULL, NULL);
483 return icon->account;
486 const char *
487 purple_buddy_icon_get_username(const PurpleBuddyIcon *icon)
489 g_return_val_if_fail(icon != NULL, NULL);
491 return icon->username;
494 const char *
495 purple_buddy_icon_get_checksum(const PurpleBuddyIcon *icon)
497 g_return_val_if_fail(icon != NULL, NULL);
499 return icon->checksum;
502 gconstpointer
503 purple_buddy_icon_get_data(const PurpleBuddyIcon *icon, size_t *len)
505 g_return_val_if_fail(icon != NULL, NULL);
507 if (icon->img)
509 if (len != NULL)
510 *len = purple_image_get_data_size(icon->img);
512 return purple_image_get_data(icon->img);
515 return NULL;
518 const char *
519 purple_buddy_icon_get_extension(const PurpleBuddyIcon *icon)
521 if (icon->img != NULL)
522 return purple_image_get_extension(icon->img);
524 return NULL;
527 void
528 purple_buddy_icons_set_for_user(PurpleAccount *account, const char *username,
529 void *icon_data, size_t icon_len,
530 const char *checksum)
532 GHashTable *icon_cache;
533 PurpleBuddyIcon *icon = NULL;
535 g_return_if_fail(account != NULL);
536 g_return_if_fail(username != NULL);
538 icon_cache = g_hash_table_lookup(account_cache, account);
540 if (icon_cache != NULL)
541 icon = g_hash_table_lookup(icon_cache, username);
543 if (icon != NULL)
544 purple_buddy_icon_set_data(icon, icon_data, icon_len, checksum);
545 else if (icon_data && icon_len > 0)
547 PurpleBuddyIcon *icon = purple_buddy_icon_new(account, username, icon_data, icon_len, checksum);
549 /* purple_buddy_icon_new() calls
550 * purple_buddy_icon_set_data(), which calls
551 * purple_buddy_icon_update(), which has the buddy list
552 * and conversations take references as appropriate.
553 * This function doesn't return icon, so we can't
554 * leave a reference dangling. */
555 purple_buddy_icon_unref(icon);
557 else
559 /* If the buddy list or a conversation was holding a
560 * reference, we'd have found the icon in the cache.
561 * Since we know we're deleting the icon, we only
562 * need a subset of purple_buddy_icon_update(). */
564 GSList *buddies = purple_blist_find_buddies(account, username);
565 while (buddies != NULL)
567 PurpleBuddy *buddy = (PurpleBuddy *)buddies->data;
569 unref_filename(purple_blist_node_get_string((PurpleBlistNode *)buddy, "buddy_icon"));
570 purple_blist_node_remove_setting((PurpleBlistNode *)buddy, "buddy_icon");
571 purple_blist_node_remove_setting((PurpleBlistNode *)buddy, "icon_checksum");
573 buddies = g_slist_delete_link(buddies, buddies);
578 const gchar *
579 purple_buddy_icon_get_full_path(PurpleBuddyIcon *icon)
581 const gchar *path;
583 g_return_val_if_fail(icon != NULL, NULL);
585 if (icon->img == NULL)
586 return NULL;
588 path = purple_image_get_path(icon->img);
589 if (!g_file_test(path, G_FILE_TEST_EXISTS))
591 return NULL;
593 return path;
596 const char *
597 purple_buddy_icons_get_checksum_for_user(PurpleBuddy *buddy)
599 return purple_blist_node_get_string((PurpleBlistNode*)buddy,
600 "icon_checksum");
603 static gboolean
604 read_icon_file(const char *path, guchar **data, size_t *len)
606 GError *err = NULL;
608 if (!g_file_get_contents(path, (gchar **)data, len, &err))
610 purple_debug_error("buddyicon", "Error reading %s: %s\n",
611 path, err->message);
612 g_error_free(err);
614 return FALSE;
617 return TRUE;
620 PurpleBuddyIcon *
621 purple_buddy_icons_find(PurpleAccount *account, const char *username)
623 GHashTable *icon_cache;
624 PurpleBuddyIcon *icon = NULL;
626 g_return_val_if_fail(account != NULL, NULL);
627 g_return_val_if_fail(username != NULL, NULL);
629 icon_cache = g_hash_table_lookup(account_cache, account);
631 if ((icon_cache == NULL) || ((icon = g_hash_table_lookup(icon_cache, username)) == NULL))
633 /* The icon is not currently cached in memory--try reading from disk */
634 PurpleBuddy *b = purple_blist_find_buddy(account, username);
635 const char *protocol_icon_file;
636 const char *dirname;
637 gboolean caching;
638 guchar *data;
639 size_t len;
641 if (!b)
642 return NULL;
644 protocol_icon_file = purple_blist_node_get_string((PurpleBlistNode*)b, "buddy_icon");
646 if (protocol_icon_file == NULL)
647 return NULL;
649 dirname = purple_buddy_icons_get_cache_dir();
651 caching = purple_buddy_icons_is_caching();
652 /* By disabling caching temporarily, we avoid a loop
653 * and don't have to add special code through several
654 * functions. */
655 purple_buddy_icons_set_caching(FALSE);
657 if (protocol_icon_file != NULL)
659 char *path = g_build_filename(dirname, protocol_icon_file, NULL);
660 if (read_icon_file(path, &data, &len))
662 const char *checksum;
664 icon = purple_buddy_icon_create(account, username);
665 icon->img = NULL;
666 checksum = purple_blist_node_get_string((PurpleBlistNode*)b, "icon_checksum");
667 purple_buddy_icon_set_data(icon, data, len, checksum);
669 else
670 delete_buddy_icon_settings((PurpleBlistNode*)b, "buddy_icon");
672 g_free(path);
675 purple_buddy_icons_set_caching(caching);
678 return (icon ? purple_buddy_icon_ref(icon) : NULL);
681 PurpleImage *
682 purple_buddy_icons_find_account_icon(PurpleAccount *account)
684 PurpleImage *img;
685 const char *account_icon_file;
686 const char *dirname;
687 char *path;
688 guchar *data;
689 size_t len;
691 g_return_val_if_fail(account != NULL, NULL);
693 img = g_hash_table_lookup(pointer_icon_cache, account);
694 if (img) {
695 g_object_ref(img);
696 return img;
699 account_icon_file = purple_account_get_string(account, "buddy_icon", NULL);
701 if (account_icon_file == NULL)
702 return NULL;
704 dirname = purple_buddy_icons_get_cache_dir();
705 path = g_build_filename(dirname, account_icon_file, NULL);
707 if (read_icon_file(path, &data, &len)) {
708 g_free(path);
709 img = purple_buddy_icons_set_account_icon(account, data, len);
710 g_object_ref(img);
711 return img;
713 g_free(path);
715 return NULL;
718 PurpleImage *
719 purple_buddy_icons_set_account_icon(PurpleAccount *account,
720 guchar *icon_data, size_t icon_len)
722 PurpleImage *old_img;
723 PurpleImage *img = NULL;
724 char *old_icon;
726 if (icon_data != NULL && icon_len > 0) {
727 img = purple_buddy_icon_data_new(icon_data, icon_len);
730 old_icon = g_strdup(purple_account_get_string(account, "buddy_icon", NULL));
731 if (img && purple_buddy_icons_is_caching())
733 const char *filename = image_get_filename(img);
734 g_warn_if_fail(filename != NULL);
735 purple_account_set_string(account, "buddy_icon", filename);
736 purple_account_set_int(account, "buddy_icon_timestamp", time(NULL));
737 ref_filename(filename);
739 else
741 purple_account_set_string(account, "buddy_icon", NULL);
742 purple_account_set_int(account, "buddy_icon_timestamp", 0);
744 unref_filename(old_icon);
746 old_img = g_hash_table_lookup(pointer_icon_cache, account);
748 if (img)
749 g_hash_table_insert(pointer_icon_cache, account, img);
750 else
751 g_hash_table_remove(pointer_icon_cache, account);
753 if (!purple_account_is_disconnected(account))
755 PurpleConnection *gc;
756 PurpleProtocol *protocol;
758 gc = purple_account_get_connection(account);
759 protocol = purple_connection_get_protocol(gc);
761 if (protocol)
762 purple_protocol_server_iface_set_buddy_icon(protocol, gc, img);
765 if (old_img)
766 g_object_unref(old_img);
767 else if (old_icon)
769 /* The old icon may not have been loaded into memory. In that
770 * case, we'll need to uncache the filename. The filenames
771 * are ref-counted, so this is safe. */
772 purple_buddy_icon_data_uncache_file(old_icon);
774 g_free(old_icon);
776 return img;
779 time_t
780 purple_buddy_icons_get_account_icon_timestamp(PurpleAccount *account)
782 time_t ret;
784 g_return_val_if_fail(account != NULL, 0);
786 ret = purple_account_get_int(account, "buddy_icon_timestamp", 0);
788 /* This deals with migration cases. */
789 if (ret == 0 && purple_account_get_string(account, "buddy_icon", NULL) != NULL)
791 ret = time(NULL);
792 purple_account_set_int(account, "buddy_icon_timestamp", ret);
795 return ret;
798 gboolean
799 purple_buddy_icons_node_has_custom_icon(PurpleBlistNode *node)
801 g_return_val_if_fail(node != NULL, FALSE);
803 return (purple_blist_node_get_string(node, "custom_buddy_icon") != NULL);
806 PurpleImage *
807 purple_buddy_icons_node_find_custom_icon(PurpleBlistNode *node)
809 char *path;
810 size_t len;
811 guchar *data;
812 PurpleImage *img;
813 const char *custom_icon_file, *dirname;
815 g_return_val_if_fail(node != NULL, NULL);
817 img = g_hash_table_lookup(pointer_icon_cache, node);
818 if (img) {
819 g_object_ref(img);
820 return img;
823 custom_icon_file = purple_blist_node_get_string(node,
824 "custom_buddy_icon");
826 if (custom_icon_file == NULL)
827 return NULL;
829 dirname = purple_buddy_icons_get_cache_dir();
830 path = g_build_filename(dirname, custom_icon_file, NULL);
832 if (read_icon_file(path, &data, &len)) {
833 g_free(path);
834 img = purple_buddy_icons_node_set_custom_icon(node, data, len);
835 g_object_ref(img);
836 return img;
838 g_free(path);
840 return NULL;
843 PurpleImage *
844 purple_buddy_icons_node_set_custom_icon(PurpleBlistNode *node,
845 guchar *icon_data, size_t icon_len)
847 char *old_icon;
848 PurpleImage *old_img;
849 PurpleImage *img = NULL;
851 g_return_val_if_fail(node != NULL, NULL);
853 if (!PURPLE_IS_CONTACT(node) &&
854 !PURPLE_IS_CHAT(node) &&
855 !PURPLE_IS_GROUP(node)) {
856 return NULL;
859 old_img = g_hash_table_lookup(pointer_icon_cache, node);
861 if (icon_data != NULL && icon_len > 0) {
862 img = purple_buddy_icon_data_new(icon_data, icon_len);
865 old_icon = g_strdup(purple_blist_node_get_string(node,
866 "custom_buddy_icon"));
867 if (img && purple_buddy_icons_is_caching()) {
868 const char *filename = image_get_filename(img);
869 g_warn_if_fail(filename);
870 purple_blist_node_set_string(node, "custom_buddy_icon",
871 filename);
872 ref_filename(filename);
873 } else {
874 purple_blist_node_remove_setting(node, "custom_buddy_icon");
876 unref_filename(old_icon);
878 if (img)
879 g_hash_table_insert(pointer_icon_cache, node, img);
880 else
881 g_hash_table_remove(pointer_icon_cache, node);
883 if (PURPLE_IS_CONTACT(node)) {
884 PurpleBlistNode *child;
885 for (child = purple_blist_node_get_first_child(node);
886 child;
887 child = purple_blist_node_get_sibling_next(child))
889 PurpleBuddy *buddy;
890 PurpleIMConversation *im;
892 if (!PURPLE_IS_BUDDY(child))
893 continue;
895 buddy = (PurpleBuddy *)child;
897 im = purple_conversations_find_im_with_account(purple_buddy_get_name(buddy), purple_buddy_get_account(buddy));
898 if (im)
899 purple_conversation_update(PURPLE_CONVERSATION(im), PURPLE_CONVERSATION_UPDATE_ICON);
901 /* Is this call necessary anymore? Can the buddies
902 * themselves need updating when the custom buddy
903 * icon changes? */
904 purple_blist_update_node(purple_blist_get_default(),
905 PURPLE_BLIST_NODE(buddy));
907 } else if (PURPLE_IS_CHAT(node)) {
908 PurpleChatConversation *chat = NULL;
910 chat = purple_conversations_find_chat_with_account(purple_chat_get_name((PurpleChat*)node), purple_chat_get_account((PurpleChat*)node));
911 if (chat) {
912 purple_conversation_update(PURPLE_CONVERSATION(chat), PURPLE_CONVERSATION_UPDATE_ICON);
916 purple_blist_update_node(purple_blist_get_default(), node);
918 if (old_img) {
919 g_object_unref(old_img);
920 } else if (old_icon) {
921 /* The old icon may not have been loaded into memory. In that
922 * case, we'll need to uncache the filename. The filenames
923 * are ref-counted, so this is safe. */
924 purple_buddy_icon_data_uncache_file(old_icon);
926 g_free(old_icon);
928 return img;
931 PurpleImage *
932 purple_buddy_icons_node_set_custom_icon_from_file(PurpleBlistNode *node,
933 const gchar *filename)
935 size_t len = 0;
936 guchar *data = NULL;
938 g_return_val_if_fail(node != NULL, NULL);
940 if (!PURPLE_IS_CONTACT(node) &&
941 !PURPLE_IS_CHAT(node) &&
942 !PURPLE_IS_GROUP(node)) {
943 return NULL;
946 if (filename != NULL) {
947 if (!read_icon_file(filename, &data, &len)) {
948 return NULL;
952 return purple_buddy_icons_node_set_custom_icon(node, data, len);
955 static void
956 delete_buddy_icon_settings(PurpleBlistNode *node, const char *setting_name)
958 purple_blist_node_remove_setting(node, setting_name);
960 if (purple_strequal(setting_name, "buddy_icon"))
962 purple_blist_node_remove_setting(node, "avatar_hash");
963 purple_blist_node_remove_setting(node, "icon_checksum");
967 void
968 _purple_buddy_icons_account_loaded_cb()
970 const char *dirname = purple_buddy_icons_get_cache_dir();
971 GList *cur;
973 for (cur = purple_accounts_get_all(); cur != NULL; cur = cur->next)
975 PurpleAccount *account = cur->data;
976 const char *account_icon_file = purple_account_get_string(account, "buddy_icon", NULL);
978 if (account_icon_file != NULL)
980 char *path = g_build_filename(dirname, account_icon_file, NULL);
981 if (!g_file_test(path, G_FILE_TEST_EXISTS))
983 purple_account_set_string(account, "buddy_icon", NULL);
984 } else {
985 ref_filename(account_icon_file);
987 g_free(path);
992 void
993 _purple_buddy_icons_blist_loaded_cb()
995 PurpleBlistNode *node = purple_blist_get_default_root();
996 const char *dirname = purple_buddy_icons_get_cache_dir();
998 while (node != NULL)
1000 if (PURPLE_IS_BUDDY(node))
1002 const char *filename;
1004 filename = purple_blist_node_get_string(node, "buddy_icon");
1005 if (filename != NULL)
1007 char *path = g_build_filename(dirname, filename, NULL);
1008 if (!g_file_test(path, G_FILE_TEST_EXISTS))
1010 purple_blist_node_remove_setting(node,
1011 "buddy_icon");
1012 purple_blist_node_remove_setting(node,
1013 "icon_checksum");
1015 else
1016 ref_filename(filename);
1017 g_free(path);
1020 else if (PURPLE_IS_CONTACT(node) ||
1021 PURPLE_IS_CHAT(node) ||
1022 PURPLE_IS_GROUP(node))
1024 const char *filename;
1026 filename = purple_blist_node_get_string(node, "custom_buddy_icon");
1027 if (filename != NULL)
1029 char *path = g_build_filename(dirname, filename, NULL);
1030 if (!g_file_test(path, G_FILE_TEST_EXISTS))
1032 purple_blist_node_remove_setting(node,
1033 "custom_buddy_icon");
1035 else
1036 ref_filename(filename);
1037 g_free(path);
1040 node = purple_blist_node_next(node, TRUE);
1044 void
1045 purple_buddy_icons_set_caching(gboolean caching)
1047 icon_caching = caching;
1050 gboolean
1051 purple_buddy_icons_is_caching(void)
1053 return icon_caching;
1056 void
1057 purple_buddy_icons_set_cache_dir(const char *dir)
1059 g_return_if_fail(dir != NULL);
1061 g_free(cache_dir);
1062 cache_dir = g_strdup(dir);
1065 const char *
1066 purple_buddy_icons_get_cache_dir(void)
1068 return cache_dir;
1071 void *
1072 purple_buddy_icons_get_handle()
1074 static int handle;
1076 return &handle;
1079 void
1080 purple_buddy_icons_init()
1082 account_cache = g_hash_table_new_full(
1083 g_direct_hash, g_direct_equal,
1084 NULL, (GFreeFunc)g_hash_table_destroy);
1086 icon_data_cache = g_hash_table_new_full(g_str_hash, g_str_equal,
1087 g_free, NULL);
1088 icon_file_cache = g_hash_table_new_full(g_str_hash, g_str_equal,
1089 g_free, NULL);
1090 pointer_icon_cache = g_hash_table_new(g_direct_hash, g_direct_equal);
1092 if (!cache_dir)
1093 cache_dir = g_build_filename(purple_cache_dir(), "icons", NULL);
1096 void
1097 purple_buddy_icons_uninit()
1099 purple_signals_disconnect_by_handle(purple_buddy_icons_get_handle());
1101 g_hash_table_destroy(account_cache);
1102 g_hash_table_destroy(icon_data_cache);
1103 g_hash_table_destroy(icon_file_cache);
1104 g_hash_table_destroy(pointer_icon_cache);
1105 g_free(cache_dir);
1107 cache_dir = NULL;
1110 GType
1111 purple_buddy_icon_get_type(void)
1113 static GType type = 0;
1115 if (type == 0) {
1116 type = g_boxed_type_register_static("PurpleBuddyIcon",
1117 (GBoxedCopyFunc)purple_buddy_icon_ref,
1118 (GBoxedFreeFunc)purple_buddy_icon_unref);
1121 return type;
1124 PurpleBuddyIconSpec *
1125 purple_buddy_icon_spec_new(char *format, int min_width, int min_height,
1126 int max_width, int max_height, size_t max_filesize,
1127 PurpleBuddyIconScaleFlags scale_rules)
1129 PurpleBuddyIconSpec *icon_spec;
1131 icon_spec = g_new0(PurpleBuddyIconSpec, 1);
1133 icon_spec->format = format;
1134 icon_spec->min_width = min_width;
1135 icon_spec->min_height = min_height;
1136 icon_spec->max_width = max_width;
1137 icon_spec->max_height = max_height;
1138 icon_spec->max_filesize = max_filesize;
1139 icon_spec->scale_rules = scale_rules;
1141 return icon_spec;
1144 static PurpleBuddyIconSpec *
1145 purple_buddy_icon_spec_copy(PurpleBuddyIconSpec *icon_spec)
1147 PurpleBuddyIconSpec *icon_spec_copy;
1149 g_return_val_if_fail(icon_spec != NULL, NULL);
1151 icon_spec_copy = g_new0(PurpleBuddyIconSpec, 1);
1152 *icon_spec_copy = *icon_spec;
1154 return icon_spec_copy;
1157 void purple_buddy_icon_spec_get_scaled_size(PurpleBuddyIconSpec *spec,
1158 int *width, int *height)
1160 int new_width, new_height;
1162 new_width = *width;
1163 new_height = *height;
1165 if (*width < spec->min_width)
1166 new_width = spec->min_width;
1167 else if (*width > spec->max_width)
1168 new_width = spec->max_width;
1170 if (*height < spec->min_height)
1171 new_height = spec->min_height;
1172 else if (*height > spec->max_height)
1173 new_height = spec->max_height;
1175 /* preserve aspect ratio */
1176 if ((double)*height * (double)new_width >
1177 (double)*width * (double)new_height) {
1178 new_width = 0.5 + (double)*width * (double)new_height / (double)*height;
1179 } else {
1180 new_height = 0.5 + (double)*height * (double)new_width / (double)*width;
1183 *width = new_width;
1184 *height = new_height;
1187 GType
1188 purple_buddy_icon_spec_get_type(void)
1190 static GType type = 0;
1192 if (type == 0) {
1193 type = g_boxed_type_register_static("PurpleBuddyIconSpec",
1194 (GBoxedCopyFunc)purple_buddy_icon_spec_copy,
1195 (GBoxedFreeFunc)g_free);
1198 return type;