Adapt migration for files
[pidgin-git.git] / libpurple / buddylist.c
blob51da1cdf912e5f35ba41d498dc5229daccdfd42a
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 "dbus-maybe.h"
27 #include "debug.h"
28 #include "notify.h"
29 #include "pounce.h"
30 #include "prefs.h"
31 #include "protocol.h"
32 #include "server.h"
33 #include "signals.h"
34 #include "util.h"
35 #include "xmlnode.h"
37 #define PURPLE_BUDDY_LIST_GET_PRIVATE(obj) \
38 (G_TYPE_INSTANCE_GET_PRIVATE((obj), PURPLE_TYPE_BUDDY_LIST, PurpleBuddyListPrivate))
40 /* Private data for a buddy list. */
41 typedef struct {
42 GHashTable *buddies; /* Every buddy in this list */
43 } PurpleBuddyListPrivate;
45 static PurpleBlistUiOps *blist_ui_ops = NULL;
47 static PurpleBuddyList *purplebuddylist = NULL;
49 static GObjectClass *parent_class;
52 * A hash table used for efficient lookups of buddies by name.
53 * PurpleAccount* => GHashTable*, with the inner hash table being
54 * struct _purple_hbuddy => PurpleBuddy*
56 static GHashTable *buddies_cache = NULL;
59 * A hash table used for efficient lookups of groups by name.
60 * UTF-8 collate-key => PurpleGroup*.
62 static GHashTable *groups_cache = NULL;
64 static guint save_timer = 0;
65 static gboolean blist_loaded = FALSE;
66 static gchar *localized_default_group_name = NULL;
68 /*********************************************************************
69 * Private utility functions *
70 *********************************************************************/
72 static gchar *
73 purple_blist_fold_name(const gchar *name)
75 gchar *res, *tmp;
77 if (name == NULL)
78 return NULL;
80 tmp = g_utf8_casefold(name, -1);
81 res = g_utf8_collate_key(tmp, -1);
82 g_free(tmp);
84 return res;
87 static PurpleBlistNode *purple_blist_get_last_sibling(PurpleBlistNode *node)
89 PurpleBlistNode *n = node;
90 if (!n)
91 return NULL;
92 while (n->next)
93 n = n->next;
94 return n;
97 PurpleBlistNode *_purple_blist_get_last_child(PurpleBlistNode *node)
99 if (!node)
100 return NULL;
101 return purple_blist_get_last_sibling(node->child);
104 struct _list_account_buddies {
105 GSList *list;
106 PurpleAccount *account;
109 struct _purple_hbuddy {
110 char *name;
111 PurpleAccount *account;
112 PurpleBlistNode *group;
115 /* This function must not use purple_normalize */
116 static guint _purple_blist_hbuddy_hash(struct _purple_hbuddy *hb)
118 return g_str_hash(hb->name) ^ g_direct_hash(hb->group) ^ g_direct_hash(hb->account);
121 /* This function must not use purple_normalize */
122 static guint _purple_blist_hbuddy_equal(struct _purple_hbuddy *hb1, struct _purple_hbuddy *hb2)
124 return (hb1->group == hb2->group &&
125 hb1->account == hb2->account &&
126 g_str_equal(hb1->name, hb2->name));
129 static void _purple_blist_hbuddy_free_key(struct _purple_hbuddy *hb)
131 g_free(hb->name);
132 g_free(hb);
135 static void
136 purple_blist_buddies_cache_add_account(PurpleAccount *account)
138 GHashTable *account_buddies = g_hash_table_new_full((GHashFunc)_purple_blist_hbuddy_hash,
139 (GEqualFunc)_purple_blist_hbuddy_equal,
140 (GDestroyNotify)_purple_blist_hbuddy_free_key, NULL);
141 g_hash_table_insert(buddies_cache, account, account_buddies);
144 static void
145 purple_blist_buddies_cache_remove_account(const PurpleAccount *account)
147 g_hash_table_remove(buddies_cache, account);
150 /*********************************************************************
151 * Writing to disk *
152 *********************************************************************/
154 static void
155 value_to_xmlnode(gpointer key, gpointer hvalue, gpointer user_data)
157 const char *name;
158 GValue *value;
159 PurpleXmlNode *node, *child;
160 char buf[21];
162 name = (const char *)key;
163 value = (GValue *)hvalue;
164 node = (PurpleXmlNode *)user_data;
166 g_return_if_fail(value != NULL);
168 child = purple_xmlnode_new_child(node, "setting");
169 purple_xmlnode_set_attrib(child, "name", name);
171 if (G_VALUE_HOLDS_INT(value)) {
172 purple_xmlnode_set_attrib(child, "type", "int");
173 g_snprintf(buf, sizeof(buf), "%d", g_value_get_int(value));
174 purple_xmlnode_insert_data(child, buf, -1);
176 else if (G_VALUE_HOLDS_STRING(value)) {
177 purple_xmlnode_set_attrib(child, "type", "string");
178 purple_xmlnode_insert_data(child, g_value_get_string(value), -1);
180 else if (G_VALUE_HOLDS_BOOLEAN(value)) {
181 purple_xmlnode_set_attrib(child, "type", "bool");
182 g_snprintf(buf, sizeof(buf), "%d", g_value_get_boolean(value));
183 purple_xmlnode_insert_data(child, buf, -1);
187 static void
188 chat_component_to_xmlnode(gpointer key, gpointer value, gpointer user_data)
190 const char *name;
191 const char *data;
192 PurpleXmlNode *node, *child;
194 name = (const char *)key;
195 data = (const char *)value;
196 node = (PurpleXmlNode *)user_data;
198 g_return_if_fail(data != NULL);
200 child = purple_xmlnode_new_child(node, "component");
201 purple_xmlnode_set_attrib(child, "name", name);
202 purple_xmlnode_insert_data(child, data, -1);
205 static PurpleXmlNode *
206 buddy_to_xmlnode(PurpleBuddy *buddy)
208 PurpleXmlNode *node, *child;
209 PurpleAccount *account = purple_buddy_get_account(buddy);
210 const char *alias = purple_buddy_get_local_alias(buddy);
212 node = purple_xmlnode_new("buddy");
213 purple_xmlnode_set_attrib(node, "account", purple_account_get_username(account));
214 purple_xmlnode_set_attrib(node, "proto", purple_account_get_protocol_id(account));
216 child = purple_xmlnode_new_child(node, "name");
217 purple_xmlnode_insert_data(child, purple_buddy_get_name(buddy), -1);
219 if (alias != NULL)
221 child = purple_xmlnode_new_child(node, "alias");
222 purple_xmlnode_insert_data(child, alias, -1);
225 /* Write buddy settings */
226 g_hash_table_foreach(purple_blist_node_get_settings(PURPLE_BLIST_NODE(buddy)),
227 value_to_xmlnode, node);
229 return node;
232 static PurpleXmlNode *
233 contact_to_xmlnode(PurpleContact *contact)
235 PurpleXmlNode *node, *child;
236 PurpleBlistNode *bnode;
237 gchar *alias;
239 node = purple_xmlnode_new("contact");
240 g_object_get(contact, "alias", &alias, NULL);
242 if (alias != NULL)
244 purple_xmlnode_set_attrib(node, "alias", alias);
247 /* Write buddies */
248 for (bnode = PURPLE_BLIST_NODE(contact)->child; bnode != NULL; bnode = bnode->next)
250 if (purple_blist_node_is_transient(bnode))
251 continue;
252 if (PURPLE_IS_BUDDY(bnode))
254 child = buddy_to_xmlnode(PURPLE_BUDDY(bnode));
255 purple_xmlnode_insert_child(node, child);
259 /* Write contact settings */
260 g_hash_table_foreach(purple_blist_node_get_settings(PURPLE_BLIST_NODE(contact)),
261 value_to_xmlnode, node);
263 g_free(alias);
264 return node;
267 static PurpleXmlNode *
268 chat_to_xmlnode(PurpleChat *chat)
270 PurpleXmlNode *node, *child;
271 PurpleAccount *account = purple_chat_get_account(chat);
272 gchar *alias;
274 g_object_get(chat, "alias", &alias, NULL);
276 node = purple_xmlnode_new("chat");
277 purple_xmlnode_set_attrib(node, "proto", purple_account_get_protocol_id(account));
278 purple_xmlnode_set_attrib(node, "account", purple_account_get_username(account));
280 if (alias != NULL)
282 child = purple_xmlnode_new_child(node, "alias");
283 purple_xmlnode_insert_data(child, alias, -1);
286 /* Write chat components */
287 g_hash_table_foreach(purple_chat_get_components(chat),
288 chat_component_to_xmlnode, node);
290 /* Write chat settings */
291 g_hash_table_foreach(purple_blist_node_get_settings(PURPLE_BLIST_NODE(chat)),
292 value_to_xmlnode, node);
294 g_free(alias);
295 return node;
298 static PurpleXmlNode *
299 group_to_xmlnode(PurpleGroup *group)
301 PurpleXmlNode *node, *child;
302 PurpleBlistNode *cnode;
304 node = purple_xmlnode_new("group");
305 if (group != purple_blist_get_default_group())
306 purple_xmlnode_set_attrib(node, "name", purple_group_get_name(group));
308 /* Write settings */
309 g_hash_table_foreach(purple_blist_node_get_settings(PURPLE_BLIST_NODE(group)),
310 value_to_xmlnode, node);
312 /* Write contacts and chats */
313 for (cnode = PURPLE_BLIST_NODE(group)->child; cnode != NULL; cnode = cnode->next)
315 if (purple_blist_node_is_transient(cnode))
316 continue;
317 if (PURPLE_IS_CONTACT(cnode))
319 child = contact_to_xmlnode(PURPLE_CONTACT(cnode));
320 purple_xmlnode_insert_child(node, child);
322 else if (PURPLE_IS_CHAT(cnode))
324 child = chat_to_xmlnode(PURPLE_CHAT(cnode));
325 purple_xmlnode_insert_child(node, child);
329 return node;
332 static PurpleXmlNode *
333 accountprivacy_to_xmlnode(PurpleAccount *account)
335 PurpleXmlNode *node, *child;
336 GSList *cur;
337 char buf[10];
339 node = purple_xmlnode_new("account");
340 purple_xmlnode_set_attrib(node, "proto", purple_account_get_protocol_id(account));
341 purple_xmlnode_set_attrib(node, "name", purple_account_get_username(account));
342 g_snprintf(buf, sizeof(buf), "%d", purple_account_get_privacy_type(account));
343 purple_xmlnode_set_attrib(node, "mode", buf);
345 for (cur = purple_account_privacy_get_permitted(account); cur; cur = cur->next)
347 child = purple_xmlnode_new_child(node, "permit");
348 purple_xmlnode_insert_data(child, cur->data, -1);
351 for (cur = purple_account_privacy_get_denied(account); cur; cur = cur->next)
353 child = purple_xmlnode_new_child(node, "block");
354 purple_xmlnode_insert_data(child, cur->data, -1);
357 return node;
360 static PurpleXmlNode *
361 blist_to_xmlnode(void)
363 PurpleXmlNode *node, *child, *grandchild;
364 PurpleBlistNode *gnode;
365 GList *cur;
366 const gchar *localized_default;
368 node = purple_xmlnode_new("purple");
369 purple_xmlnode_set_attrib(node, "version", "1.0");
371 /* Write groups */
372 child = purple_xmlnode_new_child(node, "blist");
374 localized_default = localized_default_group_name;
375 if (g_strcmp0(_("Buddies"), "Buddies") != 0)
376 localized_default = _("Buddies");
377 if (localized_default != NULL) {
378 purple_xmlnode_set_attrib(child,
379 "localized-default-group", localized_default);
382 for (gnode = purplebuddylist->root; gnode != NULL; gnode = gnode->next)
384 if (purple_blist_node_is_transient(gnode))
385 continue;
386 if (PURPLE_IS_GROUP(gnode))
388 grandchild = group_to_xmlnode(PURPLE_GROUP(gnode));
389 purple_xmlnode_insert_child(child, grandchild);
393 /* Write privacy settings */
394 child = purple_xmlnode_new_child(node, "privacy");
395 for (cur = purple_accounts_get_all(); cur != NULL; cur = cur->next)
397 grandchild = accountprivacy_to_xmlnode(cur->data);
398 purple_xmlnode_insert_child(child, grandchild);
401 return node;
404 static void
405 purple_blist_sync(void)
407 PurpleXmlNode *node;
408 char *data;
410 if (!blist_loaded)
412 purple_debug_error("buddylist", "Attempted to save buddy list before it "
413 "was read!\n");
414 return;
417 node = blist_to_xmlnode();
418 data = purple_xmlnode_to_formatted_str(node, NULL);
419 purple_util_write_data_to_file("blist.xml", data, -1);
420 g_free(data);
421 purple_xmlnode_free(node);
424 static gboolean
425 save_cb(gpointer data)
427 purple_blist_sync();
428 save_timer = 0;
429 return FALSE;
432 static void
433 _purple_blist_schedule_save()
435 if (save_timer == 0)
436 save_timer = purple_timeout_add_seconds(5, save_cb, NULL);
439 static void
440 purple_blist_save_account(PurpleAccount *account)
442 #if 1
443 _purple_blist_schedule_save();
444 #else
445 if (account != NULL) {
446 /* Save the buddies and privacy data for this account */
447 } else {
448 /* Save all buddies and privacy data */
450 #endif
453 static void
454 purple_blist_save_node(PurpleBlistNode *node)
456 _purple_blist_schedule_save();
459 void purple_blist_schedule_save()
461 PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
463 /* Save everything */
464 if (ops && ops->save_account)
465 ops->save_account(NULL);
468 /*********************************************************************
469 * Reading from disk *
470 *********************************************************************/
472 static void
473 parse_setting(PurpleBlistNode *node, PurpleXmlNode *setting)
475 const char *name = purple_xmlnode_get_attrib(setting, "name");
476 const char *type = purple_xmlnode_get_attrib(setting, "type");
477 char *value = purple_xmlnode_get_data(setting);
479 if (!value)
480 return;
482 if (!type || purple_strequal(type, "string"))
483 purple_blist_node_set_string(node, name, value);
484 else if (purple_strequal(type, "bool"))
485 purple_blist_node_set_bool(node, name, atoi(value));
486 else if (purple_strequal(type, "int"))
487 purple_blist_node_set_int(node, name, atoi(value));
489 g_free(value);
492 static void
493 parse_buddy(PurpleGroup *group, PurpleContact *contact, PurpleXmlNode *bnode)
495 PurpleAccount *account;
496 PurpleBuddy *buddy;
497 char *name = NULL, *alias = NULL;
498 const char *acct_name, *proto;
499 PurpleXmlNode *x;
501 acct_name = purple_xmlnode_get_attrib(bnode, "account");
502 proto = purple_xmlnode_get_attrib(bnode, "proto");
504 if (!acct_name || !proto)
505 return;
507 account = purple_accounts_find(acct_name, proto);
509 if (!account)
510 return;
512 if ((x = purple_xmlnode_get_child(bnode, "name")))
513 name = purple_xmlnode_get_data(x);
515 if (!name)
516 return;
518 if ((x = purple_xmlnode_get_child(bnode, "alias")))
519 alias = purple_xmlnode_get_data(x);
521 buddy = purple_buddy_new(account, name, alias);
522 purple_blist_add_buddy(buddy, contact, group,
523 _purple_blist_get_last_child((PurpleBlistNode*)contact));
525 for (x = purple_xmlnode_get_child(bnode, "setting"); x; x = purple_xmlnode_get_next_twin(x)) {
526 parse_setting((PurpleBlistNode*)buddy, x);
529 g_free(name);
530 g_free(alias);
533 static void
534 parse_contact(PurpleGroup *group, PurpleXmlNode *cnode)
536 PurpleContact *contact = purple_contact_new();
537 PurpleXmlNode *x;
538 const char *alias;
540 purple_blist_add_contact(contact, group,
541 _purple_blist_get_last_child((PurpleBlistNode*)group));
543 if ((alias = purple_xmlnode_get_attrib(cnode, "alias"))) {
544 purple_contact_set_alias(contact, alias);
547 for (x = cnode->child; x; x = x->next) {
548 if (x->type != PURPLE_XMLNODE_TYPE_TAG)
549 continue;
550 if (purple_strequal(x->name, "buddy"))
551 parse_buddy(group, contact, x);
552 else if (purple_strequal(x->name, "setting"))
553 parse_setting(PURPLE_BLIST_NODE(contact), x);
556 /* if the contact is empty, don't keep it around. it causes problems */
557 if (!PURPLE_BLIST_NODE(contact)->child)
558 purple_blist_remove_contact(contact);
561 static void
562 parse_chat(PurpleGroup *group, PurpleXmlNode *cnode)
564 PurpleChat *chat;
565 PurpleAccount *account;
566 const char *acct_name, *proto;
567 PurpleXmlNode *x;
568 char *alias = NULL;
569 GHashTable *components;
571 acct_name = purple_xmlnode_get_attrib(cnode, "account");
572 proto = purple_xmlnode_get_attrib(cnode, "proto");
574 if (!acct_name || !proto)
575 return;
577 account = purple_accounts_find(acct_name, proto);
579 if (!account)
580 return;
582 if ((x = purple_xmlnode_get_child(cnode, "alias")))
583 alias = purple_xmlnode_get_data(x);
585 components = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
587 for (x = purple_xmlnode_get_child(cnode, "component"); x; x = purple_xmlnode_get_next_twin(x)) {
588 const char *name;
589 char *value;
591 name = purple_xmlnode_get_attrib(x, "name");
592 value = purple_xmlnode_get_data(x);
593 g_hash_table_replace(components, g_strdup(name), value);
596 chat = purple_chat_new(account, alias, components);
597 purple_blist_add_chat(chat, group,
598 _purple_blist_get_last_child((PurpleBlistNode*)group));
600 for (x = purple_xmlnode_get_child(cnode, "setting"); x; x = purple_xmlnode_get_next_twin(x)) {
601 parse_setting((PurpleBlistNode*)chat, x);
604 g_free(alias);
607 static void
608 parse_group(PurpleXmlNode *groupnode)
610 const char *name = purple_xmlnode_get_attrib(groupnode, "name");
611 PurpleGroup *group;
612 PurpleXmlNode *cnode;
614 group = purple_group_new(name);
615 purple_blist_add_group(group,
616 purple_blist_get_last_sibling(purplebuddylist->root));
618 for (cnode = groupnode->child; cnode; cnode = cnode->next) {
619 if (cnode->type != PURPLE_XMLNODE_TYPE_TAG)
620 continue;
621 if (purple_strequal(cnode->name, "setting"))
622 parse_setting((PurpleBlistNode*)group, cnode);
623 else if (purple_strequal(cnode->name, "contact") ||
624 purple_strequal(cnode->name, "person"))
625 parse_contact(group, cnode);
626 else if (purple_strequal(cnode->name, "chat"))
627 parse_chat(group, cnode);
631 static void
632 load_blist(void)
634 PurpleXmlNode *purple, *blist, *privacy;
636 blist_loaded = TRUE;
638 purple = purple_util_read_xml_from_file("blist.xml", _("buddy list"));
640 if (purple == NULL)
641 return;
643 blist = purple_xmlnode_get_child(purple, "blist");
644 if (blist) {
645 PurpleXmlNode *groupnode;
647 localized_default_group_name = g_strdup(
648 purple_xmlnode_get_attrib(blist,
649 "localized-default-group"));
651 for (groupnode = purple_xmlnode_get_child(blist, "group"); groupnode != NULL;
652 groupnode = purple_xmlnode_get_next_twin(groupnode)) {
653 parse_group(groupnode);
655 } else {
656 g_free(localized_default_group_name);
657 localized_default_group_name = NULL;
660 privacy = purple_xmlnode_get_child(purple, "privacy");
661 if (privacy) {
662 PurpleXmlNode *anode;
663 for (anode = privacy->child; anode; anode = anode->next) {
664 PurpleXmlNode *x;
665 PurpleAccount *account;
666 int imode;
667 const char *acct_name, *proto, *mode;
669 acct_name = purple_xmlnode_get_attrib(anode, "name");
670 proto = purple_xmlnode_get_attrib(anode, "proto");
671 mode = purple_xmlnode_get_attrib(anode, "mode");
673 if (!acct_name || !proto || !mode)
674 continue;
676 account = purple_accounts_find(acct_name, proto);
678 if (!account)
679 continue;
681 imode = atoi(mode);
682 purple_account_set_privacy_type(account, (imode != 0 ? imode : PURPLE_ACCOUNT_PRIVACY_ALLOW_ALL));
684 for (x = anode->child; x; x = x->next) {
685 char *name;
686 if (x->type != PURPLE_XMLNODE_TYPE_TAG)
687 continue;
689 if (purple_strequal(x->name, "permit")) {
690 name = purple_xmlnode_get_data(x);
691 purple_account_privacy_permit_add(account, name, TRUE);
692 g_free(name);
693 } else if (purple_strequal(x->name, "block")) {
694 name = purple_xmlnode_get_data(x);
695 purple_account_privacy_deny_add(account, name, TRUE);
696 g_free(name);
702 purple_xmlnode_free(purple);
704 /* This tells the buddy icon code to do its thing. */
705 _purple_buddy_icons_blist_loaded_cb();
708 /*****************************************************************************
709 * Public API functions *
710 *****************************************************************************/
712 void
713 purple_blist_boot(void)
715 PurpleBlistUiOps *ui_ops;
716 GList *account;
717 PurpleBuddyList *gbl = g_object_new(PURPLE_TYPE_BUDDY_LIST, NULL);
719 ui_ops = purple_blist_get_ui_ops();
721 buddies_cache = g_hash_table_new_full(g_direct_hash, g_direct_equal,
722 NULL, (GDestroyNotify)g_hash_table_destroy);
724 groups_cache = g_hash_table_new_full((GHashFunc)g_str_hash,
725 (GEqualFunc)g_str_equal,
726 (GDestroyNotify)g_free, NULL);
728 for (account = purple_accounts_get_all(); account != NULL; account = account->next)
730 purple_blist_buddies_cache_add_account(account->data);
733 if (ui_ops != NULL && ui_ops->new_list != NULL)
734 ui_ops->new_list(gbl);
736 purplebuddylist = gbl;
738 load_blist();
741 PurpleBuddyList *
742 purple_blist_get_buddy_list()
744 return purplebuddylist;
747 PurpleBlistNode *
748 purple_blist_get_root()
750 return purplebuddylist ? purplebuddylist->root : NULL;
753 static void
754 append_buddy(gpointer key, gpointer value, gpointer user_data)
756 GSList **list = user_data;
757 *list = g_slist_prepend(*list, value);
760 GSList *
761 purple_blist_get_buddies()
763 GSList *buddies = NULL;
765 if (!purplebuddylist)
766 return NULL;
768 g_hash_table_foreach(PURPLE_BUDDY_LIST_GET_PRIVATE(purplebuddylist)->buddies,
769 append_buddy, &buddies);
770 return buddies;
773 void *
774 purple_blist_get_ui_data()
776 return purplebuddylist->ui_data;
779 void
780 purple_blist_set_ui_data(void *ui_data)
782 purplebuddylist->ui_data = ui_data;
785 void purple_blist_show()
787 PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
789 if (ops && ops->show)
790 ops->show(purplebuddylist);
793 void purple_blist_set_visible(gboolean show)
795 PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
797 if (ops && ops->set_visible)
798 ops->set_visible(purplebuddylist, show);
801 void purple_blist_update_buddies_cache(PurpleBuddy *buddy, const char *new_name)
803 struct _purple_hbuddy *hb, *hb2;
804 GHashTable *account_buddies;
805 PurpleAccount *account;
806 gchar *name;
807 PurpleBuddyListPrivate *priv = PURPLE_BUDDY_LIST_GET_PRIVATE(purplebuddylist);
809 g_return_if_fail(PURPLE_IS_BUDDY(buddy));
811 account = purple_buddy_get_account(buddy);
812 name = (gchar *)purple_buddy_get_name(buddy);
814 hb = g_new(struct _purple_hbuddy, 1);
815 hb->name = (gchar *)purple_normalize(account, name);
816 hb->account = account;
817 hb->group = PURPLE_BLIST_NODE(buddy)->parent->parent;
818 g_hash_table_remove(priv->buddies, hb);
820 account_buddies = g_hash_table_lookup(buddies_cache, account);
821 g_hash_table_remove(account_buddies, hb);
823 hb->name = g_strdup(purple_normalize(account, new_name));
824 g_hash_table_replace(priv->buddies, hb, buddy);
826 hb2 = g_new(struct _purple_hbuddy, 1);
827 hb2->name = g_strdup(hb->name);
828 hb2->account = account;
829 hb2->group = PURPLE_BLIST_NODE(buddy)->parent->parent;
831 g_hash_table_replace(account_buddies, hb2, buddy);
834 void purple_blist_update_groups_cache(PurpleGroup *group, const char *new_name)
836 gchar* key;
838 key = purple_blist_fold_name(purple_group_get_name(group));
839 g_hash_table_remove(groups_cache, key);
840 g_free(key);
842 g_hash_table_insert(groups_cache,
843 purple_blist_fold_name(new_name), group);
846 void purple_blist_add_chat(PurpleChat *chat, PurpleGroup *group, PurpleBlistNode *node)
848 PurpleBlistNode *cnode = PURPLE_BLIST_NODE(chat);
849 PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
850 PurpleCountingNode *group_counter;
852 g_return_if_fail(PURPLE_IS_CHAT(chat));
854 if (node == NULL) {
855 if (group == NULL)
856 group = purple_group_new(_("Chats"));
858 /* Add group to blist if isn't already on it. Fixes #2752. */
859 if (!purple_blist_find_group(purple_group_get_name(group))) {
860 purple_blist_add_group(group,
861 purple_blist_get_last_sibling(purplebuddylist->root));
863 } else {
864 group = PURPLE_GROUP(node->parent);
867 /* if we're moving to overtop of ourselves, do nothing */
868 if (cnode == node)
869 return;
871 if (cnode->parent) {
872 /* This chat was already in the list and is
873 * being moved.
875 group_counter = PURPLE_COUNTING_NODE(cnode->parent);
876 purple_counting_node_change_total_size(group_counter, -1);
877 if (purple_account_is_connected(purple_chat_get_account(chat))) {
878 purple_counting_node_change_online_count(group_counter, -1);
879 purple_counting_node_change_current_size(group_counter, -1);
881 if (cnode->next)
882 cnode->next->prev = cnode->prev;
883 if (cnode->prev)
884 cnode->prev->next = cnode->next;
885 if (cnode->parent->child == cnode)
886 cnode->parent->child = cnode->next;
888 if (ops && ops->remove)
889 ops->remove(purplebuddylist, cnode);
890 /* ops->remove() cleaned up the cnode's ui_data, so we need to
891 * reinitialize it */
892 if (ops && ops->new_node)
893 ops->new_node(cnode);
896 if (node != NULL) {
897 if (node->next)
898 node->next->prev = cnode;
899 cnode->next = node->next;
900 cnode->prev = node;
901 cnode->parent = node->parent;
902 node->next = cnode;
903 group_counter = PURPLE_COUNTING_NODE(node->parent);
904 purple_counting_node_change_total_size(group_counter, +1);
905 if (purple_account_is_connected(purple_chat_get_account(chat))) {
906 purple_counting_node_change_online_count(group_counter, +1);
907 purple_counting_node_change_current_size(group_counter, +1);
909 } else {
910 if (((PurpleBlistNode *)group)->child)
911 ((PurpleBlistNode *)group)->child->prev = cnode;
912 cnode->next = ((PurpleBlistNode *)group)->child;
913 cnode->prev = NULL;
914 ((PurpleBlistNode *)group)->child = cnode;
915 cnode->parent = PURPLE_BLIST_NODE(group);
916 group_counter = PURPLE_COUNTING_NODE(group);
917 purple_counting_node_change_total_size(group_counter, +1);
918 if (purple_account_is_connected(purple_chat_get_account(chat))) {
919 purple_counting_node_change_online_count(group_counter, +1);
920 purple_counting_node_change_current_size(group_counter, +1);
924 if (ops) {
925 if (ops->save_node)
926 ops->save_node(cnode);
927 if (ops->update)
928 ops->update(purplebuddylist, PURPLE_BLIST_NODE(cnode));
931 purple_signal_emit(purple_blist_get_handle(), "blist-node-added",
932 cnode);
935 void purple_blist_add_buddy(PurpleBuddy *buddy, PurpleContact *contact, PurpleGroup *group, PurpleBlistNode *node)
937 PurpleBlistNode *cnode, *bnode;
938 PurpleCountingNode *contact_counter, *group_counter;
939 PurpleGroup *g;
940 PurpleContact *c;
941 PurpleAccount *account;
942 PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
943 struct _purple_hbuddy *hb, *hb2;
944 GHashTable *account_buddies;
945 PurpleBuddyListPrivate *priv = PURPLE_BUDDY_LIST_GET_PRIVATE(purplebuddylist);
947 g_return_if_fail(PURPLE_IS_BUDDY(buddy));
949 bnode = PURPLE_BLIST_NODE(buddy);
950 account = purple_buddy_get_account(buddy);
952 /* if we're moving to overtop of ourselves, do nothing */
953 if (bnode == node || (!node && bnode->parent &&
954 contact && bnode->parent == (PurpleBlistNode*)contact
955 && bnode == bnode->parent->child))
956 return;
958 if (node && PURPLE_IS_BUDDY(node)) {
959 c = (PurpleContact*)node->parent;
960 g = (PurpleGroup*)node->parent->parent;
961 } else if (contact) {
962 c = contact;
963 g = PURPLE_GROUP(PURPLE_BLIST_NODE(c)->parent);
964 } else {
965 g = group;
966 if (g == NULL)
967 g = purple_blist_get_default_group();
968 /* Add group to blist if isn't already on it. Fixes #2752. */
969 if (!purple_blist_find_group(purple_group_get_name(g))) {
970 purple_blist_add_group(g,
971 purple_blist_get_last_sibling(purplebuddylist->root));
973 c = purple_contact_new();
974 purple_blist_add_contact(c, g,
975 _purple_blist_get_last_child((PurpleBlistNode*)g));
978 cnode = PURPLE_BLIST_NODE(c);
980 if (bnode->parent) {
981 contact_counter = PURPLE_COUNTING_NODE(bnode->parent);
982 group_counter = PURPLE_COUNTING_NODE(bnode->parent->parent);
984 if (PURPLE_BUDDY_IS_ONLINE(buddy)) {
985 purple_counting_node_change_online_count(contact_counter, -1);
986 if (purple_counting_node_get_online_count(contact_counter) == 0)
987 purple_counting_node_change_online_count(group_counter, -1);
989 if (purple_account_is_connected(account)) {
990 purple_counting_node_change_current_size(contact_counter, -1);
991 if (purple_counting_node_get_current_size(contact_counter) == 0)
992 purple_counting_node_change_current_size(group_counter, -1);
994 purple_counting_node_change_total_size(contact_counter, -1);
995 /* the group totalsize will be taken care of by remove_contact below */
997 if (bnode->parent->parent != (PurpleBlistNode*)g) {
998 purple_signal_emit(purple_blist_get_handle(), "buddy-removed-from-group", buddy);
999 purple_serv_move_buddy(buddy, (PurpleGroup *)bnode->parent->parent, g);
1002 if (bnode->next)
1003 bnode->next->prev = bnode->prev;
1004 if (bnode->prev)
1005 bnode->prev->next = bnode->next;
1006 if (bnode->parent->child == bnode)
1007 bnode->parent->child = bnode->next;
1009 if (ops && ops->remove)
1010 ops->remove(purplebuddylist, bnode);
1012 if (bnode->parent->parent != (PurpleBlistNode*)g) {
1013 struct _purple_hbuddy hb;
1014 hb.name = (gchar *)purple_normalize(account,
1015 purple_buddy_get_name(buddy));
1016 hb.account = account;
1017 hb.group = bnode->parent->parent;
1018 g_hash_table_remove(priv->buddies, &hb);
1020 account_buddies = g_hash_table_lookup(buddies_cache, account);
1021 g_hash_table_remove(account_buddies, &hb);
1024 if (!bnode->parent->child) {
1025 purple_blist_remove_contact((PurpleContact*)bnode->parent);
1026 } else {
1027 purple_contact_invalidate_priority_buddy((PurpleContact*)bnode->parent);
1029 if (ops && ops->update)
1030 ops->update(purplebuddylist, bnode->parent);
1034 if (node && PURPLE_IS_BUDDY(node)) {
1035 if (node->next)
1036 node->next->prev = bnode;
1037 bnode->next = node->next;
1038 bnode->prev = node;
1039 bnode->parent = node->parent;
1040 node->next = bnode;
1041 } else {
1042 if (cnode->child)
1043 cnode->child->prev = bnode;
1044 bnode->prev = NULL;
1045 bnode->next = cnode->child;
1046 cnode->child = bnode;
1047 bnode->parent = cnode;
1050 contact_counter = PURPLE_COUNTING_NODE(bnode->parent);
1051 group_counter = PURPLE_COUNTING_NODE(bnode->parent->parent);
1053 if (PURPLE_BUDDY_IS_ONLINE(buddy)) {
1054 purple_counting_node_change_online_count(contact_counter, +1);
1055 if (purple_counting_node_get_online_count(contact_counter) == 1)
1056 purple_counting_node_change_online_count(group_counter, +1);
1058 if (purple_account_is_connected(account)) {
1059 purple_counting_node_change_current_size(contact_counter, +1);
1060 if (purple_counting_node_get_current_size(contact_counter) == 1)
1061 purple_counting_node_change_current_size(group_counter, +1);
1063 purple_counting_node_change_total_size(contact_counter, +1);
1065 hb = g_new(struct _purple_hbuddy, 1);
1066 hb->name = g_strdup(purple_normalize(account, purple_buddy_get_name(buddy)));
1067 hb->account = account;
1068 hb->group = PURPLE_BLIST_NODE(buddy)->parent->parent;
1070 g_hash_table_replace(priv->buddies, hb, buddy);
1072 account_buddies = g_hash_table_lookup(buddies_cache, account);
1074 hb2 = g_new(struct _purple_hbuddy, 1);
1075 hb2->name = g_strdup(hb->name);
1076 hb2->account = account;
1077 hb2->group = ((PurpleBlistNode*)buddy)->parent->parent;
1079 g_hash_table_replace(account_buddies, hb2, buddy);
1081 purple_contact_invalidate_priority_buddy(purple_buddy_get_contact(buddy));
1083 if (ops) {
1084 if (ops->save_node)
1085 ops->save_node((PurpleBlistNode*) buddy);
1086 if (ops->update)
1087 ops->update(purplebuddylist, PURPLE_BLIST_NODE(buddy));
1090 /* Signal that the buddy has been added */
1091 purple_signal_emit(purple_blist_get_handle(), "blist-node-added",
1092 PURPLE_BLIST_NODE(buddy));
1095 void purple_blist_add_contact(PurpleContact *contact, PurpleGroup *group, PurpleBlistNode *node)
1097 PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
1098 PurpleGroup *g;
1099 PurpleBlistNode *gnode, *cnode, *bnode;
1100 PurpleCountingNode *contact_counter, *group_counter;
1101 PurpleBuddyListPrivate *priv = PURPLE_BUDDY_LIST_GET_PRIVATE(purplebuddylist);
1103 g_return_if_fail(PURPLE_IS_CONTACT(contact));
1105 if (PURPLE_BLIST_NODE(contact) == node)
1106 return;
1108 if (node && (PURPLE_IS_CONTACT(node) ||
1109 PURPLE_IS_CHAT(node)))
1110 g = PURPLE_GROUP(node->parent);
1111 else if (group)
1112 g = group;
1113 else
1114 g = purple_blist_get_default_group();
1116 gnode = (PurpleBlistNode*)g;
1117 cnode = (PurpleBlistNode*)contact;
1119 if (cnode->parent) {
1120 if (cnode->parent->child == cnode)
1121 cnode->parent->child = cnode->next;
1122 if (cnode->prev)
1123 cnode->prev->next = cnode->next;
1124 if (cnode->next)
1125 cnode->next->prev = cnode->prev;
1127 if (cnode->parent != gnode) {
1128 bnode = cnode->child;
1129 while (bnode) {
1130 PurpleBlistNode *next_bnode = bnode->next;
1131 PurpleBuddy *b = PURPLE_BUDDY(bnode);
1132 PurpleAccount *account = purple_buddy_get_account(b);
1133 GHashTable *account_buddies;
1135 struct _purple_hbuddy *hb, *hb2;
1137 hb = g_new(struct _purple_hbuddy, 1);
1138 hb->name = g_strdup(purple_normalize(account, purple_buddy_get_name(b)));
1139 hb->account = account;
1140 hb->group = cnode->parent;
1142 g_hash_table_remove(priv->buddies, hb);
1144 account_buddies = g_hash_table_lookup(buddies_cache, account);
1145 g_hash_table_remove(account_buddies, hb);
1147 if (!purple_blist_find_buddy_in_group(account, purple_buddy_get_name(b), g)) {
1148 hb->group = gnode;
1149 g_hash_table_replace(priv->buddies, hb, b);
1151 hb2 = g_new(struct _purple_hbuddy, 1);
1152 hb2->name = g_strdup(hb->name);
1153 hb2->account = account;
1154 hb2->group = gnode;
1156 g_hash_table_replace(account_buddies, hb2, b);
1158 if (purple_account_get_connection(account))
1159 purple_serv_move_buddy(b, (PurpleGroup *)cnode->parent, g);
1160 } else {
1161 gboolean empty_contact = FALSE;
1163 /* this buddy already exists in the group, so we're
1164 * gonna delete it instead */
1165 g_free(hb->name);
1166 g_free(hb);
1167 if (purple_account_get_connection(account))
1168 purple_account_remove_buddy(account, b, PURPLE_GROUP(cnode->parent));
1170 if (!cnode->child->next)
1171 empty_contact = TRUE;
1172 purple_blist_remove_buddy(b);
1174 /* in purple_blist_remove_buddy(), if the last buddy in a
1175 * contact is removed, the contact is cleaned up and
1176 * g_free'd, so we mustn't try to reference bnode->next */
1177 if (empty_contact)
1178 return;
1180 bnode = next_bnode;
1184 contact_counter = PURPLE_COUNTING_NODE(contact);
1185 group_counter = PURPLE_COUNTING_NODE(cnode->parent);
1187 if (purple_counting_node_get_online_count(contact_counter) > 0)
1188 purple_counting_node_change_online_count(group_counter, -1);
1189 if (purple_counting_node_get_current_size(contact_counter) > 0)
1190 purple_counting_node_change_current_size(group_counter, -1);
1191 purple_counting_node_change_total_size(group_counter, -1);
1193 if (ops && ops->remove)
1194 ops->remove(purplebuddylist, cnode);
1196 if (ops && ops->remove_node)
1197 ops->remove_node(cnode);
1200 if (node && (PURPLE_IS_CONTACT(node) ||
1201 PURPLE_IS_CHAT(node))) {
1202 if (node->next)
1203 node->next->prev = cnode;
1204 cnode->next = node->next;
1205 cnode->prev = node;
1206 cnode->parent = node->parent;
1207 node->next = cnode;
1208 } else {
1209 if (gnode->child)
1210 gnode->child->prev = cnode;
1211 cnode->prev = NULL;
1212 cnode->next = gnode->child;
1213 gnode->child = cnode;
1214 cnode->parent = gnode;
1217 contact_counter = PURPLE_COUNTING_NODE(contact);
1218 group_counter = PURPLE_COUNTING_NODE(g);
1220 if (purple_counting_node_get_online_count(contact_counter) > 0)
1221 purple_counting_node_change_online_count(group_counter, +1);
1222 if (purple_counting_node_get_current_size(contact_counter) > 0)
1223 purple_counting_node_change_current_size(group_counter, +1);
1224 purple_counting_node_change_total_size(group_counter, +1);
1226 if (ops && ops->save_node)
1228 if (cnode->child)
1229 ops->save_node(cnode);
1230 for (bnode = cnode->child; bnode; bnode = bnode->next)
1231 ops->save_node(bnode);
1234 if (ops && ops->update)
1236 if (cnode->child)
1237 ops->update(purplebuddylist, cnode);
1239 for (bnode = cnode->child; bnode; bnode = bnode->next)
1240 ops->update(purplebuddylist, bnode);
1244 void purple_blist_add_group(PurpleGroup *group, PurpleBlistNode *node)
1246 PurpleBlistUiOps *ops;
1247 PurpleBlistNode *gnode = (PurpleBlistNode*)group;
1248 gchar* key;
1250 g_return_if_fail(PURPLE_IS_GROUP(group));
1252 ops = purple_blist_get_ui_ops();
1254 /* if we're moving to overtop of ourselves, do nothing */
1255 if (gnode == node) {
1256 if (!purplebuddylist->root)
1257 node = NULL;
1258 else
1259 return;
1262 if (purple_blist_find_group(purple_group_get_name(group))) {
1263 /* This is just being moved */
1265 if (ops && ops->remove)
1266 ops->remove(purplebuddylist, (PurpleBlistNode *)group);
1268 if (gnode == purplebuddylist->root)
1269 purplebuddylist->root = gnode->next;
1270 if (gnode->prev)
1271 gnode->prev->next = gnode->next;
1272 if (gnode->next)
1273 gnode->next->prev = gnode->prev;
1274 } else {
1275 key = purple_blist_fold_name(purple_group_get_name(group));
1276 g_hash_table_insert(groups_cache, key, group);
1279 if (node && PURPLE_IS_GROUP(node)) {
1280 gnode->next = node->next;
1281 gnode->prev = node;
1282 if (node->next)
1283 node->next->prev = gnode;
1284 node->next = gnode;
1285 } else {
1286 if (purplebuddylist->root)
1287 purplebuddylist->root->prev = gnode;
1288 gnode->next = purplebuddylist->root;
1289 gnode->prev = NULL;
1290 purplebuddylist->root = gnode;
1293 if (ops && ops->save_node) {
1294 ops->save_node(gnode);
1295 for (node = gnode->child; node; node = node->next)
1296 ops->save_node(node);
1299 if (ops && ops->update) {
1300 ops->update(purplebuddylist, gnode);
1301 for (node = gnode->child; node; node = node->next)
1302 ops->update(purplebuddylist, node);
1305 purple_signal_emit(purple_blist_get_handle(), "blist-node-added",
1306 gnode);
1309 void purple_blist_remove_contact(PurpleContact *contact)
1311 PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
1312 PurpleBlistNode *node, *gnode;
1313 PurpleGroup *group;
1315 g_return_if_fail(PURPLE_IS_CONTACT(contact));
1317 node = (PurpleBlistNode *)contact;
1318 gnode = node->parent;
1319 group = PURPLE_GROUP(gnode);
1321 if (node->child) {
1323 * If this contact has children then remove them. When the last
1324 * buddy is removed from the contact, the contact is automatically
1325 * deleted.
1327 while (node->child->next) {
1328 purple_blist_remove_buddy((PurpleBuddy*)node->child);
1331 * Remove the last buddy and trigger the deletion of the contact.
1332 * It would probably be cleaner if contact-deletion was done after
1333 * a timeout? Or if it had to be done manually, like below?
1335 purple_blist_remove_buddy((PurpleBuddy*)node->child);
1336 } else {
1337 /* Remove the node from its parent */
1338 if (gnode->child == node)
1339 gnode->child = node->next;
1340 if (node->prev)
1341 node->prev->next = node->next;
1342 if (node->next)
1343 node->next->prev = node->prev;
1344 purple_counting_node_change_total_size(PURPLE_COUNTING_NODE(group), -1);
1346 /* Update the UI */
1347 if (ops && ops->remove)
1348 ops->remove(purplebuddylist, node);
1350 if (ops && ops->remove_node)
1351 ops->remove_node(node);
1353 purple_signal_emit(purple_blist_get_handle(), "blist-node-removed",
1354 PURPLE_BLIST_NODE(contact));
1356 /* Delete the node */
1357 g_object_unref(contact);
1361 void purple_blist_remove_buddy(PurpleBuddy *buddy)
1363 PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
1364 PurpleBlistNode *node, *cnode, *gnode;
1365 PurpleCountingNode *contact_counter, *group_counter;
1366 PurpleContact *contact;
1367 PurpleGroup *group;
1368 struct _purple_hbuddy hb;
1369 GHashTable *account_buddies;
1370 PurpleAccount *account;
1372 g_return_if_fail(PURPLE_IS_BUDDY(buddy));
1374 account = purple_buddy_get_account(buddy);
1375 node = PURPLE_BLIST_NODE(buddy);
1376 cnode = node->parent;
1377 gnode = (cnode != NULL) ? cnode->parent : NULL;
1378 contact = (PurpleContact *)cnode;
1379 group = (PurpleGroup *)gnode;
1381 /* Remove the node from its parent */
1382 if (node->prev)
1383 node->prev->next = node->next;
1384 if (node->next)
1385 node->next->prev = node->prev;
1386 if ((cnode != NULL) && (cnode->child == node))
1387 cnode->child = node->next;
1389 /* Adjust size counts */
1390 if (contact != NULL) {
1391 contact_counter = PURPLE_COUNTING_NODE(contact);
1392 group_counter = PURPLE_COUNTING_NODE(group);
1394 if (PURPLE_BUDDY_IS_ONLINE(buddy)) {
1395 purple_counting_node_change_online_count(contact_counter, -1);
1396 if (purple_counting_node_get_online_count(contact_counter) == 0)
1397 purple_counting_node_change_online_count(group_counter, -1);
1399 if (purple_account_is_connected(account)) {
1400 purple_counting_node_change_current_size(contact_counter, -1);
1401 if (purple_counting_node_get_current_size(contact_counter) == 0)
1402 purple_counting_node_change_current_size(group_counter, -1);
1404 purple_counting_node_change_total_size(contact_counter, -1);
1406 /* Re-sort the contact */
1407 if (cnode->child && purple_contact_get_priority_buddy(contact) == buddy) {
1408 purple_contact_invalidate_priority_buddy(contact);
1410 if (ops && ops->update)
1411 ops->update(purplebuddylist, cnode);
1415 /* Remove this buddy from the buddies hash table */
1416 hb.name = (gchar *)purple_normalize(account, purple_buddy_get_name(buddy));
1417 hb.account = account;
1418 hb.group = gnode;
1419 g_hash_table_remove(PURPLE_BUDDY_LIST_GET_PRIVATE(purplebuddylist)->buddies, &hb);
1421 account_buddies = g_hash_table_lookup(buddies_cache, account);
1422 g_hash_table_remove(account_buddies, &hb);
1424 /* Update the UI */
1425 if (ops && ops->remove)
1426 ops->remove(purplebuddylist, node);
1428 if (ops && ops->remove_node)
1429 ops->remove_node(node);
1431 /* Remove this buddy's pounces */
1432 purple_pounce_destroy_all_by_buddy(buddy);
1434 /* Signal that the buddy has been removed before freeing the memory for it */
1435 purple_signal_emit(purple_blist_get_handle(), "blist-node-removed",
1436 PURPLE_BLIST_NODE(buddy));
1438 g_object_unref(buddy);
1440 /* If the contact is empty then remove it */
1441 if ((contact != NULL) && !cnode->child)
1442 purple_blist_remove_contact(contact);
1445 void purple_blist_remove_chat(PurpleChat *chat)
1447 PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
1448 PurpleBlistNode *node, *gnode;
1449 PurpleGroup *group;
1450 PurpleCountingNode *group_counter;
1452 g_return_if_fail(PURPLE_IS_CHAT(chat));
1454 node = (PurpleBlistNode *)chat;
1455 gnode = node->parent;
1456 group = (PurpleGroup *)gnode;
1458 if (gnode != NULL)
1460 /* Remove the node from its parent */
1461 if (gnode->child == node)
1462 gnode->child = node->next;
1463 if (node->prev)
1464 node->prev->next = node->next;
1465 if (node->next)
1466 node->next->prev = node->prev;
1468 /* Adjust size counts */
1469 group_counter = PURPLE_COUNTING_NODE(group);
1470 if (purple_account_is_connected(purple_chat_get_account(chat))) {
1471 purple_counting_node_change_online_count(group_counter, -1);
1472 purple_counting_node_change_current_size(group_counter, -1);
1474 purple_counting_node_change_total_size(group_counter, -1);
1477 /* Update the UI */
1478 if (ops && ops->remove)
1479 ops->remove(purplebuddylist, node);
1481 if (ops && ops->remove_node)
1482 ops->remove_node(node);
1484 purple_signal_emit(purple_blist_get_handle(), "blist-node-removed",
1485 PURPLE_BLIST_NODE(chat));
1487 /* Delete the node */
1488 g_object_unref(chat);
1491 void purple_blist_remove_group(PurpleGroup *group)
1493 PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
1494 PurpleBlistNode *node;
1495 GList *l;
1496 gchar* key;
1498 g_return_if_fail(PURPLE_IS_GROUP(group));
1500 if (group == purple_blist_get_default_group())
1501 purple_debug_warning("buddylist", "cannot remove default group");
1503 node = (PurpleBlistNode *)group;
1505 /* Make sure the group is empty */
1506 if (node->child)
1507 return;
1509 /* Remove the node from its parent */
1510 if (purplebuddylist->root == node)
1511 purplebuddylist->root = node->next;
1512 if (node->prev)
1513 node->prev->next = node->next;
1514 if (node->next)
1515 node->next->prev = node->prev;
1517 key = purple_blist_fold_name(purple_group_get_name(group));
1518 g_hash_table_remove(groups_cache, key);
1519 g_free(key);
1521 /* Update the UI */
1522 if (ops && ops->remove)
1523 ops->remove(purplebuddylist, node);
1525 if (ops && ops->remove_node)
1526 ops->remove_node(node);
1528 purple_signal_emit(purple_blist_get_handle(), "blist-node-removed",
1529 PURPLE_BLIST_NODE(group));
1531 /* Remove the group from all accounts that are online */
1532 for (l = purple_connections_get_all(); l != NULL; l = l->next)
1534 PurpleConnection *gc = (PurpleConnection *)l->data;
1536 if (purple_connection_get_state(gc) == PURPLE_CONNECTION_CONNECTED)
1537 purple_account_remove_group(purple_connection_get_account(gc), group);
1540 /* Delete the node */
1541 g_object_unref(group);
1544 PurpleBuddy *purple_blist_find_buddy(PurpleAccount *account, const char *name)
1546 PurpleBuddy *buddy;
1547 struct _purple_hbuddy hb;
1548 PurpleBlistNode *group;
1550 g_return_val_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist), NULL);
1551 g_return_val_if_fail(PURPLE_IS_ACCOUNT(account), NULL);
1552 g_return_val_if_fail((name != NULL) && (*name != '\0'), NULL);
1554 hb.account = account;
1555 hb.name = (gchar *)purple_normalize(account, name);
1557 for (group = purplebuddylist->root; group; group = group->next) {
1558 if (!group->child)
1559 continue;
1561 hb.group = group;
1562 if ((buddy = g_hash_table_lookup(PURPLE_BUDDY_LIST_GET_PRIVATE(purplebuddylist)->buddies,
1563 &hb))) {
1564 return buddy;
1568 return NULL;
1571 PurpleBuddy *purple_blist_find_buddy_in_group(PurpleAccount *account, const char *name,
1572 PurpleGroup *group)
1574 struct _purple_hbuddy hb;
1576 g_return_val_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist), NULL);
1577 g_return_val_if_fail(PURPLE_IS_ACCOUNT(account), NULL);
1578 g_return_val_if_fail((name != NULL) && (*name != '\0'), NULL);
1580 hb.name = (gchar *)purple_normalize(account, name);
1581 hb.account = account;
1582 hb.group = (PurpleBlistNode*)group;
1584 return g_hash_table_lookup(PURPLE_BUDDY_LIST_GET_PRIVATE(purplebuddylist)->buddies,
1585 &hb);
1588 static void find_acct_buddies(gpointer key, gpointer value, gpointer data)
1590 PurpleBuddy *buddy = value;
1591 GSList **list = data;
1593 *list = g_slist_prepend(*list, buddy);
1596 GSList *purple_blist_find_buddies(PurpleAccount *account, const char *name)
1598 PurpleBuddy *buddy;
1599 PurpleBlistNode *node;
1600 GSList *ret = NULL;
1602 g_return_val_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist), NULL);
1603 g_return_val_if_fail(PURPLE_IS_ACCOUNT(account), NULL);
1605 if ((name != NULL) && (*name != '\0')) {
1606 struct _purple_hbuddy hb;
1608 hb.name = (gchar *)purple_normalize(account, name);
1609 hb.account = account;
1611 for (node = purplebuddylist->root; node != NULL; node = node->next) {
1612 if (!node->child)
1613 continue;
1615 hb.group = node;
1616 if ((buddy = g_hash_table_lookup(PURPLE_BUDDY_LIST_GET_PRIVATE(purplebuddylist)->buddies,
1617 &hb)) != NULL)
1618 ret = g_slist_prepend(ret, buddy);
1620 } else {
1621 GSList *list = NULL;
1622 GHashTable *buddies = g_hash_table_lookup(buddies_cache, account);
1623 g_hash_table_foreach(buddies, find_acct_buddies, &list);
1624 ret = list;
1627 return ret;
1630 PurpleGroup *purple_blist_find_group(const char *name)
1632 gchar* key;
1633 PurpleGroup *group;
1635 g_return_val_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist), NULL);
1637 if (name == NULL || name[0] == '\0')
1638 name = PURPLE_BLIST_DEFAULT_GROUP_NAME;
1639 if (g_strcmp0(name, "Buddies") == 0)
1640 name = PURPLE_BLIST_DEFAULT_GROUP_NAME;
1641 if (g_strcmp0(name, localized_default_group_name) == 0)
1642 name = PURPLE_BLIST_DEFAULT_GROUP_NAME;
1644 key = purple_blist_fold_name(name);
1645 group = g_hash_table_lookup(groups_cache, key);
1646 g_free(key);
1648 return group;
1651 PurpleGroup *
1652 purple_blist_get_default_group(void)
1654 PurpleGroup *group;
1656 group = purple_blist_find_group(PURPLE_BLIST_DEFAULT_GROUP_NAME);
1657 if (!group) {
1658 group = purple_group_new(PURPLE_BLIST_DEFAULT_GROUP_NAME);
1659 purple_blist_add_group(group, NULL);
1662 return group;
1665 PurpleChat *
1666 purple_blist_find_chat(PurpleAccount *account, const char *name)
1668 char *chat_name;
1669 PurpleChat *chat;
1670 PurpleProtocol *protocol = NULL;
1671 PurpleProtocolChatEntry *pce;
1672 PurpleBlistNode *node, *group;
1673 GList *parts;
1674 char *normname;
1676 g_return_val_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist), NULL);
1677 g_return_val_if_fail((name != NULL) && (*name != '\0'), NULL);
1679 if (!purple_account_is_connected(account))
1680 return NULL;
1682 protocol = purple_protocols_find(purple_account_get_protocol_id(account));
1684 if (PURPLE_PROTOCOL_IMPLEMENTS(protocol, CLIENT_IFACE, find_blist_chat))
1685 return purple_protocol_client_iface_find_blist_chat(protocol, account, name);
1687 normname = g_strdup(purple_normalize(account, name));
1688 for (group = purplebuddylist->root; group != NULL; group = group->next) {
1689 for (node = group->child; node != NULL; node = node->next) {
1690 if (PURPLE_IS_CHAT(node)) {
1692 chat = (PurpleChat*)node;
1694 if (account != purple_chat_get_account(chat))
1695 continue;
1697 parts = purple_protocol_chat_iface_info(protocol,
1698 purple_account_get_connection(purple_chat_get_account(chat)));
1700 pce = parts->data;
1701 chat_name = g_hash_table_lookup(purple_chat_get_components(chat),
1702 pce->identifier);
1703 g_list_foreach(parts, (GFunc)g_free, NULL);
1704 g_list_free(parts);
1706 if (purple_chat_get_account(chat) == account && chat_name != NULL &&
1707 normname != NULL && !strcmp(purple_normalize(account, chat_name), normname)) {
1708 g_free(normname);
1709 return chat;
1715 g_free(normname);
1716 return NULL;
1719 void purple_blist_add_account(PurpleAccount *account)
1721 PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
1722 PurpleBlistNode *gnode, *cnode, *bnode;
1723 PurpleCountingNode *contact_counter, *group_counter;
1725 g_return_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist));
1727 if (!ops || !ops->update)
1728 return;
1730 for (gnode = purplebuddylist->root; gnode; gnode = gnode->next) {
1731 if (!PURPLE_IS_GROUP(gnode))
1732 continue;
1733 for (cnode = gnode->child; cnode; cnode = cnode->next) {
1734 if (PURPLE_IS_CONTACT(cnode)) {
1735 gboolean recompute = FALSE;
1736 for (bnode = cnode->child; bnode; bnode = bnode->next) {
1737 if (PURPLE_IS_BUDDY(bnode) &&
1738 purple_buddy_get_account(PURPLE_BUDDY(bnode)) == account) {
1739 recompute = TRUE;
1740 contact_counter = PURPLE_COUNTING_NODE(cnode);
1741 group_counter = PURPLE_COUNTING_NODE(gnode);
1742 purple_counting_node_change_current_size(contact_counter, +1);
1743 if (purple_counting_node_get_current_size(contact_counter) == 1)
1744 purple_counting_node_change_current_size(group_counter, +1);
1745 ops->update(purplebuddylist, bnode);
1748 if (recompute ||
1749 purple_blist_node_get_bool(cnode, "show_offline")) {
1750 purple_contact_invalidate_priority_buddy((PurpleContact*)cnode);
1751 ops->update(purplebuddylist, cnode);
1753 } else if (PURPLE_IS_CHAT(cnode) &&
1754 purple_chat_get_account(PURPLE_CHAT(cnode)) == account) {
1755 group_counter = PURPLE_COUNTING_NODE(gnode);
1756 purple_counting_node_change_online_count(group_counter, +1);
1757 purple_counting_node_change_current_size(group_counter, +1);
1758 ops->update(purplebuddylist, cnode);
1761 ops->update(purplebuddylist, gnode);
1765 void purple_blist_remove_account(PurpleAccount *account)
1767 PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
1768 PurpleBlistNode *gnode, *cnode, *bnode;
1769 PurpleCountingNode *contact_counter, *group_counter;
1770 PurpleBuddy *buddy;
1771 PurpleChat *chat;
1772 PurpleContact *contact;
1773 PurpleGroup *group;
1774 GList *list = NULL, *iter = NULL;
1776 g_return_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist));
1778 for (gnode = purplebuddylist->root; gnode; gnode = gnode->next) {
1779 if (!PURPLE_IS_GROUP(gnode))
1780 continue;
1782 group = (PurpleGroup *)gnode;
1784 for (cnode = gnode->child; cnode; cnode = cnode->next) {
1785 if (PURPLE_IS_CONTACT(cnode)) {
1786 gboolean recompute = FALSE;
1787 contact = (PurpleContact *)cnode;
1789 for (bnode = cnode->child; bnode; bnode = bnode->next) {
1790 if (!PURPLE_IS_BUDDY(bnode))
1791 continue;
1793 buddy = (PurpleBuddy *)bnode;
1794 if (account == purple_buddy_get_account(buddy)) {
1795 PurplePresence *presence;
1797 presence = purple_buddy_get_presence(buddy);
1798 contact_counter = PURPLE_COUNTING_NODE(contact);
1799 group_counter = PURPLE_COUNTING_NODE(group);
1801 if(purple_presence_is_online(presence)) {
1802 purple_counting_node_change_online_count(contact_counter, -1);
1803 if (purple_counting_node_get_online_count(contact_counter) == 0)
1804 purple_counting_node_change_online_count(group_counter, -1);
1806 purple_blist_node_set_int(PURPLE_BLIST_NODE(buddy),
1807 "last_seen", time(NULL));
1810 purple_counting_node_change_current_size(contact_counter, -1);
1811 if (purple_counting_node_get_current_size(contact_counter) == 0)
1812 purple_counting_node_change_current_size(group_counter, -1);
1814 if (!g_list_find(list, presence))
1815 list = g_list_prepend(list, presence);
1817 if (purple_contact_get_priority_buddy(contact) == buddy)
1818 purple_contact_invalidate_priority_buddy(contact);
1819 else
1820 recompute = TRUE;
1822 if (ops && ops->remove) {
1823 ops->remove(purplebuddylist, bnode);
1827 if (recompute) {
1828 purple_contact_invalidate_priority_buddy(contact);
1830 if (ops && ops->update)
1831 ops->update(purplebuddylist, cnode);
1833 } else if (PURPLE_IS_CHAT(cnode)) {
1834 chat = PURPLE_CHAT(cnode);
1836 if(purple_chat_get_account(chat) == account) {
1837 group_counter = PURPLE_COUNTING_NODE(group);
1838 purple_counting_node_change_current_size(group_counter, -1);
1839 purple_counting_node_change_online_count(group_counter, -1);
1841 if (ops && ops->remove)
1842 ops->remove(purplebuddylist, cnode);
1848 for (iter = list; iter; iter = iter->next)
1850 purple_presence_set_status_active(iter->data, "offline", TRUE);
1852 g_list_free(list);
1855 void
1856 purple_blist_request_add_buddy(PurpleAccount *account, const char *username,
1857 const char *group, const char *alias)
1859 PurpleBlistUiOps *ui_ops;
1861 ui_ops = purple_blist_get_ui_ops();
1863 if (ui_ops != NULL && ui_ops->request_add_buddy != NULL)
1864 ui_ops->request_add_buddy(account, username, group, alias);
1867 void
1868 purple_blist_request_add_chat(PurpleAccount *account, PurpleGroup *group,
1869 const char *alias, const char *name)
1871 PurpleBlistUiOps *ui_ops;
1873 ui_ops = purple_blist_get_ui_ops();
1875 if (ui_ops != NULL && ui_ops->request_add_chat != NULL)
1876 ui_ops->request_add_chat(account, group, alias, name);
1879 void
1880 purple_blist_request_add_group(void)
1882 PurpleBlistUiOps *ui_ops;
1884 ui_ops = purple_blist_get_ui_ops();
1886 if (ui_ops != NULL && ui_ops->request_add_group != NULL)
1887 ui_ops->request_add_group();
1890 void
1891 purple_blist_set_ui_ops(PurpleBlistUiOps *ops)
1893 gboolean overrode = FALSE;
1894 blist_ui_ops = ops;
1896 if (!ops)
1897 return;
1899 if (!ops->save_node) {
1900 ops->save_node = purple_blist_save_node;
1901 overrode = TRUE;
1903 if (!ops->remove_node) {
1904 ops->remove_node = purple_blist_save_node;
1905 overrode = TRUE;
1907 if (!ops->save_account) {
1908 ops->save_account = purple_blist_save_account;
1909 overrode = TRUE;
1912 if (overrode && (ops->save_node != purple_blist_save_node ||
1913 ops->remove_node != purple_blist_save_node ||
1914 ops->save_account != purple_blist_save_account)) {
1915 purple_debug_warning("buddylist", "Only some of the blist saving UI ops "
1916 "were overridden. This probably is not what you want!\n");
1920 PurpleBlistUiOps *
1921 purple_blist_get_ui_ops(void)
1923 return blist_ui_ops;
1926 const gchar *
1927 _purple_blist_get_localized_default_group_name(void)
1929 return localized_default_group_name;
1932 void *
1933 purple_blist_get_handle(void)
1935 static int handle;
1937 return &handle;
1940 void
1941 purple_blist_init(void)
1943 void *handle = purple_blist_get_handle();
1945 purple_signal_register(handle, "buddy-status-changed",
1946 purple_marshal_VOID__POINTER_POINTER_POINTER,
1947 G_TYPE_NONE, 3, PURPLE_TYPE_BUDDY, PURPLE_TYPE_STATUS,
1948 PURPLE_TYPE_STATUS);
1949 purple_signal_register(handle, "buddy-privacy-changed",
1950 purple_marshal_VOID__POINTER, G_TYPE_NONE,
1951 1, PURPLE_TYPE_BUDDY);
1953 purple_signal_register(handle, "buddy-idle-changed",
1954 purple_marshal_VOID__POINTER_INT_INT, G_TYPE_NONE,
1955 3, PURPLE_TYPE_BUDDY, G_TYPE_INT, G_TYPE_INT);
1957 purple_signal_register(handle, "buddy-signed-on",
1958 purple_marshal_VOID__POINTER, G_TYPE_NONE, 1,
1959 PURPLE_TYPE_BUDDY);
1961 purple_signal_register(handle, "buddy-signed-off",
1962 purple_marshal_VOID__POINTER, G_TYPE_NONE, 1,
1963 PURPLE_TYPE_BUDDY);
1965 purple_signal_register(handle, "buddy-got-login-time",
1966 purple_marshal_VOID__POINTER, G_TYPE_NONE, 1,
1967 PURPLE_TYPE_BUDDY);
1969 purple_signal_register(handle, "blist-node-added",
1970 purple_marshal_VOID__POINTER, G_TYPE_NONE, 1,
1971 PURPLE_TYPE_BLIST_NODE);
1973 purple_signal_register(handle, "blist-node-removed",
1974 purple_marshal_VOID__POINTER, G_TYPE_NONE, 1,
1975 PURPLE_TYPE_BLIST_NODE);
1977 purple_signal_register(handle, "buddy-removed-from-group",
1978 purple_marshal_VOID__POINTER, G_TYPE_NONE, 1,
1979 PURPLE_TYPE_BUDDY);
1981 purple_signal_register(handle, "buddy-icon-changed",
1982 purple_marshal_VOID__POINTER, G_TYPE_NONE, 1,
1983 PURPLE_TYPE_BUDDY);
1985 purple_signal_register(handle, "update-idle", purple_marshal_VOID,
1986 G_TYPE_NONE, 0);
1988 purple_signal_register(handle, "blist-node-extended-menu",
1989 purple_marshal_VOID__POINTER_POINTER, G_TYPE_NONE, 2,
1990 PURPLE_TYPE_BLIST_NODE,
1991 G_TYPE_POINTER); /* (GList **) */
1993 purple_signal_register(handle, "blist-node-aliased",
1994 purple_marshal_VOID__POINTER_POINTER, G_TYPE_NONE, 2,
1995 PURPLE_TYPE_BLIST_NODE, G_TYPE_STRING);
1997 purple_signal_register(handle, "buddy-caps-changed",
1998 purple_marshal_VOID__POINTER_INT_INT, G_TYPE_NONE,
1999 3, PURPLE_TYPE_BUDDY, G_TYPE_INT, G_TYPE_INT);
2001 purple_signal_connect(purple_accounts_get_handle(), "account-created",
2002 handle,
2003 PURPLE_CALLBACK(purple_blist_buddies_cache_add_account),
2004 NULL);
2006 purple_signal_connect(purple_accounts_get_handle(), "account-destroying",
2007 handle,
2008 PURPLE_CALLBACK(purple_blist_buddies_cache_remove_account),
2009 NULL);
2012 static void
2013 blist_node_destroy(PurpleBlistNode *node)
2015 PurpleBlistUiOps *ui_ops;
2016 PurpleBlistNode *child, *next_child;
2018 ui_ops = purple_blist_get_ui_ops();
2019 child = node->child;
2020 while (child) {
2021 next_child = child->next;
2022 blist_node_destroy(child);
2023 child = next_child;
2026 /* Allow the UI to free data */
2027 node->parent = NULL;
2028 node->child = NULL;
2029 node->next = NULL;
2030 node->prev = NULL;
2031 if (ui_ops && ui_ops->remove)
2032 ui_ops->remove(purplebuddylist, node);
2034 g_object_unref(node);
2037 void
2038 purple_blist_uninit(void)
2040 PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
2041 PurpleBlistNode *node, *next_node;
2043 /* This happens if we quit before purple_set_blist is called. */
2044 if (purplebuddylist == NULL)
2045 return;
2047 if (save_timer != 0) {
2048 purple_timeout_remove(save_timer);
2049 save_timer = 0;
2050 purple_blist_sync();
2053 purple_debug(PURPLE_DEBUG_INFO, "buddylist", "Destroying\n");
2055 if (ops && ops->destroy)
2056 ops->destroy(purplebuddylist);
2058 node = purple_blist_get_root();
2059 while (node) {
2060 next_node = node->next;
2061 blist_node_destroy(node);
2062 node = next_node;
2064 purplebuddylist->root = NULL;
2066 g_hash_table_destroy(buddies_cache);
2067 g_hash_table_destroy(groups_cache);
2069 buddies_cache = NULL;
2070 groups_cache = NULL;
2072 g_object_unref(purplebuddylist);
2073 purplebuddylist = NULL;
2075 g_free(localized_default_group_name);
2076 localized_default_group_name = NULL;
2078 purple_signals_disconnect_by_handle(purple_blist_get_handle());
2079 purple_signals_unregister_by_instance(purple_blist_get_handle());
2082 /**************************************************************************
2083 * GBoxed code
2084 **************************************************************************/
2085 static PurpleBlistUiOps *
2086 purple_blist_ui_ops_copy(PurpleBlistUiOps *ops)
2088 PurpleBlistUiOps *ops_new;
2090 g_return_val_if_fail(ops != NULL, NULL);
2092 ops_new = g_new(PurpleBlistUiOps, 1);
2093 *ops_new = *ops;
2095 return ops_new;
2098 GType
2099 purple_blist_ui_ops_get_type(void)
2101 static GType type = 0;
2103 if (type == 0) {
2104 type = g_boxed_type_register_static("PurpleBlistUiOps",
2105 (GBoxedCopyFunc)purple_blist_ui_ops_copy,
2106 (GBoxedFreeFunc)g_free);
2109 return type;
2112 /**************************************************************************
2113 * GObject code
2114 **************************************************************************/
2116 /* GObject initialization function */
2117 static void
2118 purple_buddy_list_init(GTypeInstance *instance, gpointer klass)
2120 PurpleBuddyList *blist = PURPLE_BUDDY_LIST(instance);
2122 PURPLE_DBUS_REGISTER_POINTER(blist, PurpleBuddyList);
2124 PURPLE_BUDDY_LIST_GET_PRIVATE(blist)->buddies = g_hash_table_new_full(
2125 (GHashFunc)_purple_blist_hbuddy_hash,
2126 (GEqualFunc)_purple_blist_hbuddy_equal,
2127 (GDestroyNotify)_purple_blist_hbuddy_free_key, NULL);
2130 /* GObject finalize function */
2131 static void
2132 purple_buddy_list_finalize(GObject *object)
2134 g_hash_table_destroy(PURPLE_BUDDY_LIST_GET_PRIVATE(object)->buddies);
2136 PURPLE_DBUS_UNREGISTER_POINTER(object);
2138 G_OBJECT_CLASS(parent_class)->finalize(object);
2141 /* Class initializer function */
2142 static void purple_buddy_list_class_init(PurpleBuddyListClass *klass)
2144 GObjectClass *obj_class = G_OBJECT_CLASS(klass);
2146 parent_class = g_type_class_peek_parent(klass);
2148 obj_class->finalize = purple_buddy_list_finalize;
2150 g_type_class_add_private(klass, sizeof(PurpleBuddyListPrivate));
2153 GType
2154 purple_buddy_list_get_type(void)
2156 static GType type = 0;
2158 if(type == 0) {
2159 static const GTypeInfo info = {
2160 sizeof(PurpleBuddyListClass),
2161 NULL,
2162 NULL,
2163 (GClassInitFunc)purple_buddy_list_class_init,
2164 NULL,
2165 NULL,
2166 sizeof(PurpleBuddyList),
2168 (GInstanceInitFunc)purple_buddy_list_init,
2169 NULL,
2172 type = g_type_register_static(G_TYPE_OBJECT,
2173 "PurpleBuddyList", &info, 0);
2176 return type;