| 1 /** |
|
| 2 * @file icon.c Buddy Icon API |
|
| 3 * @ingroup core |
|
| 4 * |
|
| 5 * gaim |
|
| 6 * |
|
| 7 * Gaim is the legal property of its developers, whose names are too numerous |
|
| 8 * to list here. Please refer to the COPYRIGHT file distributed with this |
|
| 9 * source distribution. |
|
| 10 * |
|
| 11 * This program is free software; you can redistribute it and/or modify |
|
| 12 * it under the terms of the GNU General Public License as published by |
|
| 13 * the Free Software Foundation; either version 2 of the License, or |
|
| 14 * (at your option) any later version. |
|
| 15 * |
|
| 16 * This program is distributed in the hope that it will be useful, |
|
| 17 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
| 18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
| 19 * GNU General Public License for more details. |
|
| 20 * |
|
| 21 * You should have received a copy of the GNU General Public License |
|
| 22 * along with this program; if not, write to the Free Software |
|
| 23 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
|
| 24 */ |
|
| 25 #include "internal.h" |
|
| 26 #include "buddyicon.h" |
|
| 27 #include "conversation.h" |
|
| 28 #include "dbus-maybe.h" |
|
| 29 #include "debug.h" |
|
| 30 #include "util.h" |
|
| 31 |
|
| 32 static GHashTable *account_cache = NULL; |
|
| 33 static char *cache_dir = NULL; |
|
| 34 static gboolean icon_caching = TRUE; |
|
| 35 |
|
| 36 static GaimBuddyIcon * |
|
| 37 gaim_buddy_icon_create(GaimAccount *account, const char *username) |
|
| 38 { |
|
| 39 GaimBuddyIcon *icon; |
|
| 40 GHashTable *icon_cache; |
|
| 41 |
|
| 42 icon = g_new0(GaimBuddyIcon, 1); |
|
| 43 GAIM_DBUS_REGISTER_POINTER(icon, GaimBuddyIcon); |
|
| 44 |
|
| 45 gaim_buddy_icon_set_account(icon, account); |
|
| 46 gaim_buddy_icon_set_username(icon, username); |
|
| 47 |
|
| 48 icon_cache = g_hash_table_lookup(account_cache, account); |
|
| 49 |
|
| 50 if (icon_cache == NULL) |
|
| 51 { |
|
| 52 icon_cache = g_hash_table_new(g_str_hash, g_str_equal); |
|
| 53 |
|
| 54 g_hash_table_insert(account_cache, account, icon_cache); |
|
| 55 } |
|
| 56 |
|
| 57 g_hash_table_insert(icon_cache, |
|
| 58 (char *)gaim_buddy_icon_get_username(icon), icon); |
|
| 59 return icon; |
|
| 60 } |
|
| 61 |
|
| 62 GaimBuddyIcon * |
|
| 63 gaim_buddy_icon_new(GaimAccount *account, const char *username, |
|
| 64 void *icon_data, size_t icon_len) |
|
| 65 { |
|
| 66 GaimBuddyIcon *icon; |
|
| 67 |
|
| 68 g_return_val_if_fail(account != NULL, NULL); |
|
| 69 g_return_val_if_fail(username != NULL, NULL); |
|
| 70 g_return_val_if_fail(icon_data != NULL, NULL); |
|
| 71 g_return_val_if_fail(icon_len > 0, NULL); |
|
| 72 |
|
| 73 icon = gaim_buddy_icons_find(account, username); |
|
| 74 |
|
| 75 if (icon == NULL) |
|
| 76 icon = gaim_buddy_icon_create(account, username); |
|
| 77 |
|
| 78 gaim_buddy_icon_ref(icon); |
|
| 79 gaim_buddy_icon_set_data(icon, icon_data, icon_len); |
|
| 80 |
|
| 81 /* gaim_buddy_icon_set_data() makes blist.c or |
|
| 82 * conversation.c, or both, take a reference. |
|
| 83 * |
|
| 84 * Plus, we leave one for the caller of this function. |
|
| 85 */ |
|
| 86 |
|
| 87 return icon; |
|
| 88 } |
|
| 89 |
|
| 90 void |
|
| 91 gaim_buddy_icon_destroy(GaimBuddyIcon *icon) |
|
| 92 { |
|
| 93 GaimConversation *conv; |
|
| 94 GaimAccount *account; |
|
| 95 GHashTable *icon_cache; |
|
| 96 const char *username; |
|
| 97 GSList *sl, *list; |
|
| 98 |
|
| 99 g_return_if_fail(icon != NULL); |
|
| 100 |
|
| 101 if (icon->ref_count > 0) |
|
| 102 { |
|
| 103 /* If the ref count is greater than 0, then we weren't called from |
|
| 104 * gaim_buddy_icon_unref(). So we go through and ask everyone to |
|
| 105 * unref us. Then we return, since we know somewhere along the |
|
| 106 * line we got called recursively by one of the unrefs, and the |
|
| 107 * icon is already destroyed. |
|
| 108 */ |
|
| 109 account = gaim_buddy_icon_get_account(icon); |
|
| 110 username = gaim_buddy_icon_get_username(icon); |
|
| 111 |
|
| 112 conv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_IM, username, account); |
|
| 113 if (conv != NULL) |
|
| 114 gaim_conv_im_set_icon(GAIM_CONV_IM(conv), NULL); |
|
| 115 |
|
| 116 for (list = sl = gaim_find_buddies(account, username); sl != NULL; |
|
| 117 sl = sl->next) |
|
| 118 { |
|
| 119 GaimBuddy *buddy = (GaimBuddy *)sl->data; |
|
| 120 |
|
| 121 gaim_buddy_set_icon(buddy, NULL); |
|
| 122 } |
|
| 123 |
|
| 124 g_slist_free(list); |
|
| 125 |
|
| 126 return; |
|
| 127 } |
|
| 128 |
|
| 129 icon_cache = g_hash_table_lookup(account_cache, |
|
| 130 gaim_buddy_icon_get_account(icon)); |
|
| 131 |
|
| 132 if (icon_cache != NULL) |
|
| 133 g_hash_table_remove(icon_cache, gaim_buddy_icon_get_username(icon)); |
|
| 134 |
|
| 135 if (icon->username != NULL) |
|
| 136 g_free(icon->username); |
|
| 137 |
|
| 138 if (icon->data != NULL) |
|
| 139 g_free(icon->data); |
|
| 140 |
|
| 141 GAIM_DBUS_UNREGISTER_POINTER(icon); |
|
| 142 g_free(icon); |
|
| 143 } |
|
| 144 |
|
| 145 GaimBuddyIcon * |
|
| 146 gaim_buddy_icon_ref(GaimBuddyIcon *icon) |
|
| 147 { |
|
| 148 g_return_val_if_fail(icon != NULL, NULL); |
|
| 149 |
|
| 150 icon->ref_count++; |
|
| 151 |
|
| 152 return icon; |
|
| 153 } |
|
| 154 |
|
| 155 GaimBuddyIcon * |
|
| 156 gaim_buddy_icon_unref(GaimBuddyIcon *icon) |
|
| 157 { |
|
| 158 g_return_val_if_fail(icon != NULL, NULL); |
|
| 159 g_return_val_if_fail(icon->ref_count > 0, NULL); |
|
| 160 |
|
| 161 icon->ref_count--; |
|
| 162 |
|
| 163 if (icon->ref_count == 0) |
|
| 164 { |
|
| 165 gaim_buddy_icon_destroy(icon); |
|
| 166 |
|
| 167 return NULL; |
|
| 168 } |
|
| 169 |
|
| 170 return icon; |
|
| 171 } |
|
| 172 |
|
| 173 void |
|
| 174 gaim_buddy_icon_update(GaimBuddyIcon *icon) |
|
| 175 { |
|
| 176 GaimConversation *conv; |
|
| 177 GaimAccount *account; |
|
| 178 const char *username; |
|
| 179 GSList *sl, *list; |
|
| 180 |
|
| 181 g_return_if_fail(icon != NULL); |
|
| 182 |
|
| 183 account = gaim_buddy_icon_get_account(icon); |
|
| 184 username = gaim_buddy_icon_get_username(icon); |
|
| 185 |
|
| 186 for (list = sl = gaim_find_buddies(account, username); sl != NULL; |
|
| 187 sl = sl->next) |
|
| 188 { |
|
| 189 GaimBuddy *buddy = (GaimBuddy *)sl->data; |
|
| 190 |
|
| 191 gaim_buddy_set_icon(buddy, icon); |
|
| 192 } |
|
| 193 |
|
| 194 g_slist_free(list); |
|
| 195 |
|
| 196 conv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_IM, username, account); |
|
| 197 |
|
| 198 if (conv != NULL) |
|
| 199 gaim_conv_im_set_icon(GAIM_CONV_IM(conv), icon); |
|
| 200 } |
|
| 201 |
|
| 202 static void |
|
| 203 delete_icon_cache_file(const char *dirname, const char *old_icon) |
|
| 204 { |
|
| 205 struct stat st; |
|
| 206 |
|
| 207 g_return_if_fail(dirname != NULL); |
|
| 208 g_return_if_fail(old_icon != NULL); |
|
| 209 |
|
| 210 if (g_stat(old_icon, &st) == 0) |
|
| 211 g_unlink(old_icon); |
|
| 212 else |
|
| 213 { |
|
| 214 char *filename = g_build_filename(dirname, old_icon, NULL); |
|
| 215 if (g_stat(filename, &st) == 0) |
|
| 216 g_unlink(filename); |
|
| 217 g_free(filename); |
|
| 218 } |
|
| 219 gaim_debug_info("buddyicon", "Uncached file %s\n", old_icon); |
|
| 220 } |
|
| 221 |
|
| 222 void |
|
| 223 gaim_buddy_icon_cache(GaimBuddyIcon *icon, GaimBuddy *buddy) |
|
| 224 { |
|
| 225 const guchar *data; |
|
| 226 const char *dirname; |
|
| 227 char *random; |
|
| 228 char *filename; |
|
| 229 const char *old_icon; |
|
| 230 size_t len = 0; |
|
| 231 FILE *file = NULL; |
|
| 232 |
|
| 233 g_return_if_fail(icon != NULL); |
|
| 234 g_return_if_fail(buddy != NULL); |
|
| 235 |
|
| 236 if (!gaim_buddy_icons_is_caching()) |
|
| 237 return; |
|
| 238 |
|
| 239 data = gaim_buddy_icon_get_data(icon, &len); |
|
| 240 |
|
| 241 random = g_strdup_printf("%x", g_random_int()); |
|
| 242 dirname = gaim_buddy_icons_get_cache_dir(); |
|
| 243 filename = g_build_filename(dirname, random, NULL); |
|
| 244 old_icon = gaim_blist_node_get_string((GaimBlistNode*)buddy, "buddy_icon"); |
|
| 245 |
|
| 246 if (!g_file_test(dirname, G_FILE_TEST_IS_DIR)) |
|
| 247 { |
|
| 248 gaim_debug_info("buddyicon", "Creating icon cache directory.\n"); |
|
| 249 |
|
| 250 if (g_mkdir(dirname, S_IRUSR | S_IWUSR | S_IXUSR) < 0) |
|
| 251 { |
|
| 252 gaim_debug_error("buddyicon", |
|
| 253 "Unable to create directory %s: %s\n", |
|
| 254 dirname, strerror(errno)); |
|
| 255 } |
|
| 256 } |
|
| 257 |
|
| 258 if ((file = g_fopen(filename, "wb")) != NULL) |
|
| 259 { |
|
| 260 fwrite(data, 1, len, file); |
|
| 261 fclose(file); |
|
| 262 gaim_debug_info("buddyicon", "Wrote file %s\n", filename); |
|
| 263 } |
|
| 264 else |
|
| 265 { |
|
| 266 gaim_debug_error("buddyicon", "Unable to create file %s: %s\n", |
|
| 267 filename, strerror(errno)); |
|
| 268 } |
|
| 269 |
|
| 270 g_free(filename); |
|
| 271 |
|
| 272 if (old_icon != NULL) |
|
| 273 delete_icon_cache_file(dirname, old_icon); |
|
| 274 |
|
| 275 gaim_blist_node_set_string((GaimBlistNode *)buddy, "buddy_icon", random); |
|
| 276 |
|
| 277 g_free(random); |
|
| 278 } |
|
| 279 |
|
| 280 void |
|
| 281 gaim_buddy_icon_uncache(GaimBuddy *buddy) |
|
| 282 { |
|
| 283 const char *old_icon; |
|
| 284 |
|
| 285 g_return_if_fail(buddy != NULL); |
|
| 286 |
|
| 287 old_icon = gaim_blist_node_get_string((GaimBlistNode *)buddy, "buddy_icon"); |
|
| 288 |
|
| 289 if (old_icon != NULL) |
|
| 290 delete_icon_cache_file(gaim_buddy_icons_get_cache_dir(), old_icon); |
|
| 291 |
|
| 292 gaim_blist_node_remove_setting((GaimBlistNode *)buddy, "buddy_icon"); |
|
| 293 |
|
| 294 /* Unset the icon in case this function is called from |
|
| 295 * something other than gaim_buddy_set_icon(). */ |
|
| 296 if (buddy->icon != NULL) |
|
| 297 { |
|
| 298 gaim_buddy_icon_unref(buddy->icon); |
|
| 299 buddy->icon = NULL; |
|
| 300 } |
|
| 301 } |
|
| 302 |
|
| 303 void |
|
| 304 gaim_buddy_icon_set_account(GaimBuddyIcon *icon, GaimAccount *account) |
|
| 305 { |
|
| 306 g_return_if_fail(icon != NULL); |
|
| 307 g_return_if_fail(account != NULL); |
|
| 308 |
|
| 309 icon->account = account; |
|
| 310 } |
|
| 311 |
|
| 312 void |
|
| 313 gaim_buddy_icon_set_username(GaimBuddyIcon *icon, const char *username) |
|
| 314 { |
|
| 315 g_return_if_fail(icon != NULL); |
|
| 316 g_return_if_fail(username != NULL); |
|
| 317 |
|
| 318 if (icon->username != NULL) |
|
| 319 g_free(icon->username); |
|
| 320 |
|
| 321 icon->username = g_strdup(username); |
|
| 322 } |
|
| 323 |
|
| 324 void |
|
| 325 gaim_buddy_icon_set_data(GaimBuddyIcon *icon, void *data, size_t len) |
|
| 326 { |
|
| 327 g_return_if_fail(icon != NULL); |
|
| 328 |
|
| 329 if (icon->data != NULL) |
|
| 330 g_free(icon->data); |
|
| 331 |
|
| 332 if (data != NULL && len > 0) |
|
| 333 { |
|
| 334 icon->data = g_memdup(data, len); |
|
| 335 icon->len = len; |
|
| 336 } |
|
| 337 else |
|
| 338 { |
|
| 339 icon->data = NULL; |
|
| 340 icon->len = 0; |
|
| 341 } |
|
| 342 |
|
| 343 gaim_buddy_icon_update(icon); |
|
| 344 } |
|
| 345 |
|
| 346 GaimAccount * |
|
| 347 gaim_buddy_icon_get_account(const GaimBuddyIcon *icon) |
|
| 348 { |
|
| 349 g_return_val_if_fail(icon != NULL, NULL); |
|
| 350 |
|
| 351 return icon->account; |
|
| 352 } |
|
| 353 |
|
| 354 const char * |
|
| 355 gaim_buddy_icon_get_username(const GaimBuddyIcon *icon) |
|
| 356 { |
|
| 357 g_return_val_if_fail(icon != NULL, NULL); |
|
| 358 |
|
| 359 return icon->username; |
|
| 360 } |
|
| 361 |
|
| 362 const guchar * |
|
| 363 gaim_buddy_icon_get_data(const GaimBuddyIcon *icon, size_t *len) |
|
| 364 { |
|
| 365 g_return_val_if_fail(icon != NULL, NULL); |
|
| 366 |
|
| 367 if (len != NULL) |
|
| 368 *len = icon->len; |
|
| 369 |
|
| 370 return icon->data; |
|
| 371 } |
|
| 372 |
|
| 373 const char * |
|
| 374 gaim_buddy_icon_get_type(const GaimBuddyIcon *icon) |
|
| 375 { |
|
| 376 const void *data; |
|
| 377 size_t len; |
|
| 378 |
|
| 379 g_return_val_if_fail(icon != NULL, NULL); |
|
| 380 |
|
| 381 data = gaim_buddy_icon_get_data(icon, &len); |
|
| 382 |
|
| 383 /* TODO: Find a way to do this with GDK */ |
|
| 384 if (len >= 4) |
|
| 385 { |
|
| 386 if (!strncmp(data, "BM", 2)) |
|
| 387 return "bmp"; |
|
| 388 else if (!strncmp(data, "GIF8", 4)) |
|
| 389 return "gif"; |
|
| 390 else if (!strncmp(data, "\xff\xd8\xff\xe0", 4)) |
|
| 391 return "jpg"; |
|
| 392 else if (!strncmp(data, "\x89PNG", 4)) |
|
| 393 return "png"; |
|
| 394 } |
|
| 395 |
|
| 396 return NULL; |
|
| 397 } |
|
| 398 |
|
| 399 void |
|
| 400 gaim_buddy_icons_set_for_user(GaimAccount *account, const char *username, |
|
| 401 void *icon_data, size_t icon_len) |
|
| 402 { |
|
| 403 g_return_if_fail(account != NULL); |
|
| 404 g_return_if_fail(username != NULL); |
|
| 405 |
|
| 406 if (icon_data == NULL || icon_len == 0) |
|
| 407 { |
|
| 408 GaimBuddyIcon *buddy_icon; |
|
| 409 |
|
| 410 buddy_icon = gaim_buddy_icons_find(account, username); |
|
| 411 |
|
| 412 if (buddy_icon != NULL) |
|
| 413 gaim_buddy_icon_destroy(buddy_icon); |
|
| 414 } |
|
| 415 else |
|
| 416 { |
|
| 417 GaimBuddyIcon *icon = gaim_buddy_icon_new(account, username, icon_data, icon_len); |
|
| 418 gaim_buddy_icon_unref(icon); |
|
| 419 } |
|
| 420 } |
|
| 421 |
|
| 422 GaimBuddyIcon * |
|
| 423 gaim_buddy_icons_find(GaimAccount *account, const char *username) |
|
| 424 { |
|
| 425 GHashTable *icon_cache; |
|
| 426 GaimBuddyIcon *ret = NULL; |
|
| 427 char *filename = NULL; |
|
| 428 |
|
| 429 g_return_val_if_fail(account != NULL, NULL); |
|
| 430 g_return_val_if_fail(username != NULL, NULL); |
|
| 431 |
|
| 432 icon_cache = g_hash_table_lookup(account_cache, account); |
|
| 433 |
|
| 434 if ((icon_cache == NULL) || ((ret = g_hash_table_lookup(icon_cache, username)) == NULL)) { |
|
| 435 const char *file; |
|
| 436 struct stat st; |
|
| 437 GaimBuddy *b = gaim_find_buddy(account, username); |
|
| 438 |
|
| 439 if (!b) |
|
| 440 return NULL; |
|
| 441 |
|
| 442 if ((file = gaim_blist_node_get_string((GaimBlistNode*)b, "buddy_icon")) == NULL) |
|
| 443 return NULL; |
|
| 444 |
|
| 445 if (!g_stat(file, &st)) |
|
| 446 filename = g_strdup(file); |
|
| 447 else |
|
| 448 filename = g_build_filename(gaim_buddy_icons_get_cache_dir(), file, NULL); |
|
| 449 |
|
| 450 if (!g_stat(filename, &st)) { |
|
| 451 FILE *f = g_fopen(filename, "rb"); |
|
| 452 if (f) { |
|
| 453 char *data = g_malloc(st.st_size); |
|
| 454 fread(data, 1, st.st_size, f); |
|
| 455 fclose(f); |
|
| 456 ret = gaim_buddy_icon_create(account, username); |
|
| 457 gaim_buddy_icon_ref(ret); |
|
| 458 gaim_buddy_icon_set_data(ret, data, st.st_size); |
|
| 459 gaim_buddy_icon_unref(ret); |
|
| 460 g_free(data); |
|
| 461 g_free(filename); |
|
| 462 return ret; |
|
| 463 } |
|
| 464 } |
|
| 465 g_free(filename); |
|
| 466 } |
|
| 467 |
|
| 468 return ret; |
|
| 469 } |
|
| 470 |
|
| 471 void |
|
| 472 gaim_buddy_icons_set_caching(gboolean caching) |
|
| 473 { |
|
| 474 icon_caching = caching; |
|
| 475 } |
|
| 476 |
|
| 477 gboolean |
|
| 478 gaim_buddy_icons_is_caching(void) |
|
| 479 { |
|
| 480 return icon_caching; |
|
| 481 } |
|
| 482 |
|
| 483 void |
|
| 484 gaim_buddy_icons_set_cache_dir(const char *dir) |
|
| 485 { |
|
| 486 g_return_if_fail(dir != NULL); |
|
| 487 |
|
| 488 if (cache_dir != NULL) |
|
| 489 g_free(cache_dir); |
|
| 490 |
|
| 491 cache_dir = g_strdup(dir); |
|
| 492 } |
|
| 493 |
|
| 494 const char * |
|
| 495 gaim_buddy_icons_get_cache_dir(void) |
|
| 496 { |
|
| 497 return cache_dir; |
|
| 498 } |
|
| 499 |
|
| 500 char *gaim_buddy_icons_get_full_path(const char *icon) { |
|
| 501 struct stat st; |
|
| 502 |
|
| 503 if (icon == NULL) |
|
| 504 return NULL; |
|
| 505 |
|
| 506 if (g_stat(icon, &st) == 0) |
|
| 507 return g_strdup(icon); |
|
| 508 else |
|
| 509 return g_build_filename(gaim_buddy_icons_get_cache_dir(), icon, NULL); |
|
| 510 } |
|
| 511 |
|
| 512 void * |
|
| 513 gaim_buddy_icons_get_handle() |
|
| 514 { |
|
| 515 static int handle; |
|
| 516 |
|
| 517 return &handle; |
|
| 518 } |
|
| 519 |
|
| 520 void |
|
| 521 gaim_buddy_icons_init() |
|
| 522 { |
|
| 523 account_cache = g_hash_table_new_full( |
|
| 524 g_direct_hash, g_direct_equal, |
|
| 525 NULL, (GFreeFunc)g_hash_table_destroy); |
|
| 526 |
|
| 527 cache_dir = g_build_filename(gaim_user_dir(), "icons", NULL); |
|
| 528 } |
|
| 529 |
|
| 530 void |
|
| 531 gaim_buddy_icons_uninit() |
|
| 532 { |
|
| 533 g_hash_table_destroy(account_cache); |
|
| 534 } |
|
| 535 |
|
| 536 void gaim_buddy_icon_get_scale_size(GaimBuddyIconSpec *spec, int *width, int *height) |
|
| 537 { |
|
| 538 if(spec && spec->scale_rules & GAIM_ICON_SCALE_DISPLAY) { |
|
| 539 int new_width, new_height; |
|
| 540 |
|
| 541 new_width = *width; |
|
| 542 new_height = *height; |
|
| 543 |
|
| 544 if(*width < spec->min_width) |
|
| 545 new_width = spec->min_width; |
|
| 546 else if(*width > spec->max_width) |
|
| 547 new_width = spec->max_width; |
|
| 548 |
|
| 549 if(*height < spec->min_height) |
|
| 550 new_height = spec->min_height; |
|
| 551 else if(*height > spec->max_height) |
|
| 552 new_height = spec->max_height; |
|
| 553 |
|
| 554 /* preserve aspect ratio */ |
|
| 555 if ((double)*height * (double)new_width > |
|
| 556 (double)*width * (double)new_height) { |
|
| 557 new_width = 0.5 + (double)*width * (double)new_height / (double)*height; |
|
| 558 } else { |
|
| 559 new_height = 0.5 + (double)*height * (double)new_width / (double)*width; |
|
| 560 } |
|
| 561 |
|
| 562 *width = new_width; |
|
| 563 *height = new_height; |
|
| 564 } |
|
| 565 } |
|
| 566 |
|