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
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
24 #include "buddylist.h"
25 #include "conversation.h"
26 #include "dbus-maybe.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. */
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 *********************************************************************/
73 purple_blist_fold_name(const gchar
*name
)
80 tmp
= g_utf8_casefold(name
, -1);
81 res
= g_utf8_collate_key(tmp
, -1);
87 static PurpleBlistNode
*purple_blist_get_last_sibling(PurpleBlistNode
*node
)
89 PurpleBlistNode
*n
= node
;
97 PurpleBlistNode
*_purple_blist_get_last_child(PurpleBlistNode
*node
)
101 return purple_blist_get_last_sibling(node
->child
);
104 struct _list_account_buddies
{
106 PurpleAccount
*account
;
109 struct _purple_hbuddy
{
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
)
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
);
145 purple_blist_buddies_cache_remove_account(const PurpleAccount
*account
)
147 g_hash_table_remove(buddies_cache
, account
);
150 /*********************************************************************
152 *********************************************************************/
155 value_to_xmlnode(gpointer key
, gpointer hvalue
, gpointer user_data
)
159 PurpleXmlNode
*node
, *child
;
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);
188 chat_component_to_xmlnode(gpointer key
, gpointer value
, gpointer user_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);
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
);
232 static PurpleXmlNode
*
233 contact_to_xmlnode(PurpleContact
*contact
)
235 PurpleXmlNode
*node
, *child
;
236 PurpleBlistNode
*bnode
;
239 node
= purple_xmlnode_new("contact");
240 g_object_get(contact
, "alias", &alias
, NULL
);
244 purple_xmlnode_set_attrib(node
, "alias", alias
);
248 for (bnode
= PURPLE_BLIST_NODE(contact
)->child
; bnode
!= NULL
; bnode
= bnode
->next
)
250 if (purple_blist_node_is_transient(bnode
))
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
);
267 static PurpleXmlNode
*
268 chat_to_xmlnode(PurpleChat
*chat
)
270 PurpleXmlNode
*node
, *child
;
271 PurpleAccount
*account
= purple_chat_get_account(chat
);
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
));
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
);
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
));
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
))
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
);
332 static PurpleXmlNode
*
333 accountprivacy_to_xmlnode(PurpleAccount
*account
)
335 PurpleXmlNode
*node
, *child
;
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);
360 static PurpleXmlNode
*
361 blist_to_xmlnode(void)
363 PurpleXmlNode
*node
, *child
, *grandchild
;
364 PurpleBlistNode
*gnode
;
366 const gchar
*localized_default
;
368 node
= purple_xmlnode_new("purple");
369 purple_xmlnode_set_attrib(node
, "version", "1.0");
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
))
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
);
405 purple_blist_sync(void)
412 purple_debug_error("buddylist", "Attempted to save buddy list before it "
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);
421 purple_xmlnode_free(node
);
425 save_cb(gpointer data
)
433 _purple_blist_schedule_save()
436 save_timer
= purple_timeout_add_seconds(5, save_cb
, NULL
);
440 purple_blist_save_account(PurpleAccount
*account
)
443 _purple_blist_schedule_save();
445 if (account
!= NULL
) {
446 /* Save the buddies and privacy data for this account */
448 /* Save all buddies and privacy data */
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 *********************************************************************/
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
);
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
));
493 parse_buddy(PurpleGroup
*group
, PurpleContact
*contact
, PurpleXmlNode
*bnode
)
495 PurpleAccount
*account
;
497 char *name
= NULL
, *alias
= NULL
;
498 const char *acct_name
, *proto
;
501 acct_name
= purple_xmlnode_get_attrib(bnode
, "account");
502 proto
= purple_xmlnode_get_attrib(bnode
, "proto");
504 if (!acct_name
|| !proto
)
507 account
= purple_accounts_find(acct_name
, proto
);
512 if ((x
= purple_xmlnode_get_child(bnode
, "name")))
513 name
= purple_xmlnode_get_data(x
);
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
);
534 parse_contact(PurpleGroup
*group
, PurpleXmlNode
*cnode
)
536 PurpleContact
*contact
= purple_contact_new();
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
)
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
);
562 parse_chat(PurpleGroup
*group
, PurpleXmlNode
*cnode
)
565 PurpleAccount
*account
;
566 const char *acct_name
, *proto
;
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
)
577 account
= purple_accounts_find(acct_name
, proto
);
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
)) {
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
);
608 parse_group(PurpleXmlNode
*groupnode
)
610 const char *name
= purple_xmlnode_get_attrib(groupnode
, "name");
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
)
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
);
634 PurpleXmlNode
*purple
, *blist
, *privacy
;
638 purple
= purple_util_read_xml_from_file("blist.xml", _("buddy list"));
643 blist
= purple_xmlnode_get_child(purple
, "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
);
656 g_free(localized_default_group_name
);
657 localized_default_group_name
= NULL
;
660 privacy
= purple_xmlnode_get_child(purple
, "privacy");
662 PurpleXmlNode
*anode
;
663 for (anode
= privacy
->child
; anode
; anode
= anode
->next
) {
665 PurpleAccount
*account
;
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
)
676 account
= purple_accounts_find(acct_name
, proto
);
682 purple_account_set_privacy_type(account
, (imode
!= 0 ? imode
: PURPLE_ACCOUNT_PRIVACY_ALLOW_ALL
));
684 for (x
= anode
->child
; x
; x
= x
->next
) {
686 if (x
->type
!= PURPLE_XMLNODE_TYPE_TAG
)
689 if (purple_strequal(x
->name
, "permit")) {
690 name
= purple_xmlnode_get_data(x
);
691 purple_account_privacy_permit_add(account
, name
, TRUE
);
693 } else if (purple_strequal(x
->name
, "block")) {
694 name
= purple_xmlnode_get_data(x
);
695 purple_account_privacy_deny_add(account
, name
, TRUE
);
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 *****************************************************************************/
713 purple_blist_boot(void)
715 PurpleBlistUiOps
*ui_ops
;
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
;
742 purple_blist_get_buddy_list()
744 return purplebuddylist
;
748 purple_blist_get_root()
750 return purplebuddylist
? purplebuddylist
->root
: NULL
;
754 append_buddy(gpointer key
, gpointer value
, gpointer user_data
)
756 GSList
**list
= user_data
;
757 *list
= g_slist_prepend(*list
, value
);
761 purple_blist_get_buddies()
763 GSList
*buddies
= NULL
;
765 if (!purplebuddylist
)
768 g_hash_table_foreach(PURPLE_BUDDY_LIST_GET_PRIVATE(purplebuddylist
)->buddies
,
769 append_buddy
, &buddies
);
774 purple_blist_get_ui_data()
776 return purplebuddylist
->ui_data
;
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
;
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
)
838 key
= purple_blist_fold_name(purple_group_get_name(group
));
839 g_hash_table_remove(groups_cache
, 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
));
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
));
864 group
= PURPLE_GROUP(node
->parent
);
867 /* if we're moving to overtop of ourselves, do nothing */
872 /* This chat was already in the list and is
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);
882 cnode
->next
->prev
= 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
892 if (ops
&& ops
->new_node
)
893 ops
->new_node(cnode
);
898 node
->next
->prev
= cnode
;
899 cnode
->next
= node
->next
;
901 cnode
->parent
= node
->parent
;
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);
910 if (((PurpleBlistNode
*)group
)->child
)
911 ((PurpleBlistNode
*)group
)->child
->prev
= cnode
;
912 cnode
->next
= ((PurpleBlistNode
*)group
)->child
;
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);
926 ops
->save_node(cnode
);
928 ops
->update(purplebuddylist
, PURPLE_BLIST_NODE(cnode
));
931 purple_signal_emit(purple_blist_get_handle(), "blist-node-added",
935 void purple_blist_add_buddy(PurpleBuddy
*buddy
, PurpleContact
*contact
, PurpleGroup
*group
, PurpleBlistNode
*node
)
937 PurpleBlistNode
*cnode
, *bnode
;
938 PurpleCountingNode
*contact_counter
, *group_counter
;
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
))
958 if (node
&& PURPLE_IS_BUDDY(node
)) {
959 c
= (PurpleContact
*)node
->parent
;
960 g
= (PurpleGroup
*)node
->parent
->parent
;
961 } else if (contact
) {
963 g
= PURPLE_GROUP(PURPLE_BLIST_NODE(c
)->parent
);
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
);
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
);
1003 bnode
->next
->prev
= 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
);
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
)) {
1036 node
->next
->prev
= bnode
;
1037 bnode
->next
= node
->next
;
1039 bnode
->parent
= node
->parent
;
1043 cnode
->child
->prev
= bnode
;
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
));
1085 ops
->save_node((PurpleBlistNode
*) buddy
);
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();
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
)
1108 if (node
&& (PURPLE_IS_CONTACT(node
) ||
1109 PURPLE_IS_CHAT(node
)))
1110 g
= PURPLE_GROUP(node
->parent
);
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
;
1123 cnode
->prev
->next
= cnode
->next
;
1125 cnode
->next
->prev
= cnode
->prev
;
1127 if (cnode
->parent
!= gnode
) {
1128 bnode
= cnode
->child
;
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
)) {
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
;
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
);
1161 gboolean empty_contact
= FALSE
;
1163 /* this buddy already exists in the group, so we're
1164 * gonna delete it instead */
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 */
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
))) {
1203 node
->next
->prev
= cnode
;
1204 cnode
->next
= node
->next
;
1206 cnode
->parent
= node
->parent
;
1210 gnode
->child
->prev
= cnode
;
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
)
1229 ops
->save_node(cnode
);
1230 for (bnode
= cnode
->child
; bnode
; bnode
= bnode
->next
)
1231 ops
->save_node(bnode
);
1234 if (ops
&& ops
->update
)
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
;
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
)
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
;
1271 gnode
->prev
->next
= gnode
->next
;
1273 gnode
->next
->prev
= gnode
->prev
;
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
;
1283 node
->next
->prev
= gnode
;
1286 if (purplebuddylist
->root
)
1287 purplebuddylist
->root
->prev
= gnode
;
1288 gnode
->next
= purplebuddylist
->root
;
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",
1309 void purple_blist_remove_contact(PurpleContact
*contact
)
1311 PurpleBlistUiOps
*ops
= purple_blist_get_ui_ops();
1312 PurpleBlistNode
*node
, *gnode
;
1315 g_return_if_fail(PURPLE_IS_CONTACT(contact
));
1317 node
= (PurpleBlistNode
*)contact
;
1318 gnode
= node
->parent
;
1319 group
= PURPLE_GROUP(gnode
);
1323 * If this contact has children then remove them. When the last
1324 * buddy is removed from the contact, the contact is automatically
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
);
1337 /* Remove the node from its parent */
1338 if (gnode
->child
== node
)
1339 gnode
->child
= node
->next
;
1341 node
->prev
->next
= node
->next
;
1343 node
->next
->prev
= node
->prev
;
1344 purple_counting_node_change_total_size(PURPLE_COUNTING_NODE(group
), -1);
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
;
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 */
1383 node
->prev
->next
= 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
;
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
);
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
;
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
;
1460 /* Remove the node from its parent */
1461 if (gnode
->child
== node
)
1462 gnode
->child
= node
->next
;
1464 node
->prev
->next
= 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);
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
;
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 */
1509 /* Remove the node from its parent */
1510 if (purplebuddylist
->root
== node
)
1511 purplebuddylist
->root
= node
->next
;
1513 node
->prev
->next
= 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
);
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
)
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
) {
1562 if ((buddy
= g_hash_table_lookup(PURPLE_BUDDY_LIST_GET_PRIVATE(purplebuddylist
)->buddies
,
1571 PurpleBuddy
*purple_blist_find_buddy_in_group(PurpleAccount
*account
, const char *name
,
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
,
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
)
1599 PurpleBlistNode
*node
;
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
) {
1616 if ((buddy
= g_hash_table_lookup(PURPLE_BUDDY_LIST_GET_PRIVATE(purplebuddylist
)->buddies
,
1618 ret
= g_slist_prepend(ret
, buddy
);
1621 GSList
*list
= NULL
;
1622 GHashTable
*buddies
= g_hash_table_lookup(buddies_cache
, account
);
1623 g_hash_table_foreach(buddies
, find_acct_buddies
, &list
);
1630 PurpleGroup
*purple_blist_find_group(const char *name
)
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
);
1652 purple_blist_get_default_group(void)
1656 group
= purple_blist_find_group(PURPLE_BLIST_DEFAULT_GROUP_NAME
);
1658 group
= purple_group_new(PURPLE_BLIST_DEFAULT_GROUP_NAME
);
1659 purple_blist_add_group(group
, NULL
);
1666 purple_blist_find_chat(PurpleAccount
*account
, const char *name
)
1670 PurpleProtocol
*protocol
= NULL
;
1671 PurpleProtocolChatEntry
*pce
;
1672 PurpleBlistNode
*node
, *group
;
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
))
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
))
1697 parts
= purple_protocol_chat_iface_info(protocol
,
1698 purple_account_get_connection(purple_chat_get_account(chat
)));
1701 chat_name
= g_hash_table_lookup(purple_chat_get_components(chat
),
1703 g_list_foreach(parts
, (GFunc
)g_free
, NULL
);
1706 if (purple_chat_get_account(chat
) == account
&& chat_name
!= NULL
&&
1707 normname
!= NULL
&& !strcmp(purple_normalize(account
, chat_name
), normname
)) {
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
)
1730 for (gnode
= purplebuddylist
->root
; gnode
; gnode
= gnode
->next
) {
1731 if (!PURPLE_IS_GROUP(gnode
))
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
) {
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
);
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
;
1772 PurpleContact
*contact
;
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
))
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
))
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
);
1822 if (ops
&& ops
->remove
) {
1823 ops
->remove(purplebuddylist
, bnode
);
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
);
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
);
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
);
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();
1891 purple_blist_set_ui_ops(PurpleBlistUiOps
*ops
)
1893 gboolean overrode
= FALSE
;
1899 if (!ops
->save_node
) {
1900 ops
->save_node
= purple_blist_save_node
;
1903 if (!ops
->remove_node
) {
1904 ops
->remove_node
= purple_blist_save_node
;
1907 if (!ops
->save_account
) {
1908 ops
->save_account
= purple_blist_save_account
;
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");
1921 purple_blist_get_ui_ops(void)
1923 return blist_ui_ops
;
1927 _purple_blist_get_localized_default_group_name(void)
1929 return localized_default_group_name
;
1933 purple_blist_get_handle(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,
1961 purple_signal_register(handle
, "buddy-signed-off",
1962 purple_marshal_VOID__POINTER
, G_TYPE_NONE
, 1,
1965 purple_signal_register(handle
, "buddy-got-login-time",
1966 purple_marshal_VOID__POINTER
, G_TYPE_NONE
, 1,
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,
1981 purple_signal_register(handle
, "buddy-icon-changed",
1982 purple_marshal_VOID__POINTER
, G_TYPE_NONE
, 1,
1985 purple_signal_register(handle
, "update-idle", purple_marshal_VOID
,
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",
2003 PURPLE_CALLBACK(purple_blist_buddies_cache_add_account
),
2006 purple_signal_connect(purple_accounts_get_handle(), "account-destroying",
2008 PURPLE_CALLBACK(purple_blist_buddies_cache_remove_account
),
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
;
2021 next_child
= child
->next
;
2022 blist_node_destroy(child
);
2026 /* Allow the UI to free data */
2027 node
->parent
= NULL
;
2031 if (ui_ops
&& ui_ops
->remove
)
2032 ui_ops
->remove(purplebuddylist
, node
);
2034 g_object_unref(node
);
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
)
2047 if (save_timer
!= 0) {
2048 purple_timeout_remove(save_timer
);
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();
2060 next_node
= node
->next
;
2061 blist_node_destroy(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 /**************************************************************************
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);
2099 purple_blist_ui_ops_get_type(void)
2101 static GType type
= 0;
2104 type
= g_boxed_type_register_static("PurpleBlistUiOps",
2105 (GBoxedCopyFunc
)purple_blist_ui_ops_copy
,
2106 (GBoxedFreeFunc
)g_free
);
2112 /**************************************************************************
2114 **************************************************************************/
2116 /* GObject initialization function */
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 */
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
));
2154 purple_buddy_list_get_type(void)
2156 static GType type
= 0;
2159 static const GTypeInfo info
= {
2160 sizeof(PurpleBuddyListClass
),
2163 (GClassInitFunc
)purple_buddy_list_class_init
,
2166 sizeof(PurpleBuddyList
),
2168 (GInstanceInitFunc
)purple_buddy_list_init
,
2172 type
= g_type_register_static(G_TYPE_OBJECT
,
2173 "PurpleBuddyList", &info
, 0);