| |
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 gaim_buddy_icon_set_path(icon, NULL); |
| |
81 |
| |
82 /* gaim_buddy_icon_set_data() makes blist.c or |
| |
83 * conversation.c, or both, take a reference. |
| |
84 * |
| |
85 * Plus, we leave one for the caller of this function. |
| |
86 */ |
| |
87 |
| |
88 return icon; |
| |
89 } |
| |
90 |
| |
91 void |
| |
92 gaim_buddy_icon_destroy(GaimBuddyIcon *icon) |
| |
93 { |
| |
94 GaimConversation *conv; |
| |
95 GaimAccount *account; |
| |
96 GHashTable *icon_cache; |
| |
97 const char *username; |
| |
98 GSList *sl, *list; |
| |
99 |
| |
100 g_return_if_fail(icon != NULL); |
| |
101 |
| |
102 if (icon->ref_count > 0) |
| |
103 { |
| |
104 /* If the ref count is greater than 0, then we weren't called from |
| |
105 * gaim_buddy_icon_unref(). So we go through and ask everyone to |
| |
106 * unref us. Then we return, since we know somewhere along the |
| |
107 * line we got called recursively by one of the unrefs, and the |
| |
108 * icon is already destroyed. |
| |
109 */ |
| |
110 account = gaim_buddy_icon_get_account(icon); |
| |
111 username = gaim_buddy_icon_get_username(icon); |
| |
112 |
| |
113 conv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_IM, username, account); |
| |
114 if (conv != NULL) |
| |
115 gaim_conv_im_set_icon(GAIM_CONV_IM(conv), NULL); |
| |
116 |
| |
117 for (list = sl = gaim_find_buddies(account, username); sl != NULL; |
| |
118 sl = sl->next) |
| |
119 { |
| |
120 GaimBuddy *buddy = (GaimBuddy *)sl->data; |
| |
121 |
| |
122 gaim_buddy_set_icon(buddy, NULL); |
| |
123 } |
| |
124 |
| |
125 g_slist_free(list); |
| |
126 |
| |
127 return; |
| |
128 } |
| |
129 |
| |
130 icon_cache = g_hash_table_lookup(account_cache, |
| |
131 gaim_buddy_icon_get_account(icon)); |
| |
132 |
| |
133 if (icon_cache != NULL) |
| |
134 g_hash_table_remove(icon_cache, gaim_buddy_icon_get_username(icon)); |
| |
135 |
| |
136 g_free(icon->username); |
| |
137 g_free(icon->data); |
| |
138 g_free(icon->path); |
| |
139 GAIM_DBUS_UNREGISTER_POINTER(icon); |
| |
140 g_free(icon); |
| |
141 } |
| |
142 |
| |
143 GaimBuddyIcon * |
| |
144 gaim_buddy_icon_ref(GaimBuddyIcon *icon) |
| |
145 { |
| |
146 g_return_val_if_fail(icon != NULL, NULL); |
| |
147 |
| |
148 icon->ref_count++; |
| |
149 |
| |
150 return icon; |
| |
151 } |
| |
152 |
| |
153 GaimBuddyIcon * |
| |
154 gaim_buddy_icon_unref(GaimBuddyIcon *icon) |
| |
155 { |
| |
156 g_return_val_if_fail(icon != NULL, NULL); |
| |
157 g_return_val_if_fail(icon->ref_count > 0, NULL); |
| |
158 |
| |
159 icon->ref_count--; |
| |
160 |
| |
161 if (icon->ref_count == 0) |
| |
162 { |
| |
163 gaim_buddy_icon_destroy(icon); |
| |
164 |
| |
165 return NULL; |
| |
166 } |
| |
167 |
| |
168 return icon; |
| |
169 } |
| |
170 |
| |
171 void |
| |
172 gaim_buddy_icon_update(GaimBuddyIcon *icon) |
| |
173 { |
| |
174 GaimConversation *conv; |
| |
175 GaimAccount *account; |
| |
176 const char *username; |
| |
177 GSList *sl, *list; |
| |
178 |
| |
179 g_return_if_fail(icon != NULL); |
| |
180 |
| |
181 account = gaim_buddy_icon_get_account(icon); |
| |
182 username = gaim_buddy_icon_get_username(icon); |
| |
183 |
| |
184 for (list = sl = gaim_find_buddies(account, username); sl != NULL; |
| |
185 sl = sl->next) |
| |
186 { |
| |
187 GaimBuddy *buddy = (GaimBuddy *)sl->data; |
| |
188 |
| |
189 gaim_buddy_set_icon(buddy, icon); |
| |
190 } |
| |
191 |
| |
192 g_slist_free(list); |
| |
193 |
| |
194 conv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_IM, username, account); |
| |
195 |
| |
196 if (conv != NULL) |
| |
197 gaim_conv_im_set_icon(GAIM_CONV_IM(conv), icon); |
| |
198 } |
| |
199 |
| |
200 static void |
| |
201 delete_icon_cache_file(const char *dirname, const char *old_icon) |
| |
202 { |
| |
203 struct stat st; |
| |
204 |
| |
205 g_return_if_fail(dirname != NULL); |
| |
206 g_return_if_fail(old_icon != NULL); |
| |
207 |
| |
208 if (g_stat(old_icon, &st) == 0) |
| |
209 g_unlink(old_icon); |
| |
210 else |
| |
211 { |
| |
212 char *filename = g_build_filename(dirname, old_icon, NULL); |
| |
213 if (g_stat(filename, &st) == 0) |
| |
214 g_unlink(filename); |
| |
215 g_free(filename); |
| |
216 } |
| |
217 gaim_debug_info("buddyicon", "Uncached file %s\n", old_icon); |
| |
218 } |
| |
219 |
| |
220 void |
| |
221 gaim_buddy_icon_cache(GaimBuddyIcon *icon, GaimBuddy *buddy) |
| |
222 { |
| |
223 const guchar *data; |
| |
224 const char *dirname; |
| |
225 char *random; |
| |
226 char *filename; |
| |
227 const char *old_icon; |
| |
228 size_t len = 0; |
| |
229 FILE *file = NULL; |
| |
230 |
| |
231 g_return_if_fail(icon != NULL); |
| |
232 g_return_if_fail(buddy != NULL); |
| |
233 |
| |
234 if (!gaim_buddy_icons_is_caching()) |
| |
235 return; |
| |
236 |
| |
237 data = gaim_buddy_icon_get_data(icon, &len); |
| |
238 |
| |
239 random = g_strdup_printf("%x", g_random_int()); |
| |
240 dirname = gaim_buddy_icons_get_cache_dir(); |
| |
241 filename = g_build_filename(dirname, random, NULL); |
| |
242 old_icon = gaim_blist_node_get_string((GaimBlistNode*)buddy, "buddy_icon"); |
| |
243 |
| |
244 if (!g_file_test(dirname, G_FILE_TEST_IS_DIR)) |
| |
245 { |
| |
246 gaim_debug_info("buddyicon", "Creating icon cache directory.\n"); |
| |
247 |
| |
248 if (g_mkdir(dirname, S_IRUSR | S_IWUSR | S_IXUSR) < 0) |
| |
249 { |
| |
250 gaim_debug_error("buddyicon", |
| |
251 "Unable to create directory %s: %s\n", |
| |
252 dirname, strerror(errno)); |
| |
253 } |
| |
254 } |
| |
255 |
| |
256 if ((file = g_fopen(filename, "wb")) != NULL) |
| |
257 { |
| |
258 fwrite(data, 1, len, file); |
| |
259 fclose(file); |
| |
260 gaim_debug_info("buddyicon", "Wrote file %s\n", filename); |
| |
261 } |
| |
262 else |
| |
263 { |
| |
264 gaim_debug_error("buddyicon", "Unable to create file %s: %s\n", |
| |
265 filename, strerror(errno)); |
| |
266 g_free(filename); |
| |
267 g_free(random); |
| |
268 return; |
| |
269 } |
| |
270 |
| |
271 g_free(filename); |
| |
272 |
| |
273 if (old_icon != NULL) |
| |
274 delete_icon_cache_file(dirname, old_icon); |
| |
275 |
| |
276 gaim_blist_node_set_string((GaimBlistNode *)buddy, "buddy_icon", random); |
| |
277 |
| |
278 g_free(random); |
| |
279 } |
| |
280 |
| |
281 void |
| |
282 gaim_buddy_icon_uncache(GaimBuddy *buddy) |
| |
283 { |
| |
284 const char *old_icon; |
| |
285 |
| |
286 g_return_if_fail(buddy != NULL); |
| |
287 |
| |
288 old_icon = gaim_blist_node_get_string((GaimBlistNode *)buddy, "buddy_icon"); |
| |
289 |
| |
290 if (old_icon != NULL) |
| |
291 delete_icon_cache_file(gaim_buddy_icons_get_cache_dir(), old_icon); |
| |
292 |
| |
293 gaim_blist_node_remove_setting((GaimBlistNode *)buddy, "buddy_icon"); |
| |
294 |
| |
295 /* Unset the icon in case this function is called from |
| |
296 * something other than gaim_buddy_set_icon(). */ |
| |
297 if (buddy->icon != NULL) |
| |
298 { |
| |
299 gaim_buddy_icon_unref(buddy->icon); |
| |
300 buddy->icon = NULL; |
| |
301 } |
| |
302 } |
| |
303 |
| |
304 void |
| |
305 gaim_buddy_icon_set_account(GaimBuddyIcon *icon, GaimAccount *account) |
| |
306 { |
| |
307 g_return_if_fail(icon != NULL); |
| |
308 g_return_if_fail(account != NULL); |
| |
309 |
| |
310 icon->account = account; |
| |
311 } |
| |
312 |
| |
313 void |
| |
314 gaim_buddy_icon_set_username(GaimBuddyIcon *icon, const char *username) |
| |
315 { |
| |
316 g_return_if_fail(icon != NULL); |
| |
317 g_return_if_fail(username != NULL); |
| |
318 |
| |
319 g_free(icon->username); |
| |
320 icon->username = g_strdup(username); |
| |
321 } |
| |
322 |
| |
323 void |
| |
324 gaim_buddy_icon_set_data(GaimBuddyIcon *icon, void *data, size_t len) |
| |
325 { |
| |
326 g_return_if_fail(icon != NULL); |
| |
327 |
| |
328 g_free(icon->data); |
| |
329 |
| |
330 if (data != NULL && len > 0) |
| |
331 { |
| |
332 icon->data = g_memdup(data, len); |
| |
333 icon->len = len; |
| |
334 } |
| |
335 else |
| |
336 { |
| |
337 icon->data = NULL; |
| |
338 icon->len = 0; |
| |
339 } |
| |
340 |
| |
341 gaim_buddy_icon_update(icon); |
| |
342 } |
| |
343 |
| |
344 void |
| |
345 gaim_buddy_icon_set_path(GaimBuddyIcon *icon, const gchar *path) |
| |
346 { |
| |
347 g_return_if_fail(icon != NULL); |
| |
348 |
| |
349 g_free(icon->path); |
| |
350 icon->path = g_strdup(path); |
| |
351 } |
| |
352 |
| |
353 GaimAccount * |
| |
354 gaim_buddy_icon_get_account(const GaimBuddyIcon *icon) |
| |
355 { |
| |
356 g_return_val_if_fail(icon != NULL, NULL); |
| |
357 |
| |
358 return icon->account; |
| |
359 } |
| |
360 |
| |
361 const char * |
| |
362 gaim_buddy_icon_get_username(const GaimBuddyIcon *icon) |
| |
363 { |
| |
364 g_return_val_if_fail(icon != NULL, NULL); |
| |
365 |
| |
366 return icon->username; |
| |
367 } |
| |
368 |
| |
369 const guchar * |
| |
370 gaim_buddy_icon_get_data(const GaimBuddyIcon *icon, size_t *len) |
| |
371 { |
| |
372 g_return_val_if_fail(icon != NULL, NULL); |
| |
373 |
| |
374 if (len != NULL) |
| |
375 *len = icon->len; |
| |
376 |
| |
377 return icon->data; |
| |
378 } |
| |
379 |
| |
380 const char * |
| |
381 gaim_buddy_icon_get_path(GaimBuddyIcon *icon) |
| |
382 { |
| |
383 g_return_val_if_fail(icon != NULL, NULL); |
| |
384 |
| |
385 return icon->path; |
| |
386 } |
| |
387 |
| |
388 const char * |
| |
389 gaim_buddy_icon_get_type(const GaimBuddyIcon *icon) |
| |
390 { |
| |
391 const void *data; |
| |
392 size_t len; |
| |
393 |
| |
394 g_return_val_if_fail(icon != NULL, NULL); |
| |
395 |
| |
396 data = gaim_buddy_icon_get_data(icon, &len); |
| |
397 |
| |
398 /* TODO: Find a way to do this with GDK */ |
| |
399 if (len >= 4) |
| |
400 { |
| |
401 if (!strncmp(data, "BM", 2)) |
| |
402 return "bmp"; |
| |
403 else if (!strncmp(data, "GIF8", 4)) |
| |
404 return "gif"; |
| |
405 else if (!strncmp(data, "\xff\xd8\xff\xe0", 4)) |
| |
406 return "jpg"; |
| |
407 else if (!strncmp(data, "\x89PNG", 4)) |
| |
408 return "png"; |
| |
409 } |
| |
410 |
| |
411 return NULL; |
| |
412 } |
| |
413 |
| |
414 void |
| |
415 gaim_buddy_icons_set_for_user(GaimAccount *account, const char *username, |
| |
416 void *icon_data, size_t icon_len) |
| |
417 { |
| |
418 g_return_if_fail(account != NULL); |
| |
419 g_return_if_fail(username != NULL); |
| |
420 |
| |
421 if (icon_data == NULL || icon_len == 0) |
| |
422 { |
| |
423 GaimBuddyIcon *buddy_icon; |
| |
424 |
| |
425 buddy_icon = gaim_buddy_icons_find(account, username); |
| |
426 |
| |
427 if (buddy_icon != NULL) |
| |
428 gaim_buddy_icon_destroy(buddy_icon); |
| |
429 } |
| |
430 else |
| |
431 { |
| |
432 GaimBuddyIcon *icon = gaim_buddy_icon_new(account, username, icon_data, icon_len); |
| |
433 gaim_buddy_icon_unref(icon); |
| |
434 } |
| |
435 } |
| |
436 |
| |
437 GaimBuddyIcon * |
| |
438 gaim_buddy_icons_find(GaimAccount *account, const char *username) |
| |
439 { |
| |
440 GHashTable *icon_cache; |
| |
441 GaimBuddyIcon *ret = NULL; |
| |
442 char *filename = NULL; |
| |
443 |
| |
444 g_return_val_if_fail(account != NULL, NULL); |
| |
445 g_return_val_if_fail(username != NULL, NULL); |
| |
446 |
| |
447 icon_cache = g_hash_table_lookup(account_cache, account); |
| |
448 |
| |
449 if ((icon_cache == NULL) || ((ret = g_hash_table_lookup(icon_cache, username)) == NULL)) { |
| |
450 const char *file; |
| |
451 struct stat st; |
| |
452 GaimBuddy *b = gaim_find_buddy(account, username); |
| |
453 |
| |
454 if (!b) |
| |
455 return NULL; |
| |
456 |
| |
457 if ((file = gaim_blist_node_get_string((GaimBlistNode*)b, "buddy_icon")) == NULL) |
| |
458 return NULL; |
| |
459 |
| |
460 if (!g_stat(file, &st)) |
| |
461 filename = g_strdup(file); |
| |
462 else |
| |
463 filename = g_build_filename(gaim_buddy_icons_get_cache_dir(), file, NULL); |
| |
464 |
| |
465 if (!g_stat(filename, &st)) { |
| |
466 FILE *f = g_fopen(filename, "rb"); |
| |
467 if (f) { |
| |
468 char *data = g_malloc(st.st_size); |
| |
469 fread(data, 1, st.st_size, f); |
| |
470 fclose(f); |
| |
471 ret = gaim_buddy_icon_create(account, username); |
| |
472 gaim_buddy_icon_ref(ret); |
| |
473 gaim_buddy_icon_set_data(ret, data, st.st_size); |
| |
474 gaim_buddy_icon_unref(ret); |
| |
475 g_free(data); |
| |
476 g_free(filename); |
| |
477 return ret; |
| |
478 } |
| |
479 } |
| |
480 g_free(filename); |
| |
481 } |
| |
482 |
| |
483 return ret; |
| |
484 } |
| |
485 |
| |
486 void |
| |
487 gaim_buddy_icons_set_caching(gboolean caching) |
| |
488 { |
| |
489 icon_caching = caching; |
| |
490 } |
| |
491 |
| |
492 gboolean |
| |
493 gaim_buddy_icons_is_caching(void) |
| |
494 { |
| |
495 return icon_caching; |
| |
496 } |
| |
497 |
| |
498 void |
| |
499 gaim_buddy_icons_set_cache_dir(const char *dir) |
| |
500 { |
| |
501 g_return_if_fail(dir != NULL); |
| |
502 |
| |
503 g_free(cache_dir); |
| |
504 cache_dir = g_strdup(dir); |
| |
505 } |
| |
506 |
| |
507 const char * |
| |
508 gaim_buddy_icons_get_cache_dir(void) |
| |
509 { |
| |
510 return cache_dir; |
| |
511 } |
| |
512 |
| |
513 char *gaim_buddy_icons_get_full_path(const char *icon) { |
| |
514 if (icon == NULL) |
| |
515 return NULL; |
| |
516 |
| |
517 if (g_file_test(icon, G_FILE_TEST_IS_REGULAR)) |
| |
518 return g_strdup(icon); |
| |
519 else |
| |
520 return g_build_filename(gaim_buddy_icons_get_cache_dir(), icon, NULL); |
| |
521 } |
| |
522 |
| |
523 void * |
| |
524 gaim_buddy_icons_get_handle() |
| |
525 { |
| |
526 static int handle; |
| |
527 |
| |
528 return &handle; |
| |
529 } |
| |
530 |
| |
531 void |
| |
532 gaim_buddy_icons_init() |
| |
533 { |
| |
534 account_cache = g_hash_table_new_full( |
| |
535 g_direct_hash, g_direct_equal, |
| |
536 NULL, (GFreeFunc)g_hash_table_destroy); |
| |
537 |
| |
538 cache_dir = g_build_filename(gaim_user_dir(), "icons", NULL); |
| |
539 } |
| |
540 |
| |
541 void |
| |
542 gaim_buddy_icons_uninit() |
| |
543 { |
| |
544 g_hash_table_destroy(account_cache); |
| |
545 } |
| |
546 |
| |
547 void gaim_buddy_icon_get_scale_size(GaimBuddyIconSpec *spec, int *width, int *height) |
| |
548 { |
| |
549 int new_width, new_height; |
| |
550 |
| |
551 new_width = *width; |
| |
552 new_height = *height; |
| |
553 |
| |
554 if (*width < spec->min_width) |
| |
555 new_width = spec->min_width; |
| |
556 else if (*width > spec->max_width) |
| |
557 new_width = spec->max_width; |
| |
558 |
| |
559 if (*height < spec->min_height) |
| |
560 new_height = spec->min_height; |
| |
561 else if (*height > spec->max_height) |
| |
562 new_height = spec->max_height; |
| |
563 |
| |
564 /* preserve aspect ratio */ |
| |
565 if ((double)*height * (double)new_width > |
| |
566 (double)*width * (double)new_height) { |
| |
567 new_width = 0.5 + (double)*width * (double)new_height / (double)*height; |
| |
568 } else { |
| |
569 new_height = 0.5 + (double)*height * (double)new_width / (double)*width; |
| |
570 } |
| |
571 |
| |
572 *width = new_width; |
| |
573 *height = new_height; |
| |
574 } |
| |
575 |