Add the name of the guy who found the AIM/ICQ crash
[pidgin-git.git] / libpurple / blist.c
blobbd09c6373a03aadd47b9653bc89a0d167a382977
1 /*
2 * purple
4 * Purple is the legal property of its developers, whose names are too numerous
5 * to list here. Please refer to the COPYRIGHT file distributed with this
6 * source distribution.
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
23 #define _PURPLE_BLIST_C_
25 #include "internal.h"
26 #include "blist.h"
27 #include "conversation.h"
28 #include "dbus-maybe.h"
29 #include "debug.h"
30 #include "notify.h"
31 #include "pounce.h"
32 #include "prefs.h"
33 #include "privacy.h"
34 #include "prpl.h"
35 #include "server.h"
36 #include "signals.h"
37 #include "util.h"
38 #include "value.h"
39 #include "xmlnode.h"
41 static PurpleBlistUiOps *blist_ui_ops = NULL;
43 static PurpleBuddyList *purplebuddylist = NULL;
45 /**
46 * A hash table used for efficient lookups of buddies by name.
47 * PurpleAccount* => GHashTable*, with the inner hash table being
48 * struct _purple_hbuddy => PurpleBuddy*
50 static GHashTable *buddies_cache = NULL;
52 /**
53 * A hash table used for efficient lookups of groups by name.
54 * UTF-8 collate-key => PurpleGroup*.
56 static GHashTable *groups_cache = NULL;
58 static guint save_timer = 0;
59 static gboolean blist_loaded = FALSE;
61 /*********************************************************************
62 * Private utility functions *
63 *********************************************************************/
65 static PurpleBlistNode *purple_blist_get_last_sibling(PurpleBlistNode *node)
67 PurpleBlistNode *n = node;
68 if (!n)
69 return NULL;
70 while (n->next)
71 n = n->next;
72 return n;
75 static PurpleBlistNode *purple_blist_get_last_child(PurpleBlistNode *node)
77 if (!node)
78 return NULL;
79 return purple_blist_get_last_sibling(node->child);
82 struct _list_account_buddies {
83 GSList *list;
84 PurpleAccount *account;
87 struct _purple_hbuddy {
88 char *name;
89 PurpleAccount *account;
90 PurpleBlistNode *group;
93 /* This function must not use purple_normalize */
94 static guint _purple_blist_hbuddy_hash(struct _purple_hbuddy *hb)
96 return g_str_hash(hb->name) ^ g_direct_hash(hb->group) ^ g_direct_hash(hb->account);
99 /* This function must not use purple_normalize */
100 static guint _purple_blist_hbuddy_equal(struct _purple_hbuddy *hb1, struct _purple_hbuddy *hb2)
102 return (hb1->group == hb2->group &&
103 hb1->account == hb2->account &&
104 g_str_equal(hb1->name, hb2->name));
107 static void _purple_blist_hbuddy_free_key(struct _purple_hbuddy *hb)
109 g_free(hb->name);
110 g_free(hb);
113 static void
114 purple_blist_buddies_cache_add_account(PurpleAccount *account)
116 GHashTable *account_buddies = g_hash_table_new_full((GHashFunc)_purple_blist_hbuddy_hash,
117 (GEqualFunc)_purple_blist_hbuddy_equal,
118 (GDestroyNotify)_purple_blist_hbuddy_free_key, NULL);
119 g_hash_table_insert(buddies_cache, account, account_buddies);
122 static void
123 purple_blist_buddies_cache_remove_account(const PurpleAccount *account)
125 g_hash_table_remove(buddies_cache, account);
129 /*********************************************************************
130 * Writing to disk *
131 *********************************************************************/
133 static void
134 value_to_xmlnode(gpointer key, gpointer hvalue, gpointer user_data)
136 const char *name;
137 PurpleValue *value;
138 xmlnode *node, *child;
139 char buf[21];
141 name = (const char *)key;
142 value = (PurpleValue *)hvalue;
143 node = (xmlnode *)user_data;
145 g_return_if_fail(value != NULL);
147 child = xmlnode_new_child(node, "setting");
148 xmlnode_set_attrib(child, "name", name);
150 if (purple_value_get_type(value) == PURPLE_TYPE_INT) {
151 xmlnode_set_attrib(child, "type", "int");
152 g_snprintf(buf, sizeof(buf), "%d", purple_value_get_int(value));
153 xmlnode_insert_data(child, buf, -1);
155 else if (purple_value_get_type(value) == PURPLE_TYPE_STRING) {
156 xmlnode_set_attrib(child, "type", "string");
157 xmlnode_insert_data(child, purple_value_get_string(value), -1);
159 else if (purple_value_get_type(value) == PURPLE_TYPE_BOOLEAN) {
160 xmlnode_set_attrib(child, "type", "bool");
161 g_snprintf(buf, sizeof(buf), "%d", purple_value_get_boolean(value));
162 xmlnode_insert_data(child, buf, -1);
166 static void
167 chat_component_to_xmlnode(gpointer key, gpointer value, gpointer user_data)
169 const char *name;
170 const char *data;
171 xmlnode *node, *child;
173 name = (const char *)key;
174 data = (const char *)value;
175 node = (xmlnode *)user_data;
177 g_return_if_fail(data != NULL);
179 child = xmlnode_new_child(node, "component");
180 xmlnode_set_attrib(child, "name", name);
181 xmlnode_insert_data(child, data, -1);
184 static xmlnode *
185 buddy_to_xmlnode(PurpleBlistNode *bnode)
187 xmlnode *node, *child;
188 PurpleBuddy *buddy;
190 buddy = (PurpleBuddy *)bnode;
192 node = xmlnode_new("buddy");
193 xmlnode_set_attrib(node, "account", purple_account_get_username(buddy->account));
194 xmlnode_set_attrib(node, "proto", purple_account_get_protocol_id(buddy->account));
196 child = xmlnode_new_child(node, "name");
197 xmlnode_insert_data(child, buddy->name, -1);
199 if (buddy->alias != NULL)
201 child = xmlnode_new_child(node, "alias");
202 xmlnode_insert_data(child, buddy->alias, -1);
205 /* Write buddy settings */
206 g_hash_table_foreach(buddy->node.settings, value_to_xmlnode, node);
208 return node;
211 static xmlnode *
212 contact_to_xmlnode(PurpleBlistNode *cnode)
214 xmlnode *node, *child;
215 PurpleContact *contact;
216 PurpleBlistNode *bnode;
218 contact = (PurpleContact *)cnode;
220 node = xmlnode_new("contact");
222 if (contact->alias != NULL)
224 xmlnode_set_attrib(node, "alias", contact->alias);
227 /* Write buddies */
228 for (bnode = cnode->child; bnode != NULL; bnode = bnode->next)
230 if (!PURPLE_BLIST_NODE_SHOULD_SAVE(bnode))
231 continue;
232 if (PURPLE_BLIST_NODE_IS_BUDDY(bnode))
234 child = buddy_to_xmlnode(bnode);
235 xmlnode_insert_child(node, child);
239 /* Write contact settings */
240 g_hash_table_foreach(cnode->settings, value_to_xmlnode, node);
242 return node;
245 static xmlnode *
246 chat_to_xmlnode(PurpleBlistNode *cnode)
248 xmlnode *node, *child;
249 PurpleChat *chat;
251 chat = (PurpleChat *)cnode;
253 node = xmlnode_new("chat");
254 xmlnode_set_attrib(node, "proto", purple_account_get_protocol_id(chat->account));
255 xmlnode_set_attrib(node, "account", purple_account_get_username(chat->account));
257 if (chat->alias != NULL)
259 child = xmlnode_new_child(node, "alias");
260 xmlnode_insert_data(child, chat->alias, -1);
263 /* Write chat components */
264 g_hash_table_foreach(chat->components, chat_component_to_xmlnode, node);
266 /* Write chat settings */
267 g_hash_table_foreach(chat->node.settings, value_to_xmlnode, node);
269 return node;
272 static xmlnode *
273 group_to_xmlnode(PurpleBlistNode *gnode)
275 xmlnode *node, *child;
276 PurpleGroup *group;
277 PurpleBlistNode *cnode;
279 group = (PurpleGroup *)gnode;
281 node = xmlnode_new("group");
282 xmlnode_set_attrib(node, "name", group->name);
284 /* Write settings */
285 g_hash_table_foreach(group->node.settings, value_to_xmlnode, node);
287 /* Write contacts and chats */
288 for (cnode = gnode->child; cnode != NULL; cnode = cnode->next)
290 if (!PURPLE_BLIST_NODE_SHOULD_SAVE(cnode))
291 continue;
292 if (PURPLE_BLIST_NODE_IS_CONTACT(cnode))
294 child = contact_to_xmlnode(cnode);
295 xmlnode_insert_child(node, child);
297 else if (PURPLE_BLIST_NODE_IS_CHAT(cnode))
299 child = chat_to_xmlnode(cnode);
300 xmlnode_insert_child(node, child);
304 return node;
307 static xmlnode *
308 accountprivacy_to_xmlnode(PurpleAccount *account)
310 xmlnode *node, *child;
311 GSList *cur;
312 char buf[10];
314 node = xmlnode_new("account");
315 xmlnode_set_attrib(node, "proto", purple_account_get_protocol_id(account));
316 xmlnode_set_attrib(node, "name", purple_account_get_username(account));
317 g_snprintf(buf, sizeof(buf), "%d", account->perm_deny);
318 xmlnode_set_attrib(node, "mode", buf);
320 for (cur = account->permit; cur; cur = cur->next)
322 child = xmlnode_new_child(node, "permit");
323 xmlnode_insert_data(child, cur->data, -1);
326 for (cur = account->deny; cur; cur = cur->next)
328 child = xmlnode_new_child(node, "block");
329 xmlnode_insert_data(child, cur->data, -1);
332 return node;
335 static xmlnode *
336 blist_to_xmlnode(void)
338 xmlnode *node, *child, *grandchild;
339 PurpleBlistNode *gnode;
340 GList *cur;
342 node = xmlnode_new("purple");
343 xmlnode_set_attrib(node, "version", "1.0");
345 /* Write groups */
346 child = xmlnode_new_child(node, "blist");
347 for (gnode = purplebuddylist->root; gnode != NULL; gnode = gnode->next)
349 if (!PURPLE_BLIST_NODE_SHOULD_SAVE(gnode))
350 continue;
351 if (PURPLE_BLIST_NODE_IS_GROUP(gnode))
353 grandchild = group_to_xmlnode(gnode);
354 xmlnode_insert_child(child, grandchild);
358 /* Write privacy settings */
359 child = xmlnode_new_child(node, "privacy");
360 for (cur = purple_accounts_get_all(); cur != NULL; cur = cur->next)
362 grandchild = accountprivacy_to_xmlnode(cur->data);
363 xmlnode_insert_child(child, grandchild);
366 return node;
369 static void
370 purple_blist_sync(void)
372 xmlnode *node;
373 char *data;
375 if (!blist_loaded)
377 purple_debug_error("blist", "Attempted to save buddy list before it "
378 "was read!\n");
379 return;
382 node = blist_to_xmlnode();
383 data = xmlnode_to_formatted_str(node, NULL);
384 purple_util_write_data_to_file("blist.xml", data, -1);
385 g_free(data);
386 xmlnode_free(node);
389 static gboolean
390 save_cb(gpointer data)
392 purple_blist_sync();
393 save_timer = 0;
394 return FALSE;
397 static void
398 _purple_blist_schedule_save()
400 if (save_timer == 0)
401 save_timer = purple_timeout_add_seconds(5, save_cb, NULL);
404 static void
405 purple_blist_save_account(PurpleAccount *account)
407 #if 1
408 _purple_blist_schedule_save();
409 #else
410 if (account != NULL) {
411 /* Save the buddies and privacy data for this account */
412 } else {
413 /* Save all buddies and privacy data */
415 #endif
418 static void
419 purple_blist_save_node(PurpleBlistNode *node)
421 _purple_blist_schedule_save();
424 void purple_blist_schedule_save()
426 PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
428 /* Save everything */
429 if (ops && ops->save_account)
430 ops->save_account(NULL);
434 /*********************************************************************
435 * Reading from disk *
436 *********************************************************************/
438 static void
439 parse_setting(PurpleBlistNode *node, xmlnode *setting)
441 const char *name = xmlnode_get_attrib(setting, "name");
442 const char *type = xmlnode_get_attrib(setting, "type");
443 char *value = xmlnode_get_data(setting);
445 if (!value)
446 return;
448 if (!type || purple_strequal(type, "string"))
449 purple_blist_node_set_string(node, name, value);
450 else if (purple_strequal(type, "bool"))
451 purple_blist_node_set_bool(node, name, atoi(value));
452 else if (purple_strequal(type, "int"))
453 purple_blist_node_set_int(node, name, atoi(value));
455 g_free(value);
458 static void
459 parse_buddy(PurpleGroup *group, PurpleContact *contact, xmlnode *bnode)
461 PurpleAccount *account;
462 PurpleBuddy *buddy;
463 char *name = NULL, *alias = NULL;
464 const char *acct_name, *proto, *protocol;
465 xmlnode *x;
467 acct_name = xmlnode_get_attrib(bnode, "account");
468 protocol = xmlnode_get_attrib(bnode, "protocol");
469 protocol = _purple_oscar_convert(acct_name, protocol); /* XXX: Remove */
470 proto = xmlnode_get_attrib(bnode, "proto");
471 proto = _purple_oscar_convert(acct_name, proto); /* XXX: Remove */
473 if (!acct_name || (!proto && !protocol))
474 return;
476 account = purple_accounts_find(acct_name, proto ? proto : protocol);
478 if (!account)
479 return;
481 if ((x = xmlnode_get_child(bnode, "name")))
482 name = xmlnode_get_data(x);
484 if (!name)
485 return;
487 if ((x = xmlnode_get_child(bnode, "alias")))
488 alias = xmlnode_get_data(x);
490 buddy = purple_buddy_new(account, name, alias);
491 purple_blist_add_buddy(buddy, contact, group,
492 purple_blist_get_last_child((PurpleBlistNode*)contact));
494 for (x = xmlnode_get_child(bnode, "setting"); x; x = xmlnode_get_next_twin(x)) {
495 parse_setting((PurpleBlistNode*)buddy, x);
498 g_free(name);
499 g_free(alias);
502 static void
503 parse_contact(PurpleGroup *group, xmlnode *cnode)
505 PurpleContact *contact = purple_contact_new();
506 xmlnode *x;
507 const char *alias;
509 purple_blist_add_contact(contact, group,
510 purple_blist_get_last_child((PurpleBlistNode*)group));
512 if ((alias = xmlnode_get_attrib(cnode, "alias"))) {
513 purple_blist_alias_contact(contact, alias);
516 for (x = cnode->child; x; x = x->next) {
517 if (x->type != XMLNODE_TYPE_TAG)
518 continue;
519 if (purple_strequal(x->name, "buddy"))
520 parse_buddy(group, contact, x);
521 else if (purple_strequal(x->name, "setting"))
522 parse_setting((PurpleBlistNode*)contact, x);
525 /* if the contact is empty, don't keep it around. it causes problems */
526 if (!((PurpleBlistNode*)contact)->child)
527 purple_blist_remove_contact(contact);
530 static void
531 parse_chat(PurpleGroup *group, xmlnode *cnode)
533 PurpleChat *chat;
534 PurpleAccount *account;
535 const char *acct_name, *proto, *protocol;
536 xmlnode *x;
537 char *alias = NULL;
538 GHashTable *components;
540 acct_name = xmlnode_get_attrib(cnode, "account");
541 protocol = xmlnode_get_attrib(cnode, "protocol");
542 proto = xmlnode_get_attrib(cnode, "proto");
544 if (!acct_name || (!proto && !protocol))
545 return;
547 account = purple_accounts_find(acct_name, proto ? proto : protocol);
549 if (!account)
550 return;
552 if ((x = xmlnode_get_child(cnode, "alias")))
553 alias = xmlnode_get_data(x);
555 components = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
557 for (x = xmlnode_get_child(cnode, "component"); x; x = xmlnode_get_next_twin(x)) {
558 const char *name;
559 char *value;
561 name = xmlnode_get_attrib(x, "name");
562 value = xmlnode_get_data(x);
563 g_hash_table_replace(components, g_strdup(name), value);
566 chat = purple_chat_new(account, alias, components);
567 purple_blist_add_chat(chat, group,
568 purple_blist_get_last_child((PurpleBlistNode*)group));
570 for (x = xmlnode_get_child(cnode, "setting"); x; x = xmlnode_get_next_twin(x)) {
571 parse_setting((PurpleBlistNode*)chat, x);
574 g_free(alias);
577 static void
578 parse_group(xmlnode *groupnode)
580 const char *name = xmlnode_get_attrib(groupnode, "name");
581 PurpleGroup *group;
582 xmlnode *cnode;
584 if (!name)
585 name = _("Buddies");
587 group = purple_group_new(name);
588 purple_blist_add_group(group,
589 purple_blist_get_last_sibling(purplebuddylist->root));
591 for (cnode = groupnode->child; cnode; cnode = cnode->next) {
592 if (cnode->type != XMLNODE_TYPE_TAG)
593 continue;
594 if (purple_strequal(cnode->name, "setting"))
595 parse_setting((PurpleBlistNode*)group, cnode);
596 else if (purple_strequal(cnode->name, "contact") ||
597 purple_strequal(cnode->name, "person"))
598 parse_contact(group, cnode);
599 else if (purple_strequal(cnode->name, "chat"))
600 parse_chat(group, cnode);
604 /* TODO: Make static and rename to load_blist */
605 void
606 purple_blist_load()
608 xmlnode *purple, *blist, *privacy;
610 blist_loaded = TRUE;
612 purple = purple_util_read_xml_from_file("blist.xml", _("buddy list"));
614 if (purple == NULL)
615 return;
617 blist = xmlnode_get_child(purple, "blist");
618 if (blist) {
619 xmlnode *groupnode;
620 for (groupnode = xmlnode_get_child(blist, "group"); groupnode != NULL;
621 groupnode = xmlnode_get_next_twin(groupnode)) {
622 parse_group(groupnode);
626 privacy = xmlnode_get_child(purple, "privacy");
627 if (privacy) {
628 xmlnode *anode;
629 for (anode = privacy->child; anode; anode = anode->next) {
630 xmlnode *x;
631 PurpleAccount *account;
632 int imode;
633 const char *acct_name, *proto, *mode, *protocol;
635 acct_name = xmlnode_get_attrib(anode, "name");
636 protocol = xmlnode_get_attrib(anode, "protocol");
637 proto = xmlnode_get_attrib(anode, "proto");
638 mode = xmlnode_get_attrib(anode, "mode");
640 if (!acct_name || (!proto && !protocol) || !mode)
641 continue;
643 account = purple_accounts_find(acct_name, proto ? proto : protocol);
645 if (!account)
646 continue;
648 imode = atoi(mode);
649 account->perm_deny = (imode != 0 ? imode : PURPLE_PRIVACY_ALLOW_ALL);
651 for (x = anode->child; x; x = x->next) {
652 char *name;
653 if (x->type != XMLNODE_TYPE_TAG)
654 continue;
656 if (purple_strequal(x->name, "permit")) {
657 name = xmlnode_get_data(x);
658 purple_privacy_permit_add(account, name, TRUE);
659 g_free(name);
660 } else if (purple_strequal(x->name, "block")) {
661 name = xmlnode_get_data(x);
662 purple_privacy_deny_add(account, name, TRUE);
663 g_free(name);
669 xmlnode_free(purple);
671 /* This tells the buddy icon code to do its thing. */
672 _purple_buddy_icons_blist_loaded_cb();
676 /*********************************************************************
677 * Stuff *
678 *********************************************************************/
680 static void
681 purple_contact_compute_priority_buddy(PurpleContact *contact)
683 PurpleBlistNode *bnode;
684 PurpleBuddy *new_priority = NULL;
686 g_return_if_fail(contact != NULL);
688 contact->priority = NULL;
689 for (bnode = ((PurpleBlistNode*)contact)->child;
690 bnode != NULL;
691 bnode = bnode->next)
693 PurpleBuddy *buddy;
695 if (!PURPLE_BLIST_NODE_IS_BUDDY(bnode))
696 continue;
698 buddy = (PurpleBuddy*)bnode;
699 if (new_priority == NULL)
701 new_priority = buddy;
702 continue;
705 if (purple_account_is_connected(buddy->account))
707 int cmp = 1;
708 if (purple_account_is_connected(new_priority->account))
709 cmp = purple_presence_compare(purple_buddy_get_presence(new_priority),
710 purple_buddy_get_presence(buddy));
712 if (cmp > 0 || (cmp == 0 &&
713 purple_prefs_get_bool("/purple/contact/last_match")))
715 new_priority = buddy;
720 contact->priority = new_priority;
721 contact->priority_valid = TRUE;
725 /*****************************************************************************
726 * Public API functions *
727 *****************************************************************************/
729 PurpleBuddyList *purple_blist_new()
731 PurpleBlistUiOps *ui_ops;
732 GList *account;
733 PurpleBuddyList *gbl = g_new0(PurpleBuddyList, 1);
734 PURPLE_DBUS_REGISTER_POINTER(gbl, PurpleBuddyList);
736 ui_ops = purple_blist_get_ui_ops();
738 gbl->buddies = g_hash_table_new_full((GHashFunc)_purple_blist_hbuddy_hash,
739 (GEqualFunc)_purple_blist_hbuddy_equal,
740 (GDestroyNotify)_purple_blist_hbuddy_free_key, NULL);
742 buddies_cache = g_hash_table_new_full(g_direct_hash, g_direct_equal,
743 NULL, (GDestroyNotify)g_hash_table_destroy);
745 groups_cache = g_hash_table_new_full((GHashFunc)g_str_hash,
746 (GEqualFunc)g_str_equal,
747 (GDestroyNotify)g_free, NULL);
749 for (account = purple_accounts_get_all(); account != NULL; account = account->next)
751 purple_blist_buddies_cache_add_account(account->data);
754 if (ui_ops != NULL && ui_ops->new_list != NULL)
755 ui_ops->new_list(gbl);
757 return gbl;
760 void
761 purple_set_blist(PurpleBuddyList *list)
763 purplebuddylist = list;
766 PurpleBuddyList *
767 purple_get_blist()
769 return purplebuddylist;
772 PurpleBlistNode *
773 purple_blist_get_root()
775 return purplebuddylist ? purplebuddylist->root : NULL;
778 static void
779 append_buddy(gpointer key, gpointer value, gpointer user_data)
781 GSList **list = user_data;
782 *list = g_slist_prepend(*list, value);
785 GSList *
786 purple_blist_get_buddies()
788 GSList *buddies = NULL;
790 if (!purplebuddylist)
791 return NULL;
793 g_hash_table_foreach(purplebuddylist->buddies, append_buddy, &buddies);
794 return buddies;
797 void *
798 purple_blist_get_ui_data()
800 return purplebuddylist->ui_data;
803 void
804 purple_blist_set_ui_data(void *ui_data)
806 purplebuddylist->ui_data = ui_data;
809 void purple_blist_show()
811 PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
813 if (ops && ops->show)
814 ops->show(purplebuddylist);
817 void purple_blist_destroy()
819 PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
821 purple_debug(PURPLE_DEBUG_INFO, "blist", "Destroying\n");
823 if (ops && ops->destroy)
824 ops->destroy(purplebuddylist);
827 void purple_blist_set_visible(gboolean show)
829 PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
831 if (ops && ops->set_visible)
832 ops->set_visible(purplebuddylist, show);
835 static PurpleBlistNode *get_next_node(PurpleBlistNode *node, gboolean godeep)
837 if (node == NULL)
838 return NULL;
840 if (godeep && node->child)
841 return node->child;
843 if (node->next)
844 return node->next;
846 return get_next_node(node->parent, FALSE);
849 PurpleBlistNode *purple_blist_node_next(PurpleBlistNode *node, gboolean offline)
851 PurpleBlistNode *ret = node;
853 if (offline)
854 return get_next_node(ret, TRUE);
857 ret = get_next_node(ret, TRUE);
858 } while (ret && PURPLE_BLIST_NODE_IS_BUDDY(ret) &&
859 !purple_account_is_connected(purple_buddy_get_account((PurpleBuddy *)ret)));
861 return ret;
864 PurpleBlistNode *purple_blist_node_get_parent(PurpleBlistNode *node)
866 return node ? node->parent : NULL;
869 PurpleBlistNode *purple_blist_node_get_first_child(PurpleBlistNode *node)
871 return node ? node->child : NULL;
874 PurpleBlistNode *purple_blist_node_get_sibling_next(PurpleBlistNode *node)
876 return node? node->next : NULL;
879 PurpleBlistNode *purple_blist_node_get_sibling_prev(PurpleBlistNode *node)
881 return node? node->prev : NULL;
884 void *
885 purple_blist_node_get_ui_data(const PurpleBlistNode *node)
887 g_return_val_if_fail(node, NULL);
889 return node->ui_data;
892 void
893 purple_blist_node_set_ui_data(PurpleBlistNode *node, void *ui_data) {
894 g_return_if_fail(node);
896 node->ui_data = ui_data;
899 void
900 purple_blist_update_buddy_status(PurpleBuddy *buddy, PurpleStatus *old_status)
902 PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
903 PurplePresence *presence;
904 PurpleStatus *status;
905 PurpleBlistNode *cnode;
907 g_return_if_fail(buddy != NULL);
909 presence = purple_buddy_get_presence(buddy);
910 status = purple_presence_get_active_status(presence);
912 purple_debug_info("blist", "Updating buddy status for %s (%s)\n",
913 buddy->name, purple_account_get_protocol_name(buddy->account));
915 if (purple_status_is_online(status) &&
916 !purple_status_is_online(old_status)) {
918 purple_signal_emit(purple_blist_get_handle(), "buddy-signed-on", buddy);
920 cnode = buddy->node.parent;
921 if (++(PURPLE_CONTACT(cnode)->online) == 1)
922 PURPLE_GROUP(cnode->parent)->online++;
923 } else if (!purple_status_is_online(status) &&
924 purple_status_is_online(old_status)) {
926 purple_blist_node_set_int(&buddy->node, "last_seen", time(NULL));
927 purple_signal_emit(purple_blist_get_handle(), "buddy-signed-off", buddy);
929 cnode = buddy->node.parent;
930 if (--(PURPLE_CONTACT(cnode)->online) == 0)
931 PURPLE_GROUP(cnode->parent)->online--;
932 } else {
933 purple_signal_emit(purple_blist_get_handle(),
934 "buddy-status-changed", buddy, old_status,
935 status);
939 * This function used to only call the following two functions if one of
940 * the above signals had been triggered, but that's not good, because
941 * if someone's away message changes and they don't go from away to back
942 * to away then no signal is triggered.
944 * It's a safe assumption that SOMETHING called this function. PROBABLY
945 * because something, somewhere changed. Calling the stuff below
946 * certainly won't hurt anything. Unless you're on a K6-2 300.
948 purple_contact_invalidate_priority_buddy(purple_buddy_get_contact(buddy));
949 if (ops && ops->update)
950 ops->update(purplebuddylist, (PurpleBlistNode *)buddy);
953 void
954 purple_blist_update_node_icon(PurpleBlistNode *node)
956 PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
958 g_return_if_fail(node != NULL);
960 if (ops && ops->update)
961 ops->update(purplebuddylist, node);
964 void
965 purple_blist_update_buddy_icon(PurpleBuddy *buddy)
967 purple_blist_update_node_icon((PurpleBlistNode *)buddy);
971 * TODO: Maybe remove the call to this from server.c and call it
972 * from oscar.c and toc.c instead?
974 void purple_blist_rename_buddy(PurpleBuddy *buddy, const char *name)
976 PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
977 struct _purple_hbuddy *hb, *hb2;
978 GHashTable *account_buddies;
980 g_return_if_fail(buddy != NULL);
982 hb = g_new(struct _purple_hbuddy, 1);
983 hb->name = (gchar *)purple_normalize(buddy->account, buddy->name);
984 hb->account = buddy->account;
985 hb->group = ((PurpleBlistNode *)buddy)->parent->parent;
986 g_hash_table_remove(purplebuddylist->buddies, hb);
988 account_buddies = g_hash_table_lookup(buddies_cache, buddy->account);
989 g_hash_table_remove(account_buddies, hb);
991 hb->name = g_strdup(purple_normalize(buddy->account, name));
992 g_hash_table_replace(purplebuddylist->buddies, hb, buddy);
994 hb2 = g_new(struct _purple_hbuddy, 1);
995 hb2->name = g_strdup(hb->name);
996 hb2->account = buddy->account;
997 hb2->group = ((PurpleBlistNode *)buddy)->parent->parent;
999 g_hash_table_replace(account_buddies, hb2, buddy);
1001 g_free(buddy->name);
1002 buddy->name = g_strdup(name);
1004 if (ops && ops->save_node)
1005 ops->save_node((PurpleBlistNode *) buddy);
1007 if (ops && ops->update)
1008 ops->update(purplebuddylist, (PurpleBlistNode *)buddy);
1011 static gboolean
1012 purple_strings_are_different(const char *one, const char *two)
1014 return !((one && two && g_utf8_collate(one, two) == 0) ||
1015 ((one == NULL || *one == '\0') && (two == NULL || *two == '\0')));
1018 void purple_blist_alias_contact(PurpleContact *contact, const char *alias)
1020 PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
1021 PurpleConversation *conv;
1022 PurpleBlistNode *bnode;
1023 char *old_alias;
1024 char *new_alias = NULL;
1026 g_return_if_fail(contact != NULL);
1028 if ((alias != NULL) && (*alias != '\0'))
1029 new_alias = purple_utf8_strip_unprintables(alias);
1031 if (!purple_strings_are_different(contact->alias, new_alias)) {
1032 g_free(new_alias);
1033 return;
1036 old_alias = contact->alias;
1038 if ((new_alias != NULL) && (*new_alias != '\0'))
1039 contact->alias = new_alias;
1040 else {
1041 contact->alias = NULL;
1042 g_free(new_alias); /* could be "\0" */
1045 if (ops && ops->save_node)
1046 ops->save_node((PurpleBlistNode*) contact);
1048 if (ops && ops->update)
1049 ops->update(purplebuddylist, (PurpleBlistNode *)contact);
1051 for(bnode = ((PurpleBlistNode *)contact)->child; bnode != NULL; bnode = bnode->next)
1053 PurpleBuddy *buddy = (PurpleBuddy *)bnode;
1055 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, buddy->name,
1056 buddy->account);
1057 if (conv)
1058 purple_conversation_autoset_title(conv);
1061 purple_signal_emit(purple_blist_get_handle(), "blist-node-aliased",
1062 contact, old_alias);
1063 g_free(old_alias);
1066 void purple_blist_alias_chat(PurpleChat *chat, const char *alias)
1068 PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
1069 char *old_alias;
1070 char *new_alias = NULL;
1072 g_return_if_fail(chat != NULL);
1074 if ((alias != NULL) && (*alias != '\0'))
1075 new_alias = purple_utf8_strip_unprintables(alias);
1077 if (!purple_strings_are_different(chat->alias, new_alias)) {
1078 g_free(new_alias);
1079 return;
1082 old_alias = chat->alias;
1084 if ((new_alias != NULL) && (*new_alias != '\0'))
1085 chat->alias = new_alias;
1086 else {
1087 chat->alias = NULL;
1088 g_free(new_alias); /* could be "\0" */
1091 if (ops && ops->save_node)
1092 ops->save_node((PurpleBlistNode*) chat);
1094 if (ops && ops->update)
1095 ops->update(purplebuddylist, (PurpleBlistNode *)chat);
1097 purple_signal_emit(purple_blist_get_handle(), "blist-node-aliased",
1098 chat, old_alias);
1099 g_free(old_alias);
1102 void purple_blist_alias_buddy(PurpleBuddy *buddy, const char *alias)
1104 PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
1105 PurpleConversation *conv;
1106 char *old_alias;
1107 char *new_alias = NULL;
1109 g_return_if_fail(buddy != NULL);
1111 if ((alias != NULL) && (*alias != '\0'))
1112 new_alias = purple_utf8_strip_unprintables(alias);
1114 if (!purple_strings_are_different(buddy->alias, new_alias)) {
1115 g_free(new_alias);
1116 return;
1119 old_alias = buddy->alias;
1121 if ((new_alias != NULL) && (*new_alias != '\0'))
1122 buddy->alias = new_alias;
1123 else {
1124 buddy->alias = NULL;
1125 g_free(new_alias); /* could be "\0" */
1128 if (ops && ops->save_node)
1129 ops->save_node((PurpleBlistNode*) buddy);
1131 if (ops && ops->update)
1132 ops->update(purplebuddylist, (PurpleBlistNode *)buddy);
1134 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, buddy->name,
1135 buddy->account);
1136 if (conv)
1137 purple_conversation_autoset_title(conv);
1139 purple_signal_emit(purple_blist_get_handle(), "blist-node-aliased",
1140 buddy, old_alias);
1141 g_free(old_alias);
1144 void purple_blist_server_alias_buddy(PurpleBuddy *buddy, const char *alias)
1146 PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
1147 PurpleConversation *conv;
1148 char *old_alias;
1149 char *new_alias = NULL;
1151 g_return_if_fail(buddy != NULL);
1153 if ((alias != NULL) && (*alias != '\0') && g_utf8_validate(alias, -1, NULL))
1154 new_alias = purple_utf8_strip_unprintables(alias);
1156 if (!purple_strings_are_different(buddy->server_alias, new_alias)) {
1157 g_free(new_alias);
1158 return;
1161 old_alias = buddy->server_alias;
1163 if ((new_alias != NULL) && (*new_alias != '\0'))
1164 buddy->server_alias = new_alias;
1165 else {
1166 buddy->server_alias = NULL;
1167 g_free(new_alias); /* could be "\0"; */
1170 if (ops && ops->save_node)
1171 ops->save_node((PurpleBlistNode*) buddy);
1173 if (ops && ops->update)
1174 ops->update(purplebuddylist, (PurpleBlistNode *)buddy);
1176 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, buddy->name,
1177 buddy->account);
1178 if (conv)
1179 purple_conversation_autoset_title(conv);
1181 purple_signal_emit(purple_blist_get_handle(), "blist-node-aliased",
1182 buddy, old_alias);
1183 g_free(old_alias);
1187 * TODO: If merging, prompt the user if they want to merge.
1189 void purple_blist_rename_group(PurpleGroup *source, const char *name)
1191 PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
1192 PurpleGroup *dest;
1193 gchar *old_name;
1194 gchar *new_name;
1195 GList *moved_buddies = NULL;
1196 GSList *accts;
1198 g_return_if_fail(source != NULL);
1199 g_return_if_fail(name != NULL);
1201 new_name = purple_utf8_strip_unprintables(name);
1203 if (*new_name == '\0' || purple_strequal(new_name, source->name)) {
1204 g_free(new_name);
1205 return;
1208 dest = purple_find_group(new_name);
1209 if (dest != NULL && purple_utf8_strcasecmp(source->name, dest->name) != 0) {
1210 /* We're merging two groups */
1211 PurpleBlistNode *prev, *child, *next;
1213 prev = purple_blist_get_last_child((PurpleBlistNode*)dest);
1214 child = ((PurpleBlistNode*)source)->child;
1217 * TODO: This seems like a dumb way to do this... why not just
1218 * append all children from the old group to the end of the new
1219 * one? PRPLs might be expecting to receive an add_buddy() for
1220 * each moved buddy...
1222 while (child)
1224 next = child->next;
1225 if (PURPLE_BLIST_NODE_IS_CONTACT(child)) {
1226 PurpleBlistNode *bnode;
1227 purple_blist_add_contact((PurpleContact *)child, dest, prev);
1228 for (bnode = child->child; bnode != NULL; bnode = bnode->next) {
1229 purple_blist_add_buddy((PurpleBuddy *)bnode, (PurpleContact *)child,
1230 NULL, bnode->prev);
1231 moved_buddies = g_list_append(moved_buddies, bnode);
1233 prev = child;
1234 } else if (PURPLE_BLIST_NODE_IS_CHAT(child)) {
1235 purple_blist_add_chat((PurpleChat *)child, dest, prev);
1236 prev = child;
1237 } else {
1238 purple_debug(PURPLE_DEBUG_ERROR, "blist",
1239 "Unknown child type in group %s\n", source->name);
1241 child = next;
1244 /* Make a copy of the old group name and then delete the old group */
1245 old_name = g_strdup(source->name);
1246 purple_blist_remove_group(source);
1247 source = dest;
1248 g_free(new_name);
1249 } else {
1250 /* A simple rename */
1251 PurpleBlistNode *cnode, *bnode;
1252 gchar* key;
1254 /* Build a GList of all buddies in this group */
1255 for (cnode = ((PurpleBlistNode *)source)->child; cnode != NULL; cnode = cnode->next) {
1256 if (PURPLE_BLIST_NODE_IS_CONTACT(cnode))
1257 for (bnode = cnode->child; bnode != NULL; bnode = bnode->next)
1258 moved_buddies = g_list_append(moved_buddies, bnode);
1261 old_name = source->name;
1262 source->name = new_name;
1264 key = g_utf8_collate_key(old_name, -1);
1265 g_hash_table_remove(groups_cache, key);
1266 g_free(key);
1268 key = g_utf8_collate_key(new_name, -1);
1269 g_hash_table_insert(groups_cache, key, source);
1272 /* Save our changes */
1273 if (ops && ops->save_node)
1274 ops->save_node((PurpleBlistNode*) source);
1276 /* Update the UI */
1277 if (ops && ops->update)
1278 ops->update(purplebuddylist, (PurpleBlistNode*)source);
1280 /* Notify all PRPLs */
1281 /* TODO: Is this condition needed? Seems like it would always be TRUE */
1282 if(old_name && !purple_strequal(source->name, old_name)) {
1283 for (accts = purple_group_get_accounts(source); accts; accts = g_slist_remove(accts, accts->data)) {
1284 PurpleAccount *account = accts->data;
1285 PurpleConnection *gc = NULL;
1286 PurplePlugin *prpl = NULL;
1287 PurplePluginProtocolInfo *prpl_info = NULL;
1288 GList *l = NULL, *buddies = NULL;
1290 gc = purple_account_get_connection(account);
1292 if(gc)
1293 prpl = purple_connection_get_prpl(gc);
1295 if(gc && prpl)
1296 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
1298 if(!prpl_info)
1299 continue;
1301 for(l = moved_buddies; l; l = l->next) {
1302 PurpleBuddy *buddy = (PurpleBuddy *)l->data;
1304 if(buddy && buddy->account == account)
1305 buddies = g_list_append(buddies, (PurpleBlistNode *)buddy);
1308 if(prpl_info->rename_group) {
1309 prpl_info->rename_group(gc, old_name, source, buddies);
1310 } else {
1311 GList *cur, *groups = NULL;
1313 /* Make a list of what the groups each buddy is in */
1314 for(cur = buddies; cur; cur = cur->next) {
1315 PurpleBlistNode *node = (PurpleBlistNode *)cur->data;
1316 groups = g_list_prepend(groups, node->parent->parent);
1319 purple_account_remove_buddies(account, buddies, groups);
1320 g_list_free(groups);
1321 purple_account_add_buddies(account, buddies);
1324 g_list_free(buddies);
1327 g_list_free(moved_buddies);
1328 g_free(old_name);
1331 static void purple_blist_node_initialize_settings(PurpleBlistNode *node);
1333 PurpleChat *purple_chat_new(PurpleAccount *account, const char *alias, GHashTable *components)
1335 PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
1336 PurpleChat *chat;
1338 g_return_val_if_fail(account != NULL, NULL);
1339 g_return_val_if_fail(components != NULL, NULL);
1341 chat = g_new0(PurpleChat, 1);
1342 chat->account = account;
1343 if ((alias != NULL) && (*alias != '\0'))
1344 chat->alias = purple_utf8_strip_unprintables(alias);
1345 chat->components = components;
1346 purple_blist_node_initialize_settings((PurpleBlistNode *)chat);
1347 ((PurpleBlistNode *)chat)->type = PURPLE_BLIST_CHAT_NODE;
1349 if (ops != NULL && ops->new_node != NULL)
1350 ops->new_node((PurpleBlistNode *)chat);
1352 PURPLE_DBUS_REGISTER_POINTER(chat, PurpleChat);
1353 return chat;
1356 void
1357 purple_chat_destroy(PurpleChat *chat)
1359 g_hash_table_destroy(chat->components);
1360 g_hash_table_destroy(chat->node.settings);
1361 g_free(chat->alias);
1362 PURPLE_DBUS_UNREGISTER_POINTER(chat);
1363 g_free(chat);
1366 PurpleBuddy *purple_buddy_new(PurpleAccount *account, const char *name, const char *alias)
1368 PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
1369 PurpleBuddy *buddy;
1371 g_return_val_if_fail(account != NULL, NULL);
1372 g_return_val_if_fail(name != NULL, NULL);
1374 buddy = g_new0(PurpleBuddy, 1);
1375 buddy->account = account;
1376 buddy->name = purple_utf8_strip_unprintables(name);
1377 buddy->alias = purple_utf8_strip_unprintables(alias);
1378 buddy->presence = purple_presence_new_for_buddy(buddy);
1379 ((PurpleBlistNode *)buddy)->type = PURPLE_BLIST_BUDDY_NODE;
1381 purple_presence_set_status_active(buddy->presence, "offline", TRUE);
1383 purple_blist_node_initialize_settings((PurpleBlistNode *)buddy);
1385 if (ops && ops->new_node)
1386 ops->new_node((PurpleBlistNode *)buddy);
1388 PURPLE_DBUS_REGISTER_POINTER(buddy, PurpleBuddy);
1389 return buddy;
1392 void
1393 purple_buddy_destroy(PurpleBuddy *buddy)
1395 PurplePlugin *prpl;
1396 PurplePluginProtocolInfo *prpl_info;
1399 * Tell the owner PRPL that we're about to free the buddy so it
1400 * can free proto_data
1402 prpl = purple_find_prpl(purple_account_get_protocol_id(buddy->account));
1403 if (prpl) {
1404 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
1405 if (prpl_info && prpl_info->buddy_free)
1406 prpl_info->buddy_free(buddy);
1409 /* Delete the node */
1410 purple_buddy_icon_unref(buddy->icon);
1411 g_hash_table_destroy(buddy->node.settings);
1412 purple_presence_destroy(buddy->presence);
1413 g_free(buddy->name);
1414 g_free(buddy->alias);
1415 g_free(buddy->server_alias);
1417 PURPLE_DBUS_UNREGISTER_POINTER(buddy);
1418 g_free(buddy);
1420 /* FIXME: Once PurpleBuddy is a GObject, timeout callbacks can
1421 * g_object_ref() it when connecting the callback and
1422 * g_object_unref() it in the handler. That way, it won't
1423 * get freed while the timeout is pending and this line can
1424 * be removed. */
1425 while (g_source_remove_by_user_data((gpointer *)buddy));
1428 void
1429 purple_buddy_set_icon(PurpleBuddy *buddy, PurpleBuddyIcon *icon)
1431 g_return_if_fail(buddy != NULL);
1433 if (buddy->icon != icon)
1435 purple_buddy_icon_unref(buddy->icon);
1436 buddy->icon = (icon != NULL ? purple_buddy_icon_ref(icon) : NULL);
1439 purple_signal_emit(purple_blist_get_handle(), "buddy-icon-changed", buddy);
1441 purple_blist_update_node_icon((PurpleBlistNode*)buddy);
1444 PurpleAccount *
1445 purple_buddy_get_account(const PurpleBuddy *buddy)
1447 g_return_val_if_fail(buddy != NULL, NULL);
1449 return buddy->account;
1452 const char *
1453 purple_buddy_get_name(const PurpleBuddy *buddy)
1455 g_return_val_if_fail(buddy != NULL, NULL);
1457 return buddy->name;
1460 PurpleBuddyIcon *
1461 purple_buddy_get_icon(const PurpleBuddy *buddy)
1463 g_return_val_if_fail(buddy != NULL, NULL);
1465 return buddy->icon;
1468 gpointer
1469 purple_buddy_get_protocol_data(const PurpleBuddy *buddy)
1471 g_return_val_if_fail(buddy != NULL, NULL);
1473 return buddy->proto_data;
1476 void
1477 purple_buddy_set_protocol_data(PurpleBuddy *buddy, gpointer data)
1479 g_return_if_fail(buddy != NULL);
1481 buddy->proto_data = data;
1485 void purple_blist_add_chat(PurpleChat *chat, PurpleGroup *group, PurpleBlistNode *node)
1487 PurpleBlistNode *cnode = (PurpleBlistNode*)chat;
1488 PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
1490 g_return_if_fail(chat != NULL);
1491 g_return_if_fail(PURPLE_BLIST_NODE_IS_CHAT((PurpleBlistNode *)chat));
1493 if (node == NULL) {
1494 if (group == NULL)
1495 group = purple_group_new(_("Chats"));
1497 /* Add group to blist if isn't already on it. Fixes #2752. */
1498 if (!purple_find_group(group->name)) {
1499 purple_blist_add_group(group,
1500 purple_blist_get_last_sibling(purplebuddylist->root));
1502 } else {
1503 group = (PurpleGroup*)node->parent;
1506 /* if we're moving to overtop of ourselves, do nothing */
1507 if (cnode == node)
1508 return;
1510 if (cnode->parent) {
1511 /* This chat was already in the list and is
1512 * being moved.
1514 ((PurpleGroup *)cnode->parent)->totalsize--;
1515 if (purple_account_is_connected(chat->account)) {
1516 ((PurpleGroup *)cnode->parent)->online--;
1517 ((PurpleGroup *)cnode->parent)->currentsize--;
1519 if (cnode->next)
1520 cnode->next->prev = cnode->prev;
1521 if (cnode->prev)
1522 cnode->prev->next = cnode->next;
1523 if (cnode->parent->child == cnode)
1524 cnode->parent->child = cnode->next;
1526 if (ops && ops->remove)
1527 ops->remove(purplebuddylist, cnode);
1528 /* ops->remove() cleaned up the cnode's ui_data, so we need to
1529 * reinitialize it */
1530 if (ops && ops->new_node)
1531 ops->new_node(cnode);
1534 if (node != NULL) {
1535 if (node->next)
1536 node->next->prev = cnode;
1537 cnode->next = node->next;
1538 cnode->prev = node;
1539 cnode->parent = node->parent;
1540 node->next = cnode;
1541 ((PurpleGroup *)node->parent)->totalsize++;
1542 if (purple_account_is_connected(chat->account)) {
1543 ((PurpleGroup *)node->parent)->online++;
1544 ((PurpleGroup *)node->parent)->currentsize++;
1546 } else {
1547 if (((PurpleBlistNode *)group)->child)
1548 ((PurpleBlistNode *)group)->child->prev = cnode;
1549 cnode->next = ((PurpleBlistNode *)group)->child;
1550 cnode->prev = NULL;
1551 ((PurpleBlistNode *)group)->child = cnode;
1552 cnode->parent = (PurpleBlistNode *)group;
1553 group->totalsize++;
1554 if (purple_account_is_connected(chat->account)) {
1555 group->online++;
1556 group->currentsize++;
1560 if (ops && ops->save_node)
1561 ops->save_node(cnode);
1563 if (ops && ops->update)
1564 ops->update(purplebuddylist, (PurpleBlistNode *)cnode);
1566 purple_signal_emit(purple_blist_get_handle(), "blist-node-added",
1567 cnode);
1570 void purple_blist_add_buddy(PurpleBuddy *buddy, PurpleContact *contact, PurpleGroup *group, PurpleBlistNode *node)
1572 PurpleBlistNode *cnode, *bnode;
1573 PurpleGroup *g;
1574 PurpleContact *c;
1575 PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
1576 struct _purple_hbuddy *hb, *hb2;
1577 GHashTable *account_buddies;
1579 g_return_if_fail(buddy != NULL);
1580 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY((PurpleBlistNode*)buddy));
1582 bnode = (PurpleBlistNode *)buddy;
1584 /* if we're moving to overtop of ourselves, do nothing */
1585 if (bnode == node || (!node && bnode->parent &&
1586 contact && bnode->parent == (PurpleBlistNode*)contact
1587 && bnode == bnode->parent->child))
1588 return;
1590 if (node && PURPLE_BLIST_NODE_IS_BUDDY(node)) {
1591 c = (PurpleContact*)node->parent;
1592 g = (PurpleGroup*)node->parent->parent;
1593 } else if (contact) {
1594 c = contact;
1595 g = PURPLE_GROUP(PURPLE_BLIST_NODE(c)->parent);
1596 } else {
1597 g = group;
1598 if (g == NULL)
1599 g = purple_group_new(_("Buddies"));
1600 /* Add group to blist if isn't already on it. Fixes #2752. */
1601 if (!purple_find_group(g->name)) {
1602 purple_blist_add_group(g,
1603 purple_blist_get_last_sibling(purplebuddylist->root));
1605 c = purple_contact_new();
1606 purple_blist_add_contact(c, g,
1607 purple_blist_get_last_child((PurpleBlistNode*)g));
1610 cnode = (PurpleBlistNode *)c;
1612 if (bnode->parent) {
1613 if (PURPLE_BUDDY_IS_ONLINE(buddy)) {
1614 ((PurpleContact*)bnode->parent)->online--;
1615 if (((PurpleContact*)bnode->parent)->online == 0)
1616 ((PurpleGroup*)bnode->parent->parent)->online--;
1618 if (purple_account_is_connected(buddy->account)) {
1619 ((PurpleContact*)bnode->parent)->currentsize--;
1620 if (((PurpleContact*)bnode->parent)->currentsize == 0)
1621 ((PurpleGroup*)bnode->parent->parent)->currentsize--;
1623 ((PurpleContact*)bnode->parent)->totalsize--;
1624 /* the group totalsize will be taken care of by remove_contact below */
1626 if (bnode->parent->parent != (PurpleBlistNode*)g)
1627 serv_move_buddy(buddy, (PurpleGroup *)bnode->parent->parent, g);
1629 if (bnode->next)
1630 bnode->next->prev = bnode->prev;
1631 if (bnode->prev)
1632 bnode->prev->next = bnode->next;
1633 if (bnode->parent->child == bnode)
1634 bnode->parent->child = bnode->next;
1636 if (ops && ops->remove)
1637 ops->remove(purplebuddylist, bnode);
1639 if (bnode->parent->parent != (PurpleBlistNode*)g) {
1640 struct _purple_hbuddy hb;
1641 hb.name = (gchar *)purple_normalize(buddy->account, buddy->name);
1642 hb.account = buddy->account;
1643 hb.group = bnode->parent->parent;
1644 g_hash_table_remove(purplebuddylist->buddies, &hb);
1646 account_buddies = g_hash_table_lookup(buddies_cache, buddy->account);
1647 g_hash_table_remove(account_buddies, &hb);
1650 if (!bnode->parent->child) {
1651 purple_blist_remove_contact((PurpleContact*)bnode->parent);
1652 } else {
1653 purple_contact_invalidate_priority_buddy((PurpleContact*)bnode->parent);
1654 if (ops && ops->update)
1655 ops->update(purplebuddylist, bnode->parent);
1659 if (node && PURPLE_BLIST_NODE_IS_BUDDY(node)) {
1660 if (node->next)
1661 node->next->prev = bnode;
1662 bnode->next = node->next;
1663 bnode->prev = node;
1664 bnode->parent = node->parent;
1665 node->next = bnode;
1666 } else {
1667 if (cnode->child)
1668 cnode->child->prev = bnode;
1669 bnode->prev = NULL;
1670 bnode->next = cnode->child;
1671 cnode->child = bnode;
1672 bnode->parent = cnode;
1675 if (PURPLE_BUDDY_IS_ONLINE(buddy)) {
1676 if (++(PURPLE_CONTACT(bnode->parent)->online) == 1)
1677 PURPLE_GROUP(bnode->parent->parent)->online++;
1679 if (purple_account_is_connected(buddy->account)) {
1680 if (++(PURPLE_CONTACT(bnode->parent)->currentsize) == 1)
1681 PURPLE_GROUP(bnode->parent->parent)->currentsize++;
1683 PURPLE_CONTACT(bnode->parent)->totalsize++;
1685 hb = g_new(struct _purple_hbuddy, 1);
1686 hb->name = g_strdup(purple_normalize(buddy->account, buddy->name));
1687 hb->account = buddy->account;
1688 hb->group = ((PurpleBlistNode*)buddy)->parent->parent;
1690 g_hash_table_replace(purplebuddylist->buddies, hb, buddy);
1692 account_buddies = g_hash_table_lookup(buddies_cache, buddy->account);
1694 hb2 = g_new(struct _purple_hbuddy, 1);
1695 hb2->name = g_strdup(hb->name);
1696 hb2->account = buddy->account;
1697 hb2->group = ((PurpleBlistNode*)buddy)->parent->parent;
1699 g_hash_table_replace(account_buddies, hb2, buddy);
1701 purple_contact_invalidate_priority_buddy(purple_buddy_get_contact(buddy));
1703 if (ops && ops->save_node)
1704 ops->save_node((PurpleBlistNode*) buddy);
1706 if (ops && ops->update)
1707 ops->update(purplebuddylist, (PurpleBlistNode*)buddy);
1709 /* Signal that the buddy has been added */
1710 purple_signal_emit(purple_blist_get_handle(), "buddy-added", buddy);
1712 purple_signal_emit(purple_blist_get_handle(), "blist-node-added",
1713 PURPLE_BLIST_NODE(buddy));
1716 PurpleContact *purple_contact_new()
1718 PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
1720 PurpleContact *contact = g_new0(PurpleContact, 1);
1721 contact->totalsize = 0;
1722 contact->currentsize = 0;
1723 contact->online = 0;
1724 purple_blist_node_initialize_settings((PurpleBlistNode *)contact);
1725 ((PurpleBlistNode *)contact)->type = PURPLE_BLIST_CONTACT_NODE;
1727 if (ops && ops->new_node)
1728 ops->new_node((PurpleBlistNode *)contact);
1730 PURPLE_DBUS_REGISTER_POINTER(contact, PurpleContact);
1731 return contact;
1734 void
1735 purple_contact_destroy(PurpleContact *contact)
1737 g_hash_table_destroy(contact->node.settings);
1738 g_free(contact->alias);
1739 PURPLE_DBUS_UNREGISTER_POINTER(contact);
1740 g_free(contact);
1743 PurpleGroup *
1744 purple_contact_get_group(const PurpleContact *contact)
1746 g_return_val_if_fail(contact, NULL);
1748 return (PurpleGroup *)(((PurpleBlistNode *)contact)->parent);
1751 void purple_contact_set_alias(PurpleContact *contact, const char *alias)
1753 purple_blist_alias_contact(contact,alias);
1756 const char *purple_contact_get_alias(PurpleContact* contact)
1758 g_return_val_if_fail(contact != NULL, NULL);
1760 if (contact->alias)
1761 return contact->alias;
1763 return purple_buddy_get_alias(purple_contact_get_priority_buddy(contact));
1766 gboolean purple_contact_on_account(PurpleContact *c, PurpleAccount *account)
1768 PurpleBlistNode *bnode, *cnode = (PurpleBlistNode *) c;
1770 g_return_val_if_fail(c != NULL, FALSE);
1771 g_return_val_if_fail(account != NULL, FALSE);
1773 for (bnode = cnode->child; bnode; bnode = bnode->next) {
1774 PurpleBuddy *buddy;
1776 if (! PURPLE_BLIST_NODE_IS_BUDDY(bnode))
1777 continue;
1779 buddy = (PurpleBuddy *)bnode;
1780 if (buddy->account == account)
1781 return TRUE;
1783 return FALSE;
1786 void purple_contact_invalidate_priority_buddy(PurpleContact *contact)
1788 g_return_if_fail(contact != NULL);
1790 contact->priority_valid = FALSE;
1793 PurpleGroup *purple_group_new(const char *name)
1795 PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
1796 PurpleGroup *group;
1798 g_return_val_if_fail(name != NULL, NULL);
1799 g_return_val_if_fail(*name != '\0', NULL);
1801 group = purple_find_group(name);
1802 if (group != NULL)
1803 return group;
1805 group = g_new0(PurpleGroup, 1);
1806 group->name = purple_utf8_strip_unprintables(name);
1807 group->totalsize = 0;
1808 group->currentsize = 0;
1809 group->online = 0;
1810 purple_blist_node_initialize_settings((PurpleBlistNode *)group);
1811 ((PurpleBlistNode *)group)->type = PURPLE_BLIST_GROUP_NODE;
1813 if (ops && ops->new_node)
1814 ops->new_node((PurpleBlistNode *)group);
1816 PURPLE_DBUS_REGISTER_POINTER(group, PurpleGroup);
1817 return group;
1820 void
1821 purple_group_destroy(PurpleGroup *group)
1823 g_hash_table_destroy(group->node.settings);
1824 g_free(group->name);
1825 PURPLE_DBUS_UNREGISTER_POINTER(group);
1826 g_free(group);
1829 void purple_blist_add_contact(PurpleContact *contact, PurpleGroup *group, PurpleBlistNode *node)
1831 PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
1832 PurpleGroup *g;
1833 PurpleBlistNode *gnode, *cnode, *bnode;
1835 g_return_if_fail(contact != NULL);
1836 g_return_if_fail(PURPLE_BLIST_NODE_IS_CONTACT((PurpleBlistNode*)contact));
1838 if (PURPLE_BLIST_NODE(contact) == node)
1839 return;
1841 if (node && (PURPLE_BLIST_NODE_IS_CONTACT(node) ||
1842 PURPLE_BLIST_NODE_IS_CHAT(node)))
1843 g = (PurpleGroup*)node->parent;
1844 else if (group)
1845 g = group;
1846 else {
1847 g = purple_find_group(_("Buddies"));
1848 if (g == NULL) {
1849 g = purple_group_new(_("Buddies"));
1850 purple_blist_add_group(g,
1851 purple_blist_get_last_sibling(purplebuddylist->root));
1855 gnode = (PurpleBlistNode*)g;
1856 cnode = (PurpleBlistNode*)contact;
1858 if (cnode->parent) {
1859 if (cnode->parent->child == cnode)
1860 cnode->parent->child = cnode->next;
1861 if (cnode->prev)
1862 cnode->prev->next = cnode->next;
1863 if (cnode->next)
1864 cnode->next->prev = cnode->prev;
1866 if (cnode->parent != gnode) {
1867 bnode = cnode->child;
1868 while (bnode) {
1869 PurpleBlistNode *next_bnode = bnode->next;
1870 PurpleBuddy *b = (PurpleBuddy*)bnode;
1871 GHashTable *account_buddies;
1873 struct _purple_hbuddy *hb, *hb2;
1875 hb = g_new(struct _purple_hbuddy, 1);
1876 hb->name = g_strdup(purple_normalize(b->account, b->name));
1877 hb->account = b->account;
1878 hb->group = cnode->parent;
1880 g_hash_table_remove(purplebuddylist->buddies, hb);
1882 account_buddies = g_hash_table_lookup(buddies_cache, b->account);
1883 g_hash_table_remove(account_buddies, hb);
1885 if (!purple_find_buddy_in_group(b->account, b->name, g)) {
1886 hb->group = gnode;
1887 g_hash_table_replace(purplebuddylist->buddies, hb, b);
1889 hb2 = g_new(struct _purple_hbuddy, 1);
1890 hb2->name = g_strdup(hb->name);
1891 hb2->account = b->account;
1892 hb2->group = gnode;
1894 g_hash_table_replace(account_buddies, hb2, b);
1896 if (purple_account_get_connection(b->account))
1897 serv_move_buddy(b, (PurpleGroup *)cnode->parent, g);
1898 } else {
1899 gboolean empty_contact = FALSE;
1901 /* this buddy already exists in the group, so we're
1902 * gonna delete it instead */
1903 g_free(hb->name);
1904 g_free(hb);
1905 if (purple_account_get_connection(b->account))
1906 purple_account_remove_buddy(b->account, b, (PurpleGroup *)cnode->parent);
1908 if (!cnode->child->next)
1909 empty_contact = TRUE;
1910 purple_blist_remove_buddy(b);
1912 /** in purple_blist_remove_buddy(), if the last buddy in a
1913 * contact is removed, the contact is cleaned up and
1914 * g_free'd, so we mustn't try to reference bnode->next */
1915 if (empty_contact)
1916 return;
1918 bnode = next_bnode;
1922 if (contact->online > 0)
1923 ((PurpleGroup*)cnode->parent)->online--;
1924 if (contact->currentsize > 0)
1925 ((PurpleGroup*)cnode->parent)->currentsize--;
1926 ((PurpleGroup*)cnode->parent)->totalsize--;
1928 if (ops && ops->remove)
1929 ops->remove(purplebuddylist, cnode);
1931 if (ops && ops->remove_node)
1932 ops->remove_node(cnode);
1935 if (node && (PURPLE_BLIST_NODE_IS_CONTACT(node) ||
1936 PURPLE_BLIST_NODE_IS_CHAT(node))) {
1937 if (node->next)
1938 node->next->prev = cnode;
1939 cnode->next = node->next;
1940 cnode->prev = node;
1941 cnode->parent = node->parent;
1942 node->next = cnode;
1943 } else {
1944 if (gnode->child)
1945 gnode->child->prev = cnode;
1946 cnode->prev = NULL;
1947 cnode->next = gnode->child;
1948 gnode->child = cnode;
1949 cnode->parent = gnode;
1952 if (contact->online > 0)
1953 g->online++;
1954 if (contact->currentsize > 0)
1955 g->currentsize++;
1956 g->totalsize++;
1958 if (ops && ops->save_node)
1960 if (cnode->child)
1961 ops->save_node(cnode);
1962 for (bnode = cnode->child; bnode; bnode = bnode->next)
1963 ops->save_node(bnode);
1966 if (ops && ops->update)
1968 if (cnode->child)
1969 ops->update(purplebuddylist, cnode);
1971 for (bnode = cnode->child; bnode; bnode = bnode->next)
1972 ops->update(purplebuddylist, bnode);
1976 void purple_blist_merge_contact(PurpleContact *source, PurpleBlistNode *node)
1978 PurpleBlistNode *sourcenode = (PurpleBlistNode*)source;
1979 PurpleBlistNode *prev, *cur, *next;
1980 PurpleContact *target;
1982 g_return_if_fail(source != NULL);
1983 g_return_if_fail(node != NULL);
1985 if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
1986 target = (PurpleContact *)node;
1987 prev = purple_blist_get_last_child(node);
1988 } else if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
1989 target = (PurpleContact *)node->parent;
1990 prev = node;
1991 } else {
1992 return;
1995 if (source == target || !target)
1996 return;
1998 next = sourcenode->child;
2000 while (next) {
2001 cur = next;
2002 next = cur->next;
2003 if (PURPLE_BLIST_NODE_IS_BUDDY(cur)) {
2004 purple_blist_add_buddy((PurpleBuddy *)cur, target, NULL, prev);
2005 prev = cur;
2010 void purple_blist_add_group(PurpleGroup *group, PurpleBlistNode *node)
2012 PurpleBlistUiOps *ops;
2013 PurpleBlistNode *gnode = (PurpleBlistNode*)group;
2014 gchar* key;
2016 g_return_if_fail(group != NULL);
2017 g_return_if_fail(PURPLE_BLIST_NODE_IS_GROUP((PurpleBlistNode *)group));
2019 ops = purple_blist_get_ui_ops();
2021 /* if we're moving to overtop of ourselves, do nothing */
2022 if (gnode == node) {
2023 if (!purplebuddylist->root)
2024 node = NULL;
2025 else
2026 return;
2029 if (purple_find_group(group->name)) {
2030 /* This is just being moved */
2032 if (ops && ops->remove)
2033 ops->remove(purplebuddylist, (PurpleBlistNode *)group);
2035 if (gnode == purplebuddylist->root)
2036 purplebuddylist->root = gnode->next;
2037 if (gnode->prev)
2038 gnode->prev->next = gnode->next;
2039 if (gnode->next)
2040 gnode->next->prev = gnode->prev;
2041 } else {
2042 key = g_utf8_collate_key(group->name, -1);
2043 g_hash_table_insert(groups_cache, key, group);
2046 if (node && PURPLE_BLIST_NODE_IS_GROUP(node)) {
2047 gnode->next = node->next;
2048 gnode->prev = node;
2049 if (node->next)
2050 node->next->prev = gnode;
2051 node->next = gnode;
2052 } else {
2053 if (purplebuddylist->root)
2054 purplebuddylist->root->prev = gnode;
2055 gnode->next = purplebuddylist->root;
2056 gnode->prev = NULL;
2057 purplebuddylist->root = gnode;
2060 if (ops && ops->save_node) {
2061 ops->save_node(gnode);
2062 for (node = gnode->child; node; node = node->next)
2063 ops->save_node(node);
2066 if (ops && ops->update) {
2067 ops->update(purplebuddylist, gnode);
2068 for (node = gnode->child; node; node = node->next)
2069 ops->update(purplebuddylist, node);
2072 purple_signal_emit(purple_blist_get_handle(), "blist-node-added",
2073 gnode);
2076 void purple_blist_remove_contact(PurpleContact *contact)
2078 PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
2079 PurpleBlistNode *node, *gnode;
2081 g_return_if_fail(contact != NULL);
2083 node = (PurpleBlistNode *)contact;
2084 gnode = node->parent;
2086 if (node->child) {
2088 * If this contact has children then remove them. When the last
2089 * buddy is removed from the contact, the contact is automatically
2090 * deleted.
2092 while (node->child->next) {
2093 purple_blist_remove_buddy((PurpleBuddy*)node->child);
2096 * Remove the last buddy and trigger the deletion of the contact.
2097 * It would probably be cleaner if contact-deletion was done after
2098 * a timeout? Or if it had to be done manually, like below?
2100 purple_blist_remove_buddy((PurpleBuddy*)node->child);
2101 } else {
2102 /* Remove the node from its parent */
2103 if (gnode->child == node)
2104 gnode->child = node->next;
2105 if (node->prev)
2106 node->prev->next = node->next;
2107 if (node->next)
2108 node->next->prev = node->prev;
2110 /* Update the UI */
2111 if (ops && ops->remove)
2112 ops->remove(purplebuddylist, node);
2114 if (ops && ops->remove_node)
2115 ops->remove_node(node);
2117 purple_signal_emit(purple_blist_get_handle(), "blist-node-removed",
2118 PURPLE_BLIST_NODE(contact));
2120 /* Delete the node */
2121 purple_contact_destroy(contact);
2125 void purple_blist_remove_buddy(PurpleBuddy *buddy)
2127 PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
2128 PurpleBlistNode *node, *cnode, *gnode;
2129 PurpleContact *contact;
2130 PurpleGroup *group;
2131 struct _purple_hbuddy hb;
2132 GHashTable *account_buddies;
2134 g_return_if_fail(buddy != NULL);
2136 node = (PurpleBlistNode *)buddy;
2137 cnode = node->parent;
2138 gnode = (cnode != NULL) ? cnode->parent : NULL;
2139 contact = (PurpleContact *)cnode;
2140 group = (PurpleGroup *)gnode;
2142 /* Remove the node from its parent */
2143 if (node->prev)
2144 node->prev->next = node->next;
2145 if (node->next)
2146 node->next->prev = node->prev;
2147 if ((cnode != NULL) && (cnode->child == node))
2148 cnode->child = node->next;
2150 /* Adjust size counts */
2151 if (contact != NULL) {
2152 if (PURPLE_BUDDY_IS_ONLINE(buddy)) {
2153 contact->online--;
2154 if (contact->online == 0)
2155 group->online--;
2157 if (purple_account_is_connected(buddy->account)) {
2158 contact->currentsize--;
2159 if (contact->currentsize == 0)
2160 group->currentsize--;
2162 contact->totalsize--;
2164 /* Re-sort the contact */
2165 if (cnode->child && contact->priority == buddy) {
2166 purple_contact_invalidate_priority_buddy(contact);
2167 if (ops && ops->update)
2168 ops->update(purplebuddylist, cnode);
2172 /* Remove this buddy from the buddies hash table */
2173 hb.name = (gchar *)purple_normalize(buddy->account, buddy->name);
2174 hb.account = buddy->account;
2175 hb.group = gnode;
2176 g_hash_table_remove(purplebuddylist->buddies, &hb);
2178 account_buddies = g_hash_table_lookup(buddies_cache, buddy->account);
2179 g_hash_table_remove(account_buddies, &hb);
2181 /* Update the UI */
2182 if (ops && ops->remove)
2183 ops->remove(purplebuddylist, node);
2185 if (ops && ops->remove_node)
2186 ops->remove_node(node);
2188 /* Remove this buddy's pounces */
2189 purple_pounce_destroy_all_by_buddy(buddy);
2191 /* Signal that the buddy has been removed before freeing the memory for it */
2192 purple_signal_emit(purple_blist_get_handle(), "buddy-removed", buddy);
2194 purple_signal_emit(purple_blist_get_handle(), "blist-node-removed",
2195 PURPLE_BLIST_NODE(buddy));
2197 purple_buddy_destroy(buddy);
2199 /* If the contact is empty then remove it */
2200 if ((contact != NULL) && !cnode->child)
2201 purple_blist_remove_contact(contact);
2204 void purple_blist_remove_chat(PurpleChat *chat)
2206 PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
2207 PurpleBlistNode *node, *gnode;
2208 PurpleGroup *group;
2210 g_return_if_fail(chat != NULL);
2212 node = (PurpleBlistNode *)chat;
2213 gnode = node->parent;
2214 group = (PurpleGroup *)gnode;
2216 if (gnode != NULL)
2218 /* Remove the node from its parent */
2219 if (gnode->child == node)
2220 gnode->child = node->next;
2221 if (node->prev)
2222 node->prev->next = node->next;
2223 if (node->next)
2224 node->next->prev = node->prev;
2226 /* Adjust size counts */
2227 if (purple_account_is_connected(chat->account)) {
2228 group->online--;
2229 group->currentsize--;
2231 group->totalsize--;
2235 /* Update the UI */
2236 if (ops && ops->remove)
2237 ops->remove(purplebuddylist, node);
2239 if (ops && ops->remove_node)
2240 ops->remove_node(node);
2242 purple_signal_emit(purple_blist_get_handle(), "blist-node-removed",
2243 PURPLE_BLIST_NODE(chat));
2245 /* Delete the node */
2246 purple_chat_destroy(chat);
2249 void purple_blist_remove_group(PurpleGroup *group)
2251 PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
2252 PurpleBlistNode *node;
2253 GList *l;
2254 gchar* key;
2256 g_return_if_fail(group != NULL);
2258 node = (PurpleBlistNode *)group;
2260 /* Make sure the group is empty */
2261 if (node->child)
2262 return;
2264 /* Remove the node from its parent */
2265 if (purplebuddylist->root == node)
2266 purplebuddylist->root = node->next;
2267 if (node->prev)
2268 node->prev->next = node->next;
2269 if (node->next)
2270 node->next->prev = node->prev;
2272 key = g_utf8_collate_key(group->name, -1);
2273 g_hash_table_remove(groups_cache, key);
2274 g_free(key);
2276 /* Update the UI */
2277 if (ops && ops->remove)
2278 ops->remove(purplebuddylist, node);
2280 if (ops && ops->remove_node)
2281 ops->remove_node(node);
2283 purple_signal_emit(purple_blist_get_handle(), "blist-node-removed",
2284 PURPLE_BLIST_NODE(group));
2286 /* Remove the group from all accounts that are online */
2287 for (l = purple_connections_get_all(); l != NULL; l = l->next)
2289 PurpleConnection *gc = (PurpleConnection *)l->data;
2291 if (purple_connection_get_state(gc) == PURPLE_CONNECTED)
2292 purple_account_remove_group(purple_connection_get_account(gc), group);
2295 /* Delete the node */
2296 purple_group_destroy(group);
2299 PurpleBuddy *purple_contact_get_priority_buddy(PurpleContact *contact)
2301 g_return_val_if_fail(contact != NULL, NULL);
2303 if (!contact->priority_valid)
2304 purple_contact_compute_priority_buddy(contact);
2306 return contact->priority;
2309 const char *purple_buddy_get_alias_only(PurpleBuddy *buddy)
2311 g_return_val_if_fail(buddy != NULL, NULL);
2313 if ((buddy->alias != NULL) && (*buddy->alias != '\0')) {
2314 return buddy->alias;
2315 } else if ((buddy->server_alias != NULL) &&
2316 (*buddy->server_alias != '\0')) {
2318 return buddy->server_alias;
2321 return NULL;
2325 const char *purple_buddy_get_contact_alias(PurpleBuddy *buddy)
2327 PurpleContact *c;
2329 g_return_val_if_fail(buddy != NULL, NULL);
2331 /* Search for an alias for the buddy. In order of precedence: */
2332 /* The buddy alias */
2333 if (buddy->alias != NULL)
2334 return buddy->alias;
2336 /* The contact alias */
2337 c = purple_buddy_get_contact(buddy);
2338 if ((c != NULL) && (c->alias != NULL))
2339 return c->alias;
2341 /* The server alias */
2342 if ((buddy->server_alias) && (*buddy->server_alias))
2343 return buddy->server_alias;
2345 /* The buddy's user name (i.e. no alias) */
2346 return buddy->name;
2350 const char *purple_buddy_get_alias(PurpleBuddy *buddy)
2352 g_return_val_if_fail(buddy != NULL, NULL);
2354 /* Search for an alias for the buddy. In order of precedence: */
2355 /* The buddy alias */
2356 if (buddy->alias != NULL)
2357 return buddy->alias;
2359 /* The server alias */
2360 if ((buddy->server_alias) && (*buddy->server_alias))
2361 return buddy->server_alias;
2363 /* The buddy's user name (i.e. no alias) */
2364 return buddy->name;
2367 const char *purple_buddy_get_local_buddy_alias(PurpleBuddy *buddy)
2369 g_return_val_if_fail(buddy, NULL);
2370 return buddy->alias;
2373 const char *purple_buddy_get_server_alias(PurpleBuddy *buddy)
2375 g_return_val_if_fail(buddy != NULL, NULL);
2377 if ((buddy->server_alias) && (*buddy->server_alias))
2378 return buddy->server_alias;
2380 return NULL;
2383 const char *purple_buddy_get_local_alias(PurpleBuddy *buddy)
2385 PurpleContact *c;
2387 g_return_val_if_fail(buddy != NULL, NULL);
2389 /* Search for an alias for the buddy. In order of precedence: */
2390 /* The buddy alias */
2391 if (buddy->alias != NULL)
2392 return buddy->alias;
2394 /* The contact alias */
2395 c = purple_buddy_get_contact(buddy);
2396 if ((c != NULL) && (c->alias != NULL))
2397 return c->alias;
2399 /* The buddy's user name (i.e. no alias) */
2400 return buddy->name;
2403 const char *purple_chat_get_name(PurpleChat *chat)
2405 char *ret = NULL;
2406 PurplePlugin *prpl;
2407 PurplePluginProtocolInfo *prpl_info = NULL;
2409 g_return_val_if_fail(chat != NULL, NULL);
2411 if ((chat->alias != NULL) && (*chat->alias != '\0'))
2412 return chat->alias;
2414 prpl = purple_find_prpl(purple_account_get_protocol_id(chat->account));
2415 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
2417 if (prpl_info->chat_info) {
2418 struct proto_chat_entry *pce;
2419 GList *parts = prpl_info->chat_info(purple_account_get_connection(chat->account));
2420 pce = parts->data;
2421 ret = g_hash_table_lookup(chat->components, pce->identifier);
2422 g_list_foreach(parts, (GFunc)g_free, NULL);
2423 g_list_free(parts);
2426 return ret;
2429 PurpleBuddy *purple_find_buddy(PurpleAccount *account, const char *name)
2431 PurpleBuddy *buddy;
2432 struct _purple_hbuddy hb;
2433 PurpleBlistNode *group;
2435 g_return_val_if_fail(purplebuddylist != NULL, NULL);
2436 g_return_val_if_fail(account != NULL, NULL);
2437 g_return_val_if_fail((name != NULL) && (*name != '\0'), NULL);
2439 hb.account = account;
2440 hb.name = (gchar *)purple_normalize(account, name);
2442 for (group = purplebuddylist->root; group; group = group->next) {
2443 if (!group->child)
2444 continue;
2446 hb.group = group;
2447 if ((buddy = g_hash_table_lookup(purplebuddylist->buddies, &hb))) {
2448 return buddy;
2452 return NULL;
2455 PurpleBuddy *purple_find_buddy_in_group(PurpleAccount *account, const char *name,
2456 PurpleGroup *group)
2458 struct _purple_hbuddy hb;
2460 g_return_val_if_fail(purplebuddylist != NULL, NULL);
2461 g_return_val_if_fail(account != NULL, NULL);
2462 g_return_val_if_fail((name != NULL) && (*name != '\0'), NULL);
2464 hb.name = (gchar *)purple_normalize(account, name);
2465 hb.account = account;
2466 hb.group = (PurpleBlistNode*)group;
2468 return g_hash_table_lookup(purplebuddylist->buddies, &hb);
2471 static void find_acct_buddies(gpointer key, gpointer value, gpointer data)
2473 PurpleBuddy *buddy = value;
2474 GSList **list = data;
2476 *list = g_slist_prepend(*list, buddy);
2479 GSList *purple_find_buddies(PurpleAccount *account, const char *name)
2481 PurpleBuddy *buddy;
2482 PurpleBlistNode *node;
2483 GSList *ret = NULL;
2485 g_return_val_if_fail(purplebuddylist != NULL, NULL);
2486 g_return_val_if_fail(account != NULL, NULL);
2488 if ((name != NULL) && (*name != '\0')) {
2489 struct _purple_hbuddy hb;
2491 hb.name = (gchar *)purple_normalize(account, name);
2492 hb.account = account;
2494 for (node = purplebuddylist->root; node != NULL; node = node->next) {
2495 if (!node->child)
2496 continue;
2498 hb.group = node;
2499 if ((buddy = g_hash_table_lookup(purplebuddylist->buddies, &hb)) != NULL)
2500 ret = g_slist_prepend(ret, buddy);
2502 } else {
2503 GSList *list = NULL;
2504 GHashTable *buddies = g_hash_table_lookup(buddies_cache, account);
2505 g_hash_table_foreach(buddies, find_acct_buddies, &list);
2506 ret = list;
2509 return ret;
2512 PurpleGroup *purple_find_group(const char *name)
2514 gchar* key;
2515 PurpleGroup *group;
2517 g_return_val_if_fail(purplebuddylist != NULL, NULL);
2518 g_return_val_if_fail((name != NULL) && (*name != '\0'), NULL);
2520 key = g_utf8_collate_key(name, -1);
2521 group = g_hash_table_lookup(groups_cache, key);
2522 g_free(key);
2524 return group;
2527 PurpleChat *
2528 purple_blist_find_chat(PurpleAccount *account, const char *name)
2530 char *chat_name;
2531 PurpleChat *chat;
2532 PurplePlugin *prpl;
2533 PurplePluginProtocolInfo *prpl_info = NULL;
2534 struct proto_chat_entry *pce;
2535 PurpleBlistNode *node, *group;
2536 GList *parts;
2537 char *normname;
2539 g_return_val_if_fail(purplebuddylist != NULL, NULL);
2540 g_return_val_if_fail((name != NULL) && (*name != '\0'), NULL);
2542 if (!purple_account_is_connected(account))
2543 return NULL;
2545 prpl = purple_find_prpl(purple_account_get_protocol_id(account));
2546 prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
2548 if (prpl_info->find_blist_chat != NULL)
2549 return prpl_info->find_blist_chat(account, name);
2551 normname = g_strdup(purple_normalize(account, name));
2552 for (group = purplebuddylist->root; group != NULL; group = group->next) {
2553 for (node = group->child; node != NULL; node = node->next) {
2554 if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
2556 chat = (PurpleChat*)node;
2558 if (account != chat->account)
2559 continue;
2561 parts = prpl_info->chat_info(
2562 purple_account_get_connection(chat->account));
2564 pce = parts->data;
2565 chat_name = g_hash_table_lookup(chat->components,
2566 pce->identifier);
2567 g_list_foreach(parts, (GFunc)g_free, NULL);
2568 g_list_free(parts);
2570 if (chat->account == account && chat_name != NULL &&
2571 normname != NULL && !strcmp(purple_normalize(account, chat_name), normname)) {
2572 g_free(normname);
2573 return chat;
2579 g_free(normname);
2580 return NULL;
2583 PurpleGroup *
2584 purple_chat_get_group(PurpleChat *chat)
2586 g_return_val_if_fail(chat != NULL, NULL);
2588 return (PurpleGroup *)(((PurpleBlistNode *)chat)->parent);
2591 PurpleAccount *
2592 purple_chat_get_account(PurpleChat *chat)
2594 g_return_val_if_fail(chat != NULL, NULL);
2596 return chat->account;
2599 GHashTable *
2600 purple_chat_get_components(PurpleChat *chat)
2602 g_return_val_if_fail(chat != NULL, NULL);
2604 return chat->components;
2607 PurpleContact *purple_buddy_get_contact(PurpleBuddy *buddy)
2609 g_return_val_if_fail(buddy != NULL, NULL);
2611 return PURPLE_CONTACT(PURPLE_BLIST_NODE(buddy)->parent);
2614 PurplePresence *purple_buddy_get_presence(const PurpleBuddy *buddy)
2616 g_return_val_if_fail(buddy != NULL, NULL);
2617 return buddy->presence;
2620 PurpleMediaCaps purple_buddy_get_media_caps(const PurpleBuddy *buddy)
2622 g_return_val_if_fail(buddy != NULL, 0);
2623 return buddy->media_caps;
2626 void purple_buddy_set_media_caps(PurpleBuddy *buddy, PurpleMediaCaps media_caps)
2628 g_return_if_fail(buddy != NULL);
2629 buddy->media_caps = media_caps;
2632 PurpleGroup *purple_buddy_get_group(PurpleBuddy *buddy)
2634 g_return_val_if_fail(buddy != NULL, NULL);
2636 if (((PurpleBlistNode *)buddy)->parent == NULL)
2637 return NULL;
2639 return (PurpleGroup *)(((PurpleBlistNode*)buddy)->parent->parent);
2642 GSList *purple_group_get_accounts(PurpleGroup *group)
2644 GSList *l = NULL;
2645 PurpleBlistNode *gnode, *cnode, *bnode;
2647 gnode = (PurpleBlistNode *)group;
2649 for (cnode = gnode->child; cnode; cnode = cnode->next) {
2650 if (PURPLE_BLIST_NODE_IS_CHAT(cnode)) {
2651 if (!g_slist_find(l, ((PurpleChat *)cnode)->account))
2652 l = g_slist_append(l, ((PurpleChat *)cnode)->account);
2653 } else if (PURPLE_BLIST_NODE_IS_CONTACT(cnode)) {
2654 for (bnode = cnode->child; bnode; bnode = bnode->next) {
2655 if (PURPLE_BLIST_NODE_IS_BUDDY(bnode)) {
2656 if (!g_slist_find(l, ((PurpleBuddy *)bnode)->account))
2657 l = g_slist_append(l, ((PurpleBuddy *)bnode)->account);
2663 return l;
2666 void purple_blist_add_account(PurpleAccount *account)
2668 PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
2669 PurpleBlistNode *gnode, *cnode, *bnode;
2671 g_return_if_fail(purplebuddylist != NULL);
2673 if (!ops || !ops->update)
2674 return;
2676 for (gnode = purplebuddylist->root; gnode; gnode = gnode->next) {
2677 if (!PURPLE_BLIST_NODE_IS_GROUP(gnode))
2678 continue;
2679 for (cnode = gnode->child; cnode; cnode = cnode->next) {
2680 if (PURPLE_BLIST_NODE_IS_CONTACT(cnode)) {
2681 gboolean recompute = FALSE;
2682 for (bnode = cnode->child; bnode; bnode = bnode->next) {
2683 if (PURPLE_BLIST_NODE_IS_BUDDY(bnode) &&
2684 ((PurpleBuddy*)bnode)->account == account) {
2685 recompute = TRUE;
2686 ((PurpleContact*)cnode)->currentsize++;
2687 if (((PurpleContact*)cnode)->currentsize == 1)
2688 ((PurpleGroup*)gnode)->currentsize++;
2689 ops->update(purplebuddylist, bnode);
2692 if (recompute ||
2693 purple_blist_node_get_bool(cnode, "show_offline")) {
2694 purple_contact_invalidate_priority_buddy((PurpleContact*)cnode);
2695 ops->update(purplebuddylist, cnode);
2697 } else if (PURPLE_BLIST_NODE_IS_CHAT(cnode) &&
2698 ((PurpleChat*)cnode)->account == account) {
2699 ((PurpleGroup *)gnode)->online++;
2700 ((PurpleGroup *)gnode)->currentsize++;
2701 ops->update(purplebuddylist, cnode);
2704 ops->update(purplebuddylist, gnode);
2708 void purple_blist_remove_account(PurpleAccount *account)
2710 PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
2711 PurpleBlistNode *gnode, *cnode, *bnode;
2712 PurpleBuddy *buddy;
2713 PurpleChat *chat;
2714 PurpleContact *contact;
2715 PurpleGroup *group;
2716 GList *list = NULL, *iter = NULL;
2718 g_return_if_fail(purplebuddylist != NULL);
2720 for (gnode = purplebuddylist->root; gnode; gnode = gnode->next) {
2721 if (!PURPLE_BLIST_NODE_IS_GROUP(gnode))
2722 continue;
2724 group = (PurpleGroup *)gnode;
2726 for (cnode = gnode->child; cnode; cnode = cnode->next) {
2727 if (PURPLE_BLIST_NODE_IS_CONTACT(cnode)) {
2728 gboolean recompute = FALSE;
2729 contact = (PurpleContact *)cnode;
2731 for (bnode = cnode->child; bnode; bnode = bnode->next) {
2732 if (!PURPLE_BLIST_NODE_IS_BUDDY(bnode))
2733 continue;
2735 buddy = (PurpleBuddy *)bnode;
2736 if (account == buddy->account) {
2737 PurplePresence *presence;
2739 presence = purple_buddy_get_presence(buddy);
2741 if(purple_presence_is_online(presence)) {
2742 contact->online--;
2743 if (contact->online == 0)
2744 group->online--;
2746 purple_blist_node_set_int(&buddy->node,
2747 "last_seen", time(NULL));
2750 contact->currentsize--;
2751 if (contact->currentsize == 0)
2752 group->currentsize--;
2754 if (!g_list_find(list, presence))
2755 list = g_list_prepend(list, presence);
2757 if (contact->priority == buddy)
2758 purple_contact_invalidate_priority_buddy(contact);
2759 else
2760 recompute = TRUE;
2762 if (ops && ops->remove) {
2763 ops->remove(purplebuddylist, bnode);
2767 if (recompute) {
2768 purple_contact_invalidate_priority_buddy(contact);
2769 if (ops && ops->update)
2770 ops->update(purplebuddylist, cnode);
2772 } else if (PURPLE_BLIST_NODE_IS_CHAT(cnode)) {
2773 chat = (PurpleChat *)cnode;
2775 if(chat->account == account) {
2776 group->currentsize--;
2777 group->online--;
2779 if (ops && ops->remove)
2780 ops->remove(purplebuddylist, cnode);
2786 for (iter = list; iter; iter = iter->next)
2788 purple_presence_set_status_active(iter->data, "offline", TRUE);
2790 g_list_free(list);
2793 gboolean purple_group_on_account(PurpleGroup *g, PurpleAccount *account)
2795 PurpleBlistNode *cnode;
2796 for (cnode = ((PurpleBlistNode *)g)->child; cnode; cnode = cnode->next) {
2797 if (PURPLE_BLIST_NODE_IS_CONTACT(cnode)) {
2798 if(purple_contact_on_account((PurpleContact *) cnode, account))
2799 return TRUE;
2800 } else if (PURPLE_BLIST_NODE_IS_CHAT(cnode)) {
2801 PurpleChat *chat = (PurpleChat *)cnode;
2802 if ((!account && purple_account_is_connected(chat->account))
2803 || chat->account == account)
2804 return TRUE;
2807 return FALSE;
2810 const char *purple_group_get_name(PurpleGroup *group)
2812 g_return_val_if_fail(group != NULL, NULL);
2814 return group->name;
2817 void
2818 purple_blist_request_add_buddy(PurpleAccount *account, const char *username,
2819 const char *group, const char *alias)
2821 PurpleBlistUiOps *ui_ops;
2823 ui_ops = purple_blist_get_ui_ops();
2825 if (ui_ops != NULL && ui_ops->request_add_buddy != NULL)
2826 ui_ops->request_add_buddy(account, username, group, alias);
2829 void
2830 purple_blist_request_add_chat(PurpleAccount *account, PurpleGroup *group,
2831 const char *alias, const char *name)
2833 PurpleBlistUiOps *ui_ops;
2835 ui_ops = purple_blist_get_ui_ops();
2837 if (ui_ops != NULL && ui_ops->request_add_chat != NULL)
2838 ui_ops->request_add_chat(account, group, alias, name);
2841 void
2842 purple_blist_request_add_group(void)
2844 PurpleBlistUiOps *ui_ops;
2846 ui_ops = purple_blist_get_ui_ops();
2848 if (ui_ops != NULL && ui_ops->request_add_group != NULL)
2849 ui_ops->request_add_group();
2852 static void
2853 purple_blist_node_destroy(PurpleBlistNode *node)
2855 PurpleBlistUiOps *ui_ops;
2856 PurpleBlistNode *child, *next_child;
2858 ui_ops = purple_blist_get_ui_ops();
2859 child = node->child;
2860 while (child) {
2861 next_child = child->next;
2862 purple_blist_node_destroy(child);
2863 child = next_child;
2866 /* Allow the UI to free data */
2867 node->parent = NULL;
2868 node->child = NULL;
2869 node->next = NULL;
2870 node->prev = NULL;
2871 if (ui_ops && ui_ops->remove)
2872 ui_ops->remove(purplebuddylist, node);
2874 if (PURPLE_BLIST_NODE_IS_BUDDY(node))
2875 purple_buddy_destroy((PurpleBuddy*)node);
2876 else if (PURPLE_BLIST_NODE_IS_CHAT(node))
2877 purple_chat_destroy((PurpleChat*)node);
2878 else if (PURPLE_BLIST_NODE_IS_CONTACT(node))
2879 purple_contact_destroy((PurpleContact*)node);
2880 else if (PURPLE_BLIST_NODE_IS_GROUP(node))
2881 purple_group_destroy((PurpleGroup*)node);
2884 static void
2885 purple_blist_node_setting_free(gpointer data)
2887 PurpleValue *value;
2889 value = (PurpleValue *)data;
2891 purple_value_destroy(value);
2894 static void purple_blist_node_initialize_settings(PurpleBlistNode *node)
2896 if (node->settings)
2897 return;
2899 node->settings = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
2900 (GDestroyNotify)purple_blist_node_setting_free);
2903 void purple_blist_node_remove_setting(PurpleBlistNode *node, const char *key)
2905 PurpleBlistUiOps *ops;
2906 g_return_if_fail(node != NULL);
2907 g_return_if_fail(node->settings != NULL);
2908 g_return_if_fail(key != NULL);
2910 g_hash_table_remove(node->settings, key);
2912 ops = purple_blist_get_ui_ops();
2913 if (ops && ops->save_node)
2914 ops->save_node(node);
2917 void
2918 purple_blist_node_set_flags(PurpleBlistNode *node, PurpleBlistNodeFlags flags)
2920 g_return_if_fail(node != NULL);
2922 node->flags = flags;
2925 PurpleBlistNodeFlags
2926 purple_blist_node_get_flags(PurpleBlistNode *node)
2928 g_return_val_if_fail(node != NULL, 0);
2930 return node->flags;
2933 PurpleBlistNodeType
2934 purple_blist_node_get_type(PurpleBlistNode *node)
2936 g_return_val_if_fail(node != NULL, PURPLE_BLIST_OTHER_NODE);
2937 return node->type;
2940 void
2941 purple_blist_node_set_bool(PurpleBlistNode* node, const char *key, gboolean data)
2943 PurpleValue *value;
2944 PurpleBlistUiOps *ops;
2946 g_return_if_fail(node != NULL);
2947 g_return_if_fail(node->settings != NULL);
2948 g_return_if_fail(key != NULL);
2950 value = purple_value_new(PURPLE_TYPE_BOOLEAN);
2951 purple_value_set_boolean(value, data);
2953 g_hash_table_replace(node->settings, g_strdup(key), value);
2955 ops = purple_blist_get_ui_ops();
2956 if (ops && ops->save_node)
2957 ops->save_node(node);
2960 gboolean
2961 purple_blist_node_get_bool(PurpleBlistNode* node, const char *key)
2963 PurpleValue *value;
2965 g_return_val_if_fail(node != NULL, FALSE);
2966 g_return_val_if_fail(node->settings != NULL, FALSE);
2967 g_return_val_if_fail(key != NULL, FALSE);
2969 value = g_hash_table_lookup(node->settings, key);
2971 if (value == NULL)
2972 return FALSE;
2974 g_return_val_if_fail(purple_value_get_type(value) == PURPLE_TYPE_BOOLEAN, FALSE);
2976 return purple_value_get_boolean(value);
2979 void
2980 purple_blist_node_set_int(PurpleBlistNode* node, const char *key, int data)
2982 PurpleValue *value;
2983 PurpleBlistUiOps *ops;
2985 g_return_if_fail(node != NULL);
2986 g_return_if_fail(node->settings != NULL);
2987 g_return_if_fail(key != NULL);
2989 value = purple_value_new(PURPLE_TYPE_INT);
2990 purple_value_set_int(value, data);
2992 g_hash_table_replace(node->settings, g_strdup(key), value);
2994 ops = purple_blist_get_ui_ops();
2995 if (ops && ops->save_node)
2996 ops->save_node(node);
3000 purple_blist_node_get_int(PurpleBlistNode* node, const char *key)
3002 PurpleValue *value;
3004 g_return_val_if_fail(node != NULL, 0);
3005 g_return_val_if_fail(node->settings != NULL, 0);
3006 g_return_val_if_fail(key != NULL, 0);
3008 value = g_hash_table_lookup(node->settings, key);
3010 if (value == NULL)
3011 return 0;
3013 g_return_val_if_fail(purple_value_get_type(value) == PURPLE_TYPE_INT, 0);
3015 return purple_value_get_int(value);
3018 void
3019 purple_blist_node_set_string(PurpleBlistNode* node, const char *key, const char *data)
3021 PurpleValue *value;
3022 PurpleBlistUiOps *ops;
3024 g_return_if_fail(node != NULL);
3025 g_return_if_fail(node->settings != NULL);
3026 g_return_if_fail(key != NULL);
3028 value = purple_value_new(PURPLE_TYPE_STRING);
3029 purple_value_set_string(value, data);
3031 g_hash_table_replace(node->settings, g_strdup(key), value);
3033 ops = purple_blist_get_ui_ops();
3034 if (ops && ops->save_node)
3035 ops->save_node(node);
3038 const char *
3039 purple_blist_node_get_string(PurpleBlistNode* node, const char *key)
3041 PurpleValue *value;
3043 g_return_val_if_fail(node != NULL, NULL);
3044 g_return_val_if_fail(node->settings != NULL, NULL);
3045 g_return_val_if_fail(key != NULL, NULL);
3047 value = g_hash_table_lookup(node->settings, key);
3049 if (value == NULL)
3050 return NULL;
3052 g_return_val_if_fail(purple_value_get_type(value) == PURPLE_TYPE_STRING, NULL);
3054 return purple_value_get_string(value);
3057 GList *
3058 purple_blist_node_get_extended_menu(PurpleBlistNode *n)
3060 GList *menu = NULL;
3062 g_return_val_if_fail(n != NULL, NULL);
3064 purple_signal_emit(purple_blist_get_handle(),
3065 "blist-node-extended-menu",
3066 n, &menu);
3067 return menu;
3070 int purple_blist_get_group_size(PurpleGroup *group, gboolean offline)
3072 if (!group)
3073 return 0;
3075 return offline ? group->totalsize : group->currentsize;
3078 int purple_blist_get_group_online_count(PurpleGroup *group)
3080 if (!group)
3081 return 0;
3083 return group->online;
3086 void
3087 purple_blist_set_ui_ops(PurpleBlistUiOps *ops)
3089 gboolean overrode = FALSE;
3090 blist_ui_ops = ops;
3092 if (!ops)
3093 return;
3095 if (!ops->save_node) {
3096 ops->save_node = purple_blist_save_node;
3097 overrode = TRUE;
3099 if (!ops->remove_node) {
3100 ops->remove_node = purple_blist_save_node;
3101 overrode = TRUE;
3103 if (!ops->save_account) {
3104 ops->save_account = purple_blist_save_account;
3105 overrode = TRUE;
3108 if (overrode && (ops->save_node != purple_blist_save_node ||
3109 ops->remove_node != purple_blist_save_node ||
3110 ops->save_account != purple_blist_save_account)) {
3111 purple_debug_warning("blist", "Only some of the blist saving UI ops "
3112 "were overridden. This probably is not what you want!\n");
3116 PurpleBlistUiOps *
3117 purple_blist_get_ui_ops(void)
3119 return blist_ui_ops;
3123 void *
3124 purple_blist_get_handle(void)
3126 static int handle;
3128 return &handle;
3131 void
3132 purple_blist_init(void)
3134 void *handle = purple_blist_get_handle();
3136 purple_signal_register(handle, "buddy-status-changed",
3137 purple_marshal_VOID__POINTER_POINTER_POINTER, NULL,
3139 purple_value_new(PURPLE_TYPE_SUBTYPE,
3140 PURPLE_SUBTYPE_BLIST_BUDDY),
3141 purple_value_new(PURPLE_TYPE_SUBTYPE,
3142 PURPLE_SUBTYPE_STATUS),
3143 purple_value_new(PURPLE_TYPE_SUBTYPE,
3144 PURPLE_SUBTYPE_STATUS));
3145 purple_signal_register(handle, "buddy-privacy-changed",
3146 purple_marshal_VOID__POINTER, NULL,
3148 purple_value_new(PURPLE_TYPE_SUBTYPE,
3149 PURPLE_SUBTYPE_BLIST_BUDDY));
3151 purple_signal_register(handle, "buddy-idle-changed",
3152 purple_marshal_VOID__POINTER_INT_INT, NULL,
3154 purple_value_new(PURPLE_TYPE_SUBTYPE,
3155 PURPLE_SUBTYPE_BLIST_BUDDY),
3156 purple_value_new(PURPLE_TYPE_INT),
3157 purple_value_new(PURPLE_TYPE_INT));
3160 purple_signal_register(handle, "buddy-signed-on",
3161 purple_marshal_VOID__POINTER, NULL, 1,
3162 purple_value_new(PURPLE_TYPE_SUBTYPE,
3163 PURPLE_SUBTYPE_BLIST_BUDDY));
3165 purple_signal_register(handle, "buddy-signed-off",
3166 purple_marshal_VOID__POINTER, NULL, 1,
3167 purple_value_new(PURPLE_TYPE_SUBTYPE,
3168 PURPLE_SUBTYPE_BLIST_BUDDY));
3170 purple_signal_register(handle, "buddy-got-login-time",
3171 purple_marshal_VOID__POINTER, NULL, 1,
3172 purple_value_new(PURPLE_TYPE_SUBTYPE,
3173 PURPLE_SUBTYPE_BLIST_BUDDY));
3175 purple_signal_register(handle, "blist-node-added",
3176 purple_marshal_VOID__POINTER, NULL, 1,
3177 purple_value_new(PURPLE_TYPE_SUBTYPE,
3178 PURPLE_SUBTYPE_BLIST_NODE));
3180 purple_signal_register(handle, "blist-node-removed",
3181 purple_marshal_VOID__POINTER, NULL, 1,
3182 purple_value_new(PURPLE_TYPE_SUBTYPE,
3183 PURPLE_SUBTYPE_BLIST_NODE));
3185 purple_signal_register(handle, "buddy-added",
3186 purple_marshal_VOID__POINTER, NULL, 1,
3187 purple_value_new(PURPLE_TYPE_SUBTYPE,
3188 PURPLE_SUBTYPE_BLIST_BUDDY));
3190 purple_signal_register(handle, "buddy-removed",
3191 purple_marshal_VOID__POINTER, NULL, 1,
3192 purple_value_new(PURPLE_TYPE_SUBTYPE,
3193 PURPLE_SUBTYPE_BLIST_BUDDY));
3195 purple_signal_register(handle, "buddy-icon-changed",
3196 purple_marshal_VOID__POINTER, NULL, 1,
3197 purple_value_new(PURPLE_TYPE_SUBTYPE,
3198 PURPLE_SUBTYPE_BLIST_BUDDY));
3200 purple_signal_register(handle, "update-idle", purple_marshal_VOID, NULL, 0);
3202 purple_signal_register(handle, "blist-node-extended-menu",
3203 purple_marshal_VOID__POINTER_POINTER, NULL, 2,
3204 purple_value_new(PURPLE_TYPE_SUBTYPE,
3205 PURPLE_SUBTYPE_BLIST_NODE),
3206 purple_value_new(PURPLE_TYPE_BOXED, "GList **"));
3208 purple_signal_register(handle, "blist-node-aliased",
3209 purple_marshal_VOID__POINTER_POINTER, NULL, 2,
3210 purple_value_new(PURPLE_TYPE_SUBTYPE,
3211 PURPLE_SUBTYPE_BLIST_NODE),
3212 purple_value_new(PURPLE_TYPE_STRING));
3214 purple_signal_register(handle, "buddy-caps-changed",
3215 purple_marshal_VOID__POINTER_INT_INT, NULL,
3216 3, purple_value_new(PURPLE_TYPE_SUBTYPE,
3217 PURPLE_SUBTYPE_BLIST_BUDDY),
3218 purple_value_new(PURPLE_TYPE_INT),
3219 purple_value_new(PURPLE_TYPE_INT));
3221 purple_signal_connect(purple_accounts_get_handle(), "account-created",
3222 handle,
3223 PURPLE_CALLBACK(purple_blist_buddies_cache_add_account),
3224 NULL);
3226 purple_signal_connect(purple_accounts_get_handle(), "account-destroying",
3227 handle,
3228 PURPLE_CALLBACK(purple_blist_buddies_cache_remove_account),
3229 NULL);
3232 void
3233 purple_blist_uninit(void)
3235 PurpleBlistNode *node, *next_node;
3237 /* This happens if we quit before purple_set_blist is called. */
3238 if (purplebuddylist == NULL)
3239 return;
3241 if (save_timer != 0) {
3242 purple_timeout_remove(save_timer);
3243 save_timer = 0;
3244 purple_blist_sync();
3247 purple_blist_destroy();
3249 node = purple_blist_get_root();
3250 while (node) {
3251 next_node = node->next;
3252 purple_blist_node_destroy(node);
3253 node = next_node;
3255 purplebuddylist->root = NULL;
3257 g_hash_table_destroy(purplebuddylist->buddies);
3258 g_hash_table_destroy(buddies_cache);
3259 g_hash_table_destroy(groups_cache);
3261 buddies_cache = NULL;
3262 groups_cache = NULL;
3264 PURPLE_DBUS_UNREGISTER_POINTER(purplebuddylist);
3265 g_free(purplebuddylist);
3266 purplebuddylist = NULL;
3268 purple_signals_disconnect_by_handle(purple_blist_get_handle());
3269 purple_signals_unregister_by_instance(purple_blist_get_handle());