comparison src/blist.c @ 10428:04c663ccbcb1

[gaim-migrate @ 11680] Shuffle some things around in blist.c and make some things more uniform. committer: Tailor Script <tailor@pidgin.im>
author Mark Doliner <mark@kingant.net>
date Mon, 27 Dec 2004 04:43:49 +0000
parents 16d63d8c26d8
children e41f0668a648
comparison
equal deleted inserted replaced
10427:16d63d8c26d8 10428:04c663ccbcb1
49 }; 49 };
50 50
51 static GaimBlistUiOps *blist_ui_ops = NULL; 51 static GaimBlistUiOps *blist_ui_ops = NULL;
52 52
53 static GaimBuddyList *gaimbuddylist = NULL; 53 static GaimBuddyList *gaimbuddylist = NULL;
54 static guint blist_save_timer = 0; 54 static guint save_timer = 0;
55 static gboolean blist_loaded = FALSE; 55 static gboolean blist_loaded = FALSE;
56 56
57 57
58 /***************************************************************************** 58 /*********************************************************************
59 * Private Utility functions * 59 * Private utility functions *
60 *****************************************************************************/ 60 *********************************************************************/
61
61 static GaimBlistNode *gaim_blist_get_last_sibling(GaimBlistNode *node) 62 static GaimBlistNode *gaim_blist_get_last_sibling(GaimBlistNode *node)
62 { 63 {
63 GaimBlistNode *n = node; 64 GaimBlistNode *n = node;
64 if (!n) 65 if (!n)
65 return NULL; 66 return NULL;
95 { 96 {
96 g_free(hb->name); 97 g_free(hb->name);
97 g_free(hb); 98 g_free(hb);
98 } 99 }
99 100
100 void gaim_contact_invalidate_priority_buddy(GaimContact *contact) 101
101 { 102 /*********************************************************************
102 g_return_if_fail(contact != NULL); 103 * Writting to disk *
103 104 *********************************************************************/
104 contact->priority_valid = FALSE; 105
105 } 106 static void
106 107 blist_print_setting(const char *key,
107 static void gaim_contact_compute_priority_buddy(GaimContact *contact) 108 struct gaim_blist_node_setting *setting, FILE *file, int indent)
109 {
110 char *key_val, *data_val = NULL;
111 const char *type = NULL;
112 int i;
113
114 if (!key)
115 return;
116
117 switch(setting->type) {
118 case GAIM_BLIST_NODE_SETTING_BOOL:
119 type = "bool";
120 data_val = g_strdup_printf("%d", setting->value.boolean);
121 break;
122 case GAIM_BLIST_NODE_SETTING_INT:
123 type = "int";
124 data_val = g_strdup_printf("%d", setting->value.integer);
125 break;
126 case GAIM_BLIST_NODE_SETTING_STRING:
127 if (!setting->value.string)
128 return;
129
130 type = "string";
131 data_val = g_markup_escape_text(setting->value.string, -1);
132 break;
133 }
134
135 /* this can't happen */
136 if (!type || !data_val)
137 return;
138
139 for (i=0; i<indent; i++) fprintf(file, "\t");
140
141 key_val = g_markup_escape_text(key, -1);
142 fprintf(file, "<setting name=\"%s\" type=\"%s\">%s</setting>\n", key_val, type,
143 data_val);
144
145 g_free(key_val);
146 g_free(data_val);
147 }
148
149 static void
150 blist_print_group_settings(gpointer key, gpointer data,
151 gpointer user_data)
152 {
153 blist_print_setting(key, data, user_data, 3);
154 }
155
156 static void
157 blist_print_buddy_settings(gpointer key, gpointer data,
158 gpointer user_data)
159 {
160 blist_print_setting(key, data, user_data, 5);
161 }
162
163 static void
164 blist_print_cnode_settings(gpointer key, gpointer data,
165 gpointer user_data)
166 {
167 blist_print_setting(key, data, user_data, 4);
168 }
169
170 static void
171 blist_print_chat_components(gpointer key, gpointer data,
172 gpointer user_data)
173 {
174 char *key_val;
175 char *data_val;
176 FILE *file = user_data;
177
178 if (!key || !data)
179 return;
180
181 key_val = g_markup_escape_text(key, -1);
182 data_val = g_markup_escape_text(data, -1);
183
184 fprintf(file, "\t\t\t\t<component name=\"%s\">%s</component>\n", key_val,
185 data_val);
186 g_free(key_val);
187 g_free(data_val);
188 }
189
190 static void
191 print_buddy(FILE *file, GaimBuddy *buddy)
192 {
193 char *bud_name = g_markup_escape_text(buddy->name, -1);
194 char *bud_alias = NULL;
195 char *acct_name = g_markup_escape_text(buddy->account->username, -1);
196 if (buddy->alias)
197 bud_alias= g_markup_escape_text(buddy->alias, -1);
198 fprintf(file, "\t\t\t\t<buddy account=\"%s\" proto=\"%s\">\n", acct_name,
199 gaim_account_get_protocol_id(buddy->account));
200
201 fprintf(file, "\t\t\t\t\t<name>%s</name>\n", bud_name);
202 if (bud_alias) {
203 fprintf(file, "\t\t\t\t\t<alias>%s</alias>\n", bud_alias);
204 }
205 g_hash_table_foreach(buddy->node.settings, blist_print_buddy_settings, file);
206 fprintf(file, "\t\t\t\t</buddy>\n");
207 g_free(bud_name);
208 g_free(bud_alias);
209 g_free(acct_name);
210 }
211
212 /* check for flagging and account exclusion on buddy */
213 static gboolean
214 blist_buddy_should_save(GaimAccount *exp_acct, GaimBuddy *buddy)
215 {
216 if (! GAIM_BLIST_NODE_SHOULD_SAVE((GaimBlistNode *) buddy))
217 return FALSE;
218
219 if (exp_acct && buddy->account != exp_acct)
220 return FALSE;
221
222 return TRUE;
223 }
224
225 static void
226 blist_write_buddy(FILE *file, GaimAccount *exp_acct, GaimBuddy *buddy)
227 {
228 if (blist_buddy_should_save(exp_acct, buddy))
229 print_buddy(file, buddy);
230 }
231
232 /* check for flagging and account exclusion on contact and all members */
233 static gboolean
234 blist_contact_should_save(GaimAccount *exp_acct, GaimContact *contact)
235 {
236 GaimBlistNode *bnode, *cnode = (GaimBlistNode *) contact;
237
238 if (! GAIM_BLIST_NODE_SHOULD_SAVE(cnode))
239 return FALSE;
240
241 for (bnode = cnode->child; bnode; bnode = bnode->next) {
242 if (! GAIM_BLIST_NODE_IS_BUDDY(bnode))
243 continue;
244
245 if (blist_buddy_should_save(exp_acct, (GaimBuddy *) bnode))
246 return TRUE;
247 }
248
249 return FALSE;
250 }
251
252 static void
253 blist_write_contact(FILE *file, GaimAccount *exp_acct, GaimContact *contact)
254 {
255 GaimBlistNode *bnode, *cnode = (GaimBlistNode *) contact;
256
257 if (!blist_contact_should_save(exp_acct, contact))
258 return;
259
260 fprintf(file, "\t\t\t<contact");
261 if (contact->alias) {
262 char *alias = g_markup_escape_text(contact->alias, -1);
263 fprintf(file, " alias=\"%s\"", alias);
264 g_free(alias);
265 }
266 fprintf(file, ">\n");
267
268 for (bnode = cnode->child; bnode; bnode = bnode->next) {
269 if (GAIM_BLIST_NODE_IS_BUDDY(bnode)) {
270 blist_write_buddy(file, exp_acct, (GaimBuddy *) bnode);
271 }
272 }
273
274 g_hash_table_foreach(cnode->settings, blist_print_cnode_settings, file);
275 fprintf(file, "\t\t\t</contact>\n");
276 }
277
278 static void
279 blist_write_chat(FILE *file, GaimAccount *exp_acct, GaimChat *chat)
280 {
281 char *acct_name;
282
283 if (! GAIM_BLIST_NODE_SHOULD_SAVE((GaimBlistNode *) chat))
284 return;
285
286 if (exp_acct && chat->account != exp_acct)
287 return;
288
289 acct_name = g_markup_escape_text(chat->account->username, -1);
290 fprintf(file, "\t\t\t<chat proto=\"%s\" account=\"%s\">\n",
291 gaim_account_get_protocol_id(chat->account), acct_name);
292 g_free(acct_name);
293
294 if (chat->alias) {
295 char *chat_alias = g_markup_escape_text(chat->alias, -1);
296 fprintf(file, "\t\t\t\t<alias>%s</alias>\n", chat_alias);
297 g_free(chat_alias);
298 }
299
300 g_hash_table_foreach(chat->components, blist_print_chat_components, file);
301 g_hash_table_foreach(chat->node.settings, blist_print_cnode_settings, file);
302
303 fprintf(file, "\t\t\t</chat>\n");
304 }
305
306 static void
307 blist_write_group(FILE *file, GaimAccount *exp_acct, GaimGroup *group)
308 {
309 GaimBlistNode *cnode, *gnode = (GaimBlistNode *) group;
310 char *group_name;
311
312 if (! GAIM_BLIST_NODE_SHOULD_SAVE(gnode))
313 return;
314
315 if (exp_acct && ! gaim_group_on_account(group, exp_acct))
316 return;
317
318 group_name = g_markup_escape_text(group->name, -1);
319 fprintf(file, "\t\t<group name=\"%s\">\n", group_name);
320 g_free(group_name);
321
322 g_hash_table_foreach(group->node.settings,
323 blist_print_group_settings, file);
324
325 for (cnode = gnode->child; cnode; cnode = cnode->next) {
326 if (GAIM_BLIST_NODE_IS_CONTACT(cnode)) {
327 blist_write_contact(file, exp_acct, (GaimContact *) cnode);
328 } else if (GAIM_BLIST_NODE_IS_CHAT(cnode)) {
329 blist_write_chat(file, exp_acct, (GaimChat *) cnode);
330 }
331 }
332
333 fprintf(file, "\t\t</group>\n");
334 }
335
336 static void
337 blist_write_privacy_account(FILE *file, GaimAccount *exp_acct, GaimAccount *account)
338 {
339 char *acct_name;
340 GSList *buds;
341
342 if(exp_acct && exp_acct != account)
343 return;
344
345 acct_name = g_markup_escape_text(account->username, -1);
346 fprintf(file, "\t\t<account proto=\"%s\" name=\"%s\" mode=\"%d\">\n",
347 gaim_account_get_protocol_id(account),
348 acct_name, account->perm_deny);
349 g_free(acct_name);
350
351 for (buds = account->permit; buds; buds = buds->next) {
352 char *bud_name = g_markup_escape_text(buds->data, -1);
353 fprintf(file, "\t\t\t<permit>%s</permit>\n", bud_name);
354 g_free(bud_name);
355 }
356
357 for (buds = account->deny; buds; buds = buds->next) {
358 char *bud_name = g_markup_escape_text(buds->data, -1);
359 fprintf(file, "\t\t\t<block>%s</block>\n", bud_name);
360 g_free(bud_name);
361 }
362
363 fprintf(file, "\t\t</account>\n");
364 }
365
366 static void
367 gaim_blist_write(FILE *file, GaimAccount *exp_acct)
368 {
369 GList *accounts;
370 GaimBlistNode *gnode;
371
372 fprintf(file, "<?xml version='1.0' encoding='UTF-8' ?>\n\n");
373 fprintf(file, "<gaim version='1.0'>\n");
374 fprintf(file, "\t<blist>\n");
375
376 for (gnode = gaimbuddylist->root; gnode; gnode = gnode->next) {
377 if (GAIM_BLIST_NODE_IS_GROUP(gnode))
378 blist_write_group(file, exp_acct, (GaimGroup *) gnode);
379 }
380
381 fprintf(file, "\t</blist>\n");
382 fprintf(file, "\t<privacy>\n");
383
384 for (accounts = gaim_accounts_get_all(); accounts; accounts = accounts->next) {
385 blist_write_privacy_account(file, exp_acct, (GaimAccount *) accounts->data);
386 }
387
388 fprintf(file, "\t</privacy>\n");
389 fprintf(file, "</gaim>\n");
390 }
391
392 void
393 gaim_blist_sync()
394 {
395 FILE *file;
396 struct stat st;
397 const char *user_dir = gaim_user_dir();
398 char *filename;
399 char *filename_real;
400
401 g_return_if_fail(user_dir != NULL);
402
403 if (!blist_loaded) {
404 gaim_debug_warning("blist save",
405 "AHH!! Tried to write the blist before we read it!\n");
406 return;
407 }
408
409 if (!g_file_test(user_dir, G_FILE_TEST_IS_DIR))
410 mkdir(user_dir, S_IRUSR | S_IWUSR | S_IXUSR);
411
412 filename = g_build_filename(user_dir, "blist.xml.save", NULL);
413
414 if ((file = fopen(filename, "w"))) {
415 gaim_blist_write(file, NULL);
416 fclose(file);
417 chmod(filename, S_IRUSR | S_IWUSR);
418 } else {
419 gaim_debug_error("blist", "Unable to write %s\n", filename);
420 g_free(filename);
421 return;
422 }
423
424 if (stat(filename, &st) || (st.st_size == 0)) {
425 gaim_debug_error("blist", "Failed to save blist\n");
426 unlink(filename);
427 g_free(filename);
428 return;
429 }
430
431 filename_real = g_build_filename(user_dir, "blist.xml", NULL);
432
433 if (rename(filename, filename_real) < 0)
434 gaim_debug_error("blist",
435 "Error renaming %s to %s\n", filename, filename_real);
436
437 g_free(filename);
438 g_free(filename_real);
439 }
440
441 static gboolean
442 save_cb(gpointer data)
443 {
444 gaim_blist_sync();
445 save_timer = 0;
446 return FALSE;
447 }
448
449 static void
450 schedule_blist_save()
451 {
452 if (save_timer == 0)
453 save_timer = gaim_timeout_add(5000, save_cb, NULL);
454 }
455
456
457 /*********************************************************************
458 * Reading from disk *
459 *********************************************************************/
460
461 static void
462 parse_setting(GaimBlistNode *node, xmlnode *setting)
463 {
464 const char *name = xmlnode_get_attrib(setting, "name");
465 const char *type = xmlnode_get_attrib(setting, "type");
466 char *value = xmlnode_get_data(setting);
467
468 if (!value)
469 return;
470
471 if (!type || !strcmp(type, "string"))
472 gaim_blist_node_set_string(node, name, value);
473 else if (!strcmp(type, "bool"))
474 gaim_blist_node_set_bool(node, name, atoi(value));
475 else if (!strcmp(type, "int"))
476 gaim_blist_node_set_int(node, name, atoi(value));
477
478 g_free(value);
479 }
480
481 static void
482 parse_buddy(GaimGroup *group, GaimContact *contact, xmlnode *bnode)
483 {
484 GaimAccount *account;
485 GaimBuddy *buddy;
486 char *name = NULL, *alias = NULL;
487 const char *acct_name, *proto, *protocol;
488 xmlnode *x;
489
490 acct_name = xmlnode_get_attrib(bnode, "account");
491 protocol = xmlnode_get_attrib(bnode, "protocol");
492 proto = xmlnode_get_attrib(bnode, "proto");
493
494 if (!acct_name || (!proto && !protocol))
495 return;
496
497 account = gaim_accounts_find(acct_name, proto ? proto : protocol);
498
499 if (!account)
500 return;
501
502 if ((x = xmlnode_get_child(bnode, "name")))
503 name = xmlnode_get_data(x);
504
505 if (!name)
506 return;
507
508 if ((x = xmlnode_get_child(bnode, "alias")))
509 alias = xmlnode_get_data(x);
510
511 buddy = gaim_buddy_new(account, name, alias);
512 gaim_blist_add_buddy(buddy, contact, group,
513 gaim_blist_get_last_child((GaimBlistNode*)contact));
514
515 for (x = xmlnode_get_child(bnode, "setting"); x; x = xmlnode_get_next_twin(x)) {
516 parse_setting((GaimBlistNode*)buddy, x);
517 }
518
519 g_free(name);
520 if (alias)
521 g_free(alias);
522 }
523
524 static void
525 parse_contact(GaimGroup *group, xmlnode *cnode)
526 {
527 GaimContact *contact = gaim_contact_new();
528 xmlnode *x;
529 const char *alias;
530
531 gaim_blist_add_contact(contact, group,
532 gaim_blist_get_last_child((GaimBlistNode*)group));
533
534 if ((alias = xmlnode_get_attrib(cnode, "alias"))) {
535 gaim_contact_set_alias(contact, alias);
536 }
537
538 for (x = cnode->child; x; x = x->next) {
539 if (x->type != XMLNODE_TYPE_TAG)
540 continue;
541 if (!strcmp(x->name, "buddy"))
542 parse_buddy(group, contact, x);
543 else if (!strcmp(x->name, "setting"))
544 parse_setting((GaimBlistNode*)contact, x);
545 }
546
547 /* if the contact is empty, don't keep it around. it causes problems */
548 if (!((GaimBlistNode*)contact)->child)
549 gaim_blist_remove_contact(contact);
550 }
551
552 static void
553 parse_chat(GaimGroup *group, xmlnode *cnode)
554 {
555 GaimChat *chat;
556 GaimAccount *account;
557 const char *acct_name, *proto, *protocol;
558 xmlnode *x;
559 char *alias = NULL;
560 GHashTable *components;
561
562 acct_name = xmlnode_get_attrib(cnode, "account");
563 protocol = xmlnode_get_attrib(cnode, "protocol");
564 proto = xmlnode_get_attrib(cnode, "proto");
565
566 if (!acct_name || (!proto && !protocol))
567 return;
568
569 account = gaim_accounts_find(acct_name, proto ? proto : protocol);
570
571 if (!account)
572 return;
573
574 if ((x = xmlnode_get_child(cnode, "alias")))
575 alias = xmlnode_get_data(x);
576
577 components = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
578
579 for (x = xmlnode_get_child(cnode, "component"); x; x = xmlnode_get_next_twin(x)) {
580 const char *name;
581 char *value;
582
583 name = xmlnode_get_attrib(x, "name");
584 value = xmlnode_get_data(x);
585 g_hash_table_replace(components, g_strdup(name), value);
586 }
587
588 chat = gaim_chat_new(account, alias, components);
589 gaim_blist_add_chat(chat, group,
590 gaim_blist_get_last_child((GaimBlistNode*)group));
591
592 for (x = xmlnode_get_child(cnode, "setting"); x; x = xmlnode_get_next_twin(x)) {
593 parse_setting((GaimBlistNode*)chat, x);
594 }
595
596 if (alias)
597 g_free(alias);
598 }
599
600 static void
601 parse_group(xmlnode *groupnode)
602 {
603 const char *name = xmlnode_get_attrib(groupnode, "name");
604 GaimGroup *group;
605 xmlnode *cnode;
606
607 if (!name)
608 name = _("Buddies");
609
610 group = gaim_group_new(name);
611 gaim_blist_add_group(group,
612 gaim_blist_get_last_sibling(gaimbuddylist->root));
613
614 for (cnode = groupnode->child; cnode; cnode = cnode->next) {
615 if (cnode->type != XMLNODE_TYPE_TAG)
616 continue;
617 if (!strcmp(cnode->name, "setting"))
618 parse_setting((GaimBlistNode*)group, cnode);
619 else if (!strcmp(cnode->name, "contact") ||
620 !strcmp(cnode->name, "person"))
621 parse_contact(group, cnode);
622 else if (!strcmp(cnode->name, "chat"))
623 parse_chat(group, cnode);
624 }
625 }
626
627 /* TODO: Make static and rename to load_blist */
628 void
629 gaim_blist_load()
630 {
631 xmlnode *gaim, *blist, *privacy;
632
633 blist_loaded = TRUE;
634
635 gaim = gaim_util_read_xml_from_file("blist.xml", _("buddy list"));
636
637 if (gaim == NULL)
638 return;
639
640 blist = xmlnode_get_child(gaim, "blist");
641 if (blist) {
642 xmlnode *groupnode;
643 for (groupnode = xmlnode_get_child(blist, "group"); groupnode != NULL;
644 groupnode = xmlnode_get_next_twin(groupnode)) {
645 parse_group(groupnode);
646 }
647 }
648
649 privacy = xmlnode_get_child(gaim, "privacy");
650 if (privacy) {
651 xmlnode *anode;
652 for (anode = privacy->child; anode; anode = anode->next) {
653 xmlnode *x;
654 GaimAccount *account;
655 const char *acct_name, *proto, *mode, *protocol;
656
657 acct_name = xmlnode_get_attrib(anode, "name");
658 protocol = xmlnode_get_attrib(anode, "protocol");
659 proto = xmlnode_get_attrib(anode, "proto");
660 mode = xmlnode_get_attrib(anode, "mode");
661
662 if (!acct_name || (!proto && !protocol) || !mode)
663 continue;
664
665 account = gaim_accounts_find(acct_name, proto ? proto : protocol);
666
667 if (!account)
668 continue;
669
670 account->perm_deny = atoi(mode);
671
672 for (x = anode->child; x; x = x->next) {
673 char *name;
674 if (x->type != XMLNODE_TYPE_TAG)
675 continue;
676
677 if (!strcmp(x->name, "permit")) {
678 name = xmlnode_get_data(x);
679 gaim_privacy_permit_add(account, name, TRUE);
680 g_free(name);
681 } else if (!strcmp(x->name, "block")) {
682 name = xmlnode_get_data(x);
683 gaim_privacy_deny_add(account, name, TRUE);
684 g_free(name);
685 }
686 }
687 }
688 }
689
690 xmlnode_free(gaim);
691 }
692
693
694 /*********************************************************************
695 * Stuff *
696 *********************************************************************/
697
698 static void
699 gaim_contact_compute_priority_buddy(GaimContact *contact)
108 { 700 {
109 GaimBlistNode *bnode; 701 GaimBlistNode *bnode;
110 GaimBuddy *new_priority = NULL; 702 GaimBuddy *new_priority = NULL;
111 703
112 g_return_if_fail(contact != NULL); 704 g_return_if_fail(contact != NULL);
142 } 734 }
143 } 735 }
144 736
145 contact->priority = new_priority; 737 contact->priority = new_priority;
146 contact->priority_valid = TRUE; 738 contact->priority_valid = TRUE;
147 }
148
149 static gboolean blist_save_callback(gpointer data)
150 {
151 gaim_blist_sync();
152 blist_save_timer = 0;
153 return FALSE;
154 }
155
156 static void schedule_blist_save()
157 {
158 if (blist_save_timer != 0)
159 gaim_timeout_remove(blist_save_timer);
160 blist_save_timer = gaim_timeout_add(1000, blist_save_callback, NULL);
161 } 739 }
162 740
163 741
164 /***************************************************************************** 742 /*****************************************************************************
165 * Public API functions * 743 * Public API functions *
337 if (ops && ops->update) 915 if (ops && ops->update)
338 ops->update(gaimbuddylist, (GaimBlistNode *)buddy); 916 ops->update(gaimbuddylist, (GaimBlistNode *)buddy);
339 } 917 }
340 918
341 /* 919 /*
342 * XXX - Maybe remove the call to this from server.c and call it 920 * TODO: Maybe remove the call to this from server.c and call it
343 * from oscar.c and toc.c instead? 921 * from oscar.c and toc.c instead?
344 */ 922 */
345 void gaim_blist_rename_buddy(GaimBuddy *buddy, const char *name) 923 void gaim_blist_rename_buddy(GaimBuddy *buddy, const char *name)
346 { 924 {
347 GaimBlistUiOps *ops = gaimbuddylist->ui_ops; 925 GaimBlistUiOps *ops = gaimbuddylist->ui_ops;
433 if (conv) 1011 if (conv)
434 gaim_conversation_autoset_title(conv); 1012 gaim_conversation_autoset_title(conv);
435 } 1013 }
436 1014
437 /* 1015 /*
438 * XXX - If merging, prompt the user if they want to merge. 1016 * TODO: If merging, prompt the user if they want to merge.
439 */ 1017 */
440 void gaim_blist_rename_group(GaimGroup *source, const char *new_name) 1018 void gaim_blist_rename_group(GaimGroup *source, const char *new_name)
441 { 1019 {
442 GaimBlistUiOps *ops = gaimbuddylist->ui_ops; 1020 GaimBlistUiOps *ops = gaimbuddylist->ui_ops;
443 GaimGroup *dest; 1021 GaimGroup *dest;
458 1036
459 prev = gaim_blist_get_last_child((GaimBlistNode*)dest); 1037 prev = gaim_blist_get_last_child((GaimBlistNode*)dest);
460 child = ((GaimBlistNode*)source)->child; 1038 child = ((GaimBlistNode*)source)->child;
461 1039
462 /* 1040 /*
463 * XXX - This seems like a dumb way to do this... why not just 1041 * TODO: This seems like a dumb way to do this... why not just
464 * append all children from the old group to the end of the new 1042 * append all children from the old group to the end of the new
465 * one? PRPLs might be expecting to receive an add_buddy() for 1043 * one? PRPLs might be expecting to receive an add_buddy() for
466 * each moved buddy... 1044 * each moved buddy...
467 */ 1045 */
468 while (child) 1046 while (child)
913 buddy = (GaimBuddy *)bnode; 1491 buddy = (GaimBuddy *)bnode;
914 if (buddy->account == account) 1492 if (buddy->account == account)
915 return TRUE; 1493 return TRUE;
916 } 1494 }
917 return FALSE; 1495 return FALSE;
1496 }
1497
1498 void gaim_contact_invalidate_priority_buddy(GaimContact *contact)
1499 {
1500 g_return_if_fail(contact != NULL);
1501
1502 contact->priority_valid = FALSE;
918 } 1503 }
919 1504
920 GaimGroup *gaim_group_new(const char *name) 1505 GaimGroup *gaim_group_new(const char *name)
921 { 1506 {
922 GaimBlistUiOps *ops = gaim_blist_get_ui_ops(); 1507 GaimBlistUiOps *ops = gaim_blist_get_ui_ops();
1740 ((GaimGroup*)gnode)->currentsize--; 2325 ((GaimGroup*)gnode)->currentsize--;
1741 2326
1742 ((GaimBuddy*)bnode)->present = GAIM_BUDDY_OFFLINE; 2327 ((GaimBuddy*)bnode)->present = GAIM_BUDDY_OFFLINE;
1743 2328
1744 ((GaimBuddy*)bnode)->uc = 0; 2329 ((GaimBuddy*)bnode)->uc = 0;
1745 /* XXX ((GaimBuddy*)bnode)->idle = 0; */ 2330 /* TODO: ((GaimBuddy*)bnode)->idle = 0; */
1746
1747 2331
1748 if (ops && ops->remove) 2332 if (ops && ops->remove)
1749 ops->remove(gaimbuddylist, bnode); 2333 ops->remove(gaimbuddylist, bnode);
1750 } 2334 }
1751 } 2335 }
1780 } 2364 }
1781 } 2365 }
1782 return FALSE; 2366 return FALSE;
1783 } 2367 }
1784 2368
1785 static void parse_setting(GaimBlistNode *node, xmlnode *setting)
1786 {
1787 const char *name = xmlnode_get_attrib(setting, "name");
1788 const char *type = xmlnode_get_attrib(setting, "type");
1789 char *value = xmlnode_get_data(setting);
1790
1791 if (!value)
1792 return;
1793
1794 if (!type || !strcmp(type, "string"))
1795 gaim_blist_node_set_string(node, name, value);
1796 else if (!strcmp(type, "bool"))
1797 gaim_blist_node_set_bool(node, name, atoi(value));
1798 else if (!strcmp(type, "int"))
1799 gaim_blist_node_set_int(node, name, atoi(value));
1800
1801 g_free(value);
1802 }
1803
1804 static void parse_buddy(GaimGroup *group, GaimContact *contact, xmlnode *bnode)
1805 {
1806 GaimAccount *account;
1807 GaimBuddy *buddy;
1808 char *name = NULL, *alias = NULL;
1809 const char *acct_name, *proto, *protocol;
1810 xmlnode *x;
1811
1812 acct_name = xmlnode_get_attrib(bnode, "account");
1813 protocol = xmlnode_get_attrib(bnode, "protocol");
1814 proto = xmlnode_get_attrib(bnode, "proto");
1815
1816 if (!acct_name || (!proto && !protocol))
1817 return;
1818
1819 account = gaim_accounts_find(acct_name, proto ? proto : protocol);
1820
1821 if (!account)
1822 return;
1823
1824 if ((x = xmlnode_get_child(bnode, "name")))
1825 name = xmlnode_get_data(x);
1826
1827 if (!name)
1828 return;
1829
1830 if ((x = xmlnode_get_child(bnode, "alias")))
1831 alias = xmlnode_get_data(x);
1832
1833 buddy = gaim_buddy_new(account, name, alias);
1834 gaim_blist_add_buddy(buddy, contact, group,
1835 gaim_blist_get_last_child((GaimBlistNode*)contact));
1836
1837 for (x = xmlnode_get_child(bnode, "setting"); x; x = xmlnode_get_next_twin(x)) {
1838 parse_setting((GaimBlistNode*)buddy, x);
1839 }
1840
1841 g_free(name);
1842 if (alias)
1843 g_free(alias);
1844 }
1845
1846 static void parse_contact(GaimGroup *group, xmlnode *cnode)
1847 {
1848 GaimContact *contact = gaim_contact_new();
1849 xmlnode *x;
1850 const char *alias;
1851
1852 gaim_blist_add_contact(contact, group,
1853 gaim_blist_get_last_child((GaimBlistNode*)group));
1854
1855 if ((alias = xmlnode_get_attrib(cnode, "alias"))) {
1856 gaim_contact_set_alias(contact, alias);
1857 }
1858
1859 for (x = cnode->child; x; x = x->next) {
1860 if (x->type != XMLNODE_TYPE_TAG)
1861 continue;
1862 if (!strcmp(x->name, "buddy"))
1863 parse_buddy(group, contact, x);
1864 else if (!strcmp(x->name, "setting"))
1865 parse_setting((GaimBlistNode*)contact, x);
1866 }
1867
1868 /* if the contact is empty, don't keep it around. it causes problems */
1869 if (!((GaimBlistNode*)contact)->child)
1870 gaim_blist_remove_contact(contact);
1871 }
1872
1873 static void parse_chat(GaimGroup *group, xmlnode *cnode)
1874 {
1875 GaimChat *chat;
1876 GaimAccount *account;
1877 const char *acct_name, *proto, *protocol;
1878 xmlnode *x;
1879 char *alias = NULL;
1880 GHashTable *components;
1881
1882 acct_name = xmlnode_get_attrib(cnode, "account");
1883 protocol = xmlnode_get_attrib(cnode, "protocol");
1884 proto = xmlnode_get_attrib(cnode, "proto");
1885
1886 if (!acct_name || (!proto && !protocol))
1887 return;
1888
1889 account = gaim_accounts_find(acct_name, proto ? proto : protocol);
1890
1891 if (!account)
1892 return;
1893
1894 if ((x = xmlnode_get_child(cnode, "alias")))
1895 alias = xmlnode_get_data(x);
1896
1897 components = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
1898
1899 for (x = xmlnode_get_child(cnode, "component"); x; x = xmlnode_get_next_twin(x)) {
1900 const char *name;
1901 char *value;
1902
1903 name = xmlnode_get_attrib(x, "name");
1904 value = xmlnode_get_data(x);
1905 g_hash_table_replace(components, g_strdup(name), value);
1906 }
1907
1908 chat = gaim_chat_new(account, alias, components);
1909 gaim_blist_add_chat(chat, group,
1910 gaim_blist_get_last_child((GaimBlistNode*)group));
1911
1912 for (x = xmlnode_get_child(cnode, "setting"); x; x = xmlnode_get_next_twin(x)) {
1913 parse_setting((GaimBlistNode*)chat, x);
1914 }
1915
1916 if (alias)
1917 g_free(alias);
1918 }
1919
1920 static void parse_group(xmlnode *groupnode)
1921 {
1922 const char *name = xmlnode_get_attrib(groupnode, "name");
1923 GaimGroup *group;
1924 xmlnode *cnode;
1925
1926 if (!name)
1927 name = _("Buddies");
1928
1929 group = gaim_group_new(name);
1930 gaim_blist_add_group(group,
1931 gaim_blist_get_last_sibling(gaimbuddylist->root));
1932
1933 for (cnode = groupnode->child; cnode; cnode = cnode->next) {
1934 if (cnode->type != XMLNODE_TYPE_TAG)
1935 continue;
1936 if (!strcmp(cnode->name, "setting"))
1937 parse_setting((GaimBlistNode*)group, cnode);
1938 else if (!strcmp(cnode->name, "contact") ||
1939 !strcmp(cnode->name, "person"))
1940 parse_contact(group, cnode);
1941 else if (!strcmp(cnode->name, "chat"))
1942 parse_chat(group, cnode);
1943 }
1944 }
1945
1946 void gaim_blist_load()
1947 {
1948 xmlnode *gaim, *blist, *privacy;
1949
1950 blist_loaded = TRUE;
1951
1952 gaim = gaim_util_read_xml_from_file("blist.xml", _("buddy list"));
1953
1954 if (gaim == NULL)
1955 return;
1956
1957 blist = xmlnode_get_child(gaim, "blist");
1958 if (blist) {
1959 xmlnode *groupnode;
1960 for (groupnode = xmlnode_get_child(blist, "group"); groupnode != NULL;
1961 groupnode = xmlnode_get_next_twin(groupnode)) {
1962 parse_group(groupnode);
1963 }
1964 }
1965
1966 privacy = xmlnode_get_child(gaim, "privacy");
1967 if (privacy) {
1968 xmlnode *anode;
1969 for (anode = privacy->child; anode; anode = anode->next) {
1970 xmlnode *x;
1971 GaimAccount *account;
1972 const char *acct_name, *proto, *mode, *protocol;
1973
1974 acct_name = xmlnode_get_attrib(anode, "name");
1975 protocol = xmlnode_get_attrib(anode, "protocol");
1976 proto = xmlnode_get_attrib(anode, "proto");
1977 mode = xmlnode_get_attrib(anode, "mode");
1978
1979 if (!acct_name || (!proto && !protocol) || !mode)
1980 continue;
1981
1982 account = gaim_accounts_find(acct_name, proto ? proto : protocol);
1983
1984 if (!account)
1985 continue;
1986
1987 account->perm_deny = atoi(mode);
1988
1989 for (x = anode->child; x; x = x->next) {
1990 char *name;
1991 if (x->type != XMLNODE_TYPE_TAG)
1992 continue;
1993
1994 if (!strcmp(x->name, "permit")) {
1995 name = xmlnode_get_data(x);
1996 gaim_privacy_permit_add(account, name, TRUE);
1997 g_free(name);
1998 } else if (!strcmp(x->name, "block")) {
1999 name = xmlnode_get_data(x);
2000 gaim_privacy_deny_add(account, name, TRUE);
2001 g_free(name);
2002 }
2003 }
2004 }
2005 }
2006
2007 xmlnode_free(gaim);
2008 }
2009
2010 void 2369 void
2011 gaim_blist_request_add_buddy(GaimAccount *account, const char *username, 2370 gaim_blist_request_add_buddy(GaimAccount *account, const char *username,
2012 const char *group, const char *alias) 2371 const char *group, const char *alias)
2013 { 2372 {
2014 GaimBlistUiOps *ui_ops; 2373 GaimBlistUiOps *ui_ops;
2039 ui_ops = gaim_blist_get_ui_ops(); 2398 ui_ops = gaim_blist_get_ui_ops();
2040 2399
2041 if (ui_ops != NULL && ui_ops->request_add_group != NULL) 2400 if (ui_ops != NULL && ui_ops->request_add_group != NULL)
2042 ui_ops->request_add_group(); 2401 ui_ops->request_add_group();
2043 } 2402 }
2044
2045 static void blist_print_setting(const char *key,
2046 struct gaim_blist_node_setting *setting, FILE *file, int indent)
2047 {
2048 char *key_val, *data_val = NULL;
2049 const char *type = NULL;
2050 int i;
2051
2052 if (!key)
2053 return;
2054
2055 switch(setting->type) {
2056 case GAIM_BLIST_NODE_SETTING_BOOL:
2057 type = "bool";
2058 data_val = g_strdup_printf("%d", setting->value.boolean);
2059 break;
2060 case GAIM_BLIST_NODE_SETTING_INT:
2061 type = "int";
2062 data_val = g_strdup_printf("%d", setting->value.integer);
2063 break;
2064 case GAIM_BLIST_NODE_SETTING_STRING:
2065 if (!setting->value.string)
2066 return;
2067
2068 type = "string";
2069 data_val = g_markup_escape_text(setting->value.string, -1);
2070 break;
2071 }
2072
2073 /* this can't happen */
2074 if (!type || !data_val)
2075 return;
2076
2077 for (i=0; i<indent; i++) fprintf(file, "\t");
2078
2079 key_val = g_markup_escape_text(key, -1);
2080 fprintf(file, "<setting name=\"%s\" type=\"%s\">%s</setting>\n", key_val, type,
2081 data_val);
2082
2083 g_free(key_val);
2084 g_free(data_val);
2085 }
2086
2087 static void blist_print_group_settings(gpointer key, gpointer data,
2088 gpointer user_data)
2089 {
2090 blist_print_setting(key, data, user_data, 3);
2091 }
2092
2093 static void blist_print_buddy_settings(gpointer key, gpointer data,
2094 gpointer user_data)
2095 {
2096 blist_print_setting(key, data, user_data, 5);
2097 }
2098
2099 static void blist_print_cnode_settings(gpointer key, gpointer data,
2100 gpointer user_data)
2101 {
2102 blist_print_setting(key, data, user_data, 4);
2103 }
2104
2105 static void blist_print_chat_components(gpointer key, gpointer data,
2106 gpointer user_data) {
2107 char *key_val;
2108 char *data_val;
2109 FILE *file = user_data;
2110
2111 if (!key || !data)
2112 return;
2113
2114 key_val = g_markup_escape_text(key, -1);
2115 data_val = g_markup_escape_text(data, -1);
2116
2117 fprintf(file, "\t\t\t\t<component name=\"%s\">%s</component>\n", key_val,
2118 data_val);
2119 g_free(key_val);
2120 g_free(data_val);
2121 }
2122
2123 static void print_buddy(FILE *file, GaimBuddy *buddy)
2124 {
2125 char *bud_name = g_markup_escape_text(buddy->name, -1);
2126 char *bud_alias = NULL;
2127 char *acct_name = g_markup_escape_text(buddy->account->username, -1);
2128 if (buddy->alias)
2129 bud_alias= g_markup_escape_text(buddy->alias, -1);
2130 fprintf(file, "\t\t\t\t<buddy account=\"%s\" proto=\"%s\">\n", acct_name,
2131 gaim_account_get_protocol_id(buddy->account));
2132
2133 fprintf(file, "\t\t\t\t\t<name>%s</name>\n", bud_name);
2134 if (bud_alias) {
2135 fprintf(file, "\t\t\t\t\t<alias>%s</alias>\n", bud_alias);
2136 }
2137 g_hash_table_foreach(buddy->node.settings, blist_print_buddy_settings, file);
2138 fprintf(file, "\t\t\t\t</buddy>\n");
2139 g_free(bud_name);
2140 g_free(bud_alias);
2141 g_free(acct_name);
2142 }
2143
2144
2145 /* check for flagging and account exclusion on buddy */
2146 static gboolean blist_buddy_should_save(GaimAccount *exp_acct, GaimBuddy *buddy)
2147 {
2148 if (! GAIM_BLIST_NODE_SHOULD_SAVE((GaimBlistNode *) buddy))
2149 return FALSE;
2150
2151 if (exp_acct && buddy->account != exp_acct)
2152 return FALSE;
2153
2154 return TRUE;
2155 }
2156
2157
2158 static void blist_write_buddy(FILE *file, GaimAccount *exp_acct, GaimBuddy *buddy)
2159 {
2160 if (blist_buddy_should_save(exp_acct, buddy))
2161 print_buddy(file, buddy);
2162 }
2163
2164
2165 /* check for flagging and account exclusion on contact and all members */
2166 static gboolean blist_contact_should_save(GaimAccount *exp_acct, GaimContact *contact)
2167 {
2168 GaimBlistNode *bnode, *cnode = (GaimBlistNode *) contact;
2169
2170 if (! GAIM_BLIST_NODE_SHOULD_SAVE(cnode))
2171 return FALSE;
2172
2173 for (bnode = cnode->child; bnode; bnode = bnode->next) {
2174 if (! GAIM_BLIST_NODE_IS_BUDDY(bnode))
2175 continue;
2176
2177 if (blist_buddy_should_save(exp_acct, (GaimBuddy *) bnode))
2178 return TRUE;
2179 }
2180
2181 return FALSE;
2182 }
2183
2184
2185 static void blist_write_contact(FILE *file, GaimAccount *exp_acct, GaimContact *contact)
2186 {
2187 GaimBlistNode *bnode, *cnode = (GaimBlistNode *) contact;
2188
2189 if (!blist_contact_should_save(exp_acct, contact))
2190 return;
2191
2192 fprintf(file, "\t\t\t<contact");
2193 if (contact->alias) {
2194 char *alias = g_markup_escape_text(contact->alias, -1);
2195 fprintf(file, " alias=\"%s\"", alias);
2196 g_free(alias);
2197 }
2198 fprintf(file, ">\n");
2199
2200 for (bnode = cnode->child; bnode; bnode = bnode->next) {
2201 if (GAIM_BLIST_NODE_IS_BUDDY(bnode)) {
2202 blist_write_buddy(file, exp_acct, (GaimBuddy *) bnode);
2203 }
2204 }
2205
2206 g_hash_table_foreach(cnode->settings, blist_print_cnode_settings, file);
2207 fprintf(file, "\t\t\t</contact>\n");
2208 }
2209
2210
2211 static void blist_write_chat(FILE *file, GaimAccount *exp_acct, GaimChat *chat)
2212 {
2213 char *acct_name;
2214
2215 if (! GAIM_BLIST_NODE_SHOULD_SAVE((GaimBlistNode *) chat))
2216 return;
2217
2218 if (exp_acct && chat->account != exp_acct)
2219 return;
2220
2221 acct_name = g_markup_escape_text(chat->account->username, -1);
2222 fprintf(file, "\t\t\t<chat proto=\"%s\" account=\"%s\">\n",
2223 gaim_account_get_protocol_id(chat->account), acct_name);
2224 g_free(acct_name);
2225
2226 if (chat->alias) {
2227 char *chat_alias = g_markup_escape_text(chat->alias, -1);
2228 fprintf(file, "\t\t\t\t<alias>%s</alias>\n", chat_alias);
2229 g_free(chat_alias);
2230 }
2231
2232 g_hash_table_foreach(chat->components, blist_print_chat_components, file);
2233 g_hash_table_foreach(chat->node.settings, blist_print_cnode_settings, file);
2234
2235 fprintf(file, "\t\t\t</chat>\n");
2236 }
2237
2238
2239 static void blist_write_group(FILE *file, GaimAccount *exp_acct, GaimGroup *group)
2240 {
2241 GaimBlistNode *cnode, *gnode = (GaimBlistNode *) group;
2242 char *group_name;
2243
2244 if (! GAIM_BLIST_NODE_SHOULD_SAVE(gnode))
2245 return;
2246
2247 if (exp_acct && ! gaim_group_on_account(group, exp_acct))
2248 return;
2249
2250 group_name = g_markup_escape_text(group->name, -1);
2251 fprintf(file, "\t\t<group name=\"%s\">\n", group_name);
2252 g_free(group_name);
2253
2254 g_hash_table_foreach(group->node.settings,
2255 blist_print_group_settings, file);
2256
2257 for (cnode = gnode->child; cnode; cnode = cnode->next) {
2258 if (GAIM_BLIST_NODE_IS_CONTACT(cnode)) {
2259 blist_write_contact(file, exp_acct, (GaimContact *) cnode);
2260 } else if (GAIM_BLIST_NODE_IS_CHAT(cnode)) {
2261 blist_write_chat(file, exp_acct, (GaimChat *) cnode);
2262 }
2263 }
2264
2265 fprintf(file, "\t\t</group>\n");
2266 }
2267
2268
2269 static void blist_write_privacy_account(FILE *file, GaimAccount *exp_acct, GaimAccount *account)
2270 {
2271 char *acct_name;
2272 GSList *buds;
2273
2274 if(exp_acct && exp_acct != account)
2275 return;
2276
2277 acct_name = g_markup_escape_text(account->username, -1);
2278 fprintf(file, "\t\t<account proto=\"%s\" name=\"%s\" mode=\"%d\">\n",
2279 gaim_account_get_protocol_id(account),
2280 acct_name, account->perm_deny);
2281 g_free(acct_name);
2282
2283 for (buds = account->permit; buds; buds = buds->next) {
2284 char *bud_name = g_markup_escape_text(buds->data, -1);
2285 fprintf(file, "\t\t\t<permit>%s</permit>\n", bud_name);
2286 g_free(bud_name);
2287 }
2288
2289 for (buds = account->deny; buds; buds = buds->next) {
2290 char *bud_name = g_markup_escape_text(buds->data, -1);
2291 fprintf(file, "\t\t\t<block>%s</block>\n", bud_name);
2292 g_free(bud_name);
2293 }
2294
2295 fprintf(file, "\t\t</account>\n");
2296 }
2297
2298
2299 static void gaim_blist_write(FILE *file, GaimAccount *exp_acct)
2300 {
2301 GList *accounts;
2302 GaimBlistNode *gnode;
2303
2304 fprintf(file, "<?xml version='1.0' encoding='UTF-8' ?>\n\n");
2305 fprintf(file, "<gaim version='1.0'>\n");
2306 fprintf(file, "\t<blist>\n");
2307
2308 for (gnode = gaimbuddylist->root; gnode; gnode = gnode->next) {
2309 if (GAIM_BLIST_NODE_IS_GROUP(gnode))
2310 blist_write_group(file, exp_acct, (GaimGroup *) gnode);
2311 }
2312
2313 fprintf(file, "\t</blist>\n");
2314 fprintf(file, "\t<privacy>\n");
2315
2316 for (accounts = gaim_accounts_get_all(); accounts; accounts = accounts->next) {
2317 blist_write_privacy_account(file, exp_acct, (GaimAccount *) accounts->data);
2318 }
2319
2320 fprintf(file, "\t</privacy>\n");
2321 fprintf(file, "</gaim>\n");
2322 }
2323
2324
2325 void gaim_blist_sync()
2326 {
2327 FILE *file;
2328 struct stat st;
2329 const char *user_dir = gaim_user_dir();
2330 char *filename;
2331 char *filename_real;
2332
2333 g_return_if_fail(user_dir != NULL);
2334
2335 if (!blist_loaded) {
2336 gaim_debug_warning("blist save",
2337 "AHH!! Tried to write the blist before we read it!\n");
2338 return;
2339 }
2340
2341 if (!g_file_test(user_dir, G_FILE_TEST_IS_DIR))
2342 mkdir(user_dir, S_IRUSR | S_IWUSR | S_IXUSR);
2343
2344 filename = g_build_filename(user_dir, "blist.xml.save", NULL);
2345
2346 if ((file = fopen(filename, "w"))) {
2347 gaim_blist_write(file, NULL);
2348 fclose(file);
2349 chmod(filename, S_IRUSR | S_IWUSR);
2350 } else {
2351 gaim_debug_error("blist", "Unable to write %s\n", filename);
2352 g_free(filename);
2353 return;
2354 }
2355
2356 if (stat(filename, &st) || (st.st_size == 0)) {
2357 gaim_debug_error("blist", "Failed to save blist\n");
2358 unlink(filename);
2359 g_free(filename);
2360 return;
2361 }
2362
2363 filename_real = g_build_filename(user_dir, "blist.xml", NULL);
2364
2365 if (rename(filename, filename_real) < 0)
2366 gaim_debug_error("blist",
2367 "Error renaming %s to %s\n", filename, filename_real);
2368
2369 g_free(filename);
2370 g_free(filename_real);
2371 }
2372
2373 2403
2374 static void gaim_blist_node_setting_free(struct gaim_blist_node_setting *setting) 2404 static void gaim_blist_node_setting_free(struct gaim_blist_node_setting *setting)
2375 { 2405 {
2376 switch(setting->type) { 2406 switch(setting->type) {
2377 case GAIM_BLIST_NODE_SETTING_BOOL: 2407 case GAIM_BLIST_NODE_SETTING_BOOL:
2402 g_hash_table_remove(node->settings, key); 2432 g_hash_table_remove(node->settings, key);
2403 2433
2404 schedule_blist_save(); 2434 schedule_blist_save();
2405 } 2435 }
2406 2436
2407
2408 void gaim_blist_node_set_bool(GaimBlistNode* node, const char *key, gboolean value) 2437 void gaim_blist_node_set_bool(GaimBlistNode* node, const char *key, gboolean value)
2409 { 2438 {
2410 struct gaim_blist_node_setting *setting; 2439 struct gaim_blist_node_setting *setting;
2411 2440
2412 g_return_if_fail(node != NULL); 2441 g_return_if_fail(node != NULL);
2520 gaim_signal_emit(gaim_blist_get_handle(), 2549 gaim_signal_emit(gaim_blist_get_handle(),
2521 "blist-node-extended-menu", 2550 "blist-node-extended-menu",
2522 n, &menu); 2551 n, &menu);
2523 return menu; 2552 return menu;
2524 } 2553 }
2525
2526 2554
2527 GaimBlistNodeAction * 2555 GaimBlistNodeAction *
2528 gaim_blist_node_action_new(char *label, 2556 gaim_blist_node_action_new(char *label,
2529 void (*callback)(GaimBlistNode *, gpointer), 2557 void (*callback)(GaimBlistNode *, gpointer),
2530 gpointer data) 2558 gpointer data)
2533 act->label = label; 2561 act->label = label;
2534 act->callback = callback; 2562 act->callback = callback;
2535 act->data = data; 2563 act->data = data;
2536 return act; 2564 return act;
2537 } 2565 }
2538
2539 2566
2540 int gaim_blist_get_group_size(GaimGroup *group, gboolean offline) 2567 int gaim_blist_get_group_size(GaimGroup *group, gboolean offline)
2541 { 2568 {
2542 if (!group) 2569 if (!group)
2543 return 0; 2570 return 0;
2622 } 2649 }
2623 2650
2624 void 2651 void
2625 gaim_blist_uninit(void) 2652 gaim_blist_uninit(void)
2626 { 2653 {
2627 if (blist_save_timer != 0) { 2654 if (save_timer != 0)
2628 gaim_timeout_remove(blist_save_timer); 2655 {
2629 blist_save_timer = 0; 2656 gaim_timeout_remove(save_timer);
2657 save_timer = 0;
2630 gaim_blist_sync(); 2658 gaim_blist_sync();
2631 } 2659 }
2632 2660
2633 gaim_signals_unregister_by_instance(gaim_blist_get_handle()); 2661 gaim_signals_unregister_by_instance(gaim_blist_get_handle());
2634 } 2662 }