comparison libpurple/buddyicon.c @ 16373:c9b4ff420140

The buddy icon code as it stands, with lots of bugs and design flaws.
author Richard Laager <rlaager@wiktel.com>
date Mon, 23 Apr 2007 17:39:58 +0000
parents 32c366eeeb99
children 391a79778f89
comparison
equal deleted inserted replaced
16372:bb08332c7456 16373:c9b4ff420140
22 * along with this program; if not, write to the Free Software 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 23 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 */ 24 */
25 #include "internal.h" 25 #include "internal.h"
26 #include "buddyicon.h" 26 #include "buddyicon.h"
27 #include "cipher.h"
27 #include "conversation.h" 28 #include "conversation.h"
28 #include "dbus-maybe.h" 29 #include "dbus-maybe.h"
29 #include "debug.h" 30 #include "debug.h"
30 #include "util.h" 31 #include "util.h"
31 32
33 typedef struct _PurpleBuddyIconData PurpleBuddyIconData;
34
35 struct _PurpleBuddyIcon
36 {
37 PurpleAccount *account; /**< The account the user is on. */
38 char *username; /**< The username the icon belongs to. */
39 PurpleBuddyIconData *protocol_icon; /**< The icon data. */
40 PurpleBuddyIconData *custom_icon; /**< The data for a user-set custom icon. */
41 int ref_count; /**< The buddy icon reference count. */
42 };
43
44 struct _PurpleBuddyIconData
45 {
46 guchar *image_data; /**< The buddy icon data. */
47 size_t len; /**< The length of the buddy icon data. */
48 char *filename; /**< The filename of the cache file. */
49 int ref_count; /**< The buddy icon reference count. */
50 };
51
32 static GHashTable *account_cache = NULL; 52 static GHashTable *account_cache = NULL;
53 static GHashTable *icon_data_cache = NULL;
54 static GHashTable *icon_file_cache = NULL;
33 static char *cache_dir = NULL; 55 static char *cache_dir = NULL;
34 static gboolean icon_caching = TRUE; 56 static gboolean icon_caching = TRUE;
35 57
58 /* For ~/.gaim to ~/.purple migration. */
59 static char *old_icons_dir = NULL;
60
61 static void
62 ref_filename(const char *filename)
63 {
64 int refs;
65
66 g_return_if_fail(filename != NULL);
67
68 refs = GPOINTER_TO_INT(g_hash_table_lookup(icon_file_cache, filename));
69
70 g_hash_table_insert(icon_file_cache, g_strdup(filename),
71 GINT_TO_POINTER(refs + 1));
72 }
73
74 static void
75 unref_filename(const char *filename)
76 {
77 int refs;
78
79 if (filename == NULL)
80 return;
81
82 refs = GPOINTER_TO_INT(g_hash_table_lookup(icon_file_cache, filename));
83
84 if (refs == 1)
85 {
86 g_hash_table_remove(icon_file_cache, filename);
87 }
88 else
89 {
90 g_hash_table_insert(icon_file_cache, g_strdup(filename),
91 GINT_TO_POINTER(refs - 1));
92 }
93 }
94
95 static const char *
96 get_icon_type(guchar *icon_data, size_t icon_len)
97 {
98 g_return_val_if_fail(icon_data != NULL, NULL);
99 g_return_val_if_fail(icon_len > 0, NULL);
100
101 if (icon_len >= 4)
102 {
103 if (!strncmp((char *)icon_data, "BM", 2))
104 return "bmp";
105 else if (!strncmp((char *)icon_data, "GIF8", 4))
106 return "gif";
107 else if (!strncmp((char *)icon_data, "\xff\xd8\xff\xe0", 4))
108 return "jpg";
109 else if (!strncmp((char *)icon_data, "\x89PNG", 4))
110 return "png";
111 }
112
113 return "icon";
114 }
115
116 static const char *
117 purple_buddy_icon_data_get_type(PurpleBuddyIconData *data)
118 {
119 return get_icon_type(data->image_data, data->len);
120 }
121
122 static char *
123 purple_buddy_icon_data_calculate_filename(guchar *icon_data, size_t icon_len)
124 {
125 PurpleCipherContext *context;
126 gchar digest[41];
127
128 context = purple_cipher_context_new_by_name("sha1", NULL);
129 if (context == NULL)
130 {
131 purple_debug_error("buddyicon", "Could not find sha1 cipher\n");
132 g_return_val_if_reached(NULL);
133 }
134
135 /* Hash the icon data */
136 purple_cipher_context_append(context, icon_data, icon_len);
137 if (!purple_cipher_context_digest_to_str(context, sizeof(digest), digest, NULL))
138 {
139 purple_debug_error("buddyicon", "Failed to get SHA-1 digest.\n");
140 g_return_val_if_reached(NULL);
141 }
142 purple_cipher_context_destroy(context);
143
144 /* Return the filename */
145 return g_strdup_printf("%s.%s", digest,
146 get_icon_type(icon_data, icon_len));
147 }
148
149 static void
150 purple_buddy_icon_data_cache(PurpleBuddyIconData *data)
151 {
152 const char *dirname;
153 char *path;
154 FILE *file = NULL;
155
156 if (!purple_buddy_icons_is_caching())
157 return;
158
159 dirname = purple_buddy_icons_get_cache_dir();
160 path = g_build_filename(dirname, data->filename, NULL);
161
162 if (!g_file_test(dirname, G_FILE_TEST_IS_DIR))
163 {
164 purple_debug_info("buddyicon", "Creating icon cache directory.\n");
165
166 if (g_mkdir(dirname, S_IRUSR | S_IWUSR | S_IXUSR) < 0)
167 {
168 purple_debug_error("buddyicon",
169 "Unable to create directory %s: %s\n",
170 dirname, strerror(errno));
171 }
172 }
173
174 if ((file = g_fopen(path, "wb")) != NULL)
175 {
176 if (!fwrite(data->image_data, data->len, 1, file))
177 {
178 purple_debug_error("buddyicon", "Error writing %s: %s\n",
179 path, strerror(errno));
180 }
181 else
182 purple_debug_info("buddyicon", "Wrote cache file: %s\n", path);
183
184 fclose(file);
185 }
186 else
187 {
188 purple_debug_error("buddyicon", "Unable to create file %s: %s\n",
189 path, strerror(errno));
190 g_free(path);
191 return;
192 }
193 g_free(path);
194 }
195
196 static void
197 purple_buddy_icon_data_uncache(PurpleBuddyIconData *data)
198 {
199 const char *dirname;
200 char *path;
201
202 g_return_if_fail(data != NULL);
203
204 /* It's possible that there are other references to this icon
205 * cache file that are not currently loaded into memory. */
206 if (g_hash_table_lookup(icon_file_cache, data->filename))
207 return;
208
209 dirname = purple_buddy_icons_get_cache_dir();
210 path = g_build_filename(dirname, data->filename, NULL);
211
212 if (g_file_test(path, G_FILE_TEST_EXISTS))
213 {
214 if (g_unlink(path))
215 {
216 purple_debug_error("buddyicon", "Failed to delete %s: %s\n",
217 path, strerror(errno));
218 }
219 else
220 purple_debug_info("buddyicon", "Deleted cache file: %s\n", path);
221 }
222
223 g_free(path);
224 }
225
226 static PurpleBuddyIconData *
227 purple_buddy_icon_data_ref(PurpleBuddyIconData *data)
228 {
229 g_return_val_if_fail(data != NULL, NULL);
230
231 data->ref_count++;
232
233 return data;
234 }
235
236 static PurpleBuddyIconData *
237 purple_buddy_icon_data_unref(PurpleBuddyIconData *data)
238 {
239 if (data == NULL)
240 return NULL;
241
242 g_return_val_if_fail(data->ref_count > 0, NULL);
243
244 data->ref_count--;
245
246 if (data->ref_count == 0)
247 {
248 g_hash_table_remove(icon_data_cache, data->filename);
249
250 purple_buddy_icon_data_uncache(data);
251
252 g_free(data->image_data);
253 g_free(data->filename);
254 g_free(data);
255
256 return NULL;
257 }
258
259 return data;
260 }
261
262 static PurpleBuddyIconData *
263 purple_buddy_icon_data_new(guchar *icon_data, size_t icon_len)
264 {
265 PurpleBuddyIconData *data;
266 char *filename;
267
268 g_return_val_if_fail(icon_data != NULL, NULL);
269 g_return_val_if_fail(icon_len > 0, NULL);
270
271 filename = purple_buddy_icon_data_calculate_filename(icon_data, icon_len);
272 if (filename == NULL)
273 return NULL;
274
275 if ((data = g_hash_table_lookup(icon_data_cache, filename)))
276 {
277 g_free(filename);
278 return purple_buddy_icon_data_ref(data);
279 }
280
281 data = g_new0(PurpleBuddyIconData, 1);
282 data->image_data = g_memdup(icon_data, icon_len);
283 data->len = icon_len;
284 data->filename = filename;
285
286 purple_buddy_icon_data_cache(data);
287
288 return data;
289 }
290
36 static PurpleBuddyIcon * 291 static PurpleBuddyIcon *
37 purple_buddy_icon_create(PurpleAccount *account, const char *username) 292 purple_buddy_icon_create(PurpleAccount *account, const char *username)
38 { 293 {
39 PurpleBuddyIcon *icon; 294 PurpleBuddyIcon *icon;
40 GHashTable *icon_cache; 295 GHashTable *icon_cache;
41 296
42 icon = g_new0(PurpleBuddyIcon, 1); 297 icon = g_new0(PurpleBuddyIcon, 1);
43 PURPLE_DBUS_REGISTER_POINTER(icon, PurpleBuddyIcon); 298 PURPLE_DBUS_REGISTER_POINTER(icon, PurpleBuddyIcon);
44 299
45 purple_buddy_icon_set_account(icon, account); 300 icon->account = account;
46 purple_buddy_icon_set_username(icon, username); 301 icon->username = g_strdup(username);
47 302
48 icon_cache = g_hash_table_lookup(account_cache, account); 303 icon_cache = g_hash_table_lookup(account_cache, account);
49 304
50 if (icon_cache == NULL) 305 if (icon_cache == NULL)
51 { 306 {
53 308
54 g_hash_table_insert(account_cache, account, icon_cache); 309 g_hash_table_insert(account_cache, account, icon_cache);
55 } 310 }
56 311
57 g_hash_table_insert(icon_cache, 312 g_hash_table_insert(icon_cache,
58 (char *)purple_buddy_icon_get_username(icon), icon); 313 (char *)purple_buddy_icon_get_username(icon), icon);
59 return icon; 314 return icon;
60 } 315 }
61 316
62 PurpleBuddyIcon * 317 PurpleBuddyIcon *
63 purple_buddy_icon_new(PurpleAccount *account, const char *username, 318 purple_buddy_icon_new(PurpleAccount *account, const char *username,
64 void *icon_data, size_t icon_len) 319 void *protocol_icon_data, size_t protocol_icon_len,
320 void *custom_icon_data, size_t custom_icon_len)
65 { 321 {
66 PurpleBuddyIcon *icon; 322 PurpleBuddyIcon *icon;
67 323
68 g_return_val_if_fail(account != NULL, NULL); 324 g_return_val_if_fail(account != NULL, NULL);
69 g_return_val_if_fail(username != NULL, NULL); 325 g_return_val_if_fail(username != NULL, NULL);
70 g_return_val_if_fail(icon_data != NULL, NULL); 326 g_return_val_if_fail((protocol_icon_data != NULL && protocol_icon_len > 0) ||
71 g_return_val_if_fail(icon_len > 0, NULL); 327 (custom_icon_data != NULL && custom_icon_len > 0), NULL);
72 328
73 icon = purple_buddy_icons_find(account, username); 329 icon = purple_buddy_icons_find(account, username);
74 330
75 if (icon == NULL) 331 if (icon == NULL)
76 icon = purple_buddy_icon_create(account, username); 332 icon = purple_buddy_icon_create(account, username);
77 333
334 /* Take a reference for the caller of this function. */
78 purple_buddy_icon_ref(icon); 335 purple_buddy_icon_ref(icon);
79 purple_buddy_icon_set_data(icon, icon_data, icon_len); 336
80 purple_buddy_icon_set_path(icon, NULL); 337 if (protocol_icon_data != NULL && protocol_icon_len > 0)
81 338 purple_buddy_icon_set_protocol_data(icon, protocol_icon_data, protocol_icon_len);
82 /* purple_buddy_icon_set_data() makes blist.c or 339
83 * conversation.c, or both, take a reference. 340 if (custom_icon_data != NULL && custom_icon_len > 0)
84 * 341 purple_buddy_icon_set_custom_data(icon, custom_icon_data, custom_icon_len);
85 * Plus, we leave one for the caller of this function.
86 */
87 342
88 return icon; 343 return icon;
89 }
90
91 void
92 purple_buddy_icon_destroy(PurpleBuddyIcon *icon)
93 {
94 PurpleConversation *conv;
95 PurpleAccount *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 * purple_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 = purple_buddy_icon_get_account(icon);
111 username = purple_buddy_icon_get_username(icon);
112
113 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, username, account);
114 if (conv != NULL)
115 purple_conv_im_set_icon(PURPLE_CONV_IM(conv), NULL);
116
117 for (list = sl = purple_find_buddies(account, username); sl != NULL;
118 sl = sl->next)
119 {
120 PurpleBuddy *buddy = (PurpleBuddy *)sl->data;
121
122 purple_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 purple_buddy_icon_get_account(icon));
132
133 if (icon_cache != NULL)
134 g_hash_table_remove(icon_cache, purple_buddy_icon_get_username(icon));
135
136 g_free(icon->username);
137 g_free(icon->data);
138 g_free(icon->path);
139 PURPLE_DBUS_UNREGISTER_POINTER(icon);
140 g_free(icon);
141 } 344 }
142 345
143 PurpleBuddyIcon * 346 PurpleBuddyIcon *
144 purple_buddy_icon_ref(PurpleBuddyIcon *icon) 347 purple_buddy_icon_ref(PurpleBuddyIcon *icon)
145 { 348 {
151 } 354 }
152 355
153 PurpleBuddyIcon * 356 PurpleBuddyIcon *
154 purple_buddy_icon_unref(PurpleBuddyIcon *icon) 357 purple_buddy_icon_unref(PurpleBuddyIcon *icon)
155 { 358 {
156 g_return_val_if_fail(icon != NULL, NULL); 359 if (icon == NULL)
360 return NULL;
361
157 g_return_val_if_fail(icon->ref_count > 0, NULL); 362 g_return_val_if_fail(icon->ref_count > 0, NULL);
158 363
159 icon->ref_count--; 364 icon->ref_count--;
160 365
161 if (icon->ref_count == 0) 366 if (icon->ref_count == 0)
162 { 367 {
163 purple_buddy_icon_destroy(icon); 368 GHashTable *icon_cache = g_hash_table_lookup(account_cache, purple_buddy_icon_get_account(icon));
369
370 if (icon_cache != NULL)
371 g_hash_table_remove(icon_cache, purple_buddy_icon_get_username(icon));
372
373 g_free(icon->username);
374 purple_buddy_icon_data_unref(icon->protocol_icon);
375 purple_buddy_icon_data_unref(icon->custom_icon);
376
377 PURPLE_DBUS_UNREGISTER_POINTER(icon);
378 g_free(icon);
164 379
165 return NULL; 380 return NULL;
166 } 381 }
167 382
168 return icon; 383 return icon;
172 purple_buddy_icon_update(PurpleBuddyIcon *icon) 387 purple_buddy_icon_update(PurpleBuddyIcon *icon)
173 { 388 {
174 PurpleConversation *conv; 389 PurpleConversation *conv;
175 PurpleAccount *account; 390 PurpleAccount *account;
176 const char *username; 391 const char *username;
392 PurpleBuddyIcon *icon_to_set;
177 GSList *sl, *list; 393 GSList *sl, *list;
178 394
179 g_return_if_fail(icon != NULL); 395 g_return_if_fail(icon != NULL);
180 396
181 account = purple_buddy_icon_get_account(icon); 397 account = purple_buddy_icon_get_account(icon);
182 username = purple_buddy_icon_get_username(icon); 398 username = purple_buddy_icon_get_username(icon);
183 399
184 for (list = sl = purple_find_buddies(account, username); sl != NULL; 400 /* If neither type of data exists, then call the functions below with
185 sl = sl->next) 401 * NULL to unset the icon. They will then unref the icon and it
402 * should be destroyed. The only way it wouldn't be destroyed is if
403 * someone else is holding a reference to it, in which case they can
404 * kill the icon when they realize it has no data any more. */
405 icon_to_set = (icon->protocol_icon || icon->custom_icon) ? icon : NULL;
406
407 for (list = sl = purple_find_buddies(account, username);
408 sl != NULL;
409 sl = sl->next)
186 { 410 {
187 PurpleBuddy *buddy = (PurpleBuddy *)sl->data; 411 PurpleBuddy *buddy = (PurpleBuddy *)sl->data;
188 412 const char *old_icon;
189 purple_buddy_set_icon(buddy, icon); 413
414 purple_buddy_set_icon(buddy, icon_to_set);
415
416
417 old_icon = purple_blist_node_get_string((PurpleBlistNode *)buddy,
418 "buddy_icon");
419 if (icon->protocol_icon)
420 {
421 old_icon = purple_blist_node_get_string((PurpleBlistNode *)buddy,
422 "buddy_icon");
423 purple_blist_node_set_string((PurpleBlistNode *)buddy,
424 "buddy_icon",
425 icon->protocol_icon->filename);
426 ref_filename(icon->protocol_icon->filename);
427 }
428 else
429 {
430 purple_blist_node_remove_setting((PurpleBlistNode *)buddy, "buddy_icon");
431 }
432 unref_filename(old_icon);
433
434
435 old_icon = purple_blist_node_get_string((PurpleBlistNode *)buddy,
436 "custom_buddy_icon");
437 if (icon->custom_icon)
438 {
439 old_icon = purple_blist_node_get_string((PurpleBlistNode *)buddy,
440 "custom_buddy_icon");
441 purple_blist_node_set_string((PurpleBlistNode *)buddy,
442 "custom_buddy_icon",
443 icon->custom_icon->filename);
444 ref_filename(icon->custom_icon->filename);
445 }
446 else
447 {
448 purple_blist_node_remove_setting((PurpleBlistNode *)buddy,
449 "custom_buddy_icon");
450 }
451 unref_filename(old_icon);
190 } 452 }
191 453
192 g_slist_free(list); 454 g_slist_free(list);
193 455
194 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, username, account); 456 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, username, account);
195 457
196 if (conv != NULL) 458 if (conv != NULL)
197 purple_conv_im_set_icon(PURPLE_CONV_IM(conv), icon); 459 purple_conv_im_set_icon(PURPLE_CONV_IM(conv), icon_to_set);
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 purple_debug_info("buddyicon", "Uncached file %s\n", old_icon);
218 } 460 }
219 461
220 void 462 void
221 purple_buddy_icon_cache(PurpleBuddyIcon *icon, PurpleBuddy *buddy) 463 purple_buddy_icon_set_custom_data(PurpleBuddyIcon *icon, guchar *data, size_t len)
222 { 464 {
223 const guchar *data; 465 PurpleBuddyIconData *old_data;
224 const char *dirname; 466
225 char *random; 467 g_return_if_fail(icon != NULL);
226 char *filename; 468
227 const char *old_icon; 469 old_data = icon->custom_icon;
228 size_t len = 0; 470 icon->custom_icon = NULL;
229 FILE *file = NULL; 471
230 472 if (data != NULL && len > 0)
231 g_return_if_fail(icon != NULL); 473 icon->custom_icon = purple_buddy_icon_data_new(data, len);
232 g_return_if_fail(buddy != NULL); 474
233 475 purple_buddy_icon_update(icon);
234 if (!purple_buddy_icons_is_caching()) 476
235 return; 477 purple_buddy_icon_data_unref(icon->custom_icon);
236
237 data = purple_buddy_icon_get_data(icon, &len);
238
239 random = g_strdup_printf("%x", g_random_int());
240 dirname = purple_buddy_icons_get_cache_dir();
241 filename = g_build_filename(dirname, random, NULL);
242 old_icon = purple_blist_node_get_string((PurpleBlistNode*)buddy, "buddy_icon");
243
244 if (!g_file_test(dirname, G_FILE_TEST_IS_DIR))
245 {
246 purple_debug_info("buddyicon", "Creating icon cache directory.\n");
247
248 if (g_mkdir(dirname, S_IRUSR | S_IWUSR | S_IXUSR) < 0)
249 {
250 purple_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 purple_debug_info("buddyicon", "Wrote file %s\n", filename);
261 }
262 else
263 {
264 purple_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 purple_blist_node_set_string((PurpleBlistNode *)buddy, "buddy_icon", random);
277
278 g_free(random);
279 } 478 }
280 479
281 void 480 void
282 purple_buddy_icon_uncache(PurpleBuddy *buddy) 481 purple_buddy_icon_set_protocol_data(PurpleBuddyIcon *icon, guchar *data, size_t len)
283 { 482 {
284 const char *old_icon; 483 PurpleBuddyIconData *old_data;
285 484
286 g_return_if_fail(buddy != NULL);
287
288 old_icon = purple_blist_node_get_string((PurpleBlistNode *)buddy, "buddy_icon");
289
290 if (old_icon != NULL)
291 delete_icon_cache_file(purple_buddy_icons_get_cache_dir(), old_icon);
292
293 purple_blist_node_remove_setting((PurpleBlistNode *)buddy, "buddy_icon");
294
295 /* Unset the icon in case this function is called from
296 * something other than purple_buddy_set_icon(). */
297 if (buddy->icon != NULL)
298 {
299 purple_buddy_icon_unref(buddy->icon);
300 buddy->icon = NULL;
301 }
302 }
303
304 void
305 purple_buddy_icon_set_account(PurpleBuddyIcon *icon, PurpleAccount *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 purple_buddy_icon_set_username(PurpleBuddyIcon *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 purple_buddy_icon_set_data(PurpleBuddyIcon *icon, void *data, size_t len)
325 {
326 g_return_if_fail(icon != NULL); 485 g_return_if_fail(icon != NULL);
327 486
328 g_free(icon->data); 487 old_data = icon->protocol_icon;
488 icon->protocol_icon = NULL;
329 489
330 if (data != NULL && len > 0) 490 if (data != NULL && len > 0)
331 { 491 icon->protocol_icon = purple_buddy_icon_data_new(data, len);
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 492
341 purple_buddy_icon_update(icon); 493 purple_buddy_icon_update(icon);
342 } 494
343 495 purple_buddy_icon_data_unref(old_data);
344 void
345 purple_buddy_icon_set_path(PurpleBuddyIcon *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 } 496 }
352 497
353 PurpleAccount * 498 PurpleAccount *
354 purple_buddy_icon_get_account(const PurpleBuddyIcon *icon) 499 purple_buddy_icon_get_account(const PurpleBuddyIcon *icon)
355 { 500 {
369 const guchar * 514 const guchar *
370 purple_buddy_icon_get_data(const PurpleBuddyIcon *icon, size_t *len) 515 purple_buddy_icon_get_data(const PurpleBuddyIcon *icon, size_t *len)
371 { 516 {
372 g_return_val_if_fail(icon != NULL, NULL); 517 g_return_val_if_fail(icon != NULL, NULL);
373 518
374 if (len != NULL) 519 if (icon->custom_icon)
375 *len = icon->len; 520 {
376 521 if (len != NULL)
377 return icon->data; 522 *len = icon->custom_icon->len;
378 } 523
379 524 return icon->custom_icon->image_data;
380 const char * 525 }
381 purple_buddy_icon_get_path(PurpleBuddyIcon *icon) 526
382 { 527 if (icon->protocol_icon)
383 g_return_val_if_fail(icon != NULL, NULL); 528 {
384 529 if (len != NULL)
385 return icon->path; 530 *len = icon->protocol_icon->len;
531
532 return icon->protocol_icon->image_data;
533 }
534
535 return NULL;
386 } 536 }
387 537
388 const char * 538 const char *
389 purple_buddy_icon_get_type(const PurpleBuddyIcon *icon) 539 purple_buddy_icon_get_type(const PurpleBuddyIcon *icon)
390 { 540 {
391 const void *data; 541 if (icon->custom_icon != NULL)
392 size_t len; 542 return purple_buddy_icon_data_get_type(icon->custom_icon);
393 543
394 g_return_val_if_fail(icon != NULL, NULL); 544 if (icon->protocol_icon != NULL)
395 545 return purple_buddy_icon_data_get_type(icon->protocol_icon);
396 data = purple_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 546
411 return NULL; 547 return NULL;
412 } 548 }
413 549
414 void 550 void
415 purple_buddy_icons_set_for_user(PurpleAccount *account, const char *username, 551 purple_buddy_icons_set_for_user(PurpleAccount *account, const char *username,
416 void *icon_data, size_t icon_len) 552 void *icon_data, size_t icon_len)
417 { 553 {
418 g_return_if_fail(account != NULL); 554 g_return_if_fail(account != NULL);
419 g_return_if_fail(username != NULL); 555 g_return_if_fail(username != NULL);
420 556
421 if (icon_data == NULL || icon_len == 0) 557 if (icon_data == NULL || icon_len == 0)
422 { 558 {
423 PurpleBuddyIcon *buddy_icon; 559 PurpleBuddyIcon *icon;
424 560
425 buddy_icon = purple_buddy_icons_find(account, username); 561 icon = purple_buddy_icons_find(account, username);
426 562
427 if (buddy_icon != NULL) 563 if (icon != NULL)
428 purple_buddy_icon_destroy(buddy_icon); 564 purple_buddy_icon_set_protocol_data(icon, icon_data, icon_len);
429 } 565 }
430 else 566 else
431 { 567 {
432 PurpleBuddyIcon *icon = purple_buddy_icon_new(account, username, icon_data, icon_len); 568 PurpleBuddyIcon *icon = purple_buddy_icon_new(account, username, icon_data, icon_len, NULL, 0);
433 purple_buddy_icon_unref(icon); 569 purple_buddy_icon_unref(icon);
434 } 570 }
571 }
572
573 static gboolean
574 read_icon_file(const char *path, guchar **data, size_t *len)
575 {
576 struct stat st;
577
578 if (!g_stat(path, &st))
579 {
580 FILE *f = g_fopen(path, "rb");
581 if (f)
582 {
583 *data = g_malloc(st.st_size);
584 if (!fread(*data, st.st_size, 1, f))
585 {
586 purple_debug_error("buddyicon", "Error reading %s: %s\n",
587 path, strerror(errno));
588 g_free(*data);
589 return FALSE;
590 }
591 fclose(f);
592
593 *len = st.st_size;
594 return TRUE;
595 }
596 else
597 {
598 purple_debug_error("buddyicon", "Unable to open file %s for reading: %s\n",
599 path, strerror(errno));
600 }
601 }
602 return FALSE;
435 } 603 }
436 604
437 PurpleBuddyIcon * 605 PurpleBuddyIcon *
438 purple_buddy_icons_find(PurpleAccount *account, const char *username) 606 purple_buddy_icons_find(PurpleAccount *account, const char *username)
439 { 607 {
440 GHashTable *icon_cache; 608 GHashTable *icon_cache;
441 PurpleBuddyIcon *ret = NULL; 609 PurpleBuddyIcon *icon = NULL;
442 char *filename = NULL;
443 610
444 g_return_val_if_fail(account != NULL, NULL); 611 g_return_val_if_fail(account != NULL, NULL);
445 g_return_val_if_fail(username != NULL, NULL); 612 g_return_val_if_fail(username != NULL, NULL);
446 613
447 icon_cache = g_hash_table_lookup(account_cache, account); 614 icon_cache = g_hash_table_lookup(account_cache, account);
448 615
449 if ((icon_cache == NULL) || ((ret = g_hash_table_lookup(icon_cache, username)) == NULL)) { 616 if ((icon_cache == NULL) || ((icon = g_hash_table_lookup(icon_cache, username)) == NULL))
450 const char *file; 617 {
451 struct stat st;
452 PurpleBuddy *b = purple_find_buddy(account, username); 618 PurpleBuddy *b = purple_find_buddy(account, username);
619 const char *protocol_icon_file;
620 const char *custom_icon_file;
621 const char *dirname;
622 gboolean caching;
623 guchar *data;
624 size_t len;
453 625
454 if (!b) 626 if (!b)
455 return NULL; 627 return NULL;
456 628
457 if ((file = purple_blist_node_get_string((PurpleBlistNode*)b, "buddy_icon")) == NULL) 629 protocol_icon_file = purple_blist_node_get_string((PurpleBlistNode*)b, "buddy_icon");
630 custom_icon_file = purple_blist_node_get_string((PurpleBlistNode*)b, "custom_buddy_icon");
631
632 if (protocol_icon_file == NULL && custom_icon_file == NULL)
458 return NULL; 633 return NULL;
459 634
460 if (!g_stat(file, &st)) 635 dirname = purple_buddy_icons_get_cache_dir();
461 filename = g_strdup(file); 636
637 caching = purple_buddy_icons_is_caching();
638 /* By disabling caching temporarily, we avoid a loop
639 * and don't have to add special code through several
640 * functions. */
641 purple_buddy_icons_set_caching(FALSE);
642
643 if (custom_icon_file != NULL)
644 {
645 char *path = g_build_filename(dirname, custom_icon_file, NULL);
646 if (read_icon_file(path, &data, &len))
647 {
648 icon = purple_buddy_icon_create(account, username);
649 purple_buddy_icon_set_custom_data(icon, data, len);
650 }
651 g_free(path);
652 }
653
654 if (protocol_icon_file != NULL)
655 {
656 char *path = g_build_filename(dirname, protocol_icon_file, NULL);
657 if (read_icon_file(path, &data, &len))
658 {
659 if (icon == NULL)
660 icon = purple_buddy_icon_create(account, username);
661 purple_buddy_icon_set_protocol_data(icon, data, len);
662 }
663 g_free(path);
664 }
665
666 purple_buddy_icons_set_caching(caching);
667 }
668
669 return icon;
670 }
671
672 void
673 purple_buddy_icon_set_old_icons_dir(const char *dirname)
674 {
675 old_icons_dir = g_strdup(dirname);
676 }
677
678 static void
679 migrate_buddy_icon(PurpleBlistNode *node, const char *setting_name,
680 const char *dirname, const char *filename)
681 {
682 char *path;
683
684 if (filename[0] != '/')
685 {
686 path = g_build_filename(dirname, filename, NULL);
687 if (g_file_test(path, G_FILE_TEST_EXISTS))
688 {
689 g_free(path);
690 return;
691 }
692 g_free(path);
693
694 path = g_build_filename(old_icons_dir, filename, NULL);
695 }
696 else
697 path = g_strdup(filename);
698
699 if (g_file_test(path, G_FILE_TEST_EXISTS))
700 {
701 guchar *icon_data;
702 size_t icon_len;
703 FILE *file;
704 char *new_filename;
705
706 if (!read_icon_file(path, &icon_data, &icon_len))
707 {
708 g_free(path);
709 return;
710 }
711
712 g_free(path);
713
714 new_filename = purple_buddy_icon_data_calculate_filename(icon_data, icon_len);
715 if (new_filename == NULL)
716 {
717 return;
718 }
719
720 path = g_build_filename(dirname, new_filename, NULL);
721 if ((file = g_fopen(path, "wb")) != NULL)
722 {
723 if (!fwrite(icon_data, icon_len, 1, file))
724 {
725 purple_debug_error("buddyicon", "Error writing %s: %s\n",
726 path, strerror(errno));
727 }
728 else
729 purple_debug_info("buddyicon", "Wrote migrated cache file: %s\n", path);
730
731 fclose(file);
732 }
462 else 733 else
463 filename = g_build_filename(purple_buddy_icons_get_cache_dir(), file, NULL); 734 {
464 735 purple_debug_error("buddyicon", "Unable to create file %s: %s\n",
465 if (!g_stat(filename, &st)) { 736 path, strerror(errno));
466 FILE *f = g_fopen(filename, "rb"); 737 g_free(new_filename);
467 if (f) { 738 g_free(path);
468 char *data = g_malloc(st.st_size); 739 return;
469 fread(data, 1, st.st_size, f); 740 }
470 fclose(f); 741 g_free(path);
471 ret = purple_buddy_icon_create(account, username); 742
472 purple_buddy_icon_ref(ret); 743 purple_blist_node_set_string(node,
473 purple_buddy_icon_set_data(ret, data, st.st_size); 744 setting_name,
474 purple_buddy_icon_unref(ret); 745 new_filename);
475 g_free(data); 746 ref_filename(new_filename);
476 g_free(filename); 747
477 return ret; 748 g_free(new_filename);
749 }
750 else
751 {
752 /* If the icon is gone, drop the setting... */
753 purple_blist_node_remove_setting(node,
754 setting_name);
755 g_free(path);
756 }
757 }
758
759 void
760 purple_buddy_icons_blist_loaded_cb()
761 {
762 PurpleBlistNode *node = purple_blist_get_root();
763 const char *dirname = purple_buddy_icons_get_cache_dir();
764
765 /* Doing this once here saves having to check it inside a loop. */
766 if (old_icons_dir != NULL)
767 {
768 if (!g_file_test(dirname, G_FILE_TEST_IS_DIR))
769 {
770 purple_debug_info("buddyicon", "Creating icon cache directory.\n");
771
772 if (g_mkdir(dirname, S_IRUSR | S_IWUSR | S_IXUSR) < 0)
773 {
774 purple_debug_error("buddyicon",
775 "Unable to create directory %s: %s\n",
776 dirname, strerror(errno));
478 } 777 }
479 } 778 }
480 g_free(filename); 779 }
481 } 780
482 781 while (node != NULL)
483 return ret; 782 {
783 if (PURPLE_BLIST_NODE_IS_BUDDY(node))
784 {
785 const char *filename;
786
787 filename = purple_blist_node_get_string(node, "buddy_icon");
788 if (filename != NULL)
789 {
790 if (old_icons_dir != NULL)
791 {
792 migrate_buddy_icon(node,
793 "buddy_icon",
794 dirname, filename);
795 }
796 else
797 {
798 // TODO: If filename doesn't exist, drop the setting.
799 ref_filename(filename);
800 }
801 }
802
803 filename = purple_blist_node_get_string(node, "custom_buddy_icon");
804 if (filename != NULL)
805 {
806 if (old_icons_dir != NULL)
807 {
808 migrate_buddy_icon(node,
809 "custom_buddy_icon",
810 dirname, filename);
811 }
812 else
813 {
814 // TODO: If filename doesn't exist, drop the setting.
815 ref_filename(filename);
816 }
817 }
818 }
819 node = purple_blist_node_next(node, TRUE);
820 }
484 } 821 }
485 822
486 void 823 void
487 purple_buddy_icons_set_caching(gboolean caching) 824 purple_buddy_icons_set_caching(gboolean caching)
488 { 825 {
508 purple_buddy_icons_get_cache_dir(void) 845 purple_buddy_icons_get_cache_dir(void)
509 { 846 {
510 return cache_dir; 847 return cache_dir;
511 } 848 }
512 849
850 // TODO: Deal with this
513 char *purple_buddy_icons_get_full_path(const char *icon) { 851 char *purple_buddy_icons_get_full_path(const char *icon) {
514 if (icon == NULL) 852 if (icon == NULL)
515 return NULL; 853 return NULL;
516 854
517 if (g_file_test(icon, G_FILE_TEST_IS_REGULAR)) 855 if (g_file_test(icon, G_FILE_TEST_IS_REGULAR))
533 { 871 {
534 account_cache = g_hash_table_new_full( 872 account_cache = g_hash_table_new_full(
535 g_direct_hash, g_direct_equal, 873 g_direct_hash, g_direct_equal,
536 NULL, (GFreeFunc)g_hash_table_destroy); 874 NULL, (GFreeFunc)g_hash_table_destroy);
537 875
876 icon_data_cache = g_hash_table_new(g_str_hash, g_str_equal);
877 icon_file_cache = g_hash_table_new_full(g_str_hash, g_str_equal,
878 g_free, NULL);
879
538 cache_dir = g_build_filename(purple_user_dir(), "icons", NULL); 880 cache_dir = g_build_filename(purple_user_dir(), "icons", NULL);
539 } 881 }
540 882
541 void 883 void
542 purple_buddy_icons_uninit() 884 purple_buddy_icons_uninit()
543 { 885 {
544 g_hash_table_destroy(account_cache); 886 g_hash_table_destroy(account_cache);
887 g_hash_table_destroy(icon_data_cache);
888 g_hash_table_destroy(icon_file_cache);
889 g_free(old_icons_dir);
545 } 890 }
546 891
547 void purple_buddy_icon_get_scale_size(PurpleBuddyIconSpec *spec, int *width, int *height) 892 void purple_buddy_icon_get_scale_size(PurpleBuddyIconSpec *spec, int *width, int *height)
548 { 893 {
549 int new_width, new_height; 894 int new_width, new_height;
570 } 915 }
571 916
572 *width = new_width; 917 *width = new_width;
573 *height = new_height; 918 *height = new_height;
574 } 919 }
575