Respect -c option
[pidgin-git.git] / libpurple / buddyicon.c
bloba27fade51e3a71253a272a8bec6cd90616b3c469
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
21 #define _PURPLE_BUDDYICON_C_
23 #include "internal.h"
24 #include "buddyicon.h"
25 #include "conversation.h"
26 #include "dbus-maybe.h"
27 #include "debug.h"
28 #include "image.h"
29 #include "util.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
37 the icon data. */
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
76 * time Pidgin starts.
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
93 * account icons.
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
108 static void
109 ref_filename(const char *filename)
111 int refs;
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));
121 static void
122 unref_filename(const char *filename)
124 int refs;
126 if (filename == NULL)
127 return;
129 refs = GPOINTER_TO_INT(g_hash_table_lookup(icon_file_cache, filename));
131 if (refs == 1)
133 g_hash_table_remove(icon_file_cache, filename);
135 else
137 g_hash_table_insert(icon_file_cache, g_strdup(filename),
138 GINT_TO_POINTER(refs - 1));
142 static const gchar *
143 image_get_filename(PurpleImage *img)
145 return g_object_get_data(G_OBJECT(img), "purple-buddyicon-filename");
148 static void
149 purple_buddy_icon_data_cache(PurpleImage *img)
151 const gchar *dirname, *filename;
152 gchar *path;
154 g_return_if_fail(PURPLE_IS_IMAGE(img));
156 if (!purple_buddy_icons_is_caching())
157 return;
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));
173 return;
177 if (!purple_image_save(img, path))
178 purple_debug_error("buddyicon", "failed to save icon %s", path);
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(gpointer _filename)
231 PurpleImage *img;
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);
242 g_free(filename);
245 static PurpleImage *
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);
262 if (oldimg) {
263 g_warn_if_fail(PURPLE_IS_IMAGE(oldimg));
264 g_object_unref(newimg);
265 g_object_ref(oldimg);
266 return 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);
278 return 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;
299 icon->ref_count = 1;
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);
312 return icon;
315 PurpleBuddyIcon *
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 */
332 if (icon == NULL)
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 */
337 icon->img = NULL;
338 purple_buddy_icon_set_data(icon, icon_data, icon_len, checksum);
340 return icon;
343 PurpleBuddyIcon *
344 purple_buddy_icon_ref(PurpleBuddyIcon *icon)
346 g_return_val_if_fail(icon != NULL, NULL);
348 icon->ref_count++;
350 return icon;
353 void
354 purple_buddy_icon_unref(PurpleBuddyIcon *icon)
356 if (icon == NULL)
357 return;
359 g_return_if_fail(icon->ref_count > 0);
361 icon->ref_count--;
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);
379 void
380 purple_buddy_icon_update(PurpleBuddyIcon *icon)
382 PurpleIMConversation *im;
383 PurpleAccount *account;
384 const char *username;
385 PurpleBuddyIcon *icon_to_set;
386 GSList *buddies;
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;
407 char *old_icon;
409 purple_buddy_set_icon(buddy, icon_to_set);
410 old_icon = g_strdup(purple_blist_node_get_string((PurpleBlistNode *)buddy,
411 "buddy_icon"));
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,
417 "buddy_icon",
418 filename);
420 if (icon->checksum && *icon->checksum)
422 purple_blist_node_set_string((PurpleBlistNode *)buddy,
423 "icon_checksum",
424 icon->checksum);
426 else
428 purple_blist_node_remove_setting((PurpleBlistNode *)buddy,
429 "icon_checksum");
431 ref_filename(filename);
433 else if (!icon->img)
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);
439 g_free(old_icon);
441 buddies = g_slist_delete_link(buddies, buddies);
444 im = purple_conversations_find_im_with_account(username, account);
446 if (im != NULL)
447 purple_im_conversation_set_icon(im, icon_to_set);
449 /* icon's refcount was incremented above */
450 purple_buddy_icon_unref(icon);
453 void
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);
461 old_img = icon->img;
462 icon->img = NULL;
464 if (data != NULL)
466 if (len > 0)
467 icon->img = purple_buddy_icon_data_new(data, len);
468 else
469 g_free(data);
472 g_free(icon->checksum);
473 icon->checksum = g_strdup(checksum);
475 purple_buddy_icon_update(icon);
477 if (old_img)
478 g_object_unref(old_img);
481 PurpleAccount *
482 purple_buddy_icon_get_account(const PurpleBuddyIcon *icon)
484 g_return_val_if_fail(icon != NULL, NULL);
486 return icon->account;
489 const char *
490 purple_buddy_icon_get_username(const PurpleBuddyIcon *icon)
492 g_return_val_if_fail(icon != NULL, NULL);
494 return icon->username;
497 const char *
498 purple_buddy_icon_get_checksum(const PurpleBuddyIcon *icon)
500 g_return_val_if_fail(icon != NULL, NULL);
502 return icon->checksum;
505 gconstpointer
506 purple_buddy_icon_get_data(const PurpleBuddyIcon *icon, size_t *len)
508 g_return_val_if_fail(icon != NULL, NULL);
510 if (icon->img)
512 if (len != NULL)
513 *len = purple_image_get_size(icon->img);
515 return purple_image_get_data(icon->img);
518 return NULL;
521 const char *
522 purple_buddy_icon_get_extension(const PurpleBuddyIcon *icon)
524 if (icon->img != NULL)
525 return purple_image_get_extension(icon->img);
527 return NULL;
530 void
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);
546 if (icon != NULL)
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);
560 else
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);
581 const gchar *
582 purple_buddy_icon_get_full_path(PurpleBuddyIcon *icon)
584 const gchar *path;
586 g_return_val_if_fail(icon != NULL, NULL);
588 if (icon->img == NULL)
589 return NULL;
591 path = purple_image_get_path(icon->img);
592 if (!g_file_test(path, G_FILE_TEST_EXISTS))
594 return NULL;
596 return path;
599 const char *
600 purple_buddy_icons_get_checksum_for_user(PurpleBuddy *buddy)
602 return purple_blist_node_get_string((PurpleBlistNode*)buddy,
603 "icon_checksum");
606 static gboolean
607 read_icon_file(const char *path, guchar **data, size_t *len)
609 GError *err = NULL;
611 if (!g_file_get_contents(path, (gchar **)data, len, &err))
613 purple_debug_error("buddyicon", "Error reading %s: %s\n",
614 path, err->message);
615 g_error_free(err);
617 return FALSE;
620 return TRUE;
623 PurpleBuddyIcon *
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;
639 const char *dirname;
640 gboolean caching;
641 guchar *data;
642 size_t len;
644 if (!b)
645 return NULL;
647 protocol_icon_file = purple_blist_node_get_string((PurpleBlistNode*)b, "buddy_icon");
649 if (protocol_icon_file == NULL)
650 return 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
657 * functions. */
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);
668 icon->img = NULL;
669 checksum = purple_blist_node_get_string((PurpleBlistNode*)b, "icon_checksum");
670 purple_buddy_icon_set_data(icon, data, len, checksum);
672 else
673 delete_buddy_icon_settings((PurpleBlistNode*)b, "buddy_icon");
675 g_free(path);
678 purple_buddy_icons_set_caching(caching);
681 return (icon ? purple_buddy_icon_ref(icon) : NULL);
684 PurpleImage *
685 purple_buddy_icons_find_account_icon(PurpleAccount *account)
687 PurpleImage *img;
688 const char *account_icon_file;
689 const char *dirname;
690 char *path;
691 guchar *data;
692 size_t len;
694 g_return_val_if_fail(account != NULL, NULL);
696 img = g_hash_table_lookup(pointer_icon_cache, account);
697 if (img) {
698 g_object_ref(img);
699 return img;
702 account_icon_file = purple_account_get_string(account, "buddy_icon", NULL);
704 if (account_icon_file == NULL)
705 return 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)) {
711 g_free(path);
712 img = purple_buddy_icons_set_account_icon(account, data, len);
713 g_object_ref(img);
714 return img;
716 g_free(path);
718 return NULL;
721 PurpleImage *
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;
727 char *old_icon;
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);
742 else
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);
751 if (img)
752 g_hash_table_insert(pointer_icon_cache, account, img);
753 else
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);
764 if (protocol)
765 purple_protocol_server_iface_set_buddy_icon(protocol, gc, img);
768 if (old_img)
769 g_object_unref(old_img);
770 else if (old_icon)
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);
777 g_free(old_icon);
779 return img;
782 time_t
783 purple_buddy_icons_get_account_icon_timestamp(PurpleAccount *account)
785 time_t ret;
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)
794 ret = time(NULL);
795 purple_account_set_int(account, "buddy_icon_timestamp", ret);
798 return ret;
801 gboolean
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);
809 PurpleImage *
810 purple_buddy_icons_node_find_custom_icon(PurpleBlistNode *node)
812 char *path;
813 size_t len;
814 guchar *data;
815 PurpleImage *img;
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);
821 if (img) {
822 g_object_ref(img);
823 return img;
826 custom_icon_file = purple_blist_node_get_string(node,
827 "custom_buddy_icon");
829 if (custom_icon_file == NULL)
830 return 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)) {
836 g_free(path);
837 img = purple_buddy_icons_node_set_custom_icon(node, data, len);
838 g_object_ref(img);
839 return img;
841 g_free(path);
843 return NULL;
846 PurpleImage *
847 purple_buddy_icons_node_set_custom_icon(PurpleBlistNode *node,
848 guchar *icon_data, size_t icon_len)
850 char *old_icon;
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)) {
860 return NULL;
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",
875 filename);
876 ref_filename(filename);
877 } else {
878 purple_blist_node_remove_setting(node, "custom_buddy_icon");
880 unref_filename(old_icon);
882 if (img)
883 g_hash_table_insert(pointer_icon_cache, node, img);
884 else
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);
890 child;
891 child = purple_blist_node_get_sibling_next(child))
893 PurpleBuddy *buddy;
894 PurpleIMConversation *im;
896 if (!PURPLE_IS_BUDDY(child))
897 continue;
899 buddy = (PurpleBuddy *)child;
901 im = purple_conversations_find_im_with_account(purple_buddy_get_name(buddy), purple_buddy_get_account(buddy));
902 if (im)
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
907 * icon changes? */
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));
915 if (chat) {
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);
923 if (old_img) {
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);
931 g_free(old_icon);
933 return img;
936 PurpleImage *
937 purple_buddy_icons_node_set_custom_icon_from_file(PurpleBlistNode *node,
938 const gchar *filename)
940 size_t len = 0;
941 guchar *data = NULL;
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)) {
948 return NULL;
951 if (filename != NULL) {
952 if (!read_icon_file(filename, &data, &len)) {
953 return NULL;
957 return purple_buddy_icons_node_set_custom_icon(node, data, len);
960 static void
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");
972 void
973 _purple_buddy_icons_account_loaded_cb()
975 const char *dirname = purple_buddy_icons_get_cache_dir();
976 GList *cur;
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);
989 } else {
990 ref_filename(account_icon_file);
992 g_free(path);
997 void
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,
1016 "buddy_icon");
1017 purple_blist_node_remove_setting(node,
1018 "icon_checksum");
1020 else
1021 ref_filename(filename);
1022 g_free(path);
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");
1040 else
1041 ref_filename(filename);
1042 g_free(path);
1045 node = purple_blist_node_next(node, TRUE);
1049 void
1050 purple_buddy_icons_set_caching(gboolean caching)
1052 icon_caching = caching;
1055 gboolean
1056 purple_buddy_icons_is_caching(void)
1058 return icon_caching;
1061 void
1062 purple_buddy_icons_set_cache_dir(const char *dir)
1064 g_return_if_fail(dir != NULL);
1066 g_free(cache_dir);
1067 cache_dir = g_strdup(dir);
1070 const char *
1071 purple_buddy_icons_get_cache_dir(void)
1073 return cache_dir;
1076 void *
1077 purple_buddy_icons_get_handle()
1079 static int handle;
1081 return &handle;
1084 void
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,
1092 g_free, NULL);
1093 icon_file_cache = g_hash_table_new_full(g_str_hash, g_str_equal,
1094 g_free, NULL);
1095 pointer_icon_cache = g_hash_table_new(g_direct_hash, g_direct_equal);
1097 if (!cache_dir)
1098 cache_dir = g_build_filename(purple_cache_dir(), "icons", NULL);
1101 void
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);
1110 g_free(cache_dir);
1112 cache_dir = NULL;
1115 GType
1116 purple_buddy_icon_get_type(void)
1118 static GType type = 0;
1120 if (type == 0) {
1121 type = g_boxed_type_register_static("PurpleBuddyIcon",
1122 (GBoxedCopyFunc)purple_buddy_icon_ref,
1123 (GBoxedFreeFunc)purple_buddy_icon_unref);
1126 return type;
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;
1146 return icon_spec;
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;
1167 new_width = *width;
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;
1184 } else {
1185 new_height = 0.5 + (double)*height * (double)new_width / (double)*width;
1188 *width = new_width;
1189 *height = new_height;
1192 GType
1193 purple_buddy_icon_spec_get_type(void)
1195 static GType type = 0;
1197 if (type == 0) {
1198 type = g_boxed_type_register_static("PurpleBuddyIconSpec",
1199 (GBoxedCopyFunc)purple_buddy_icon_spec_copy,
1200 (GBoxedFreeFunc)g_free);
1203 return type;