Replace g_list_remove_link+g_list_free_1 with g_list_delete_link
[pidgin-git.git] / libpurple / buddylist.c
blob218ed20843c83cdae19662380601f6a731c0266e
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 #include "internal.h"
24 #include "buddylist.h"
25 #include "conversation.h"
26 #include "debug.h"
27 #include "notify.h"
28 #include "pounce.h"
29 #include "prefs.h"
30 #include "protocol.h"
31 #include "server.h"
32 #include "signals.h"
33 #include "util.h"
34 #include "xmlnode.h"
36 /* Private data for a buddy list. */
37 typedef struct {
38 PurpleBlistNode *root;
39 GHashTable *buddies; /* Every buddy in this list */
40 } PurpleBuddyListPrivate;
42 static GType buddy_list_type = G_TYPE_INVALID;
43 static PurpleBuddyList *purplebuddylist = NULL;
45 G_DEFINE_TYPE_WITH_PRIVATE(PurpleBuddyList, purple_buddy_list, G_TYPE_OBJECT);
48 * A hash table used for efficient lookups of buddies by name.
49 * PurpleAccount* => GHashTable*, with the inner hash table being
50 * struct _purple_hbuddy => PurpleBuddy*
52 static GHashTable *buddies_cache = NULL;
55 * A hash table used for efficient lookups of groups by name.
56 * UTF-8 collate-key => PurpleGroup*.
58 static GHashTable *groups_cache = NULL;
60 static guint save_timer = 0;
61 static gboolean blist_loaded = FALSE;
62 static gchar *localized_default_group_name = NULL;
64 /*********************************************************************
65 * Private utility functions *
66 *********************************************************************/
68 static gchar *
69 purple_blist_fold_name(const gchar *name)
71 gchar *res, *tmp;
73 if (name == NULL)
74 return NULL;
76 tmp = g_utf8_casefold(name, -1);
77 res = g_utf8_collate_key(tmp, -1);
78 g_free(tmp);
80 return res;
83 static PurpleBlistNode *purple_blist_get_last_sibling(PurpleBlistNode *node)
85 PurpleBlistNode *n = node;
86 if (!n)
87 return NULL;
88 while (n->next)
89 n = n->next;
90 return n;
93 PurpleBlistNode *_purple_blist_get_last_child(PurpleBlistNode *node)
95 if (!node)
96 return NULL;
97 return purple_blist_get_last_sibling(node->child);
100 struct _list_account_buddies {
101 GSList *list;
102 PurpleAccount *account;
105 struct _purple_hbuddy {
106 char *name;
107 PurpleAccount *account;
108 PurpleBlistNode *group;
111 /* This function must not use purple_normalize */
112 static guint _purple_blist_hbuddy_hash(struct _purple_hbuddy *hb)
114 return g_str_hash(hb->name) ^ g_direct_hash(hb->group) ^ g_direct_hash(hb->account);
117 /* This function must not use purple_normalize */
118 static guint _purple_blist_hbuddy_equal(struct _purple_hbuddy *hb1, struct _purple_hbuddy *hb2)
120 return (hb1->group == hb2->group &&
121 hb1->account == hb2->account &&
122 purple_strequal(hb1->name, hb2->name));
125 static void _purple_blist_hbuddy_free_key(struct _purple_hbuddy *hb)
127 g_free(hb->name);
128 g_free(hb);
131 static void
132 purple_blist_buddies_cache_add_account(PurpleAccount *account)
134 GHashTable *account_buddies = g_hash_table_new_full((GHashFunc)_purple_blist_hbuddy_hash,
135 (GEqualFunc)_purple_blist_hbuddy_equal,
136 (GDestroyNotify)_purple_blist_hbuddy_free_key, NULL);
137 g_hash_table_insert(buddies_cache, account, account_buddies);
140 static void
141 purple_blist_buddies_cache_remove_account(const PurpleAccount *account)
143 g_hash_table_remove(buddies_cache, account);
146 /*********************************************************************
147 * Writing to disk *
148 *********************************************************************/
150 static void
151 value_to_xmlnode(gpointer key, gpointer hvalue, gpointer user_data)
153 const char *name;
154 GValue *value;
155 PurpleXmlNode *node, *child;
156 char buf[21];
158 name = (const char *)key;
159 value = (GValue *)hvalue;
160 node = (PurpleXmlNode *)user_data;
162 g_return_if_fail(value != NULL);
164 child = purple_xmlnode_new_child(node, "setting");
165 purple_xmlnode_set_attrib(child, "name", name);
167 if (G_VALUE_HOLDS_INT(value)) {
168 purple_xmlnode_set_attrib(child, "type", "int");
169 g_snprintf(buf, sizeof(buf), "%d", g_value_get_int(value));
170 purple_xmlnode_insert_data(child, buf, -1);
172 else if (G_VALUE_HOLDS_STRING(value)) {
173 purple_xmlnode_set_attrib(child, "type", "string");
174 purple_xmlnode_insert_data(child, g_value_get_string(value), -1);
176 else if (G_VALUE_HOLDS_BOOLEAN(value)) {
177 purple_xmlnode_set_attrib(child, "type", "bool");
178 g_snprintf(buf, sizeof(buf), "%d", g_value_get_boolean(value));
179 purple_xmlnode_insert_data(child, buf, -1);
183 static void
184 chat_component_to_xmlnode(gpointer key, gpointer value, gpointer user_data)
186 const char *name;
187 const char *data;
188 PurpleXmlNode *node, *child;
190 name = (const char *)key;
191 data = (const char *)value;
192 node = (PurpleXmlNode *)user_data;
194 g_return_if_fail(data != NULL);
196 child = purple_xmlnode_new_child(node, "component");
197 purple_xmlnode_set_attrib(child, "name", name);
198 purple_xmlnode_insert_data(child, data, -1);
201 static PurpleXmlNode *
202 buddy_to_xmlnode(PurpleBuddy *buddy)
204 PurpleXmlNode *node, *child;
205 PurpleAccount *account = purple_buddy_get_account(buddy);
206 const char *alias = purple_buddy_get_local_alias(buddy);
208 node = purple_xmlnode_new("buddy");
209 purple_xmlnode_set_attrib(node, "account", purple_account_get_username(account));
210 purple_xmlnode_set_attrib(node, "proto", purple_account_get_protocol_id(account));
212 child = purple_xmlnode_new_child(node, "name");
213 purple_xmlnode_insert_data(child, purple_buddy_get_name(buddy), -1);
215 if (alias != NULL)
217 child = purple_xmlnode_new_child(node, "alias");
218 purple_xmlnode_insert_data(child, alias, -1);
221 /* Write buddy settings */
222 g_hash_table_foreach(purple_blist_node_get_settings(PURPLE_BLIST_NODE(buddy)),
223 value_to_xmlnode, node);
225 return node;
228 static PurpleXmlNode *
229 contact_to_xmlnode(PurpleContact *contact)
231 PurpleXmlNode *node, *child;
232 PurpleBlistNode *bnode;
233 gchar *alias;
235 node = purple_xmlnode_new("contact");
236 g_object_get(contact, "alias", &alias, NULL);
238 if (alias != NULL)
240 purple_xmlnode_set_attrib(node, "alias", alias);
243 /* Write buddies */
244 for (bnode = PURPLE_BLIST_NODE(contact)->child; bnode != NULL; bnode = bnode->next)
246 if (purple_blist_node_is_transient(bnode))
247 continue;
248 if (PURPLE_IS_BUDDY(bnode))
250 child = buddy_to_xmlnode(PURPLE_BUDDY(bnode));
251 purple_xmlnode_insert_child(node, child);
255 /* Write contact settings */
256 g_hash_table_foreach(purple_blist_node_get_settings(PURPLE_BLIST_NODE(contact)),
257 value_to_xmlnode, node);
259 g_free(alias);
260 return node;
263 static PurpleXmlNode *
264 chat_to_xmlnode(PurpleChat *chat)
266 PurpleXmlNode *node, *child;
267 PurpleAccount *account = purple_chat_get_account(chat);
268 gchar *alias;
270 g_object_get(chat, "alias", &alias, NULL);
272 node = purple_xmlnode_new("chat");
273 purple_xmlnode_set_attrib(node, "proto", purple_account_get_protocol_id(account));
274 purple_xmlnode_set_attrib(node, "account", purple_account_get_username(account));
276 if (alias != NULL)
278 child = purple_xmlnode_new_child(node, "alias");
279 purple_xmlnode_insert_data(child, alias, -1);
282 /* Write chat components */
283 g_hash_table_foreach(purple_chat_get_components(chat),
284 chat_component_to_xmlnode, node);
286 /* Write chat settings */
287 g_hash_table_foreach(purple_blist_node_get_settings(PURPLE_BLIST_NODE(chat)),
288 value_to_xmlnode, node);
290 g_free(alias);
291 return node;
294 static PurpleXmlNode *
295 group_to_xmlnode(PurpleGroup *group)
297 PurpleXmlNode *node, *child;
298 PurpleBlistNode *cnode;
300 node = purple_xmlnode_new("group");
301 if (group != purple_blist_get_default_group())
302 purple_xmlnode_set_attrib(node, "name", purple_group_get_name(group));
304 /* Write settings */
305 g_hash_table_foreach(purple_blist_node_get_settings(PURPLE_BLIST_NODE(group)),
306 value_to_xmlnode, node);
308 /* Write contacts and chats */
309 for (cnode = PURPLE_BLIST_NODE(group)->child; cnode != NULL; cnode = cnode->next)
311 if (purple_blist_node_is_transient(cnode))
312 continue;
313 if (PURPLE_IS_CONTACT(cnode))
315 child = contact_to_xmlnode(PURPLE_CONTACT(cnode));
316 purple_xmlnode_insert_child(node, child);
318 else if (PURPLE_IS_CHAT(cnode))
320 child = chat_to_xmlnode(PURPLE_CHAT(cnode));
321 purple_xmlnode_insert_child(node, child);
325 return node;
328 static PurpleXmlNode *
329 accountprivacy_to_xmlnode(PurpleAccount *account)
331 PurpleXmlNode *node, *child;
332 GSList *cur;
333 char buf[10];
335 node = purple_xmlnode_new("account");
336 purple_xmlnode_set_attrib(node, "proto", purple_account_get_protocol_id(account));
337 purple_xmlnode_set_attrib(node, "name", purple_account_get_username(account));
338 g_snprintf(buf, sizeof(buf), "%d", purple_account_get_privacy_type(account));
339 purple_xmlnode_set_attrib(node, "mode", buf);
341 for (cur = purple_account_privacy_get_permitted(account); cur; cur = cur->next)
343 child = purple_xmlnode_new_child(node, "permit");
344 purple_xmlnode_insert_data(child, cur->data, -1);
347 for (cur = purple_account_privacy_get_denied(account); cur; cur = cur->next)
349 child = purple_xmlnode_new_child(node, "block");
350 purple_xmlnode_insert_data(child, cur->data, -1);
353 return node;
356 static PurpleXmlNode *
357 blist_to_xmlnode(void)
359 PurpleXmlNode *node, *child, *grandchild;
360 PurpleBlistNode *gnode;
361 GList *cur;
362 const gchar *localized_default;
364 node = purple_xmlnode_new("purple");
365 purple_xmlnode_set_attrib(node, "version", "1.0");
367 /* Write groups */
368 child = purple_xmlnode_new_child(node, "blist");
370 localized_default = localized_default_group_name;
371 if (!purple_strequal(_("Buddies"), "Buddies"))
372 localized_default = _("Buddies");
373 if (localized_default != NULL) {
374 purple_xmlnode_set_attrib(child,
375 "localized-default-group", localized_default);
378 for (gnode = purple_blist_get_default_root(); gnode != NULL;
379 gnode = gnode->next) {
380 if (purple_blist_node_is_transient(gnode))
381 continue;
382 if (PURPLE_IS_GROUP(gnode))
384 grandchild = group_to_xmlnode(PURPLE_GROUP(gnode));
385 purple_xmlnode_insert_child(child, grandchild);
389 /* Write privacy settings */
390 child = purple_xmlnode_new_child(node, "privacy");
391 for (cur = purple_accounts_get_all(); cur != NULL; cur = cur->next)
393 grandchild = accountprivacy_to_xmlnode(cur->data);
394 purple_xmlnode_insert_child(child, grandchild);
397 return node;
400 static void
401 purple_blist_sync(void)
403 PurpleXmlNode *node;
404 char *data;
406 if (!blist_loaded)
408 purple_debug_error("buddylist", "Attempted to save buddy list before it "
409 "was read!\n");
410 return;
413 node = blist_to_xmlnode();
414 data = purple_xmlnode_to_formatted_str(node, NULL);
415 purple_util_write_data_to_config_file("blist.xml", data, -1);
416 g_free(data);
417 purple_xmlnode_free(node);
420 static gboolean
421 save_cb(gpointer data)
423 purple_blist_sync();
424 save_timer = 0;
425 return FALSE;
428 static void
429 purple_blist_real_schedule_save(void)
431 if (save_timer == 0)
432 save_timer = g_timeout_add_seconds(5, save_cb, NULL);
435 static void
436 purple_blist_real_save_account(PurpleBuddyList *list, PurpleAccount *account)
438 #if 1
439 purple_blist_real_schedule_save();
440 #else
441 if (account != NULL) {
442 /* Save the buddies and privacy data for this account */
443 } else {
444 /* Save all buddies and privacy data */
446 #endif
449 static void
450 purple_blist_real_save_node(PurpleBuddyList *list, PurpleBlistNode *node)
452 purple_blist_real_schedule_save();
455 void purple_blist_schedule_save()
457 PurpleBuddyListClass *klass = NULL;
459 g_return_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist));
461 klass = PURPLE_BUDDY_LIST_GET_CLASS(purplebuddylist);
463 /* Save everything */
464 if (klass && klass->save_account) {
465 klass->save_account(purplebuddylist, NULL);
469 /*********************************************************************
470 * Reading from disk *
471 *********************************************************************/
473 static void
474 parse_setting(PurpleBlistNode *node, PurpleXmlNode *setting)
476 const char *name = purple_xmlnode_get_attrib(setting, "name");
477 const char *type = purple_xmlnode_get_attrib(setting, "type");
478 char *value = purple_xmlnode_get_data(setting);
480 if (!value)
481 return;
483 if (!type || purple_strequal(type, "string"))
484 purple_blist_node_set_string(node, name, value);
485 else if (purple_strequal(type, "bool"))
486 purple_blist_node_set_bool(node, name, atoi(value));
487 else if (purple_strequal(type, "int"))
488 purple_blist_node_set_int(node, name, atoi(value));
490 g_free(value);
493 static void
494 parse_buddy(PurpleGroup *group, PurpleContact *contact, PurpleXmlNode *bnode)
496 PurpleAccount *account;
497 PurpleBuddy *buddy;
498 char *name = NULL, *alias = NULL;
499 const char *acct_name, *proto;
500 PurpleXmlNode *x;
502 acct_name = purple_xmlnode_get_attrib(bnode, "account");
503 proto = purple_xmlnode_get_attrib(bnode, "proto");
505 if (!acct_name || !proto)
506 return;
508 account = purple_accounts_find(acct_name, proto);
510 if (!account)
511 return;
513 if ((x = purple_xmlnode_get_child(bnode, "name")))
514 name = purple_xmlnode_get_data(x);
516 if (!name)
517 return;
519 if ((x = purple_xmlnode_get_child(bnode, "alias")))
520 alias = purple_xmlnode_get_data(x);
522 buddy = purple_buddy_new(account, name, alias);
523 purple_blist_add_buddy(buddy, contact, group,
524 _purple_blist_get_last_child((PurpleBlistNode*)contact));
526 for (x = purple_xmlnode_get_child(bnode, "setting"); x; x = purple_xmlnode_get_next_twin(x)) {
527 parse_setting((PurpleBlistNode*)buddy, x);
530 g_free(name);
531 g_free(alias);
534 static void
535 parse_contact(PurpleGroup *group, PurpleXmlNode *cnode)
537 PurpleContact *contact = purple_contact_new();
538 PurpleXmlNode *x;
539 const char *alias;
541 purple_blist_add_contact(contact, group,
542 _purple_blist_get_last_child((PurpleBlistNode*)group));
544 if ((alias = purple_xmlnode_get_attrib(cnode, "alias"))) {
545 purple_contact_set_alias(contact, alias);
548 for (x = cnode->child; x; x = x->next) {
549 if (x->type != PURPLE_XMLNODE_TYPE_TAG)
550 continue;
551 if (purple_strequal(x->name, "buddy"))
552 parse_buddy(group, contact, x);
553 else if (purple_strequal(x->name, "setting"))
554 parse_setting(PURPLE_BLIST_NODE(contact), x);
557 /* if the contact is empty, don't keep it around. it causes problems */
558 if (!PURPLE_BLIST_NODE(contact)->child)
559 purple_blist_remove_contact(contact);
562 static void
563 parse_chat(PurpleGroup *group, PurpleXmlNode *cnode)
565 PurpleChat *chat;
566 PurpleAccount *account;
567 const char *acct_name, *proto;
568 PurpleXmlNode *x;
569 char *alias = NULL;
570 GHashTable *components;
572 acct_name = purple_xmlnode_get_attrib(cnode, "account");
573 proto = purple_xmlnode_get_attrib(cnode, "proto");
575 if (!acct_name || !proto)
576 return;
578 account = purple_accounts_find(acct_name, proto);
580 if (!account)
581 return;
583 if ((x = purple_xmlnode_get_child(cnode, "alias")))
584 alias = purple_xmlnode_get_data(x);
586 components = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
588 for (x = purple_xmlnode_get_child(cnode, "component"); x; x = purple_xmlnode_get_next_twin(x)) {
589 const char *name;
590 char *value;
592 name = purple_xmlnode_get_attrib(x, "name");
593 value = purple_xmlnode_get_data(x);
594 g_hash_table_replace(components, g_strdup(name), value);
597 chat = purple_chat_new(account, alias, components);
598 purple_blist_add_chat(chat, group,
599 _purple_blist_get_last_child((PurpleBlistNode*)group));
601 for (x = purple_xmlnode_get_child(cnode, "setting"); x; x = purple_xmlnode_get_next_twin(x)) {
602 parse_setting((PurpleBlistNode*)chat, x);
605 g_free(alias);
608 static void
609 parse_group(PurpleXmlNode *groupnode)
611 const char *name = purple_xmlnode_get_attrib(groupnode, "name");
612 PurpleGroup *group;
613 PurpleXmlNode *cnode;
615 group = purple_group_new(name);
616 purple_blist_add_group(group, purple_blist_get_last_sibling(
617 purple_blist_get_default_root()));
619 for (cnode = groupnode->child; cnode; cnode = cnode->next) {
620 if (cnode->type != PURPLE_XMLNODE_TYPE_TAG)
621 continue;
622 if (purple_strequal(cnode->name, "setting"))
623 parse_setting((PurpleBlistNode*)group, cnode);
624 else if (purple_strequal(cnode->name, "contact") ||
625 purple_strequal(cnode->name, "person"))
626 parse_contact(group, cnode);
627 else if (purple_strequal(cnode->name, "chat"))
628 parse_chat(group, cnode);
632 static void
633 load_blist(void)
635 PurpleXmlNode *purple, *blist, *privacy;
637 blist_loaded = TRUE;
639 purple = purple_util_read_xml_from_config_file("blist.xml", _("buddy list"));
641 if (purple == NULL)
642 return;
644 blist = purple_xmlnode_get_child(purple, "blist");
645 if (blist) {
646 PurpleXmlNode *groupnode;
648 localized_default_group_name = g_strdup(
649 purple_xmlnode_get_attrib(blist,
650 "localized-default-group"));
652 for (groupnode = purple_xmlnode_get_child(blist, "group"); groupnode != NULL;
653 groupnode = purple_xmlnode_get_next_twin(groupnode)) {
654 parse_group(groupnode);
656 } else {
657 g_free(localized_default_group_name);
658 localized_default_group_name = NULL;
661 privacy = purple_xmlnode_get_child(purple, "privacy");
662 if (privacy) {
663 PurpleXmlNode *anode;
664 for (anode = privacy->child; anode; anode = anode->next) {
665 PurpleXmlNode *x;
666 PurpleAccount *account;
667 int imode;
668 const char *acct_name, *proto, *mode;
670 acct_name = purple_xmlnode_get_attrib(anode, "name");
671 proto = purple_xmlnode_get_attrib(anode, "proto");
672 mode = purple_xmlnode_get_attrib(anode, "mode");
674 if (!acct_name || !proto || !mode)
675 continue;
677 account = purple_accounts_find(acct_name, proto);
679 if (!account)
680 continue;
682 imode = atoi(mode);
683 purple_account_set_privacy_type(account, (imode != 0 ? imode : PURPLE_ACCOUNT_PRIVACY_ALLOW_ALL));
685 for (x = anode->child; x; x = x->next) {
686 char *name;
687 if (x->type != PURPLE_XMLNODE_TYPE_TAG)
688 continue;
690 if (purple_strequal(x->name, "permit")) {
691 name = purple_xmlnode_get_data(x);
692 purple_account_privacy_permit_add(account, name, TRUE);
693 g_free(name);
694 } else if (purple_strequal(x->name, "block")) {
695 name = purple_xmlnode_get_data(x);
696 purple_account_privacy_deny_add(account, name, TRUE);
697 g_free(name);
703 purple_xmlnode_free(purple);
705 /* This tells the buddy icon code to do its thing. */
706 _purple_buddy_icons_blist_loaded_cb();
709 /*****************************************************************************
710 * Public API functions *
711 *****************************************************************************/
713 void
714 purple_blist_set_ui(GType type)
716 g_return_if_fail(g_type_is_a(type, PURPLE_TYPE_BUDDY_LIST) ||
717 type == G_TYPE_INVALID);
718 buddy_list_type = type;
721 void
722 purple_blist_boot(void)
724 GList *account;
725 PurpleBuddyList *gbl = g_object_new(buddy_list_type, NULL);
727 buddies_cache = g_hash_table_new_full(g_direct_hash, g_direct_equal,
728 NULL, (GDestroyNotify)g_hash_table_destroy);
730 groups_cache = g_hash_table_new_full((GHashFunc)g_str_hash,
731 (GEqualFunc)g_str_equal,
732 (GDestroyNotify)g_free, NULL);
734 for (account = purple_accounts_get_all(); account != NULL; account = account->next)
736 purple_blist_buddies_cache_add_account(account->data);
739 purplebuddylist = gbl;
741 load_blist();
744 PurpleBuddyList *
745 purple_blist_get_default(void)
747 return purplebuddylist;
750 PurpleBlistNode *
751 purple_blist_get_default_root(void)
753 if (purplebuddylist) {
754 PurpleBuddyListPrivate *priv =
755 purple_buddy_list_get_instance_private(purplebuddylist);
756 return priv->root;
758 return NULL;
761 PurpleBlistNode *
762 purple_blist_get_root(PurpleBuddyList *list)
764 PurpleBuddyListPrivate *priv = NULL;
766 g_return_val_if_fail(PURPLE_IS_BUDDY_LIST(list), NULL);
767 priv = purple_buddy_list_get_instance_private(list);
769 return priv->root;
772 static void
773 append_buddy(gpointer key, gpointer value, gpointer user_data)
775 GSList **list = user_data;
776 *list = g_slist_prepend(*list, value);
779 GSList *
780 purple_blist_get_buddies()
782 PurpleBuddyListPrivate *priv;
783 GSList *buddies = NULL;
785 if (!purplebuddylist)
786 return NULL;
788 priv = purple_buddy_list_get_instance_private(purplebuddylist);
789 g_hash_table_foreach(priv->buddies, append_buddy, &buddies);
790 return buddies;
793 void purple_blist_show()
795 PurpleBuddyListClass *klass = NULL;
797 g_return_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist));
798 klass = PURPLE_BUDDY_LIST_GET_CLASS(purplebuddylist);
800 if (klass && klass->show) {
801 klass->show(purplebuddylist);
805 void purple_blist_set_visible(gboolean show)
807 PurpleBuddyListClass *klass = NULL;
809 g_return_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist));
810 klass = PURPLE_BUDDY_LIST_GET_CLASS(purplebuddylist);
812 if (klass && klass->set_visible) {
813 klass->set_visible(purplebuddylist, show);
817 void purple_blist_update_buddies_cache(PurpleBuddy *buddy, const char *new_name)
819 struct _purple_hbuddy *hb, *hb2;
820 GHashTable *account_buddies;
821 PurpleAccount *account;
822 gchar *name;
823 PurpleBuddyListPrivate *priv =
824 purple_buddy_list_get_instance_private(purplebuddylist);
826 g_return_if_fail(PURPLE_IS_BUDDY(buddy));
828 account = purple_buddy_get_account(buddy);
829 name = (gchar *)purple_buddy_get_name(buddy);
831 hb = g_new(struct _purple_hbuddy, 1);
832 hb->name = (gchar *)purple_normalize(account, name);
833 hb->account = account;
834 hb->group = PURPLE_BLIST_NODE(buddy)->parent->parent;
835 g_hash_table_remove(priv->buddies, hb);
837 account_buddies = g_hash_table_lookup(buddies_cache, account);
838 g_hash_table_remove(account_buddies, hb);
840 hb->name = g_strdup(purple_normalize(account, new_name));
841 g_hash_table_replace(priv->buddies, hb, buddy);
843 hb2 = g_new(struct _purple_hbuddy, 1);
844 hb2->name = g_strdup(hb->name);
845 hb2->account = account;
846 hb2->group = PURPLE_BLIST_NODE(buddy)->parent->parent;
848 g_hash_table_replace(account_buddies, hb2, buddy);
851 void purple_blist_update_groups_cache(PurpleGroup *group, const char *new_name)
853 gchar* key;
855 key = purple_blist_fold_name(purple_group_get_name(group));
856 g_hash_table_remove(groups_cache, key);
857 g_free(key);
859 g_hash_table_insert(groups_cache,
860 purple_blist_fold_name(new_name), group);
863 void purple_blist_add_chat(PurpleChat *chat, PurpleGroup *group, PurpleBlistNode *node)
865 PurpleBlistNode *cnode = PURPLE_BLIST_NODE(chat);
866 PurpleBuddyListClass *klass = NULL;
867 PurpleCountingNode *group_counter;
869 g_return_if_fail(PURPLE_IS_CHAT(chat));
870 g_return_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist));
871 klass = PURPLE_BUDDY_LIST_GET_CLASS(purplebuddylist);
873 if (node == NULL) {
874 if (group == NULL)
875 group = purple_group_new(_("Chats"));
877 /* Add group to blist if isn't already on it. Fixes #2752. */
878 if (!purple_blist_find_group(purple_group_get_name(group))) {
879 purple_blist_add_group(
880 group,
881 purple_blist_get_last_sibling(
882 purple_blist_get_default_root()));
884 } else {
885 group = PURPLE_GROUP(node->parent);
888 /* if we're moving to overtop of ourselves, do nothing */
889 if (cnode == node)
890 return;
892 if (cnode->parent) {
893 /* This chat was already in the list and is
894 * being moved.
896 group_counter = PURPLE_COUNTING_NODE(cnode->parent);
897 purple_counting_node_change_total_size(group_counter, -1);
898 if (purple_account_is_connected(purple_chat_get_account(chat))) {
899 purple_counting_node_change_online_count(group_counter, -1);
900 purple_counting_node_change_current_size(group_counter, -1);
902 if (cnode->next)
903 cnode->next->prev = cnode->prev;
904 if (cnode->prev)
905 cnode->prev->next = cnode->next;
906 if (cnode->parent->child == cnode)
907 cnode->parent->child = cnode->next;
909 if (klass && klass->remove) {
910 klass->remove(purplebuddylist, cnode);
912 /* ops->remove() cleaned up the cnode's ui_data, so we need to
913 * reinitialize it */
914 if (klass && klass->new_node) {
915 klass->new_node(purplebuddylist, cnode);
919 if (node != NULL) {
920 if (node->next)
921 node->next->prev = cnode;
922 cnode->next = node->next;
923 cnode->prev = node;
924 cnode->parent = node->parent;
925 node->next = cnode;
926 group_counter = PURPLE_COUNTING_NODE(node->parent);
927 purple_counting_node_change_total_size(group_counter, +1);
928 if (purple_account_is_connected(purple_chat_get_account(chat))) {
929 purple_counting_node_change_online_count(group_counter, +1);
930 purple_counting_node_change_current_size(group_counter, +1);
932 } else {
933 if (((PurpleBlistNode *)group)->child)
934 ((PurpleBlistNode *)group)->child->prev = cnode;
935 cnode->next = ((PurpleBlistNode *)group)->child;
936 cnode->prev = NULL;
937 ((PurpleBlistNode *)group)->child = cnode;
938 cnode->parent = PURPLE_BLIST_NODE(group);
939 group_counter = PURPLE_COUNTING_NODE(group);
940 purple_counting_node_change_total_size(group_counter, +1);
941 if (purple_account_is_connected(purple_chat_get_account(chat))) {
942 purple_counting_node_change_online_count(group_counter, +1);
943 purple_counting_node_change_current_size(group_counter, +1);
947 if (klass) {
948 if (klass->save_node) {
949 klass->save_node(purplebuddylist, cnode);
951 if (klass->update) {
952 klass->update(purplebuddylist,
953 PURPLE_BLIST_NODE(cnode));
957 purple_signal_emit(purple_blist_get_handle(), "blist-node-added",
958 cnode);
961 void purple_blist_add_buddy(PurpleBuddy *buddy, PurpleContact *contact, PurpleGroup *group, PurpleBlistNode *node)
963 PurpleBuddyListClass *klass = NULL;
964 PurpleBuddyListPrivate *priv = NULL;
965 PurpleBlistNode *cnode, *bnode;
966 PurpleCountingNode *contact_counter, *group_counter;
967 PurpleGroup *g;
968 PurpleContact *c;
969 PurpleAccount *account;
970 struct _purple_hbuddy *hb, *hb2;
971 GHashTable *account_buddies;
973 g_return_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist));
974 g_return_if_fail(PURPLE_IS_BUDDY(buddy));
976 klass = PURPLE_BUDDY_LIST_GET_CLASS(purplebuddylist);
977 priv = purple_buddy_list_get_instance_private(purplebuddylist);
978 bnode = PURPLE_BLIST_NODE(buddy);
979 account = purple_buddy_get_account(buddy);
981 /* if we're moving to overtop of ourselves, do nothing */
982 if (bnode == node || (!node && bnode->parent &&
983 contact && bnode->parent == (PurpleBlistNode*)contact
984 && bnode == bnode->parent->child))
985 return;
987 if (node && PURPLE_IS_BUDDY(node)) {
988 c = (PurpleContact*)node->parent;
989 g = (PurpleGroup*)node->parent->parent;
990 } else if (contact) {
991 c = contact;
992 g = PURPLE_GROUP(PURPLE_BLIST_NODE(c)->parent);
993 } else {
994 g = group;
995 if (g == NULL)
996 g = purple_blist_get_default_group();
997 /* Add group to blist if isn't already on it. Fixes #2752. */
998 if (!purple_blist_find_group(purple_group_get_name(g))) {
999 purple_blist_add_group(
1000 g, purple_blist_get_last_sibling(priv->root));
1002 c = purple_contact_new();
1003 purple_blist_add_contact(c, g,
1004 _purple_blist_get_last_child((PurpleBlistNode*)g));
1007 cnode = PURPLE_BLIST_NODE(c);
1009 if (bnode->parent) {
1010 contact_counter = PURPLE_COUNTING_NODE(bnode->parent);
1011 group_counter = PURPLE_COUNTING_NODE(bnode->parent->parent);
1013 if (PURPLE_BUDDY_IS_ONLINE(buddy)) {
1014 purple_counting_node_change_online_count(contact_counter, -1);
1015 if (purple_counting_node_get_online_count(contact_counter) == 0)
1016 purple_counting_node_change_online_count(group_counter, -1);
1018 if (purple_account_is_connected(account)) {
1019 purple_counting_node_change_current_size(contact_counter, -1);
1020 if (purple_counting_node_get_current_size(contact_counter) == 0)
1021 purple_counting_node_change_current_size(group_counter, -1);
1023 purple_counting_node_change_total_size(contact_counter, -1);
1024 /* the group totalsize will be taken care of by remove_contact below */
1026 if (bnode->parent->parent != (PurpleBlistNode*)g) {
1027 purple_signal_emit(purple_blist_get_handle(), "buddy-removed-from-group", buddy);
1028 purple_serv_move_buddy(buddy, (PurpleGroup *)bnode->parent->parent, g);
1031 if (bnode->next)
1032 bnode->next->prev = bnode->prev;
1033 if (bnode->prev)
1034 bnode->prev->next = bnode->next;
1035 if (bnode->parent->child == bnode)
1036 bnode->parent->child = bnode->next;
1038 if (klass && klass->remove) {
1039 klass->remove(purplebuddylist, bnode);
1042 if (bnode->parent->parent != (PurpleBlistNode*)g) {
1043 struct _purple_hbuddy hb;
1044 hb.name = (gchar *)purple_normalize(account,
1045 purple_buddy_get_name(buddy));
1046 hb.account = account;
1047 hb.group = bnode->parent->parent;
1048 g_hash_table_remove(priv->buddies, &hb);
1050 account_buddies = g_hash_table_lookup(buddies_cache, account);
1051 g_hash_table_remove(account_buddies, &hb);
1054 if (!bnode->parent->child) {
1055 purple_blist_remove_contact((PurpleContact*)bnode->parent);
1056 } else {
1057 purple_contact_invalidate_priority_buddy((PurpleContact*)bnode->parent);
1059 if (klass && klass->update) {
1060 klass->update(purplebuddylist, bnode->parent);
1065 if (node && PURPLE_IS_BUDDY(node)) {
1066 if (node->next)
1067 node->next->prev = bnode;
1068 bnode->next = node->next;
1069 bnode->prev = node;
1070 bnode->parent = node->parent;
1071 node->next = bnode;
1072 } else {
1073 if (cnode->child)
1074 cnode->child->prev = bnode;
1075 bnode->prev = NULL;
1076 bnode->next = cnode->child;
1077 cnode->child = bnode;
1078 bnode->parent = cnode;
1081 contact_counter = PURPLE_COUNTING_NODE(bnode->parent);
1082 group_counter = PURPLE_COUNTING_NODE(bnode->parent->parent);
1084 if (PURPLE_BUDDY_IS_ONLINE(buddy)) {
1085 purple_counting_node_change_online_count(contact_counter, +1);
1086 if (purple_counting_node_get_online_count(contact_counter) == 1)
1087 purple_counting_node_change_online_count(group_counter, +1);
1089 if (purple_account_is_connected(account)) {
1090 purple_counting_node_change_current_size(contact_counter, +1);
1091 if (purple_counting_node_get_current_size(contact_counter) == 1)
1092 purple_counting_node_change_current_size(group_counter, +1);
1094 purple_counting_node_change_total_size(contact_counter, +1);
1096 hb = g_new(struct _purple_hbuddy, 1);
1097 hb->name = g_strdup(purple_normalize(account, purple_buddy_get_name(buddy)));
1098 hb->account = account;
1099 hb->group = PURPLE_BLIST_NODE(buddy)->parent->parent;
1101 g_hash_table_replace(priv->buddies, hb, buddy);
1103 account_buddies = g_hash_table_lookup(buddies_cache, account);
1105 hb2 = g_new(struct _purple_hbuddy, 1);
1106 hb2->name = g_strdup(hb->name);
1107 hb2->account = account;
1108 hb2->group = ((PurpleBlistNode*)buddy)->parent->parent;
1110 g_hash_table_replace(account_buddies, hb2, buddy);
1112 purple_contact_invalidate_priority_buddy(purple_buddy_get_contact(buddy));
1114 if (klass) {
1115 if (klass->save_node) {
1116 klass->save_node(purplebuddylist,
1117 (PurpleBlistNode *)buddy);
1119 if (klass->update) {
1120 klass->update(purplebuddylist,
1121 PURPLE_BLIST_NODE(buddy));
1125 /* Signal that the buddy has been added */
1126 purple_signal_emit(purple_blist_get_handle(), "blist-node-added",
1127 PURPLE_BLIST_NODE(buddy));
1130 void purple_blist_add_contact(PurpleContact *contact, PurpleGroup *group, PurpleBlistNode *node)
1132 PurpleBuddyListClass *klass = NULL;
1133 PurpleBuddyListPrivate *priv = NULL;
1134 PurpleGroup *g;
1135 PurpleBlistNode *gnode, *cnode, *bnode;
1136 PurpleCountingNode *contact_counter, *group_counter;
1138 g_return_if_fail(PURPLE_IS_CONTACT(contact));
1139 g_return_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist));
1141 if (PURPLE_BLIST_NODE(contact) == node)
1142 return;
1144 klass = PURPLE_BUDDY_LIST_GET_CLASS(purplebuddylist);
1145 priv = purple_buddy_list_get_instance_private(purplebuddylist);
1147 if (node && (PURPLE_IS_CONTACT(node) ||
1148 PURPLE_IS_CHAT(node)))
1149 g = PURPLE_GROUP(node->parent);
1150 else if (group)
1151 g = group;
1152 else
1153 g = purple_blist_get_default_group();
1155 gnode = (PurpleBlistNode*)g;
1156 cnode = (PurpleBlistNode*)contact;
1158 if (cnode->parent) {
1159 if (cnode->parent->child == cnode)
1160 cnode->parent->child = cnode->next;
1161 if (cnode->prev)
1162 cnode->prev->next = cnode->next;
1163 if (cnode->next)
1164 cnode->next->prev = cnode->prev;
1166 if (cnode->parent != gnode) {
1167 bnode = cnode->child;
1168 while (bnode) {
1169 PurpleBlistNode *next_bnode = bnode->next;
1170 PurpleBuddy *b = PURPLE_BUDDY(bnode);
1171 PurpleAccount *account = purple_buddy_get_account(b);
1172 GHashTable *account_buddies;
1174 struct _purple_hbuddy *hb, *hb2;
1176 hb = g_new(struct _purple_hbuddy, 1);
1177 hb->name = g_strdup(purple_normalize(account, purple_buddy_get_name(b)));
1178 hb->account = account;
1179 hb->group = cnode->parent;
1181 g_hash_table_remove(priv->buddies, hb);
1183 account_buddies = g_hash_table_lookup(buddies_cache, account);
1184 g_hash_table_remove(account_buddies, hb);
1186 if (!purple_blist_find_buddy_in_group(account, purple_buddy_get_name(b), g)) {
1187 hb->group = gnode;
1188 g_hash_table_replace(priv->buddies, hb, b);
1190 hb2 = g_new(struct _purple_hbuddy, 1);
1191 hb2->name = g_strdup(hb->name);
1192 hb2->account = account;
1193 hb2->group = gnode;
1195 g_hash_table_replace(account_buddies, hb2, b);
1197 if (purple_account_get_connection(account))
1198 purple_serv_move_buddy(b, (PurpleGroup *)cnode->parent, g);
1199 } else {
1200 gboolean empty_contact = FALSE;
1202 /* this buddy already exists in the group, so we're
1203 * gonna delete it instead */
1204 g_free(hb->name);
1205 g_free(hb);
1206 if (purple_account_get_connection(account))
1207 purple_account_remove_buddy(account, b, PURPLE_GROUP(cnode->parent));
1209 if (!cnode->child->next)
1210 empty_contact = TRUE;
1211 purple_blist_remove_buddy(b);
1213 /* in purple_blist_remove_buddy(), if the last buddy in a
1214 * contact is removed, the contact is cleaned up and
1215 * g_free'd, so we mustn't try to reference bnode->next */
1216 if (empty_contact)
1217 return;
1219 bnode = next_bnode;
1223 contact_counter = PURPLE_COUNTING_NODE(contact);
1224 group_counter = PURPLE_COUNTING_NODE(cnode->parent);
1226 if (purple_counting_node_get_online_count(contact_counter) > 0)
1227 purple_counting_node_change_online_count(group_counter, -1);
1228 if (purple_counting_node_get_current_size(contact_counter) > 0)
1229 purple_counting_node_change_current_size(group_counter, -1);
1230 purple_counting_node_change_total_size(group_counter, -1);
1232 if (klass && klass->remove) {
1233 klass->remove(purplebuddylist, cnode);
1236 if (klass && klass->remove_node) {
1237 klass->remove_node(purplebuddylist, cnode);
1241 if (node && (PURPLE_IS_CONTACT(node) ||
1242 PURPLE_IS_CHAT(node))) {
1243 if (node->next)
1244 node->next->prev = cnode;
1245 cnode->next = node->next;
1246 cnode->prev = node;
1247 cnode->parent = node->parent;
1248 node->next = cnode;
1249 } else {
1250 if (gnode->child)
1251 gnode->child->prev = cnode;
1252 cnode->prev = NULL;
1253 cnode->next = gnode->child;
1254 gnode->child = cnode;
1255 cnode->parent = gnode;
1258 contact_counter = PURPLE_COUNTING_NODE(contact);
1259 group_counter = PURPLE_COUNTING_NODE(g);
1261 if (purple_counting_node_get_online_count(contact_counter) > 0)
1262 purple_counting_node_change_online_count(group_counter, +1);
1263 if (purple_counting_node_get_current_size(contact_counter) > 0)
1264 purple_counting_node_change_current_size(group_counter, +1);
1265 purple_counting_node_change_total_size(group_counter, +1);
1267 if (klass && klass->save_node) {
1268 if (cnode->child) {
1269 klass->save_node(purplebuddylist, cnode);
1271 for (bnode = cnode->child; bnode; bnode = bnode->next) {
1272 klass->save_node(purplebuddylist, bnode);
1276 if (klass && klass->update) {
1277 if (cnode->child) {
1278 klass->update(purplebuddylist, cnode);
1281 for (bnode = cnode->child; bnode; bnode = bnode->next) {
1282 klass->update(purplebuddylist, bnode);
1287 void purple_blist_add_group(PurpleGroup *group, PurpleBlistNode *node)
1289 PurpleBuddyListClass *klass = NULL;
1290 PurpleBuddyListPrivate *priv = NULL;
1291 PurpleBlistNode *gnode = (PurpleBlistNode*)group;
1292 gchar* key;
1294 g_return_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist));
1295 g_return_if_fail(PURPLE_IS_GROUP(group));
1297 klass = PURPLE_BUDDY_LIST_GET_CLASS(purplebuddylist);
1298 priv = purple_buddy_list_get_instance_private(purplebuddylist);
1300 /* if we're moving to overtop of ourselves, do nothing */
1301 if (gnode == node) {
1302 if (!priv->root) {
1303 node = NULL;
1304 } else {
1305 return;
1309 if (purple_blist_find_group(purple_group_get_name(group))) {
1310 /* This is just being moved */
1312 if (klass && klass->remove) {
1313 klass->remove(purplebuddylist,
1314 (PurpleBlistNode *)group);
1317 if (gnode == priv->root) {
1318 priv->root = gnode->next;
1320 if (gnode->prev)
1321 gnode->prev->next = gnode->next;
1322 if (gnode->next)
1323 gnode->next->prev = gnode->prev;
1324 } else {
1325 key = purple_blist_fold_name(purple_group_get_name(group));
1326 g_hash_table_insert(groups_cache, key, group);
1329 if (node && PURPLE_IS_GROUP(node)) {
1330 gnode->next = node->next;
1331 gnode->prev = node;
1332 if (node->next)
1333 node->next->prev = gnode;
1334 node->next = gnode;
1335 } else {
1336 if (priv->root) {
1337 priv->root->prev = gnode;
1339 gnode->next = priv->root;
1340 gnode->prev = NULL;
1341 priv->root = gnode;
1344 if (klass && klass->save_node) {
1345 klass->save_node(purplebuddylist, gnode);
1346 for (node = gnode->child; node; node = node->next) {
1347 klass->save_node(purplebuddylist, node);
1351 if (klass && klass->update) {
1352 klass->update(purplebuddylist, gnode);
1353 for (node = gnode->child; node; node = node->next) {
1354 klass->update(purplebuddylist, node);
1358 purple_signal_emit(purple_blist_get_handle(), "blist-node-added",
1359 gnode);
1362 void purple_blist_remove_contact(PurpleContact *contact)
1364 PurpleBuddyListClass *klass = NULL;
1365 PurpleBlistNode *node, *gnode;
1366 PurpleGroup *group;
1368 g_return_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist));
1369 g_return_if_fail(PURPLE_IS_CONTACT(contact));
1371 klass = PURPLE_BUDDY_LIST_GET_CLASS(purplebuddylist);
1372 node = (PurpleBlistNode *)contact;
1373 gnode = node->parent;
1374 group = PURPLE_GROUP(gnode);
1376 if (node->child) {
1378 * If this contact has children then remove them. When the last
1379 * buddy is removed from the contact, the contact is automatically
1380 * deleted.
1382 while (node->child->next) {
1383 purple_blist_remove_buddy((PurpleBuddy*)node->child);
1386 * Remove the last buddy and trigger the deletion of the contact.
1387 * It would probably be cleaner if contact-deletion was done after
1388 * a timeout? Or if it had to be done manually, like below?
1390 purple_blist_remove_buddy((PurpleBuddy*)node->child);
1391 } else {
1392 /* Remove the node from its parent */
1393 if (gnode->child == node)
1394 gnode->child = node->next;
1395 if (node->prev)
1396 node->prev->next = node->next;
1397 if (node->next)
1398 node->next->prev = node->prev;
1399 purple_counting_node_change_total_size(PURPLE_COUNTING_NODE(group), -1);
1401 /* Update the UI */
1402 if (klass && klass->remove) {
1403 klass->remove(purplebuddylist, node);
1406 if (klass && klass->remove_node) {
1407 klass->remove_node(purplebuddylist, node);
1410 purple_signal_emit(purple_blist_get_handle(), "blist-node-removed",
1411 PURPLE_BLIST_NODE(contact));
1413 /* Delete the node */
1414 g_object_unref(contact);
1418 void purple_blist_remove_buddy(PurpleBuddy *buddy)
1420 PurpleBuddyListClass *klass = NULL;
1421 PurpleBuddyListPrivate *priv = NULL;
1422 PurpleBlistNode *node, *cnode, *gnode;
1423 PurpleCountingNode *contact_counter, *group_counter;
1424 PurpleContact *contact;
1425 PurpleGroup *group;
1426 struct _purple_hbuddy hb;
1427 GHashTable *account_buddies;
1428 PurpleAccount *account;
1430 g_return_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist));
1431 g_return_if_fail(PURPLE_IS_BUDDY(buddy));
1433 klass = PURPLE_BUDDY_LIST_GET_CLASS(purplebuddylist);
1434 priv = purple_buddy_list_get_instance_private(purplebuddylist);
1435 account = purple_buddy_get_account(buddy);
1436 node = PURPLE_BLIST_NODE(buddy);
1437 cnode = node->parent;
1438 gnode = (cnode != NULL) ? cnode->parent : NULL;
1439 contact = (PurpleContact *)cnode;
1440 group = (PurpleGroup *)gnode;
1442 /* Remove the node from its parent */
1443 if (node->prev)
1444 node->prev->next = node->next;
1445 if (node->next)
1446 node->next->prev = node->prev;
1447 if ((cnode != NULL) && (cnode->child == node))
1448 cnode->child = node->next;
1450 /* Adjust size counts */
1451 if (contact != NULL) {
1452 contact_counter = PURPLE_COUNTING_NODE(contact);
1453 group_counter = PURPLE_COUNTING_NODE(group);
1455 if (PURPLE_BUDDY_IS_ONLINE(buddy)) {
1456 purple_counting_node_change_online_count(contact_counter, -1);
1457 if (purple_counting_node_get_online_count(contact_counter) == 0)
1458 purple_counting_node_change_online_count(group_counter, -1);
1460 if (purple_account_is_connected(account)) {
1461 purple_counting_node_change_current_size(contact_counter, -1);
1462 if (purple_counting_node_get_current_size(contact_counter) == 0)
1463 purple_counting_node_change_current_size(group_counter, -1);
1465 purple_counting_node_change_total_size(contact_counter, -1);
1467 /* Re-sort the contact */
1468 if (cnode->child && purple_contact_get_priority_buddy(contact) == buddy) {
1469 purple_contact_invalidate_priority_buddy(contact);
1471 if (klass && klass->update) {
1472 klass->update(purplebuddylist, cnode);
1477 /* Remove this buddy from the buddies hash table */
1478 hb.name = (gchar *)purple_normalize(account, purple_buddy_get_name(buddy));
1479 hb.account = account;
1480 hb.group = gnode;
1481 g_hash_table_remove(priv->buddies, &hb);
1483 account_buddies = g_hash_table_lookup(buddies_cache, account);
1484 g_hash_table_remove(account_buddies, &hb);
1486 /* Update the UI */
1487 if (klass && klass->remove) {
1488 klass->remove(purplebuddylist, node);
1491 if (klass && klass->remove_node) {
1492 klass->remove_node(purplebuddylist, node);
1495 /* Remove this buddy's pounces */
1496 purple_pounce_destroy_all_by_buddy(buddy);
1498 /* Signal that the buddy has been removed before freeing the memory for it */
1499 purple_signal_emit(purple_blist_get_handle(), "blist-node-removed",
1500 PURPLE_BLIST_NODE(buddy));
1502 g_object_unref(buddy);
1504 /* If the contact is empty then remove it */
1505 if ((contact != NULL) && !cnode->child)
1506 purple_blist_remove_contact(contact);
1509 void purple_blist_remove_chat(PurpleChat *chat)
1511 PurpleBuddyListClass *klass = NULL;
1512 PurpleBlistNode *node, *gnode;
1513 PurpleGroup *group;
1514 PurpleCountingNode *group_counter;
1516 g_return_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist));
1517 g_return_if_fail(PURPLE_IS_CHAT(chat));
1519 klass = PURPLE_BUDDY_LIST_GET_CLASS(purplebuddylist);
1520 node = (PurpleBlistNode *)chat;
1521 gnode = node->parent;
1522 group = (PurpleGroup *)gnode;
1524 if (gnode != NULL)
1526 /* Remove the node from its parent */
1527 if (gnode->child == node)
1528 gnode->child = node->next;
1529 if (node->prev)
1530 node->prev->next = node->next;
1531 if (node->next)
1532 node->next->prev = node->prev;
1534 /* Adjust size counts */
1535 group_counter = PURPLE_COUNTING_NODE(group);
1536 if (purple_account_is_connected(purple_chat_get_account(chat))) {
1537 purple_counting_node_change_online_count(group_counter, -1);
1538 purple_counting_node_change_current_size(group_counter, -1);
1540 purple_counting_node_change_total_size(group_counter, -1);
1543 /* Update the UI */
1544 if (klass && klass->remove) {
1545 klass->remove(purplebuddylist, node);
1548 if (klass && klass->remove_node) {
1549 klass->remove_node(purplebuddylist, node);
1552 purple_signal_emit(purple_blist_get_handle(), "blist-node-removed",
1553 PURPLE_BLIST_NODE(chat));
1555 /* Delete the node */
1556 g_object_unref(chat);
1559 void purple_blist_remove_group(PurpleGroup *group)
1561 PurpleBuddyListClass *klass = NULL;
1562 PurpleBuddyListPrivate *priv = NULL;
1563 PurpleBlistNode *node;
1564 GList *l;
1565 gchar* key;
1567 g_return_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist));
1568 g_return_if_fail(PURPLE_IS_GROUP(group));
1570 if (group == purple_blist_get_default_group())
1571 purple_debug_warning("buddylist", "cannot remove default group");
1573 klass = PURPLE_BUDDY_LIST_GET_CLASS(purplebuddylist);
1574 priv = purple_buddy_list_get_instance_private(purplebuddylist);
1575 node = (PurpleBlistNode *)group;
1577 /* Make sure the group is empty */
1578 if (node->child)
1579 return;
1581 /* Remove the node from its parent */
1582 if (priv->root == node) {
1583 priv->root = node->next;
1585 if (node->prev)
1586 node->prev->next = node->next;
1587 if (node->next)
1588 node->next->prev = node->prev;
1590 key = purple_blist_fold_name(purple_group_get_name(group));
1591 g_hash_table_remove(groups_cache, key);
1592 g_free(key);
1594 /* Update the UI */
1595 if (klass && klass->remove) {
1596 klass->remove(purplebuddylist, node);
1599 if (klass && klass->remove_node) {
1600 klass->remove_node(purplebuddylist, node);
1603 purple_signal_emit(purple_blist_get_handle(), "blist-node-removed",
1604 PURPLE_BLIST_NODE(group));
1606 /* Remove the group from all accounts that are online */
1607 for (l = purple_connections_get_all(); l != NULL; l = l->next)
1609 PurpleConnection *gc = (PurpleConnection *)l->data;
1611 if (purple_connection_get_state(gc) == PURPLE_CONNECTION_CONNECTED)
1612 purple_account_remove_group(purple_connection_get_account(gc), group);
1615 /* Delete the node */
1616 g_object_unref(group);
1619 PurpleBuddy *purple_blist_find_buddy(PurpleAccount *account, const char *name)
1621 PurpleBuddyListPrivate *priv =
1622 purple_buddy_list_get_instance_private(purplebuddylist);
1623 PurpleBuddy *buddy;
1624 struct _purple_hbuddy hb;
1625 PurpleBlistNode *group;
1627 g_return_val_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist), NULL);
1628 g_return_val_if_fail(PURPLE_IS_ACCOUNT(account), NULL);
1629 g_return_val_if_fail((name != NULL) && (*name != '\0'), NULL);
1631 hb.account = account;
1632 hb.name = (gchar *)purple_normalize(account, name);
1634 for (group = priv->root; group; group = group->next) {
1635 if (!group->child)
1636 continue;
1638 hb.group = group;
1639 if ((buddy = g_hash_table_lookup(priv->buddies, &hb))) {
1640 return buddy;
1644 return NULL;
1647 PurpleBuddy *purple_blist_find_buddy_in_group(PurpleAccount *account, const char *name,
1648 PurpleGroup *group)
1650 PurpleBuddyListPrivate *priv =
1651 purple_buddy_list_get_instance_private(purplebuddylist);
1652 struct _purple_hbuddy hb;
1654 g_return_val_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist), NULL);
1655 g_return_val_if_fail(PURPLE_IS_ACCOUNT(account), NULL);
1656 g_return_val_if_fail((name != NULL) && (*name != '\0'), NULL);
1658 hb.name = (gchar *)purple_normalize(account, name);
1659 hb.account = account;
1660 hb.group = (PurpleBlistNode*)group;
1662 return g_hash_table_lookup(priv->buddies, &hb);
1665 static void find_acct_buddies(gpointer key, gpointer value, gpointer data)
1667 PurpleBuddy *buddy = value;
1668 GSList **list = data;
1670 *list = g_slist_prepend(*list, buddy);
1673 GSList *purple_blist_find_buddies(PurpleAccount *account, const char *name)
1675 PurpleBuddyListPrivate *priv =
1676 purple_buddy_list_get_instance_private(purplebuddylist);
1677 PurpleBuddy *buddy;
1678 PurpleBlistNode *node;
1679 GSList *ret = NULL;
1681 g_return_val_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist), NULL);
1682 g_return_val_if_fail(PURPLE_IS_ACCOUNT(account), NULL);
1684 if ((name != NULL) && (*name != '\0')) {
1685 struct _purple_hbuddy hb;
1687 hb.name = (gchar *)purple_normalize(account, name);
1688 hb.account = account;
1690 for (node = priv->root; node != NULL; node = node->next) {
1691 if (!node->child)
1692 continue;
1694 hb.group = node;
1695 if ((buddy = g_hash_table_lookup(priv->buddies,
1696 &hb)) != NULL)
1697 ret = g_slist_prepend(ret, buddy);
1699 } else {
1700 GSList *list = NULL;
1701 GHashTable *buddies = g_hash_table_lookup(buddies_cache, account);
1702 g_hash_table_foreach(buddies, find_acct_buddies, &list);
1703 ret = list;
1706 return ret;
1709 PurpleGroup *purple_blist_find_group(const char *name)
1711 gchar* key;
1712 PurpleGroup *group;
1714 g_return_val_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist), NULL);
1716 if (name == NULL || name[0] == '\0')
1717 name = PURPLE_BLIST_DEFAULT_GROUP_NAME;
1718 if (purple_strequal(name, "Buddies"))
1719 name = PURPLE_BLIST_DEFAULT_GROUP_NAME;
1720 if (purple_strequal(name, localized_default_group_name))
1721 name = PURPLE_BLIST_DEFAULT_GROUP_NAME;
1723 key = purple_blist_fold_name(name);
1724 group = g_hash_table_lookup(groups_cache, key);
1725 g_free(key);
1727 return group;
1730 PurpleGroup *
1731 purple_blist_get_default_group(void)
1733 PurpleGroup *group;
1735 group = purple_blist_find_group(PURPLE_BLIST_DEFAULT_GROUP_NAME);
1736 if (!group) {
1737 group = purple_group_new(PURPLE_BLIST_DEFAULT_GROUP_NAME);
1738 purple_blist_add_group(group, NULL);
1741 return group;
1744 PurpleChat *
1745 purple_blist_find_chat(PurpleAccount *account, const char *name)
1747 char *chat_name;
1748 PurpleChat *chat;
1749 PurpleProtocol *protocol = NULL;
1750 PurpleProtocolChatEntry *pce;
1751 PurpleBlistNode *node, *group;
1752 GList *parts;
1753 char *normname;
1755 g_return_val_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist), NULL);
1756 g_return_val_if_fail((name != NULL) && (*name != '\0'), NULL);
1758 if (!purple_account_is_connected(account))
1759 return NULL;
1761 protocol = purple_protocols_find(purple_account_get_protocol_id(account));
1763 if (PURPLE_PROTOCOL_IMPLEMENTS(protocol, CLIENT, find_blist_chat))
1764 return purple_protocol_client_iface_find_blist_chat(protocol, account, name);
1766 normname = g_strdup(purple_normalize(account, name));
1767 for (group = purple_blist_get_default_root(); group != NULL;
1768 group = group->next) {
1769 for (node = group->child; node != NULL; node = node->next) {
1770 if (PURPLE_IS_CHAT(node)) {
1772 chat = (PurpleChat*)node;
1774 if (account != purple_chat_get_account(chat))
1775 continue;
1777 parts = purple_protocol_chat_iface_info(protocol,
1778 purple_account_get_connection(purple_chat_get_account(chat)));
1780 pce = parts->data;
1781 chat_name = g_hash_table_lookup(purple_chat_get_components(chat),
1782 pce->identifier);
1783 g_list_free_full(parts, g_free);
1785 if (purple_chat_get_account(chat) == account && chat_name != NULL &&
1786 purple_strequal(purple_normalize(account, chat_name), normname)) {
1787 g_free(normname);
1788 return chat;
1794 g_free(normname);
1795 return NULL;
1798 void purple_blist_add_account(PurpleAccount *account)
1800 PurpleBuddyListClass *klass = NULL;
1801 PurpleBlistNode *gnode, *cnode, *bnode;
1802 PurpleCountingNode *contact_counter, *group_counter;
1804 g_return_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist));
1806 klass = PURPLE_BUDDY_LIST_GET_CLASS(purplebuddylist);
1807 if (!klass || !klass->update) {
1808 return;
1811 for (gnode = purple_blist_get_default_root(); gnode;
1812 gnode = gnode->next) {
1813 if (!PURPLE_IS_GROUP(gnode))
1814 continue;
1815 for (cnode = gnode->child; cnode; cnode = cnode->next) {
1816 if (PURPLE_IS_CONTACT(cnode)) {
1817 gboolean recompute = FALSE;
1818 for (bnode = cnode->child; bnode; bnode = bnode->next) {
1819 if (PURPLE_IS_BUDDY(bnode) &&
1820 purple_buddy_get_account(PURPLE_BUDDY(bnode)) == account) {
1821 recompute = TRUE;
1822 contact_counter = PURPLE_COUNTING_NODE(cnode);
1823 group_counter = PURPLE_COUNTING_NODE(gnode);
1824 purple_counting_node_change_current_size(contact_counter, +1);
1825 if (purple_counting_node_get_current_size(contact_counter) == 1)
1826 purple_counting_node_change_current_size(group_counter, +1);
1827 klass->update(
1828 purplebuddylist,
1829 bnode);
1832 if (recompute ||
1833 purple_blist_node_get_bool(
1834 cnode, "show_offline")) {
1835 purple_contact_invalidate_priority_buddy(
1836 (PurpleContact *)cnode);
1837 klass->update(purplebuddylist,
1838 cnode);
1840 } else if (PURPLE_IS_CHAT(cnode) &&
1841 purple_chat_get_account(PURPLE_CHAT(cnode)) == account) {
1842 group_counter = PURPLE_COUNTING_NODE(gnode);
1843 purple_counting_node_change_online_count(group_counter, +1);
1844 purple_counting_node_change_current_size(group_counter, +1);
1845 klass->update(purplebuddylist, cnode);
1848 klass->update(purplebuddylist, gnode);
1852 void purple_blist_remove_account(PurpleAccount *account)
1854 PurpleBuddyListClass *klass = NULL;
1855 PurpleBlistNode *gnode, *cnode, *bnode;
1856 PurpleCountingNode *contact_counter, *group_counter;
1857 PurpleBuddy *buddy;
1858 PurpleChat *chat;
1859 PurpleContact *contact;
1860 PurpleGroup *group;
1861 GList *list = NULL, *iter = NULL;
1863 g_return_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist));
1864 klass = PURPLE_BUDDY_LIST_GET_CLASS(purplebuddylist);
1866 for (gnode = purple_blist_get_default_root(); gnode;
1867 gnode = gnode->next) {
1868 if (!PURPLE_IS_GROUP(gnode))
1869 continue;
1871 group = (PurpleGroup *)gnode;
1873 for (cnode = gnode->child; cnode; cnode = cnode->next) {
1874 if (PURPLE_IS_CONTACT(cnode)) {
1875 gboolean recompute = FALSE;
1876 contact = (PurpleContact *)cnode;
1878 for (bnode = cnode->child; bnode; bnode = bnode->next) {
1879 if (!PURPLE_IS_BUDDY(bnode))
1880 continue;
1882 buddy = (PurpleBuddy *)bnode;
1883 if (account == purple_buddy_get_account(buddy)) {
1884 PurplePresence *presence;
1886 presence = purple_buddy_get_presence(buddy);
1887 contact_counter = PURPLE_COUNTING_NODE(contact);
1888 group_counter = PURPLE_COUNTING_NODE(group);
1890 if(purple_presence_is_online(presence)) {
1891 purple_counting_node_change_online_count(contact_counter, -1);
1892 if (purple_counting_node_get_online_count(contact_counter) == 0)
1893 purple_counting_node_change_online_count(group_counter, -1);
1895 purple_blist_node_set_int(PURPLE_BLIST_NODE(buddy),
1896 "last_seen", time(NULL));
1899 purple_counting_node_change_current_size(contact_counter, -1);
1900 if (purple_counting_node_get_current_size(contact_counter) == 0)
1901 purple_counting_node_change_current_size(group_counter, -1);
1903 if (!g_list_find(list, presence))
1904 list = g_list_prepend(list, presence);
1906 if (purple_contact_get_priority_buddy(contact) == buddy)
1907 purple_contact_invalidate_priority_buddy(contact);
1908 else
1909 recompute = TRUE;
1911 if (klass && klass->remove) {
1912 klass->remove(
1913 purplebuddylist,
1914 bnode);
1918 if (recompute) {
1919 purple_contact_invalidate_priority_buddy(contact);
1921 if (klass && klass->update) {
1922 klass->update(purplebuddylist,
1923 cnode);
1926 } else if (PURPLE_IS_CHAT(cnode)) {
1927 chat = PURPLE_CHAT(cnode);
1929 if(purple_chat_get_account(chat) == account) {
1930 group_counter = PURPLE_COUNTING_NODE(group);
1931 purple_counting_node_change_current_size(group_counter, -1);
1932 purple_counting_node_change_online_count(group_counter, -1);
1934 if (klass && klass->remove) {
1935 klass->remove(purplebuddylist,
1936 cnode);
1943 for (iter = list; iter; iter = iter->next)
1945 purple_presence_set_status_active(iter->data, "offline", TRUE);
1947 g_list_free(list);
1950 void
1951 purple_blist_walk(PurpleBlistWalkFunc group_func,
1952 PurpleBlistWalkFunc chat_func,
1953 PurpleBlistWalkFunc meta_contact_func,
1954 PurpleBlistWalkFunc contact_func,
1955 gpointer data)
1957 PurpleBlistNode *group = NULL, *meta_contact = NULL, *contact = NULL;
1959 for (group = purple_blist_get_default_root(); group != NULL;
1960 group = group->next) {
1961 if(group_func != NULL) {
1962 group_func(group, data);
1965 for(meta_contact = group->child; meta_contact != NULL; meta_contact = meta_contact->next) {
1966 if(PURPLE_IS_CONTACT(meta_contact)) {
1967 if(meta_contact_func != NULL) {
1968 meta_contact_func(meta_contact, data);
1971 if(contact_func != NULL) {
1972 for(contact = meta_contact->child; contact != NULL; contact = contact->next) {
1973 contact_func(contact, data);
1976 } else {
1977 if(PURPLE_IS_CHAT(meta_contact) && chat_func != NULL) {
1978 chat_func(meta_contact, data);
1986 void
1987 purple_blist_request_add_buddy(PurpleAccount *account, const char *username,
1988 const char *group, const char *alias)
1990 PurpleBuddyListClass *klass = NULL;
1992 g_return_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist));
1994 klass = PURPLE_BUDDY_LIST_GET_CLASS(purplebuddylist);
1995 if (klass != NULL && klass->request_add_buddy != NULL) {
1996 klass->request_add_buddy(purplebuddylist, account, username,
1997 group, alias);
2001 void
2002 purple_blist_request_add_chat(PurpleAccount *account, PurpleGroup *group,
2003 const char *alias, const char *name)
2005 PurpleBuddyListClass *klass = NULL;
2007 g_return_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist));
2009 klass = PURPLE_BUDDY_LIST_GET_CLASS(purplebuddylist);
2010 if (klass != NULL && klass->request_add_chat != NULL) {
2011 klass->request_add_chat(purplebuddylist, account, group, alias,
2012 name);
2016 void
2017 purple_blist_request_add_group(void)
2019 PurpleBuddyListClass *klass = NULL;
2021 g_return_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist));
2023 klass = PURPLE_BUDDY_LIST_GET_CLASS(purplebuddylist);
2024 if (klass != NULL && klass->request_add_group != NULL) {
2025 klass->request_add_group(purplebuddylist);
2029 void
2030 purple_blist_new_node(PurpleBuddyList *list, PurpleBlistNode *node)
2032 PurpleBuddyListClass *klass = NULL;
2034 g_return_if_fail(PURPLE_IS_BUDDY_LIST(list));
2036 klass = PURPLE_BUDDY_LIST_GET_CLASS(list);
2037 if (klass && klass->new_node) {
2038 klass->new_node(list, node);
2042 void
2043 purple_blist_update_node(PurpleBuddyList *list, PurpleBlistNode *node)
2045 PurpleBuddyListClass *klass = NULL;
2047 g_return_if_fail(PURPLE_IS_BUDDY_LIST(list));
2049 klass = PURPLE_BUDDY_LIST_GET_CLASS(list);
2050 if (klass && klass->update) {
2051 klass->update(list, node);
2055 void
2056 purple_blist_save_node(PurpleBuddyList *list, PurpleBlistNode *node)
2058 PurpleBuddyListClass *klass = NULL;
2060 g_return_if_fail(PURPLE_IS_BUDDY_LIST(list));
2062 klass = PURPLE_BUDDY_LIST_GET_CLASS(list);
2063 if (klass && klass->save_node) {
2064 klass->save_node(list, node);
2068 void
2069 purple_blist_save_account(PurpleBuddyList *list, PurpleAccount *account)
2071 PurpleBuddyListClass *klass = NULL;
2073 /* XXX: There's a chicken and egg problem with the accounts api, where
2074 * it'll call this function before purple_blist_init is called, this will
2075 * cause the following g_return_if_fail to fail, and muck up the logs. We
2076 * need to find a better fix for this, but this gets rid of it for now.
2078 if(G_UNLIKELY(list == NULL && purplebuddylist == NULL)) {
2079 return;
2082 g_return_if_fail(PURPLE_IS_BUDDY_LIST(list));
2084 klass = PURPLE_BUDDY_LIST_GET_CLASS(list);
2085 if (klass && klass->save_account) {
2086 klass->save_account(list, account);
2090 const gchar *
2091 _purple_blist_get_localized_default_group_name(void)
2093 return localized_default_group_name;
2096 void *
2097 purple_blist_get_handle(void)
2099 static int handle;
2101 return &handle;
2104 void
2105 purple_blist_init(void)
2107 void *handle = purple_blist_get_handle();
2109 /* Set a default, which can't be done as a static initializer. */
2110 buddy_list_type = PURPLE_TYPE_BUDDY_LIST;
2112 purple_signal_register(handle, "buddy-status-changed",
2113 purple_marshal_VOID__POINTER_POINTER_POINTER,
2114 G_TYPE_NONE, 3, PURPLE_TYPE_BUDDY, PURPLE_TYPE_STATUS,
2115 PURPLE_TYPE_STATUS);
2116 purple_signal_register(handle, "buddy-privacy-changed",
2117 purple_marshal_VOID__POINTER, G_TYPE_NONE,
2118 1, PURPLE_TYPE_BUDDY);
2120 purple_signal_register(handle, "buddy-idle-changed",
2121 purple_marshal_VOID__POINTER_INT_INT, G_TYPE_NONE,
2122 3, PURPLE_TYPE_BUDDY, G_TYPE_INT, G_TYPE_INT);
2124 purple_signal_register(handle, "buddy-signed-on",
2125 purple_marshal_VOID__POINTER, G_TYPE_NONE, 1,
2126 PURPLE_TYPE_BUDDY);
2128 purple_signal_register(handle, "buddy-signed-off",
2129 purple_marshal_VOID__POINTER, G_TYPE_NONE, 1,
2130 PURPLE_TYPE_BUDDY);
2132 purple_signal_register(handle, "buddy-got-login-time",
2133 purple_marshal_VOID__POINTER, G_TYPE_NONE, 1,
2134 PURPLE_TYPE_BUDDY);
2136 purple_signal_register(handle, "blist-node-added",
2137 purple_marshal_VOID__POINTER, G_TYPE_NONE, 1,
2138 PURPLE_TYPE_BLIST_NODE);
2140 purple_signal_register(handle, "blist-node-removed",
2141 purple_marshal_VOID__POINTER, G_TYPE_NONE, 1,
2142 PURPLE_TYPE_BLIST_NODE);
2144 purple_signal_register(handle, "buddy-removed-from-group",
2145 purple_marshal_VOID__POINTER, G_TYPE_NONE, 1,
2146 PURPLE_TYPE_BUDDY);
2148 purple_signal_register(handle, "buddy-icon-changed",
2149 purple_marshal_VOID__POINTER, G_TYPE_NONE, 1,
2150 PURPLE_TYPE_BUDDY);
2152 purple_signal_register(handle, "update-idle", purple_marshal_VOID,
2153 G_TYPE_NONE, 0);
2155 purple_signal_register(handle, "blist-node-extended-menu",
2156 purple_marshal_VOID__POINTER_POINTER, G_TYPE_NONE, 2,
2157 PURPLE_TYPE_BLIST_NODE,
2158 G_TYPE_POINTER); /* (GList **) */
2160 purple_signal_register(handle, "blist-node-aliased",
2161 purple_marshal_VOID__POINTER_POINTER, G_TYPE_NONE, 2,
2162 PURPLE_TYPE_BLIST_NODE, G_TYPE_STRING);
2164 purple_signal_register(handle, "buddy-caps-changed",
2165 purple_marshal_VOID__POINTER_INT_INT, G_TYPE_NONE,
2166 3, PURPLE_TYPE_BUDDY, G_TYPE_INT, G_TYPE_INT);
2168 purple_signal_connect(purple_accounts_get_handle(), "account-created",
2169 handle,
2170 PURPLE_CALLBACK(purple_blist_buddies_cache_add_account),
2171 NULL);
2173 purple_signal_connect(purple_accounts_get_handle(), "account-destroying",
2174 handle,
2175 PURPLE_CALLBACK(purple_blist_buddies_cache_remove_account),
2176 NULL);
2179 static void
2180 blist_node_destroy(PurpleBuddyListClass *klass, PurpleBuddyList *list,
2181 PurpleBlistNode *node)
2183 PurpleBlistNode *child, *next_child;
2185 child = node->child;
2186 while (child) {
2187 next_child = child->next;
2188 blist_node_destroy(klass, list, child);
2189 child = next_child;
2192 /* Allow the UI to free data */
2193 node->parent = NULL;
2194 node->child = NULL;
2195 node->next = NULL;
2196 node->prev = NULL;
2197 if (klass && klass->remove) {
2198 klass->remove(list, node);
2201 g_object_unref(node);
2204 void
2205 purple_blist_uninit(void)
2207 /* This happens if we quit before purple_set_blist is called. */
2208 if (purplebuddylist == NULL)
2209 return;
2211 if (save_timer != 0) {
2212 g_source_remove(save_timer);
2213 save_timer = 0;
2214 purple_blist_sync();
2217 purple_debug(PURPLE_DEBUG_INFO, "buddylist", "Destroying\n");
2219 g_hash_table_destroy(buddies_cache);
2220 g_hash_table_destroy(groups_cache);
2222 buddies_cache = NULL;
2223 groups_cache = NULL;
2225 g_clear_object(&purplebuddylist);
2227 g_free(localized_default_group_name);
2228 localized_default_group_name = NULL;
2230 purple_signals_disconnect_by_handle(purple_blist_get_handle());
2231 purple_signals_unregister_by_instance(purple_blist_get_handle());
2234 /**************************************************************************
2235 * GObject code
2236 **************************************************************************/
2238 /* GObject initialization function */
2239 static void
2240 purple_buddy_list_init(PurpleBuddyList *blist)
2242 PurpleBuddyListPrivate *priv =
2243 purple_buddy_list_get_instance_private(blist);
2245 priv->buddies = g_hash_table_new_full(
2246 (GHashFunc)_purple_blist_hbuddy_hash,
2247 (GEqualFunc)_purple_blist_hbuddy_equal,
2248 (GDestroyNotify)_purple_blist_hbuddy_free_key, NULL);
2251 /* GObject finalize function */
2252 static void
2253 purple_buddy_list_finalize(GObject *object)
2255 PurpleBuddyList *list = PURPLE_BUDDY_LIST(object);
2256 PurpleBuddyListClass *klass = PURPLE_BUDDY_LIST_GET_CLASS(list);
2257 PurpleBuddyListPrivate *priv =
2258 purple_buddy_list_get_instance_private(list);
2259 PurpleBlistNode *node, *next_node;
2261 g_hash_table_destroy(priv->buddies);
2263 node = priv->root;
2264 while (node) {
2265 next_node = node->next;
2266 blist_node_destroy(klass, list, node);
2267 node = next_node;
2269 priv->root = NULL;
2271 G_OBJECT_CLASS(purple_buddy_list_parent_class)->finalize(object);
2274 /* Class initializer function */
2275 static void purple_buddy_list_class_init(PurpleBuddyListClass *klass)
2277 GObjectClass *obj_class = G_OBJECT_CLASS(klass);
2279 obj_class->finalize = purple_buddy_list_finalize;
2281 klass->save_node = purple_blist_real_save_node;
2282 klass->remove_node = purple_blist_real_save_node;
2283 klass->save_account = purple_blist_real_save_account;