Mercurial > pidgin
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 |