1 /*
2  * purple
3  *
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.
7  *
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.
12  *
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.
17  *
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
21  *
22  */
23 #define _PURPLE_BLIST_C_
24 
25 #include "internal.h"
26 #include "blist.h"
27 #include "conversation.h"
28 #include "dbus-maybe.h"
29 #include "debug.h"
30 #include "glibcompat.h"
31 #include "notify.h"
32 #include "pounce.h"
33 #include "prefs.h"
34 #include "privacy.h"
35 #include "prpl.h"
36 #include "server.h"
37 #include "signals.h"
38 #include "util.h"
39 #include "value.h"
40 #include "xmlnode.h"
41 
42 static PurpleBlistUiOps *blist_ui_ops = NULL;
43 
44 static PurpleBuddyList *purplebuddylist = NULL;
45 
46 /**
47  * A hash table used for efficient lookups of buddies by name.
48  * PurpleAccount* => GHashTable*, with the inner hash table being
49  * struct _purple_hbuddy => PurpleBuddy*
50  */
51 static GHashTable *buddies_cache = NULL;
52 
53 /**
54  * A hash table used for efficient lookups of groups by name.
55  * UTF-8 collate-key => PurpleGroup*.
56  */
57 static GHashTable *groups_cache = NULL;
58 
59 static guint          save_timer = 0;
60 static gboolean       blist_loaded = FALSE;
61 
62 /*********************************************************************
63  * Private utility functions                                         *
64  *********************************************************************/
65 
purple_blist_get_last_sibling(PurpleBlistNode * node)66 static PurpleBlistNode *purple_blist_get_last_sibling(PurpleBlistNode *node)
67 {
68 	PurpleBlistNode *n = node;
69 	if (!n)
70 		return NULL;
71 	while (n->next)
72 		n = n->next;
73 	return n;
74 }
75 
purple_blist_get_last_child(PurpleBlistNode * node)76 static PurpleBlistNode *purple_blist_get_last_child(PurpleBlistNode *node)
77 {
78 	if (!node)
79 		return NULL;
80 	return purple_blist_get_last_sibling(node->child);
81 }
82 
83 struct _list_account_buddies {
84 	GSList *list;
85 	PurpleAccount *account;
86 };
87 
88 struct _purple_hbuddy {
89 	char *name;
90 	PurpleAccount *account;
91 	PurpleBlistNode *group;
92 };
93 
94 /* This function must not use purple_normalize */
_purple_blist_hbuddy_hash(struct _purple_hbuddy * hb)95 static guint _purple_blist_hbuddy_hash(struct _purple_hbuddy *hb)
96 {
97 	return g_str_hash(hb->name) ^ g_direct_hash(hb->group) ^ g_direct_hash(hb->account);
98 }
99 
100 /* This function must not use purple_normalize */
_purple_blist_hbuddy_equal(struct _purple_hbuddy * hb1,struct _purple_hbuddy * hb2)101 static guint _purple_blist_hbuddy_equal(struct _purple_hbuddy *hb1, struct _purple_hbuddy *hb2)
102 {
103 	return (hb1->group == hb2->group &&
104 	        hb1->account == hb2->account &&
105 	        purple_strequal(hb1->name, hb2->name));
106 }
107 
_purple_blist_hbuddy_free_key(struct _purple_hbuddy * hb)108 static void _purple_blist_hbuddy_free_key(struct _purple_hbuddy *hb)
109 {
110 	g_free(hb->name);
111 	g_free(hb);
112 }
113 
114 static void
purple_blist_buddies_cache_add_account(PurpleAccount * account)115 purple_blist_buddies_cache_add_account(PurpleAccount *account)
116 {
117 	GHashTable *account_buddies = g_hash_table_new_full((GHashFunc)_purple_blist_hbuddy_hash,
118 						(GEqualFunc)_purple_blist_hbuddy_equal,
119 						(GDestroyNotify)_purple_blist_hbuddy_free_key, NULL);
120 	g_hash_table_insert(buddies_cache, account, account_buddies);
121 }
122 
123 static void
purple_blist_buddies_cache_remove_account(const PurpleAccount * account)124 purple_blist_buddies_cache_remove_account(const PurpleAccount *account)
125 {
126 	g_hash_table_remove(buddies_cache, account);
127 }
128 
129 
130 /*********************************************************************
131  * Writing to disk                                                   *
132  *********************************************************************/
133 
134 static void
value_to_xmlnode(gpointer key,gpointer hvalue,gpointer user_data)135 value_to_xmlnode(gpointer key, gpointer hvalue, gpointer user_data)
136 {
137 	const char *name;
138 	PurpleValue *value;
139 	xmlnode *node, *child;
140 	char buf[21];
141 
142 	name    = (const char *)key;
143 	value   = (PurpleValue *)hvalue;
144 	node    = (xmlnode *)user_data;
145 
146 	g_return_if_fail(value != NULL);
147 
148 	child = xmlnode_new_child(node, "setting");
149 	xmlnode_set_attrib(child, "name", name);
150 
151 	if (purple_value_get_type(value) == PURPLE_TYPE_INT) {
152 		xmlnode_set_attrib(child, "type", "int");
153 		g_snprintf(buf, sizeof(buf), "%d", purple_value_get_int(value));
154 		xmlnode_insert_data(child, buf, -1);
155 	}
156 	else if (purple_value_get_type(value) == PURPLE_TYPE_STRING) {
157 		xmlnode_set_attrib(child, "type", "string");
158 		xmlnode_insert_data(child, purple_value_get_string(value), -1);
159 	}
160 	else if (purple_value_get_type(value) == PURPLE_TYPE_BOOLEAN) {
161 		xmlnode_set_attrib(child, "type", "bool");
162 		g_snprintf(buf, sizeof(buf), "%d", purple_value_get_boolean(value));
163 		xmlnode_insert_data(child, buf, -1);
164 	}
165 }
166 
167 static void
chat_component_to_xmlnode(gpointer key,gpointer value,gpointer user_data)168 chat_component_to_xmlnode(gpointer key, gpointer value, gpointer user_data)
169 {
170 	const char *name;
171 	const char *data;
172 	xmlnode *node, *child;
173 
174 	name = (const char *)key;
175 	data = (const char *)value;
176 	node = (xmlnode *)user_data;
177 
178 	g_return_if_fail(data != NULL);
179 
180 	child = xmlnode_new_child(node, "component");
181 	xmlnode_set_attrib(child, "name", name);
182 	xmlnode_insert_data(child, data, -1);
183 }
184 
185 static xmlnode *
buddy_to_xmlnode(PurpleBlistNode * bnode)186 buddy_to_xmlnode(PurpleBlistNode *bnode)
187 {
188 	xmlnode *node, *child;
189 	PurpleBuddy *buddy;
190 
191 	buddy = (PurpleBuddy *)bnode;
192 
193 	node = xmlnode_new("buddy");
194 	xmlnode_set_attrib(node, "account", purple_account_get_username(buddy->account));
195 	xmlnode_set_attrib(node, "proto", purple_account_get_protocol_id(buddy->account));
196 
197 	child = xmlnode_new_child(node, "name");
198 	xmlnode_insert_data(child, buddy->name, -1);
199 
200 	if (buddy->alias != NULL)
201 	{
202 		child = xmlnode_new_child(node, "alias");
203 		xmlnode_insert_data(child, buddy->alias, -1);
204 	}
205 
206 	/* Write buddy settings */
207 	g_hash_table_foreach(buddy->node.settings, value_to_xmlnode, node);
208 
209 	return node;
210 }
211 
212 static xmlnode *
contact_to_xmlnode(PurpleBlistNode * cnode)213 contact_to_xmlnode(PurpleBlistNode *cnode)
214 {
215 	xmlnode *node, *child;
216 	PurpleContact *contact;
217 	PurpleBlistNode *bnode;
218 
219 	contact = (PurpleContact *)cnode;
220 
221 	node = xmlnode_new("contact");
222 
223 	if (contact->alias != NULL)
224 	{
225 		xmlnode_set_attrib(node, "alias", contact->alias);
226 	}
227 
228 	/* Write buddies */
229 	for (bnode = cnode->child; bnode != NULL; bnode = bnode->next)
230 	{
231 		if (!PURPLE_BLIST_NODE_SHOULD_SAVE(bnode))
232 			continue;
233 		if (PURPLE_BLIST_NODE_IS_BUDDY(bnode))
234 		{
235 			child = buddy_to_xmlnode(bnode);
236 			xmlnode_insert_child(node, child);
237 		}
238 	}
239 
240 	/* Write contact settings */
241 	g_hash_table_foreach(cnode->settings, value_to_xmlnode, node);
242 
243 	return node;
244 }
245 
246 static xmlnode *
chat_to_xmlnode(PurpleBlistNode * cnode)247 chat_to_xmlnode(PurpleBlistNode *cnode)
248 {
249 	xmlnode *node, *child;
250 	PurpleChat *chat;
251 
252 	chat = (PurpleChat *)cnode;
253 
254 	node = xmlnode_new("chat");
255 	xmlnode_set_attrib(node, "proto", purple_account_get_protocol_id(chat->account));
256 	xmlnode_set_attrib(node, "account", purple_account_get_username(chat->account));
257 
258 	if (chat->alias != NULL)
259 	{
260 		child = xmlnode_new_child(node, "alias");
261 		xmlnode_insert_data(child, chat->alias, -1);
262 	}
263 
264 	/* Write chat components */
265 	g_hash_table_foreach(chat->components, chat_component_to_xmlnode, node);
266 
267 	/* Write chat settings */
268 	g_hash_table_foreach(chat->node.settings, value_to_xmlnode, node);
269 
270 	return node;
271 }
272 
273 static xmlnode *
group_to_xmlnode(PurpleBlistNode * gnode)274 group_to_xmlnode(PurpleBlistNode *gnode)
275 {
276 	xmlnode *node, *child;
277 	PurpleGroup *group;
278 	PurpleBlistNode *cnode;
279 
280 	group = (PurpleGroup *)gnode;
281 
282 	node = xmlnode_new("group");
283 	if (!purple_strequal(group->name, _("Buddies")))
284 		xmlnode_set_attrib(node, "name", group->name);
285 
286 	/* Write settings */
287 	g_hash_table_foreach(group->node.settings, value_to_xmlnode, node);
288 
289 	/* Write contacts and chats */
290 	for (cnode = gnode->child; cnode != NULL; cnode = cnode->next)
291 	{
292 		if (!PURPLE_BLIST_NODE_SHOULD_SAVE(cnode))
293 			continue;
294 		if (PURPLE_BLIST_NODE_IS_CONTACT(cnode))
295 		{
296 			child = contact_to_xmlnode(cnode);
297 			xmlnode_insert_child(node, child);
298 		}
299 		else if (PURPLE_BLIST_NODE_IS_CHAT(cnode))
300 		{
301 			child = chat_to_xmlnode(cnode);
302 			xmlnode_insert_child(node, child);
303 		}
304 	}
305 
306 	return node;
307 }
308 
309 static xmlnode *
accountprivacy_to_xmlnode(PurpleAccount * account)310 accountprivacy_to_xmlnode(PurpleAccount *account)
311 {
312 	xmlnode *node, *child;
313 	GSList *cur;
314 	char buf[10];
315 
316 	node = xmlnode_new("account");
317 	xmlnode_set_attrib(node, "proto", purple_account_get_protocol_id(account));
318 	xmlnode_set_attrib(node, "name", purple_account_get_username(account));
319 	g_snprintf(buf, sizeof(buf), "%d", account->perm_deny);
320 	xmlnode_set_attrib(node, "mode", buf);
321 
322 	for (cur = account->permit; cur; cur = cur->next)
323 	{
324 		child = xmlnode_new_child(node, "permit");
325 		xmlnode_insert_data(child, cur->data, -1);
326 	}
327 
328 	for (cur = account->deny; cur; cur = cur->next)
329 	{
330 		child = xmlnode_new_child(node, "block");
331 		xmlnode_insert_data(child, cur->data, -1);
332 	}
333 
334 	return node;
335 }
336 
337 static xmlnode *
blist_to_xmlnode(void)338 blist_to_xmlnode(void)
339 {
340 	xmlnode *node, *child, *grandchild;
341 	PurpleBlistNode *gnode;
342 	GList *cur;
343 
344 	node = xmlnode_new("purple");
345 	xmlnode_set_attrib(node, "version", "1.0");
346 
347 	/* Write groups */
348 	child = xmlnode_new_child(node, "blist");
349 	for (gnode = purplebuddylist->root; gnode != NULL; gnode = gnode->next)
350 	{
351 		if (!PURPLE_BLIST_NODE_SHOULD_SAVE(gnode))
352 			continue;
353 		if (PURPLE_BLIST_NODE_IS_GROUP(gnode))
354 		{
355 			grandchild = group_to_xmlnode(gnode);
356 			xmlnode_insert_child(child, grandchild);
357 		}
358 	}
359 
360 	/* Write privacy settings */
361 	child = xmlnode_new_child(node, "privacy");
362 	for (cur = purple_accounts_get_all(); cur != NULL; cur = cur->next)
363 	{
364 		grandchild = accountprivacy_to_xmlnode(cur->data);
365 		xmlnode_insert_child(child, grandchild);
366 	}
367 
368 	return node;
369 }
370 
371 static void
purple_blist_sync(void)372 purple_blist_sync(void)
373 {
374 	xmlnode *node;
375 	char *data;
376 
377 	if (!blist_loaded)
378 	{
379 		purple_debug_error("blist", "Attempted to save buddy list before it "
380 						 "was read!\n");
381 		return;
382 	}
383 
384 	node = blist_to_xmlnode();
385 	data = xmlnode_to_formatted_str(node, NULL);
386 	purple_util_write_data_to_file("blist.xml", data, -1);
387 	g_free(data);
388 	xmlnode_free(node);
389 }
390 
391 static gboolean
save_cb(gpointer data)392 save_cb(gpointer data)
393 {
394 	purple_blist_sync();
395 	save_timer = 0;
396 	return FALSE;
397 }
398 
399 static void
_purple_blist_schedule_save()400 _purple_blist_schedule_save()
401 {
402 	if (save_timer == 0)
403 		save_timer = purple_timeout_add_seconds(5, save_cb, NULL);
404 }
405 
406 static void
purple_blist_save_account(PurpleAccount * account)407 purple_blist_save_account(PurpleAccount *account)
408 {
409 #if 1
410 	_purple_blist_schedule_save();
411 #else
412 	if (account != NULL) {
413 		/* Save the buddies and privacy data for this account */
414 	} else {
415 		/* Save all buddies and privacy data */
416 	}
417 #endif
418 }
419 
420 static void
purple_blist_save_node(PurpleBlistNode * node)421 purple_blist_save_node(PurpleBlistNode *node)
422 {
423 	_purple_blist_schedule_save();
424 }
425 
purple_blist_schedule_save()426 void purple_blist_schedule_save()
427 {
428 	PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
429 
430 	/* Save everything */
431 	if (ops && ops->save_account)
432 		ops->save_account(NULL);
433 }
434 
435 
436 /*********************************************************************
437  * Reading from disk                                                 *
438  *********************************************************************/
439 
440 static void
parse_setting(PurpleBlistNode * node,xmlnode * setting)441 parse_setting(PurpleBlistNode *node, xmlnode *setting)
442 {
443 	const char *name = xmlnode_get_attrib(setting, "name");
444 	const char *type = xmlnode_get_attrib(setting, "type");
445 	char *value = xmlnode_get_data(setting);
446 
447 	if (!value)
448 		return;
449 
450 	if (!type || purple_strequal(type, "string"))
451 		purple_blist_node_set_string(node, name, value);
452 	else if (purple_strequal(type, "bool"))
453 		purple_blist_node_set_bool(node, name, atoi(value));
454 	else if (purple_strequal(type, "int"))
455 		purple_blist_node_set_int(node, name, atoi(value));
456 
457 	g_free(value);
458 }
459 
460 static void
parse_buddy(PurpleGroup * group,PurpleContact * contact,xmlnode * bnode)461 parse_buddy(PurpleGroup *group, PurpleContact *contact, xmlnode *bnode)
462 {
463 	PurpleAccount *account;
464 	PurpleBuddy *buddy;
465 	char *name = NULL, *alias = NULL;
466 	const char *acct_name, *proto, *protocol;
467 	xmlnode *x;
468 
469 	acct_name = xmlnode_get_attrib(bnode, "account");
470 	protocol = xmlnode_get_attrib(bnode, "protocol");
471 	protocol = _purple_oscar_convert(acct_name, protocol); /* XXX: Remove */
472 	proto = xmlnode_get_attrib(bnode, "proto");
473 	proto = _purple_oscar_convert(acct_name, proto); /* XXX: Remove */
474 
475 	if (!acct_name || (!proto && !protocol))
476 		return;
477 
478 	account = purple_accounts_find(acct_name, proto ? proto : protocol);
479 
480 	if (!account)
481 		return;
482 
483 	if ((x = xmlnode_get_child(bnode, "name")))
484 		name = xmlnode_get_data(x);
485 
486 	if (!name)
487 		return;
488 
489 	if ((x = xmlnode_get_child(bnode, "alias")))
490 		alias = xmlnode_get_data(x);
491 
492 	buddy = purple_buddy_new(account, name, alias);
493 	purple_blist_add_buddy(buddy, contact, group,
494 			purple_blist_get_last_child((PurpleBlistNode*)contact));
495 
496 	for (x = xmlnode_get_child(bnode, "setting"); x; x = xmlnode_get_next_twin(x)) {
497 		parse_setting((PurpleBlistNode*)buddy, x);
498 	}
499 
500 	g_free(name);
501 	g_free(alias);
502 }
503 
504 static void
parse_contact(PurpleGroup * group,xmlnode * cnode)505 parse_contact(PurpleGroup *group, xmlnode *cnode)
506 {
507 	PurpleContact *contact = purple_contact_new();
508 	xmlnode *x;
509 	const char *alias;
510 
511 	purple_blist_add_contact(contact, group,
512 			purple_blist_get_last_child((PurpleBlistNode*)group));
513 
514 	if ((alias = xmlnode_get_attrib(cnode, "alias"))) {
515 		purple_blist_alias_contact(contact, alias);
516 	}
517 
518 	for (x = cnode->child; x; x = x->next) {
519 		if (x->type != XMLNODE_TYPE_TAG)
520 			continue;
521 		if (purple_strequal(x->name, "buddy"))
522 			parse_buddy(group, contact, x);
523 		else if (purple_strequal(x->name, "setting"))
524 			parse_setting((PurpleBlistNode*)contact, x);
525 	}
526 
527 	/* if the contact is empty, don't keep it around.  it causes problems */
528 	if (!((PurpleBlistNode*)contact)->child)
529 		purple_blist_remove_contact(contact);
530 }
531 
532 static void
parse_chat(PurpleGroup * group,xmlnode * cnode)533 parse_chat(PurpleGroup *group, xmlnode *cnode)
534 {
535 	PurpleChat *chat;
536 	PurpleAccount *account;
537 	const char *acct_name, *proto, *protocol;
538 	xmlnode *x;
539 	char *alias = NULL;
540 	GHashTable *components;
541 
542 	acct_name = xmlnode_get_attrib(cnode, "account");
543 	protocol = xmlnode_get_attrib(cnode, "protocol");
544 	proto = xmlnode_get_attrib(cnode, "proto");
545 
546 	if (!acct_name || (!proto && !protocol))
547 		return;
548 
549 	account = purple_accounts_find(acct_name, proto ? proto : protocol);
550 
551 	if (!account)
552 		return;
553 
554 	if ((x = xmlnode_get_child(cnode, "alias")))
555 		alias = xmlnode_get_data(x);
556 
557 	components = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
558 
559 	for (x = xmlnode_get_child(cnode, "component"); x; x = xmlnode_get_next_twin(x)) {
560 		const char *name;
561 		char *value;
562 
563 		name = xmlnode_get_attrib(x, "name");
564 		value = xmlnode_get_data(x);
565 		g_hash_table_replace(components, g_strdup(name), value);
566 	}
567 
568 	chat = purple_chat_new(account, alias, components);
569 	purple_blist_add_chat(chat, group,
570 			purple_blist_get_last_child((PurpleBlistNode*)group));
571 
572 	for (x = xmlnode_get_child(cnode, "setting"); x; x = xmlnode_get_next_twin(x)) {
573 		parse_setting((PurpleBlistNode*)chat, x);
574 	}
575 
576 	g_free(alias);
577 }
578 
579 static void
parse_group(xmlnode * groupnode)580 parse_group(xmlnode *groupnode)
581 {
582 	const char *name = xmlnode_get_attrib(groupnode, "name");
583 	PurpleGroup *group;
584 	xmlnode *cnode;
585 
586 	if (!name)
587 		name = _("Buddies");
588 
589 	group = purple_group_new(name);
590 	purple_blist_add_group(group,
591 			purple_blist_get_last_sibling(purplebuddylist->root));
592 
593 	for (cnode = groupnode->child; cnode; cnode = cnode->next) {
594 		if (cnode->type != XMLNODE_TYPE_TAG)
595 			continue;
596 		if (purple_strequal(cnode->name, "setting"))
597 			parse_setting((PurpleBlistNode*)group, cnode);
598 		else if (purple_strequal(cnode->name, "contact") ||
599 				purple_strequal(cnode->name, "person"))
600 			parse_contact(group, cnode);
601 		else if (purple_strequal(cnode->name, "chat"))
602 			parse_chat(group, cnode);
603 	}
604 }
605 
606 /* TODO: Make static and rename to load_blist */
607 void
purple_blist_load()608 purple_blist_load()
609 {
610 	xmlnode *purple, *blist, *privacy;
611 
612 	blist_loaded = TRUE;
613 
614 	purple = purple_util_read_xml_from_file("blist.xml", _("buddy list"));
615 
616 	if (purple == NULL)
617 		return;
618 
619 	blist = xmlnode_get_child(purple, "blist");
620 	if (blist) {
621 		xmlnode *groupnode;
622 		for (groupnode = xmlnode_get_child(blist, "group"); groupnode != NULL;
623 				groupnode = xmlnode_get_next_twin(groupnode)) {
624 			parse_group(groupnode);
625 		}
626 	}
627 
628 	privacy = xmlnode_get_child(purple, "privacy");
629 	if (privacy) {
630 		xmlnode *anode;
631 		for (anode = privacy->child; anode; anode = anode->next) {
632 			xmlnode *x;
633 			PurpleAccount *account;
634 			int imode;
635 			const char *acct_name, *proto, *mode, *protocol;
636 
637 			acct_name = xmlnode_get_attrib(anode, "name");
638 			protocol = xmlnode_get_attrib(anode, "protocol");
639 			proto = xmlnode_get_attrib(anode, "proto");
640 			mode = xmlnode_get_attrib(anode, "mode");
641 
642 			if (!acct_name || (!proto && !protocol) || !mode)
643 				continue;
644 
645 			account = purple_accounts_find(acct_name, proto ? proto : protocol);
646 
647 			if (!account)
648 				continue;
649 
650 			imode = atoi(mode);
651 			account->perm_deny = (imode != 0 ? imode : PURPLE_PRIVACY_ALLOW_ALL);
652 
653 			for (x = anode->child; x; x = x->next) {
654 				char *name;
655 				if (x->type != XMLNODE_TYPE_TAG)
656 					continue;
657 
658 				if (purple_strequal(x->name, "permit")) {
659 					name = xmlnode_get_data(x);
660 					purple_privacy_permit_add(account, name, TRUE);
661 					g_free(name);
662 				} else if (purple_strequal(x->name, "block")) {
663 					name = xmlnode_get_data(x);
664 					purple_privacy_deny_add(account, name, TRUE);
665 					g_free(name);
666 				}
667 			}
668 		}
669 	}
670 
671 	xmlnode_free(purple);
672 
673 	/* This tells the buddy icon code to do its thing. */
674 	_purple_buddy_icons_blist_loaded_cb();
675 }
676 
677 
678 /*********************************************************************
679  * Stuff                                                             *
680  *********************************************************************/
681 
682 static void
purple_contact_compute_priority_buddy(PurpleContact * contact)683 purple_contact_compute_priority_buddy(PurpleContact *contact)
684 {
685 	PurpleBlistNode *bnode;
686 	PurpleBuddy *new_priority = NULL;
687 
688 	g_return_if_fail(contact != NULL);
689 
690 	contact->priority = NULL;
691 	for (bnode = ((PurpleBlistNode*)contact)->child;
692 			bnode != NULL;
693 			bnode = bnode->next)
694 	{
695 		PurpleBuddy *buddy;
696 
697 		if (!PURPLE_BLIST_NODE_IS_BUDDY(bnode))
698 			continue;
699 
700 		buddy = (PurpleBuddy*)bnode;
701 		if (new_priority == NULL)
702 		{
703 			new_priority = buddy;
704 			continue;
705 		}
706 
707 		if (purple_account_is_connected(buddy->account))
708 		{
709 			int cmp = 1;
710 			if (purple_account_is_connected(new_priority->account))
711 				cmp = purple_presence_compare(purple_buddy_get_presence(new_priority),
712 						purple_buddy_get_presence(buddy));
713 
714 			if (cmp > 0 || (cmp == 0 &&
715 			                purple_prefs_get_bool("/purple/contact/last_match")))
716 			{
717 				new_priority = buddy;
718 			}
719 		}
720 	}
721 
722 	contact->priority = new_priority;
723 	contact->priority_valid = TRUE;
724 }
725 
726 
727 /*****************************************************************************
728  * Public API functions                                                      *
729  *****************************************************************************/
730 
purple_blist_new()731 PurpleBuddyList *purple_blist_new()
732 {
733 	PurpleBlistUiOps *ui_ops;
734 	GList *account;
735 	PurpleBuddyList *gbl = g_new0(PurpleBuddyList, 1);
736 	PURPLE_DBUS_REGISTER_POINTER(gbl, PurpleBuddyList);
737 
738 	ui_ops = purple_blist_get_ui_ops();
739 
740 	gbl->buddies = g_hash_table_new_full((GHashFunc)_purple_blist_hbuddy_hash,
741 					 (GEqualFunc)_purple_blist_hbuddy_equal,
742 					 (GDestroyNotify)_purple_blist_hbuddy_free_key, NULL);
743 
744 	buddies_cache = g_hash_table_new_full(g_direct_hash, g_direct_equal,
745 					 NULL, (GDestroyNotify)g_hash_table_destroy);
746 
747 	groups_cache = g_hash_table_new_full((GHashFunc)g_str_hash,
748 					 (GEqualFunc)g_str_equal,
749 					 (GDestroyNotify)g_free, NULL);
750 
751 	for (account = purple_accounts_get_all(); account != NULL; account = account->next)
752 	{
753 		purple_blist_buddies_cache_add_account(account->data);
754 	}
755 
756 	if (ui_ops != NULL && ui_ops->new_list != NULL)
757 		ui_ops->new_list(gbl);
758 
759 	return gbl;
760 }
761 
762 void
purple_set_blist(PurpleBuddyList * list)763 purple_set_blist(PurpleBuddyList *list)
764 {
765 	purplebuddylist = list;
766 }
767 
768 PurpleBuddyList *
purple_get_blist()769 purple_get_blist()
770 {
771 	return purplebuddylist;
772 }
773 
774 PurpleBlistNode *
purple_blist_get_root()775 purple_blist_get_root()
776 {
777 	return purplebuddylist ? purplebuddylist->root : NULL;
778 }
779 
780 static void
append_buddy(gpointer key,gpointer value,gpointer user_data)781 append_buddy(gpointer key, gpointer value, gpointer user_data)
782 {
783 	GSList **list = user_data;
784 	*list = g_slist_prepend(*list, value);
785 }
786 
787 GSList *
purple_blist_get_buddies()788 purple_blist_get_buddies()
789 {
790 	GSList *buddies = NULL;
791 
792 	if (!purplebuddylist)
793 		return NULL;
794 
795 	g_hash_table_foreach(purplebuddylist->buddies, append_buddy, &buddies);
796 	return buddies;
797 }
798 
799 void *
purple_blist_get_ui_data()800 purple_blist_get_ui_data()
801 {
802 	return purplebuddylist->ui_data;
803 }
804 
805 void
purple_blist_set_ui_data(void * ui_data)806 purple_blist_set_ui_data(void *ui_data)
807 {
808 	purplebuddylist->ui_data = ui_data;
809 }
810 
purple_blist_show()811 void purple_blist_show()
812 {
813 	PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
814 
815 	if (ops && ops->show)
816 		ops->show(purplebuddylist);
817 }
818 
purple_blist_destroy()819 void purple_blist_destroy()
820 {
821 	PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
822 
823 	purple_debug(PURPLE_DEBUG_INFO, "blist", "Destroying\n");
824 
825 	if (ops && ops->destroy)
826 		ops->destroy(purplebuddylist);
827 }
828 
purple_blist_set_visible(gboolean show)829 void purple_blist_set_visible(gboolean show)
830 {
831 	PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
832 
833 	if (ops && ops->set_visible)
834 		ops->set_visible(purplebuddylist, show);
835 }
836 
get_next_node(PurpleBlistNode * node,gboolean godeep)837 static PurpleBlistNode *get_next_node(PurpleBlistNode *node, gboolean godeep)
838 {
839 	if (node == NULL)
840 		return NULL;
841 
842 	if (godeep && node->child)
843 		return node->child;
844 
845 	if (node->next)
846 		return node->next;
847 
848 	return get_next_node(node->parent, FALSE);
849 }
850 
purple_blist_node_next(PurpleBlistNode * node,gboolean offline)851 PurpleBlistNode *purple_blist_node_next(PurpleBlistNode *node, gboolean offline)
852 {
853 	PurpleBlistNode *ret = node;
854 
855 	if (offline)
856 		return get_next_node(ret, TRUE);
857 	do
858 	{
859 		ret = get_next_node(ret, TRUE);
860 	} while (ret && PURPLE_BLIST_NODE_IS_BUDDY(ret) &&
861 			!purple_account_is_connected(purple_buddy_get_account((PurpleBuddy *)ret)));
862 
863 	return ret;
864 }
865 
purple_blist_node_get_parent(PurpleBlistNode * node)866 PurpleBlistNode *purple_blist_node_get_parent(PurpleBlistNode *node)
867 {
868 	return node ? node->parent : NULL;
869 }
870 
purple_blist_node_get_first_child(PurpleBlistNode * node)871 PurpleBlistNode *purple_blist_node_get_first_child(PurpleBlistNode *node)
872 {
873 	return node ? node->child : NULL;
874 }
875 
purple_blist_node_get_sibling_next(PurpleBlistNode * node)876 PurpleBlistNode *purple_blist_node_get_sibling_next(PurpleBlistNode *node)
877 {
878 	return node? node->next : NULL;
879 }
880 
purple_blist_node_get_sibling_prev(PurpleBlistNode * node)881 PurpleBlistNode *purple_blist_node_get_sibling_prev(PurpleBlistNode *node)
882 {
883 	return node? node->prev : NULL;
884 }
885 
886 void *
purple_blist_node_get_ui_data(const PurpleBlistNode * node)887 purple_blist_node_get_ui_data(const PurpleBlistNode *node)
888 {
889 	g_return_val_if_fail(node, NULL);
890 
891 	return node->ui_data;
892 }
893 
894 void
purple_blist_node_set_ui_data(PurpleBlistNode * node,void * ui_data)895 purple_blist_node_set_ui_data(PurpleBlistNode *node, void *ui_data) {
896 	g_return_if_fail(node);
897 
898 	node->ui_data = ui_data;
899 }
900 
901 void
purple_blist_update_buddy_status(PurpleBuddy * buddy,PurpleStatus * old_status)902 purple_blist_update_buddy_status(PurpleBuddy *buddy, PurpleStatus *old_status)
903 {
904 	PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
905 	PurplePresence *presence;
906 	PurpleStatus *status;
907 	PurpleBlistNode *cnode;
908 
909 	g_return_if_fail(buddy != NULL);
910 
911 	presence = purple_buddy_get_presence(buddy);
912 	status = purple_presence_get_active_status(presence);
913 
914 	purple_debug_info("blist", "Updating buddy status for %s (%s)\n",
915 			buddy->name, purple_account_get_protocol_name(buddy->account));
916 
917 	if (purple_status_is_online(status) &&
918 		!purple_status_is_online(old_status)) {
919 
920 		purple_signal_emit(purple_blist_get_handle(), "buddy-signed-on", buddy);
921 
922 		cnode = buddy->node.parent;
923 		if (++(PURPLE_CONTACT(cnode)->online) == 1)
924 			PURPLE_GROUP(cnode->parent)->online++;
925 	} else if (!purple_status_is_online(status) &&
926 				purple_status_is_online(old_status)) {
927 
928 		purple_blist_node_set_int(&buddy->node, "last_seen", time(NULL));
929 		purple_signal_emit(purple_blist_get_handle(), "buddy-signed-off", buddy);
930 
931 		cnode = buddy->node.parent;
932 		if (--(PURPLE_CONTACT(cnode)->online) == 0)
933 			PURPLE_GROUP(cnode->parent)->online--;
934 	} else {
935 		purple_signal_emit(purple_blist_get_handle(),
936 		                 "buddy-status-changed", buddy, old_status,
937 		                 status);
938 	}
939 
940 	/*
941 	 * This function used to only call the following two functions if one of
942 	 * the above signals had been triggered, but that's not good, because
943 	 * if someone's away message changes and they don't go from away to back
944 	 * to away then no signal is triggered.
945 	 *
946 	 * It's a safe assumption that SOMETHING called this function.  PROBABLY
947 	 * because something, somewhere changed.  Calling the stuff below
948 	 * certainly won't hurt anything.  Unless you're on a K6-2 300.
949 	 */
950 	purple_contact_invalidate_priority_buddy(purple_buddy_get_contact(buddy));
951 	if (ops && ops->update)
952 		ops->update(purplebuddylist, (PurpleBlistNode *)buddy);
953 }
954 
955 void
purple_blist_update_node_icon(PurpleBlistNode * node)956 purple_blist_update_node_icon(PurpleBlistNode *node)
957 {
958 	PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
959 
960 	g_return_if_fail(node != NULL);
961 
962 	if (ops && ops->update)
963 		ops->update(purplebuddylist, node);
964 }
965 
966 void
purple_blist_update_buddy_icon(PurpleBuddy * buddy)967 purple_blist_update_buddy_icon(PurpleBuddy *buddy)
968 {
969 	purple_blist_update_node_icon((PurpleBlistNode *)buddy);
970 }
971 
972 /*
973  * TODO: Maybe remove the call to this from server.c and call it
974  * from oscar.c and toc.c instead?
975  */
purple_blist_rename_buddy(PurpleBuddy * buddy,const char * name)976 void purple_blist_rename_buddy(PurpleBuddy *buddy, const char *name)
977 {
978 	PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
979 	struct _purple_hbuddy *hb, *hb2;
980 	GHashTable *account_buddies;
981 
982 	g_return_if_fail(buddy != NULL);
983 
984 	hb = g_new(struct _purple_hbuddy, 1);
985 	hb->name = (gchar *)purple_normalize(buddy->account, buddy->name);
986 	hb->account = buddy->account;
987 	hb->group = ((PurpleBlistNode *)buddy)->parent->parent;
988 	g_hash_table_remove(purplebuddylist->buddies, hb);
989 
990 	account_buddies = g_hash_table_lookup(buddies_cache, buddy->account);
991 	g_hash_table_remove(account_buddies, hb);
992 
993 	hb->name = g_strdup(purple_normalize(buddy->account, name));
994 	g_hash_table_replace(purplebuddylist->buddies, hb, buddy);
995 
996 	hb2 = g_new(struct _purple_hbuddy, 1);
997 	hb2->name = g_strdup(hb->name);
998 	hb2->account = buddy->account;
999 	hb2->group = ((PurpleBlistNode *)buddy)->parent->parent;
1000 
1001 	g_hash_table_replace(account_buddies, hb2, buddy);
1002 
1003 	g_free(buddy->name);
1004 	buddy->name = g_strdup(name);
1005 
1006 	if (ops && ops->save_node)
1007 		ops->save_node((PurpleBlistNode *) buddy);
1008 
1009 	if (ops && ops->update)
1010 		ops->update(purplebuddylist, (PurpleBlistNode *)buddy);
1011 }
1012 
1013 static gboolean
purple_strings_are_different(const char * one,const char * two)1014 purple_strings_are_different(const char *one, const char *two)
1015 {
1016 	return !((one && two && g_utf8_collate(one, two) == 0) ||
1017 			((one == NULL || *one == '\0') && (two == NULL || *two == '\0')));
1018 }
1019 
purple_blist_alias_contact(PurpleContact * contact,const char * alias)1020 void purple_blist_alias_contact(PurpleContact *contact, const char *alias)
1021 {
1022 	PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
1023 	PurpleConversation *conv;
1024 	PurpleBlistNode *bnode;
1025 	char *old_alias;
1026 	char *new_alias = NULL;
1027 
1028 	g_return_if_fail(contact != NULL);
1029 
1030 	if ((alias != NULL) && (*alias != '\0'))
1031 		new_alias = purple_utf8_strip_unprintables(alias);
1032 
1033 	if (!purple_strings_are_different(contact->alias, new_alias)) {
1034 		g_free(new_alias);
1035 		return;
1036 	}
1037 
1038 	old_alias = contact->alias;
1039 
1040 	if ((new_alias != NULL) && (*new_alias != '\0'))
1041 		contact->alias = new_alias;
1042 	else {
1043 		contact->alias = NULL;
1044 		g_free(new_alias); /* could be "\0" */
1045 	}
1046 
1047 	if (ops && ops->save_node)
1048 		ops->save_node((PurpleBlistNode*) contact);
1049 
1050 	if (ops && ops->update)
1051 		ops->update(purplebuddylist, (PurpleBlistNode *)contact);
1052 
1053 	for(bnode = ((PurpleBlistNode *)contact)->child; bnode != NULL; bnode = bnode->next)
1054 	{
1055 		PurpleBuddy *buddy = (PurpleBuddy *)bnode;
1056 
1057 		conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, buddy->name,
1058 												   buddy->account);
1059 		if (conv)
1060 			purple_conversation_autoset_title(conv);
1061 	}
1062 
1063 	purple_signal_emit(purple_blist_get_handle(), "blist-node-aliased",
1064 					 contact, old_alias);
1065 	g_free(old_alias);
1066 }
1067 
purple_blist_alias_chat(PurpleChat * chat,const char * alias)1068 void purple_blist_alias_chat(PurpleChat *chat, const char *alias)
1069 {
1070 	PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
1071 	char *old_alias;
1072 	char *new_alias = NULL;
1073 
1074 	g_return_if_fail(chat != NULL);
1075 
1076 	if ((alias != NULL) && (*alias != '\0'))
1077 		new_alias = purple_utf8_strip_unprintables(alias);
1078 
1079 	if (!purple_strings_are_different(chat->alias, new_alias)) {
1080 		g_free(new_alias);
1081 		return;
1082 	}
1083 
1084 	old_alias = chat->alias;
1085 
1086 	if ((new_alias != NULL) && (*new_alias != '\0'))
1087 		chat->alias = new_alias;
1088 	else {
1089 		chat->alias = NULL;
1090 		g_free(new_alias); /* could be "\0" */
1091 	}
1092 
1093 	if (ops && ops->save_node)
1094 		ops->save_node((PurpleBlistNode*) chat);
1095 
1096 	if (ops && ops->update)
1097 		ops->update(purplebuddylist, (PurpleBlistNode *)chat);
1098 
1099 	purple_signal_emit(purple_blist_get_handle(), "blist-node-aliased",
1100 					 chat, old_alias);
1101 	g_free(old_alias);
1102 }
1103 
purple_blist_alias_buddy(PurpleBuddy * buddy,const char * alias)1104 void purple_blist_alias_buddy(PurpleBuddy *buddy, const char *alias)
1105 {
1106 	PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
1107 	PurpleConversation *conv;
1108 	char *old_alias;
1109 	char *new_alias = NULL;
1110 
1111 	g_return_if_fail(buddy != NULL);
1112 
1113 	if ((alias != NULL) && (*alias != '\0'))
1114 		new_alias = purple_utf8_strip_unprintables(alias);
1115 
1116 	if (!purple_strings_are_different(buddy->alias, new_alias)) {
1117 		g_free(new_alias);
1118 		return;
1119 	}
1120 
1121 	old_alias = buddy->alias;
1122 
1123 	if ((new_alias != NULL) && (*new_alias != '\0'))
1124 		buddy->alias = new_alias;
1125 	else {
1126 		buddy->alias = NULL;
1127 		g_free(new_alias); /* could be "\0" */
1128 	}
1129 
1130 	if (ops && ops->save_node)
1131 		ops->save_node((PurpleBlistNode*) buddy);
1132 
1133 	if (ops && ops->update)
1134 		ops->update(purplebuddylist, (PurpleBlistNode *)buddy);
1135 
1136 	conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, buddy->name,
1137 											   buddy->account);
1138 	if (conv)
1139 		purple_conversation_autoset_title(conv);
1140 
1141 	purple_signal_emit(purple_blist_get_handle(), "blist-node-aliased",
1142 					 buddy, old_alias);
1143 	g_free(old_alias);
1144 }
1145 
purple_blist_server_alias_buddy(PurpleBuddy * buddy,const char * alias)1146 void purple_blist_server_alias_buddy(PurpleBuddy *buddy, const char *alias)
1147 {
1148 	PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
1149 	PurpleConversation *conv;
1150 	char *old_alias;
1151 	char *new_alias = NULL;
1152 
1153 	g_return_if_fail(buddy != NULL);
1154 
1155 	if ((alias != NULL) && (*alias != '\0') && g_utf8_validate(alias, -1, NULL))
1156 		new_alias = purple_utf8_strip_unprintables(alias);
1157 
1158 	if (!purple_strings_are_different(buddy->server_alias, new_alias)) {
1159 		g_free(new_alias);
1160 		return;
1161 	}
1162 
1163 	old_alias = buddy->server_alias;
1164 
1165 	if ((new_alias != NULL) && (*new_alias != '\0'))
1166 		buddy->server_alias = new_alias;
1167 	else {
1168 		buddy->server_alias = NULL;
1169 		g_free(new_alias); /* could be "\0"; */
1170 	}
1171 
1172 	if (ops && ops->save_node)
1173 		ops->save_node((PurpleBlistNode*) buddy);
1174 
1175 	if (ops && ops->update)
1176 		ops->update(purplebuddylist, (PurpleBlistNode *)buddy);
1177 
1178 	conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, buddy->name,
1179 											   buddy->account);
1180 	if (conv)
1181 		purple_conversation_autoset_title(conv);
1182 
1183 	purple_signal_emit(purple_blist_get_handle(), "blist-node-aliased",
1184 					 buddy, old_alias);
1185 	g_free(old_alias);
1186 }
1187 
1188 /*
1189  * TODO: If merging, prompt the user if they want to merge.
1190  */
purple_blist_rename_group(PurpleGroup * source,const char * name)1191 void purple_blist_rename_group(PurpleGroup *source, const char *name)
1192 {
1193 	PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
1194 	PurpleGroup *dest;
1195 	gchar *old_name;
1196 	gchar *new_name;
1197 	GList *moved_buddies = NULL;
1198 	GSList *accts;
1199 
1200 	g_return_if_fail(source != NULL);
1201 	g_return_if_fail(name != NULL);
1202 
1203 	new_name = purple_utf8_strip_unprintables(name);
1204 
1205 	if (*new_name == '\0' || purple_strequal(new_name, source->name)) {
1206 		g_free(new_name);
1207 		return;
1208 	}
1209 
1210 	dest = purple_find_group(new_name);
1211 	if (dest != NULL && purple_utf8_strcasecmp(source->name, dest->name) != 0) {
1212 		/* We're merging two groups */
1213 		PurpleBlistNode *prev, *child, *next;
1214 
1215 		prev = purple_blist_get_last_child((PurpleBlistNode*)dest);
1216 		child = ((PurpleBlistNode*)source)->child;
1217 
1218 		/*
1219 		 * TODO: This seems like a dumb way to do this... why not just
1220 		 * append all children from the old group to the end of the new
1221 		 * one?  PRPLs might be expecting to receive an add_buddy() for
1222 		 * each moved buddy...
1223 		 */
1224 		while (child)
1225 		{
1226 			next = child->next;
1227 			if (PURPLE_BLIST_NODE_IS_CONTACT(child)) {
1228 				PurpleBlistNode *bnode;
1229 				purple_blist_add_contact((PurpleContact *)child, dest, prev);
1230 				for (bnode = child->child; bnode != NULL; bnode = bnode->next) {
1231 					purple_blist_add_buddy((PurpleBuddy *)bnode, (PurpleContact *)child,
1232 							NULL, bnode->prev);
1233 					moved_buddies = g_list_append(moved_buddies, bnode);
1234 				}
1235 				prev = child;
1236 			} else if (PURPLE_BLIST_NODE_IS_CHAT(child)) {
1237 				purple_blist_add_chat((PurpleChat *)child, dest, prev);
1238 				prev = child;
1239 			} else {
1240 				purple_debug(PURPLE_DEBUG_ERROR, "blist",
1241 						"Unknown child type in group %s\n", source->name);
1242 			}
1243 			child = next;
1244 		}
1245 
1246 		/* Make a copy of the old group name and then delete the old group */
1247 		old_name = g_strdup(source->name);
1248 		purple_blist_remove_group(source);
1249 		source = dest;
1250 		g_free(new_name);
1251 	} else {
1252 		/* A simple rename */
1253 		PurpleBlistNode *cnode, *bnode;
1254 		gchar* key;
1255 
1256 		/* Build a GList of all buddies in this group */
1257 		for (cnode = ((PurpleBlistNode *)source)->child; cnode != NULL; cnode = cnode->next) {
1258 			if (PURPLE_BLIST_NODE_IS_CONTACT(cnode))
1259 				for (bnode = cnode->child; bnode != NULL; bnode = bnode->next)
1260 					moved_buddies = g_list_append(moved_buddies, bnode);
1261 		}
1262 
1263 		old_name = source->name;
1264 		source->name = new_name;
1265 
1266 		key = g_utf8_collate_key(old_name, -1);
1267 		g_hash_table_remove(groups_cache, key);
1268 		g_free(key);
1269 
1270 		key = g_utf8_collate_key(new_name, -1);
1271 		g_hash_table_insert(groups_cache, key, source);
1272 	}
1273 
1274 	/* Save our changes */
1275 	if (ops && ops->save_node)
1276 		ops->save_node((PurpleBlistNode*) source);
1277 
1278 	/* Update the UI */
1279 	if (ops && ops->update)
1280 		ops->update(purplebuddylist, (PurpleBlistNode*)source);
1281 
1282 	/* Notify all PRPLs */
1283 	/* TODO: Is this condition needed?  Seems like it would always be TRUE */
1284 	if(old_name && !purple_strequal(source->name, old_name)) {
1285 		for (accts = purple_group_get_accounts(source); accts; accts = g_slist_remove(accts, accts->data)) {
1286 			PurpleAccount *account = accts->data;
1287 			PurpleConnection *gc = NULL;
1288 			PurplePlugin *prpl = NULL;
1289 			PurplePluginProtocolInfo *prpl_info = NULL;
1290 			GList *l = NULL, *buddies = NULL;
1291 
1292 			gc = purple_account_get_connection(account);
1293 
1294 			if(gc)
1295 				prpl = purple_connection_get_prpl(gc);
1296 
1297 			if(gc && prpl)
1298 				prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
1299 
1300 			if(!prpl_info)
1301 				continue;
1302 
1303 			for(l = moved_buddies; l; l = l->next) {
1304 				PurpleBuddy *buddy = (PurpleBuddy *)l->data;
1305 
1306 				if(buddy && buddy->account == account)
1307 					buddies = g_list_append(buddies, (PurpleBlistNode *)buddy);
1308 			}
1309 
1310 			if(prpl_info->rename_group) {
1311 				prpl_info->rename_group(gc, old_name, source, buddies);
1312 			} else {
1313 				GList *cur, *groups = NULL;
1314 
1315 				/* Make a list of what the groups each buddy is in */
1316 				for(cur = buddies; cur; cur = cur->next) {
1317 					PurpleBlistNode *node = (PurpleBlistNode *)cur->data;
1318 					groups = g_list_prepend(groups, node->parent->parent);
1319 				}
1320 
1321 				purple_account_remove_buddies(account, buddies, groups);
1322 				g_list_free(groups);
1323 				purple_account_add_buddies(account, buddies);
1324 			}
1325 
1326 			g_list_free(buddies);
1327 		}
1328 	}
1329 	g_list_free(moved_buddies);
1330 	g_free(old_name);
1331 }
1332 
1333 static void purple_blist_node_initialize_settings(PurpleBlistNode *node);
1334 
purple_chat_new(PurpleAccount * account,const char * alias,GHashTable * components)1335 PurpleChat *purple_chat_new(PurpleAccount *account, const char *alias, GHashTable *components)
1336 {
1337 	PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
1338 	PurpleChat *chat;
1339 
1340 	g_return_val_if_fail(account != NULL, NULL);
1341 	g_return_val_if_fail(components != NULL, NULL);
1342 
1343 	chat = g_new0(PurpleChat, 1);
1344 	chat->account = account;
1345 	if ((alias != NULL) && (*alias != '\0'))
1346 		chat->alias = purple_utf8_strip_unprintables(alias);
1347 	chat->components = components;
1348 	purple_blist_node_initialize_settings((PurpleBlistNode *)chat);
1349 	((PurpleBlistNode *)chat)->type = PURPLE_BLIST_CHAT_NODE;
1350 
1351 	if (ops != NULL && ops->new_node != NULL)
1352 		ops->new_node((PurpleBlistNode *)chat);
1353 
1354 	PURPLE_DBUS_REGISTER_POINTER(chat, PurpleChat);
1355 	return chat;
1356 }
1357 
1358 void
purple_chat_destroy(PurpleChat * chat)1359 purple_chat_destroy(PurpleChat *chat)
1360 {
1361 	g_hash_table_destroy(chat->components);
1362 	g_hash_table_destroy(chat->node.settings);
1363 	g_free(chat->alias);
1364 	PURPLE_DBUS_UNREGISTER_POINTER(chat);
1365 	g_free(chat);
1366 }
1367 
purple_buddy_new(PurpleAccount * account,const char * name,const char * alias)1368 PurpleBuddy *purple_buddy_new(PurpleAccount *account, const char *name, const char *alias)
1369 {
1370 	PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
1371 	PurpleBuddy *buddy;
1372 
1373 	g_return_val_if_fail(account != NULL, NULL);
1374 	g_return_val_if_fail(name != NULL, NULL);
1375 
1376 	buddy = g_new0(PurpleBuddy, 1);
1377 	buddy->account  = account;
1378 	buddy->name     = purple_utf8_strip_unprintables(name);
1379 	buddy->alias    = purple_utf8_strip_unprintables(alias);
1380 	buddy->presence = purple_presence_new_for_buddy(buddy);
1381 	((PurpleBlistNode *)buddy)->type = PURPLE_BLIST_BUDDY_NODE;
1382 
1383 	purple_presence_set_status_active(buddy->presence, "offline", TRUE);
1384 
1385 	purple_blist_node_initialize_settings((PurpleBlistNode *)buddy);
1386 
1387 	if (ops && ops->new_node)
1388 		ops->new_node((PurpleBlistNode *)buddy);
1389 
1390 	PURPLE_DBUS_REGISTER_POINTER(buddy, PurpleBuddy);
1391 	return buddy;
1392 }
1393 
1394 void
purple_buddy_destroy(PurpleBuddy * buddy)1395 purple_buddy_destroy(PurpleBuddy *buddy)
1396 {
1397 	PurplePlugin *prpl;
1398 	PurplePluginProtocolInfo *prpl_info;
1399 
1400 	/*
1401 	 * Tell the owner PRPL that we're about to free the buddy so it
1402 	 * can free proto_data
1403 	 */
1404 	prpl = purple_find_prpl(purple_account_get_protocol_id(buddy->account));
1405 	if (prpl) {
1406 		prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
1407 		if (prpl_info && prpl_info->buddy_free)
1408 			prpl_info->buddy_free(buddy);
1409 	}
1410 
1411 	/* Delete the node */
1412 	purple_buddy_icon_unref(buddy->icon);
1413 	g_hash_table_destroy(buddy->node.settings);
1414 	purple_presence_destroy(buddy->presence);
1415 	g_free(buddy->name);
1416 	g_free(buddy->alias);
1417 	g_free(buddy->server_alias);
1418 
1419 	PURPLE_DBUS_UNREGISTER_POINTER(buddy);
1420 
1421 	/* FIXME: Once PurpleBuddy is a GObject, timeout callbacks can
1422 	 * g_object_ref() it when connecting the callback and
1423 	 * g_object_unref() it in the handler.  That way, it won't
1424 	 * get freed while the timeout is pending and this line can
1425 	 * be removed. */
1426 	while (g_source_remove_by_user_data((gpointer *)buddy)) {
1427 	}
1428 
1429 	g_free(buddy);
1430 }
1431 
1432 void
purple_buddy_set_icon(PurpleBuddy * buddy,PurpleBuddyIcon * icon)1433 purple_buddy_set_icon(PurpleBuddy *buddy, PurpleBuddyIcon *icon)
1434 {
1435 	g_return_if_fail(buddy != NULL);
1436 
1437 	if (buddy->icon != icon)
1438 	{
1439 		purple_buddy_icon_unref(buddy->icon);
1440 		buddy->icon = (icon != NULL ? purple_buddy_icon_ref(icon) : NULL);
1441 	}
1442 
1443 	purple_signal_emit(purple_blist_get_handle(), "buddy-icon-changed", buddy);
1444 
1445 	purple_blist_update_node_icon((PurpleBlistNode*)buddy);
1446 }
1447 
1448 PurpleAccount *
purple_buddy_get_account(const PurpleBuddy * buddy)1449 purple_buddy_get_account(const PurpleBuddy *buddy)
1450 {
1451 	g_return_val_if_fail(buddy != NULL, NULL);
1452 
1453 	return buddy->account;
1454 }
1455 
1456 const char *
purple_buddy_get_name(const PurpleBuddy * buddy)1457 purple_buddy_get_name(const PurpleBuddy *buddy)
1458 {
1459 	g_return_val_if_fail(buddy != NULL, NULL);
1460 
1461 	return buddy->name;
1462 }
1463 
1464 PurpleBuddyIcon *
purple_buddy_get_icon(const PurpleBuddy * buddy)1465 purple_buddy_get_icon(const PurpleBuddy *buddy)
1466 {
1467 	g_return_val_if_fail(buddy != NULL, NULL);
1468 
1469 	return buddy->icon;
1470 }
1471 
1472 gpointer
purple_buddy_get_protocol_data(const PurpleBuddy * buddy)1473 purple_buddy_get_protocol_data(const PurpleBuddy *buddy)
1474 {
1475 	g_return_val_if_fail(buddy != NULL, NULL);
1476 
1477 	return buddy->proto_data;
1478 }
1479 
1480 void
purple_buddy_set_protocol_data(PurpleBuddy * buddy,gpointer data)1481 purple_buddy_set_protocol_data(PurpleBuddy *buddy, gpointer data)
1482 {
1483 	g_return_if_fail(buddy != NULL);
1484 
1485 	buddy->proto_data = data;
1486 }
1487 
1488 
purple_blist_add_chat(PurpleChat * chat,PurpleGroup * group,PurpleBlistNode * node)1489 void purple_blist_add_chat(PurpleChat *chat, PurpleGroup *group, PurpleBlistNode *node)
1490 {
1491 	PurpleBlistNode *cnode = (PurpleBlistNode*)chat;
1492 	PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
1493 
1494 	g_return_if_fail(chat != NULL);
1495 	g_return_if_fail(PURPLE_BLIST_NODE_IS_CHAT((PurpleBlistNode *)chat));
1496 
1497 	if (node == NULL) {
1498 		if (group == NULL)
1499 			group = purple_group_new(_("Chats"));
1500 
1501 		/* Add group to blist if isn't already on it. Fixes #2752. */
1502 		if (!purple_find_group(group->name)) {
1503 			purple_blist_add_group(group,
1504 					purple_blist_get_last_sibling(purplebuddylist->root));
1505 		}
1506 	} else {
1507 		group = (PurpleGroup*)node->parent;
1508 	}
1509 
1510 	/* if we're moving to overtop of ourselves, do nothing */
1511 	if (cnode == node)
1512 		return;
1513 
1514 	if (cnode->parent) {
1515 		/* This chat was already in the list and is
1516 		 * being moved.
1517 		 */
1518 		((PurpleGroup *)cnode->parent)->totalsize--;
1519 		if (purple_account_is_connected(chat->account)) {
1520 			((PurpleGroup *)cnode->parent)->online--;
1521 			((PurpleGroup *)cnode->parent)->currentsize--;
1522 		}
1523 		if (cnode->next)
1524 			cnode->next->prev = cnode->prev;
1525 		if (cnode->prev)
1526 			cnode->prev->next = cnode->next;
1527 		if (cnode->parent->child == cnode)
1528 			cnode->parent->child = cnode->next;
1529 
1530 		if (ops && ops->remove)
1531 			ops->remove(purplebuddylist, cnode);
1532 		/* ops->remove() cleaned up the cnode's ui_data, so we need to
1533 		 * reinitialize it */
1534 		if (ops && ops->new_node)
1535 			ops->new_node(cnode);
1536 	}
1537 
1538 	if (node != NULL) {
1539 		if (node->next)
1540 			node->next->prev = cnode;
1541 		cnode->next = node->next;
1542 		cnode->prev = node;
1543 		cnode->parent = node->parent;
1544 		node->next = cnode;
1545 		((PurpleGroup *)node->parent)->totalsize++;
1546 		if (purple_account_is_connected(chat->account)) {
1547 			((PurpleGroup *)node->parent)->online++;
1548 			((PurpleGroup *)node->parent)->currentsize++;
1549 		}
1550 	} else {
1551 		if (((PurpleBlistNode *)group)->child)
1552 			((PurpleBlistNode *)group)->child->prev = cnode;
1553 		cnode->next = ((PurpleBlistNode *)group)->child;
1554 		cnode->prev = NULL;
1555 		((PurpleBlistNode *)group)->child = cnode;
1556 		cnode->parent = (PurpleBlistNode *)group;
1557 		group->totalsize++;
1558 		if (purple_account_is_connected(chat->account)) {
1559 			group->online++;
1560 			group->currentsize++;
1561 		}
1562 	}
1563 
1564 	if (ops && ops->save_node)
1565 		ops->save_node(cnode);
1566 
1567 	if (ops && ops->update)
1568 		ops->update(purplebuddylist, (PurpleBlistNode *)cnode);
1569 
1570 	purple_signal_emit(purple_blist_get_handle(), "blist-node-added",
1571 			cnode);
1572 }
1573 
purple_blist_add_buddy(PurpleBuddy * buddy,PurpleContact * contact,PurpleGroup * group,PurpleBlistNode * node)1574 void purple_blist_add_buddy(PurpleBuddy *buddy, PurpleContact *contact, PurpleGroup *group, PurpleBlistNode *node)
1575 {
1576 	PurpleBlistNode *cnode, *bnode;
1577 	PurpleGroup *g;
1578 	PurpleContact *c;
1579 	PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
1580 	struct _purple_hbuddy *hb, *hb2;
1581 	GHashTable *account_buddies;
1582 
1583 	g_return_if_fail(buddy != NULL);
1584 	g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY((PurpleBlistNode*)buddy));
1585 
1586 	bnode = (PurpleBlistNode *)buddy;
1587 
1588 	/* if we're moving to overtop of ourselves, do nothing */
1589 	if (bnode == node || (!node && bnode->parent &&
1590 				contact && bnode->parent == (PurpleBlistNode*)contact
1591 				&& bnode == bnode->parent->child))
1592 		return;
1593 
1594 	if (node && PURPLE_BLIST_NODE_IS_BUDDY(node)) {
1595 		c = (PurpleContact*)node->parent;
1596 		g = (PurpleGroup*)node->parent->parent;
1597 	} else if (contact) {
1598 		c = contact;
1599 		g = PURPLE_GROUP(PURPLE_BLIST_NODE(c)->parent);
1600 	} else {
1601 		g = group;
1602 		if (g == NULL)
1603 			g = purple_group_new(_("Buddies"));
1604 		/* Add group to blist if isn't already on it. Fixes #2752. */
1605 		if (!purple_find_group(g->name)) {
1606 			purple_blist_add_group(g,
1607 					purple_blist_get_last_sibling(purplebuddylist->root));
1608 		}
1609 		c = purple_contact_new();
1610 		purple_blist_add_contact(c, g,
1611 				purple_blist_get_last_child((PurpleBlistNode*)g));
1612 	}
1613 
1614 	cnode = (PurpleBlistNode *)c;
1615 
1616 	if (bnode->parent) {
1617 		if (PURPLE_BUDDY_IS_ONLINE(buddy)) {
1618 			((PurpleContact*)bnode->parent)->online--;
1619 			if (((PurpleContact*)bnode->parent)->online == 0)
1620 				((PurpleGroup*)bnode->parent->parent)->online--;
1621 		}
1622 		if (purple_account_is_connected(buddy->account)) {
1623 			((PurpleContact*)bnode->parent)->currentsize--;
1624 			if (((PurpleContact*)bnode->parent)->currentsize == 0)
1625 				((PurpleGroup*)bnode->parent->parent)->currentsize--;
1626 		}
1627 		((PurpleContact*)bnode->parent)->totalsize--;
1628 		/* the group totalsize will be taken care of by remove_contact below */
1629 
1630 		if (bnode->parent->parent != (PurpleBlistNode*)g) {
1631 			purple_signal_emit(purple_blist_get_handle(), "buddy-removed-from-group", buddy);
1632 			serv_move_buddy(buddy, (PurpleGroup *)bnode->parent->parent, g);
1633 		}
1634 
1635 		if (bnode->next)
1636 			bnode->next->prev = bnode->prev;
1637 		if (bnode->prev)
1638 			bnode->prev->next = bnode->next;
1639 		if (bnode->parent->child == bnode)
1640 			bnode->parent->child = bnode->next;
1641 
1642 		if (ops && ops->remove)
1643 			ops->remove(purplebuddylist, bnode);
1644 
1645 		if (bnode->parent->parent != (PurpleBlistNode*)g) {
1646 			struct _purple_hbuddy hb;
1647 			hb.name = (gchar *)purple_normalize(buddy->account, buddy->name);
1648 			hb.account = buddy->account;
1649 			hb.group = bnode->parent->parent;
1650 			g_hash_table_remove(purplebuddylist->buddies, &hb);
1651 
1652 			account_buddies = g_hash_table_lookup(buddies_cache, buddy->account);
1653 			g_hash_table_remove(account_buddies, &hb);
1654 		}
1655 
1656 		if (!bnode->parent->child) {
1657 			purple_blist_remove_contact((PurpleContact*)bnode->parent);
1658 		} else {
1659 			purple_contact_invalidate_priority_buddy((PurpleContact*)bnode->parent);
1660 			if (ops && ops->update)
1661 				ops->update(purplebuddylist, bnode->parent);
1662 		}
1663 	}
1664 
1665 	if (node && PURPLE_BLIST_NODE_IS_BUDDY(node)) {
1666 		if (node->next)
1667 			node->next->prev = bnode;
1668 		bnode->next = node->next;
1669 		bnode->prev = node;
1670 		bnode->parent = node->parent;
1671 		node->next = bnode;
1672 	} else {
1673 		if (cnode->child)
1674 			cnode->child->prev = bnode;
1675 		bnode->prev = NULL;
1676 		bnode->next = cnode->child;
1677 		cnode->child = bnode;
1678 		bnode->parent = cnode;
1679 	}
1680 
1681 	if (PURPLE_BUDDY_IS_ONLINE(buddy)) {
1682 		if (++(PURPLE_CONTACT(bnode->parent)->online) == 1)
1683 			PURPLE_GROUP(bnode->parent->parent)->online++;
1684 	}
1685 	if (purple_account_is_connected(buddy->account)) {
1686 		if (++(PURPLE_CONTACT(bnode->parent)->currentsize) == 1)
1687 			PURPLE_GROUP(bnode->parent->parent)->currentsize++;
1688 	}
1689 	PURPLE_CONTACT(bnode->parent)->totalsize++;
1690 
1691 	hb = g_new(struct _purple_hbuddy, 1);
1692 	hb->name = g_strdup(purple_normalize(buddy->account, buddy->name));
1693 	hb->account = buddy->account;
1694 	hb->group = ((PurpleBlistNode*)buddy)->parent->parent;
1695 
1696 	g_hash_table_replace(purplebuddylist->buddies, hb, buddy);
1697 
1698 	account_buddies = g_hash_table_lookup(buddies_cache, buddy->account);
1699 
1700 	hb2 = g_new(struct _purple_hbuddy, 1);
1701 	hb2->name = g_strdup(hb->name);
1702 	hb2->account = buddy->account;
1703 	hb2->group = ((PurpleBlistNode*)buddy)->parent->parent;
1704 
1705 	g_hash_table_replace(account_buddies, hb2, buddy);
1706 
1707 	purple_contact_invalidate_priority_buddy(purple_buddy_get_contact(buddy));
1708 
1709 	if (ops && ops->save_node)
1710 		ops->save_node((PurpleBlistNode*) buddy);
1711 
1712 	if (ops && ops->update)
1713 		ops->update(purplebuddylist, (PurpleBlistNode*)buddy);
1714 
1715 	/* Signal that the buddy has been added */
1716 	purple_signal_emit(purple_blist_get_handle(), "buddy-added", buddy);
1717 
1718 	purple_signal_emit(purple_blist_get_handle(), "blist-node-added",
1719 			PURPLE_BLIST_NODE(buddy));
1720 }
1721 
purple_contact_new()1722 PurpleContact *purple_contact_new()
1723 {
1724 	PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
1725 
1726 	PurpleContact *contact = g_new0(PurpleContact, 1);
1727 	contact->totalsize = 0;
1728 	contact->currentsize = 0;
1729 	contact->online = 0;
1730 	purple_blist_node_initialize_settings((PurpleBlistNode *)contact);
1731 	((PurpleBlistNode *)contact)->type = PURPLE_BLIST_CONTACT_NODE;
1732 
1733 	if (ops && ops->new_node)
1734 		ops->new_node((PurpleBlistNode *)contact);
1735 
1736 	PURPLE_DBUS_REGISTER_POINTER(contact, PurpleContact);
1737 	return contact;
1738 }
1739 
1740 void
purple_contact_destroy(PurpleContact * contact)1741 purple_contact_destroy(PurpleContact *contact)
1742 {
1743 	g_hash_table_destroy(contact->node.settings);
1744 	g_free(contact->alias);
1745 	PURPLE_DBUS_UNREGISTER_POINTER(contact);
1746 	g_free(contact);
1747 }
1748 
1749 PurpleGroup *
purple_contact_get_group(const PurpleContact * contact)1750 purple_contact_get_group(const PurpleContact *contact)
1751 {
1752 	g_return_val_if_fail(contact, NULL);
1753 
1754 	return (PurpleGroup *)(((PurpleBlistNode *)contact)->parent);
1755 }
1756 
purple_contact_set_alias(PurpleContact * contact,const char * alias)1757 void purple_contact_set_alias(PurpleContact *contact, const char *alias)
1758 {
1759 	purple_blist_alias_contact(contact,alias);
1760 }
1761 
purple_contact_get_alias(PurpleContact * contact)1762 const char *purple_contact_get_alias(PurpleContact* contact)
1763 {
1764 	g_return_val_if_fail(contact != NULL, NULL);
1765 
1766 	if (contact->alias)
1767 		return contact->alias;
1768 
1769 	return purple_buddy_get_alias(purple_contact_get_priority_buddy(contact));
1770 }
1771 
purple_contact_on_account(PurpleContact * c,PurpleAccount * account)1772 gboolean purple_contact_on_account(PurpleContact *c, PurpleAccount *account)
1773 {
1774 	PurpleBlistNode *bnode, *cnode = (PurpleBlistNode *) c;
1775 
1776 	g_return_val_if_fail(c != NULL, FALSE);
1777 	g_return_val_if_fail(account != NULL, FALSE);
1778 
1779 	for (bnode = cnode->child; bnode; bnode = bnode->next) {
1780 		PurpleBuddy *buddy;
1781 
1782 		if (! PURPLE_BLIST_NODE_IS_BUDDY(bnode))
1783 			continue;
1784 
1785 		buddy = (PurpleBuddy *)bnode;
1786 		if (buddy->account == account)
1787 			return TRUE;
1788 	}
1789 	return FALSE;
1790 }
1791 
purple_contact_invalidate_priority_buddy(PurpleContact * contact)1792 void purple_contact_invalidate_priority_buddy(PurpleContact *contact)
1793 {
1794 	g_return_if_fail(contact != NULL);
1795 
1796 	contact->priority_valid = FALSE;
1797 }
1798 
purple_group_new(const char * name)1799 PurpleGroup *purple_group_new(const char *name)
1800 {
1801 	PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
1802 	PurpleGroup *group;
1803 
1804 	g_return_val_if_fail(name  != NULL, NULL);
1805 	g_return_val_if_fail(*name != '\0', NULL);
1806 
1807 	group = purple_find_group(name);
1808 	if (group != NULL)
1809 		return group;
1810 
1811 	group = g_new0(PurpleGroup, 1);
1812 	group->name = purple_utf8_strip_unprintables(name);
1813 	group->totalsize = 0;
1814 	group->currentsize = 0;
1815 	group->online = 0;
1816 	purple_blist_node_initialize_settings((PurpleBlistNode *)group);
1817 	((PurpleBlistNode *)group)->type = PURPLE_BLIST_GROUP_NODE;
1818 
1819 	if (ops && ops->new_node)
1820 		ops->new_node((PurpleBlistNode *)group);
1821 
1822 	PURPLE_DBUS_REGISTER_POINTER(group, PurpleGroup);
1823 	return group;
1824 }
1825 
1826 void
purple_group_destroy(PurpleGroup * group)1827 purple_group_destroy(PurpleGroup *group)
1828 {
1829 	g_hash_table_destroy(group->node.settings);
1830 	g_free(group->name);
1831 	PURPLE_DBUS_UNREGISTER_POINTER(group);
1832 	g_free(group);
1833 }
1834 
purple_blist_add_contact(PurpleContact * contact,PurpleGroup * group,PurpleBlistNode * node)1835 void purple_blist_add_contact(PurpleContact *contact, PurpleGroup *group, PurpleBlistNode *node)
1836 {
1837 	PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
1838 	PurpleGroup *g;
1839 	PurpleBlistNode *gnode, *cnode, *bnode;
1840 
1841 	g_return_if_fail(contact != NULL);
1842 	g_return_if_fail(PURPLE_BLIST_NODE_IS_CONTACT((PurpleBlistNode*)contact));
1843 
1844 	if (PURPLE_BLIST_NODE(contact) == node)
1845 		return;
1846 
1847 	if (node && (PURPLE_BLIST_NODE_IS_CONTACT(node) ||
1848 				PURPLE_BLIST_NODE_IS_CHAT(node)))
1849 		g = (PurpleGroup*)node->parent;
1850 	else if (group)
1851 		g = group;
1852 	else {
1853 		g = purple_find_group(_("Buddies"));
1854 		if (g == NULL) {
1855 			g = purple_group_new(_("Buddies"));
1856 			purple_blist_add_group(g,
1857 					purple_blist_get_last_sibling(purplebuddylist->root));
1858 		}
1859 	}
1860 
1861 	gnode = (PurpleBlistNode*)g;
1862 	cnode = (PurpleBlistNode*)contact;
1863 
1864 	if (cnode->parent) {
1865 		if (cnode->parent->child == cnode)
1866 			cnode->parent->child = cnode->next;
1867 		if (cnode->prev)
1868 			cnode->prev->next = cnode->next;
1869 		if (cnode->next)
1870 			cnode->next->prev = cnode->prev;
1871 
1872 		if (cnode->parent != gnode) {
1873 			bnode = cnode->child;
1874 			while (bnode) {
1875 				PurpleBlistNode *next_bnode = bnode->next;
1876 				PurpleBuddy *b = (PurpleBuddy*)bnode;
1877 				GHashTable *account_buddies;
1878 
1879 				struct _purple_hbuddy *hb, *hb2;
1880 
1881 				hb = g_new(struct _purple_hbuddy, 1);
1882 				hb->name = g_strdup(purple_normalize(b->account, b->name));
1883 				hb->account = b->account;
1884 				hb->group = cnode->parent;
1885 
1886 				g_hash_table_remove(purplebuddylist->buddies, hb);
1887 
1888 				account_buddies = g_hash_table_lookup(buddies_cache, b->account);
1889 				g_hash_table_remove(account_buddies, hb);
1890 
1891 				if (!purple_find_buddy_in_group(b->account, b->name, g)) {
1892 					hb->group = gnode;
1893 					g_hash_table_replace(purplebuddylist->buddies, hb, b);
1894 
1895 					hb2 = g_new(struct _purple_hbuddy, 1);
1896 					hb2->name = g_strdup(hb->name);
1897 					hb2->account = b->account;
1898 					hb2->group = gnode;
1899 
1900 					g_hash_table_replace(account_buddies, hb2, b);
1901 
1902 					if (purple_account_get_connection(b->account))
1903 						serv_move_buddy(b, (PurpleGroup *)cnode->parent, g);
1904 				} else {
1905 					gboolean empty_contact = FALSE;
1906 
1907 					/* this buddy already exists in the group, so we're
1908 					 * gonna delete it instead */
1909 					g_free(hb->name);
1910 					g_free(hb);
1911 					if (purple_account_get_connection(b->account))
1912 						purple_account_remove_buddy(b->account, b, (PurpleGroup *)cnode->parent);
1913 
1914 					if (!cnode->child->next)
1915 						empty_contact = TRUE;
1916 					purple_blist_remove_buddy(b);
1917 
1918 					/** in purple_blist_remove_buddy(), if the last buddy in a
1919 					 * contact is removed, the contact is cleaned up and
1920 					 * g_free'd, so we mustn't try to reference bnode->next */
1921 					if (empty_contact)
1922 						return;
1923 				}
1924 				bnode = next_bnode;
1925 			}
1926 		}
1927 
1928 		if (contact->online > 0)
1929 			((PurpleGroup*)cnode->parent)->online--;
1930 		if (contact->currentsize > 0)
1931 			((PurpleGroup*)cnode->parent)->currentsize--;
1932 		((PurpleGroup*)cnode->parent)->totalsize--;
1933 
1934 		if (ops && ops->remove)
1935 			ops->remove(purplebuddylist, cnode);
1936 
1937 		if (ops && ops->remove_node)
1938 			ops->remove_node(cnode);
1939 	}
1940 
1941 	if (node && (PURPLE_BLIST_NODE_IS_CONTACT(node) ||
1942 				PURPLE_BLIST_NODE_IS_CHAT(node))) {
1943 		if (node->next)
1944 			node->next->prev = cnode;
1945 		cnode->next = node->next;
1946 		cnode->prev = node;
1947 		cnode->parent = node->parent;
1948 		node->next = cnode;
1949 	} else {
1950 		if (gnode->child)
1951 			gnode->child->prev = cnode;
1952 		cnode->prev = NULL;
1953 		cnode->next = gnode->child;
1954 		gnode->child = cnode;
1955 		cnode->parent = gnode;
1956 	}
1957 
1958 	if (contact->online > 0)
1959 		g->online++;
1960 	if (contact->currentsize > 0)
1961 		g->currentsize++;
1962 	g->totalsize++;
1963 
1964 	if (ops && ops->save_node)
1965 	{
1966 		if (cnode->child)
1967 			ops->save_node(cnode);
1968 		for (bnode = cnode->child; bnode; bnode = bnode->next)
1969 			ops->save_node(bnode);
1970 	}
1971 
1972 	if (ops && ops->update)
1973 	{
1974 		if (cnode->child)
1975 			ops->update(purplebuddylist, cnode);
1976 
1977 		for (bnode = cnode->child; bnode; bnode = bnode->next)
1978 			ops->update(purplebuddylist, bnode);
1979 	}
1980 }
1981 
purple_blist_merge_contact(PurpleContact * source,PurpleBlistNode * node)1982 void purple_blist_merge_contact(PurpleContact *source, PurpleBlistNode *node)
1983 {
1984 	PurpleBlistNode *sourcenode = (PurpleBlistNode*)source;
1985 	PurpleBlistNode *prev, *cur, *next;
1986 	PurpleContact *target;
1987 
1988 	g_return_if_fail(source != NULL);
1989 	g_return_if_fail(node != NULL);
1990 
1991 	if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
1992 		target = (PurpleContact *)node;
1993 		prev = purple_blist_get_last_child(node);
1994 	} else if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
1995 		target = (PurpleContact *)node->parent;
1996 		prev = node;
1997 	} else {
1998 		return;
1999 	}
2000 
2001 	if (source == target || !target)
2002 		return;
2003 
2004 	next = sourcenode->child;
2005 
2006 	while (next) {
2007 		cur = next;
2008 		next = cur->next;
2009 		if (PURPLE_BLIST_NODE_IS_BUDDY(cur)) {
2010 			purple_blist_add_buddy((PurpleBuddy *)cur, target, NULL, prev);
2011 			prev = cur;
2012 		}
2013 	}
2014 }
2015 
purple_blist_add_group(PurpleGroup * group,PurpleBlistNode * node)2016 void purple_blist_add_group(PurpleGroup *group, PurpleBlistNode *node)
2017 {
2018 	PurpleBlistUiOps *ops;
2019 	PurpleBlistNode *gnode = (PurpleBlistNode*)group;
2020 	gchar* key;
2021 
2022 	g_return_if_fail(group != NULL);
2023 	g_return_if_fail(PURPLE_BLIST_NODE_IS_GROUP((PurpleBlistNode *)group));
2024 
2025 	ops = purple_blist_get_ui_ops();
2026 
2027 	/* if we're moving to overtop of ourselves, do nothing */
2028 	if (gnode == node) {
2029 		if (!purplebuddylist->root)
2030 			node = NULL;
2031 		else
2032 			return;
2033 	}
2034 
2035 	if (purple_find_group(group->name)) {
2036 		/* This is just being moved */
2037 
2038 		if (ops && ops->remove)
2039 			ops->remove(purplebuddylist, (PurpleBlistNode *)group);
2040 
2041 		if (gnode == purplebuddylist->root)
2042 			purplebuddylist->root = gnode->next;
2043 		if (gnode->prev)
2044 			gnode->prev->next = gnode->next;
2045 		if (gnode->next)
2046 			gnode->next->prev = gnode->prev;
2047 	} else {
2048 		key = g_utf8_collate_key(group->name, -1);
2049 		g_hash_table_insert(groups_cache, key, group);
2050 	}
2051 
2052 	if (node && PURPLE_BLIST_NODE_IS_GROUP(node)) {
2053 		gnode->next = node->next;
2054 		gnode->prev = node;
2055 		if (node->next)
2056 			node->next->prev = gnode;
2057 		node->next = gnode;
2058 	} else {
2059 		if (purplebuddylist->root)
2060 			purplebuddylist->root->prev = gnode;
2061 		gnode->next = purplebuddylist->root;
2062 		gnode->prev = NULL;
2063 		purplebuddylist->root = gnode;
2064 	}
2065 
2066 	if (ops && ops->save_node) {
2067 		ops->save_node(gnode);
2068 		for (node = gnode->child; node; node = node->next)
2069 			ops->save_node(node);
2070 	}
2071 
2072 	if (ops && ops->update) {
2073 		ops->update(purplebuddylist, gnode);
2074 		for (node = gnode->child; node; node = node->next)
2075 			ops->update(purplebuddylist, node);
2076 	}
2077 
2078 	purple_signal_emit(purple_blist_get_handle(), "blist-node-added",
2079 			gnode);
2080 }
2081 
purple_blist_remove_contact(PurpleContact * contact)2082 void purple_blist_remove_contact(PurpleContact *contact)
2083 {
2084 	PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
2085 	PurpleBlistNode *node, *gnode;
2086 
2087 	g_return_if_fail(contact != NULL);
2088 
2089 	node = (PurpleBlistNode *)contact;
2090 	gnode = node->parent;
2091 
2092 	if (node->child) {
2093 		/*
2094 		 * If this contact has children then remove them.  When the last
2095 		 * buddy is removed from the contact, the contact is automatically
2096 		 * deleted.
2097 		 */
2098 		while (node->child->next) {
2099 			purple_blist_remove_buddy((PurpleBuddy*)node->child);
2100 		}
2101 		/*
2102 		 * Remove the last buddy and trigger the deletion of the contact.
2103 		 * It would probably be cleaner if contact-deletion was done after
2104 		 * a timeout?  Or if it had to be done manually, like below?
2105 		 */
2106 		purple_blist_remove_buddy((PurpleBuddy*)node->child);
2107 	} else {
2108 		/* Remove the node from its parent */
2109 		if (gnode->child == node)
2110 			gnode->child = node->next;
2111 		if (node->prev)
2112 			node->prev->next = node->next;
2113 		if (node->next)
2114 			node->next->prev = node->prev;
2115 
2116 		/* Update the UI */
2117 		if (ops && ops->remove)
2118 			ops->remove(purplebuddylist, node);
2119 
2120 		if (ops && ops->remove_node)
2121 			ops->remove_node(node);
2122 
2123 		purple_signal_emit(purple_blist_get_handle(), "blist-node-removed",
2124 				PURPLE_BLIST_NODE(contact));
2125 
2126 		/* Delete the node */
2127 		purple_contact_destroy(contact);
2128 	}
2129 }
2130 
purple_blist_remove_buddy(PurpleBuddy * buddy)2131 void purple_blist_remove_buddy(PurpleBuddy *buddy)
2132 {
2133 	PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
2134 	PurpleBlistNode *node, *cnode, *gnode;
2135 	PurpleContact *contact;
2136 	PurpleGroup *group;
2137 	struct _purple_hbuddy hb;
2138 	GHashTable *account_buddies;
2139 
2140 	g_return_if_fail(buddy != NULL);
2141 
2142 	node = (PurpleBlistNode *)buddy;
2143 	cnode = node->parent;
2144 	gnode = (cnode != NULL) ? cnode->parent : NULL;
2145 	contact = (PurpleContact *)cnode;
2146 	group = (PurpleGroup *)gnode;
2147 
2148 	/* Remove the node from its parent */
2149 	if (node->prev)
2150 		node->prev->next = node->next;
2151 	if (node->next)
2152 		node->next->prev = node->prev;
2153 	if ((cnode != NULL) && (cnode->child == node))
2154 		cnode->child = node->next;
2155 
2156 	/* Adjust size counts */
2157 	if (contact != NULL) {
2158 		if (PURPLE_BUDDY_IS_ONLINE(buddy)) {
2159 			contact->online--;
2160 			if (contact->online == 0)
2161 				group->online--;
2162 		}
2163 		if (purple_account_is_connected(buddy->account)) {
2164 			contact->currentsize--;
2165 			if (contact->currentsize == 0)
2166 				group->currentsize--;
2167 		}
2168 		contact->totalsize--;
2169 
2170 		/* Re-sort the contact */
2171 		if (cnode->child && contact->priority == buddy) {
2172 			purple_contact_invalidate_priority_buddy(contact);
2173 			if (ops && ops->update)
2174 				ops->update(purplebuddylist, cnode);
2175 		}
2176 	}
2177 
2178 	/* Remove this buddy from the buddies hash table */
2179 	hb.name = (gchar *)purple_normalize(buddy->account, buddy->name);
2180 	hb.account = buddy->account;
2181 	hb.group = gnode;
2182 	g_hash_table_remove(purplebuddylist->buddies, &hb);
2183 
2184 	account_buddies = g_hash_table_lookup(buddies_cache, buddy->account);
2185 	g_hash_table_remove(account_buddies, &hb);
2186 
2187 	/* Update the UI */
2188 	if (ops && ops->remove)
2189 		ops->remove(purplebuddylist, node);
2190 
2191 	if (ops && ops->remove_node)
2192 		ops->remove_node(node);
2193 
2194 	/* Remove this buddy's pounces */
2195 	purple_pounce_destroy_all_by_buddy(buddy);
2196 
2197 	/* Signal that the buddy has been removed before freeing the memory for it */
2198 	purple_signal_emit(purple_blist_get_handle(), "buddy-removed", buddy);
2199 
2200 	purple_signal_emit(purple_blist_get_handle(), "blist-node-removed",
2201 			PURPLE_BLIST_NODE(buddy));
2202 
2203 	purple_buddy_destroy(buddy);
2204 
2205 	/* If the contact is empty then remove it */
2206 	if ((contact != NULL) && !cnode->child)
2207 		purple_blist_remove_contact(contact);
2208 }
2209 
purple_blist_remove_chat(PurpleChat * chat)2210 void purple_blist_remove_chat(PurpleChat *chat)
2211 {
2212 	PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
2213 	PurpleBlistNode *node, *gnode;
2214 	PurpleGroup *group;
2215 
2216 	g_return_if_fail(chat != NULL);
2217 
2218 	node = (PurpleBlistNode *)chat;
2219 	gnode = node->parent;
2220 	group = (PurpleGroup *)gnode;
2221 
2222 	if (gnode != NULL)
2223 	{
2224 		/* Remove the node from its parent */
2225 		if (gnode->child == node)
2226 			gnode->child = node->next;
2227 		if (node->prev)
2228 			node->prev->next = node->next;
2229 		if (node->next)
2230 			node->next->prev = node->prev;
2231 
2232 		/* Adjust size counts */
2233 		if (purple_account_is_connected(chat->account)) {
2234 			group->online--;
2235 			group->currentsize--;
2236 		}
2237 		group->totalsize--;
2238 
2239 	}
2240 
2241 	/* Update the UI */
2242 	if (ops && ops->remove)
2243 		ops->remove(purplebuddylist, node);
2244 
2245 	if (ops && ops->remove_node)
2246 		ops->remove_node(node);
2247 
2248 	purple_signal_emit(purple_blist_get_handle(), "blist-node-removed",
2249 			PURPLE_BLIST_NODE(chat));
2250 
2251 	/* Delete the node */
2252 	purple_chat_destroy(chat);
2253 }
2254 
purple_blist_remove_group(PurpleGroup * group)2255 void purple_blist_remove_group(PurpleGroup *group)
2256 {
2257 	PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
2258 	PurpleBlistNode *node;
2259 	GList *l;
2260 	gchar* key;
2261 
2262 	g_return_if_fail(group != NULL);
2263 
2264 	node = (PurpleBlistNode *)group;
2265 
2266 	/* Make sure the group is empty */
2267 	if (node->child)
2268 		return;
2269 
2270 	/* Remove the node from its parent */
2271 	if (purplebuddylist->root == node)
2272 		purplebuddylist->root = node->next;
2273 	if (node->prev)
2274 		node->prev->next = node->next;
2275 	if (node->next)
2276 		node->next->prev = node->prev;
2277 
2278 	key = g_utf8_collate_key(group->name, -1);
2279 	g_hash_table_remove(groups_cache, key);
2280 	g_free(key);
2281 
2282 	/* Update the UI */
2283 	if (ops && ops->remove)
2284 		ops->remove(purplebuddylist, node);
2285 
2286 	if (ops && ops->remove_node)
2287 		ops->remove_node(node);
2288 
2289 	purple_signal_emit(purple_blist_get_handle(), "blist-node-removed",
2290 			PURPLE_BLIST_NODE(group));
2291 
2292 	/* Remove the group from all accounts that are online */
2293 	for (l = purple_connections_get_all(); l != NULL; l = l->next)
2294 	{
2295 		PurpleConnection *gc = (PurpleConnection *)l->data;
2296 
2297 		if (purple_connection_get_state(gc) == PURPLE_CONNECTED)
2298 			purple_account_remove_group(purple_connection_get_account(gc), group);
2299 	}
2300 
2301 	/* Delete the node */
2302 	purple_group_destroy(group);
2303 }
2304 
purple_contact_get_priority_buddy(PurpleContact * contact)2305 PurpleBuddy *purple_contact_get_priority_buddy(PurpleContact *contact)
2306 {
2307 	g_return_val_if_fail(contact != NULL, NULL);
2308 
2309 	if (!contact->priority_valid)
2310 		purple_contact_compute_priority_buddy(contact);
2311 
2312 	return contact->priority;
2313 }
2314 
purple_buddy_get_alias_only(PurpleBuddy * buddy)2315 const char *purple_buddy_get_alias_only(PurpleBuddy *buddy)
2316 {
2317 	g_return_val_if_fail(buddy != NULL, NULL);
2318 
2319 	if ((buddy->alias != NULL) && (*buddy->alias != '\0')) {
2320 		return buddy->alias;
2321 	} else if ((buddy->server_alias != NULL) &&
2322 		   (*buddy->server_alias != '\0')) {
2323 
2324 		return buddy->server_alias;
2325 	}
2326 
2327 	return NULL;
2328 }
2329 
2330 
purple_buddy_get_contact_alias(PurpleBuddy * buddy)2331 const char *purple_buddy_get_contact_alias(PurpleBuddy *buddy)
2332 {
2333 	PurpleContact *c;
2334 
2335 	g_return_val_if_fail(buddy != NULL, NULL);
2336 
2337 	/* Search for an alias for the buddy. In order of precedence: */
2338 	/* The buddy alias */
2339 	if (buddy->alias != NULL)
2340 		return buddy->alias;
2341 
2342 	/* The contact alias */
2343 	c = purple_buddy_get_contact(buddy);
2344 	if ((c != NULL) && (c->alias != NULL))
2345 		return c->alias;
2346 
2347 	/* The server alias */
2348 	if ((buddy->server_alias) && (*buddy->server_alias))
2349 		return buddy->server_alias;
2350 
2351 	/* The buddy's user name (i.e. no alias) */
2352 	return buddy->name;
2353 }
2354 
2355 
purple_buddy_get_alias(PurpleBuddy * buddy)2356 const char *purple_buddy_get_alias(PurpleBuddy *buddy)
2357 {
2358 	g_return_val_if_fail(buddy != NULL, NULL);
2359 
2360 	/* Search for an alias for the buddy. In order of precedence: */
2361 	/* The buddy alias */
2362 	if (buddy->alias != NULL)
2363 		return buddy->alias;
2364 
2365 	/* The server alias */
2366 	if ((buddy->server_alias) && (*buddy->server_alias))
2367 		return buddy->server_alias;
2368 
2369 	/* The buddy's user name (i.e. no alias) */
2370 	return buddy->name;
2371 }
2372 
purple_buddy_get_local_buddy_alias(PurpleBuddy * buddy)2373 const char *purple_buddy_get_local_buddy_alias(PurpleBuddy *buddy)
2374 {
2375 	g_return_val_if_fail(buddy, NULL);
2376 	return buddy->alias;
2377 }
2378 
purple_buddy_get_server_alias(PurpleBuddy * buddy)2379 const char *purple_buddy_get_server_alias(PurpleBuddy *buddy)
2380 {
2381         g_return_val_if_fail(buddy != NULL, NULL);
2382 
2383 	if ((buddy->server_alias) && (*buddy->server_alias))
2384 	    return buddy->server_alias;
2385 
2386 	return NULL;
2387 }
2388 
purple_buddy_get_local_alias(PurpleBuddy * buddy)2389 const char *purple_buddy_get_local_alias(PurpleBuddy *buddy)
2390 {
2391 	PurpleContact *c;
2392 
2393 	g_return_val_if_fail(buddy != NULL, NULL);
2394 
2395 	/* Search for an alias for the buddy. In order of precedence: */
2396 	/* The buddy alias */
2397 	if (buddy->alias != NULL)
2398 		return buddy->alias;
2399 
2400 	/* The contact alias */
2401 	c = purple_buddy_get_contact(buddy);
2402 	if ((c != NULL) && (c->alias != NULL))
2403 		return c->alias;
2404 
2405 	/* The buddy's user name (i.e. no alias) */
2406 	return buddy->name;
2407 }
2408 
purple_chat_get_name(PurpleChat * chat)2409 const char *purple_chat_get_name(PurpleChat *chat)
2410 {
2411 	char *ret = NULL;
2412 	PurplePlugin *prpl;
2413 	PurplePluginProtocolInfo *prpl_info = NULL;
2414 
2415 	g_return_val_if_fail(chat != NULL, NULL);
2416 
2417 	if ((chat->alias != NULL) && (*chat->alias != '\0'))
2418 		return chat->alias;
2419 
2420 	prpl = purple_find_prpl(purple_account_get_protocol_id(chat->account));
2421 	prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
2422 
2423 	if (prpl_info->chat_info) {
2424 		struct proto_chat_entry *pce;
2425 		GList *parts = prpl_info->chat_info(purple_account_get_connection(chat->account));
2426 		pce = parts->data;
2427 		ret = g_hash_table_lookup(chat->components, pce->identifier);
2428 		g_list_free_full(parts, (GDestroyNotify)g_free);
2429 	}
2430 
2431 	return ret;
2432 }
2433 
purple_find_buddy(PurpleAccount * account,const char * name)2434 PurpleBuddy *purple_find_buddy(PurpleAccount *account, const char *name)
2435 {
2436 	PurpleBuddy *buddy, *fallback_buddy = NULL;
2437 	struct _purple_hbuddy hb;
2438 	PurpleBlistNode *group;
2439 
2440 	g_return_val_if_fail(purplebuddylist != NULL, NULL);
2441 	g_return_val_if_fail(account != NULL, NULL);
2442 	g_return_val_if_fail((name != NULL) && (*name != '\0'), NULL);
2443 
2444 	hb.account = account;
2445 	hb.name = (gchar *)purple_normalize(account, name);
2446 
2447 	for (group = purplebuddylist->root; group; group = group->next) {
2448 		if (!group->child)
2449 			continue;
2450 
2451 		hb.group = group;
2452 		if ((buddy = g_hash_table_lookup(purplebuddylist->buddies, &hb))) {
2453 			if (PURPLE_BLIST_NODE_IS_VISIBLE(buddy))
2454 				return buddy;
2455 			/* Only return invisible buddies if there are no visible ones */
2456 			fallback_buddy = buddy;
2457 		}
2458 	}
2459 
2460 	return fallback_buddy;
2461 }
2462 
purple_find_buddy_in_group(PurpleAccount * account,const char * name,PurpleGroup * group)2463 PurpleBuddy *purple_find_buddy_in_group(PurpleAccount *account, const char *name,
2464 		PurpleGroup *group)
2465 {
2466 	struct _purple_hbuddy hb;
2467 
2468 	g_return_val_if_fail(purplebuddylist != NULL, NULL);
2469 	g_return_val_if_fail(account != NULL, NULL);
2470 	g_return_val_if_fail((name != NULL) && (*name != '\0'), NULL);
2471 
2472 	hb.name = (gchar *)purple_normalize(account, name);
2473 	hb.account = account;
2474 	hb.group = (PurpleBlistNode*)group;
2475 
2476 	return g_hash_table_lookup(purplebuddylist->buddies, &hb);
2477 }
2478 
find_acct_buddies(gpointer key,gpointer value,gpointer data)2479 static void find_acct_buddies(gpointer key, gpointer value, gpointer data)
2480 {
2481 	PurpleBuddy *buddy = value;
2482 	GSList **list = data;
2483 
2484 	*list = g_slist_prepend(*list, buddy);
2485 }
2486 
purple_find_buddies(PurpleAccount * account,const char * name)2487 GSList *purple_find_buddies(PurpleAccount *account, const char *name)
2488 {
2489 	PurpleBuddy *buddy;
2490 	PurpleBlistNode *node;
2491 	GSList *ret = NULL;
2492 
2493 	g_return_val_if_fail(purplebuddylist != NULL, NULL);
2494 	g_return_val_if_fail(account != NULL, NULL);
2495 
2496 	if ((name != NULL) && (*name != '\0')) {
2497 		struct _purple_hbuddy hb;
2498 
2499 		hb.name = (gchar *)purple_normalize(account, name);
2500 		hb.account = account;
2501 
2502 		for (node = purplebuddylist->root; node != NULL; node = node->next) {
2503 			if (!node->child)
2504 				continue;
2505 
2506 			hb.group = node;
2507 			if ((buddy = g_hash_table_lookup(purplebuddylist->buddies, &hb)) != NULL)
2508 				ret = g_slist_prepend(ret, buddy);
2509 		}
2510 	} else {
2511 		GSList *list = NULL;
2512 		GHashTable *buddies = g_hash_table_lookup(buddies_cache, account);
2513 		g_hash_table_foreach(buddies, find_acct_buddies, &list);
2514 		ret = list;
2515 	}
2516 
2517 	return ret;
2518 }
2519 
purple_find_group(const char * name)2520 PurpleGroup *purple_find_group(const char *name)
2521 {
2522 	gchar* key;
2523 	PurpleGroup *group;
2524 
2525 	g_return_val_if_fail(purplebuddylist != NULL, NULL);
2526 	g_return_val_if_fail((name != NULL) && (*name != '\0'), NULL);
2527 
2528 	key = g_utf8_collate_key(name, -1);
2529 	group = g_hash_table_lookup(groups_cache, key);
2530 	g_free(key);
2531 
2532 	return group;
2533 }
2534 
2535 PurpleChat *
purple_blist_find_chat(PurpleAccount * account,const char * name)2536 purple_blist_find_chat(PurpleAccount *account, const char *name)
2537 {
2538 	char *chat_name;
2539 	PurpleChat *chat;
2540 	PurplePlugin *prpl;
2541 	PurplePluginProtocolInfo *prpl_info = NULL;
2542 	struct proto_chat_entry *pce;
2543 	PurpleBlistNode *node, *group;
2544 	GList *parts;
2545 	char *normname;
2546 
2547 	g_return_val_if_fail(purplebuddylist != NULL, NULL);
2548 	g_return_val_if_fail((name != NULL) && (*name != '\0'), NULL);
2549 
2550 	if (!purple_account_is_connected(account))
2551 		return NULL;
2552 
2553 	prpl = purple_find_prpl(purple_account_get_protocol_id(account));
2554 	prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
2555 
2556 	if (prpl_info->find_blist_chat != NULL)
2557 		return prpl_info->find_blist_chat(account, name);
2558 
2559 	normname = g_strdup(purple_normalize(account, name));
2560 	for (group = purplebuddylist->root; group != NULL; group = group->next) {
2561 		for (node = group->child; node != NULL; node = node->next) {
2562 			if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
2563 
2564 				chat = (PurpleChat*)node;
2565 
2566 				if (account != chat->account)
2567 					continue;
2568 
2569 				parts = prpl_info->chat_info(
2570 					purple_account_get_connection(chat->account));
2571 
2572 				pce = parts->data;
2573 				chat_name = g_hash_table_lookup(chat->components,
2574 												pce->identifier);
2575 				g_list_free_full(parts, (GDestroyNotify)g_free);
2576 
2577 				if (chat->account == account && chat_name != NULL &&
2578 					purple_strequal(purple_normalize(account, chat_name), normname)) {
2579 					g_free(normname);
2580 					return chat;
2581 				}
2582 			}
2583 		}
2584 	}
2585 
2586 	g_free(normname);
2587 	return NULL;
2588 }
2589 
2590 PurpleGroup *
purple_chat_get_group(PurpleChat * chat)2591 purple_chat_get_group(PurpleChat *chat)
2592 {
2593 	g_return_val_if_fail(chat != NULL, NULL);
2594 
2595 	return (PurpleGroup *)(((PurpleBlistNode *)chat)->parent);
2596 }
2597 
2598 PurpleAccount *
purple_chat_get_account(PurpleChat * chat)2599 purple_chat_get_account(PurpleChat *chat)
2600 {
2601 	g_return_val_if_fail(chat != NULL, NULL);
2602 
2603 	return chat->account;
2604 }
2605 
2606 GHashTable *
purple_chat_get_components(PurpleChat * chat)2607 purple_chat_get_components(PurpleChat *chat)
2608 {
2609 	g_return_val_if_fail(chat != NULL, NULL);
2610 
2611 	return chat->components;
2612 }
2613 
purple_buddy_get_contact(PurpleBuddy * buddy)2614 PurpleContact *purple_buddy_get_contact(PurpleBuddy *buddy)
2615 {
2616 	g_return_val_if_fail(buddy != NULL, NULL);
2617 
2618 	return PURPLE_CONTACT(PURPLE_BLIST_NODE(buddy)->parent);
2619 }
2620 
purple_buddy_get_presence(const PurpleBuddy * buddy)2621 PurplePresence *purple_buddy_get_presence(const PurpleBuddy *buddy)
2622 {
2623 	g_return_val_if_fail(buddy != NULL, NULL);
2624 	return buddy->presence;
2625 }
2626 
purple_buddy_get_media_caps(const PurpleBuddy * buddy)2627 PurpleMediaCaps purple_buddy_get_media_caps(const PurpleBuddy *buddy)
2628 {
2629 	g_return_val_if_fail(buddy != NULL, 0);
2630 	return buddy->media_caps;
2631 }
2632 
purple_buddy_set_media_caps(PurpleBuddy * buddy,PurpleMediaCaps media_caps)2633 void purple_buddy_set_media_caps(PurpleBuddy *buddy, PurpleMediaCaps media_caps)
2634 {
2635 	g_return_if_fail(buddy != NULL);
2636 	buddy->media_caps = media_caps;
2637 }
2638 
purple_buddy_get_group(PurpleBuddy * buddy)2639 PurpleGroup *purple_buddy_get_group(PurpleBuddy *buddy)
2640 {
2641 	g_return_val_if_fail(buddy != NULL, NULL);
2642 
2643 	if (((PurpleBlistNode *)buddy)->parent == NULL)
2644 		return NULL;
2645 
2646 	return (PurpleGroup *)(((PurpleBlistNode*)buddy)->parent->parent);
2647 }
2648 
purple_group_get_accounts(PurpleGroup * group)2649 GSList *purple_group_get_accounts(PurpleGroup *group)
2650 {
2651 	GSList *l = NULL;
2652 	PurpleBlistNode *gnode, *cnode, *bnode;
2653 
2654 	gnode = (PurpleBlistNode *)group;
2655 
2656 	for (cnode = gnode->child;  cnode; cnode = cnode->next) {
2657 		if (PURPLE_BLIST_NODE_IS_CHAT(cnode)) {
2658 			if (!g_slist_find(l, ((PurpleChat *)cnode)->account))
2659 				l = g_slist_append(l, ((PurpleChat *)cnode)->account);
2660 		} else if (PURPLE_BLIST_NODE_IS_CONTACT(cnode)) {
2661 			for (bnode = cnode->child; bnode; bnode = bnode->next) {
2662 				if (PURPLE_BLIST_NODE_IS_BUDDY(bnode)) {
2663 					if (!g_slist_find(l, ((PurpleBuddy *)bnode)->account))
2664 						l = g_slist_append(l, ((PurpleBuddy *)bnode)->account);
2665 				}
2666 			}
2667 		}
2668 	}
2669 
2670 	return l;
2671 }
2672 
purple_blist_add_account(PurpleAccount * account)2673 void purple_blist_add_account(PurpleAccount *account)
2674 {
2675 	PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
2676 	PurpleBlistNode *gnode, *cnode, *bnode;
2677 
2678 	g_return_if_fail(purplebuddylist != NULL);
2679 
2680 	if (!ops || !ops->update)
2681 		return;
2682 
2683 	for (gnode = purplebuddylist->root; gnode; gnode = gnode->next) {
2684 		if (!PURPLE_BLIST_NODE_IS_GROUP(gnode))
2685 			continue;
2686 		for (cnode = gnode->child; cnode; cnode = cnode->next) {
2687 			if (PURPLE_BLIST_NODE_IS_CONTACT(cnode)) {
2688 				gboolean recompute = FALSE;
2689 					for (bnode = cnode->child; bnode; bnode = bnode->next) {
2690 						if (PURPLE_BLIST_NODE_IS_BUDDY(bnode) &&
2691 								((PurpleBuddy*)bnode)->account == account) {
2692 							recompute = TRUE;
2693 							((PurpleContact*)cnode)->currentsize++;
2694 							if (((PurpleContact*)cnode)->currentsize == 1)
2695 								((PurpleGroup*)gnode)->currentsize++;
2696 							ops->update(purplebuddylist, bnode);
2697 						}
2698 					}
2699 					if (recompute ||
2700 							purple_blist_node_get_bool(cnode, "show_offline")) {
2701 						purple_contact_invalidate_priority_buddy((PurpleContact*)cnode);
2702 						ops->update(purplebuddylist, cnode);
2703 					}
2704 			} else if (PURPLE_BLIST_NODE_IS_CHAT(cnode) &&
2705 					((PurpleChat*)cnode)->account == account) {
2706 				((PurpleGroup *)gnode)->online++;
2707 				((PurpleGroup *)gnode)->currentsize++;
2708 				ops->update(purplebuddylist, cnode);
2709 			}
2710 		}
2711 		ops->update(purplebuddylist, gnode);
2712 	}
2713 }
2714 
purple_blist_remove_account(PurpleAccount * account)2715 void purple_blist_remove_account(PurpleAccount *account)
2716 {
2717 	PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
2718 	PurpleBlistNode *gnode, *cnode, *bnode;
2719 	PurpleBuddy *buddy;
2720 	PurpleChat *chat;
2721 	PurpleContact *contact;
2722 	PurpleGroup *group;
2723 	GList *list = NULL, *iter = NULL;
2724 
2725 	g_return_if_fail(purplebuddylist != NULL);
2726 
2727 	for (gnode = purplebuddylist->root; gnode; gnode = gnode->next) {
2728 		if (!PURPLE_BLIST_NODE_IS_GROUP(gnode))
2729 			continue;
2730 
2731 		group = (PurpleGroup *)gnode;
2732 
2733 		for (cnode = gnode->child; cnode; cnode = cnode->next) {
2734 			if (PURPLE_BLIST_NODE_IS_CONTACT(cnode)) {
2735 				gboolean recompute = FALSE;
2736 				contact = (PurpleContact *)cnode;
2737 
2738 				for (bnode = cnode->child; bnode; bnode = bnode->next) {
2739 					if (!PURPLE_BLIST_NODE_IS_BUDDY(bnode))
2740 						continue;
2741 
2742 					buddy = (PurpleBuddy *)bnode;
2743 					if (account == buddy->account) {
2744 						PurplePresence *presence;
2745 
2746 						presence = purple_buddy_get_presence(buddy);
2747 
2748 						if(purple_presence_is_online(presence)) {
2749 							contact->online--;
2750 							if (contact->online == 0)
2751 								group->online--;
2752 
2753 							purple_blist_node_set_int(&buddy->node,
2754 													"last_seen", time(NULL));
2755 						}
2756 
2757 						contact->currentsize--;
2758 						if (contact->currentsize == 0)
2759 							group->currentsize--;
2760 
2761 						if (!g_list_find(list, presence))
2762 							list = g_list_prepend(list, presence);
2763 
2764 						if (contact->priority == buddy)
2765 							purple_contact_invalidate_priority_buddy(contact);
2766 						else
2767 							recompute = TRUE;
2768 
2769 						if (ops && ops->remove) {
2770 							ops->remove(purplebuddylist, bnode);
2771 						}
2772 					}
2773 				}
2774 				if (recompute) {
2775 					purple_contact_invalidate_priority_buddy(contact);
2776 					if (ops && ops->update)
2777 						ops->update(purplebuddylist, cnode);
2778 				}
2779 			} else if (PURPLE_BLIST_NODE_IS_CHAT(cnode)) {
2780 				chat = (PurpleChat *)cnode;
2781 
2782 				if(chat->account == account) {
2783 					group->currentsize--;
2784 					group->online--;
2785 
2786 					if (ops && ops->remove)
2787 						ops->remove(purplebuddylist, cnode);
2788 				}
2789 			}
2790 		}
2791 	}
2792 
2793 	for (iter = list; iter; iter = iter->next)
2794 	{
2795 		purple_presence_set_status_active(iter->data, "offline", TRUE);
2796 	}
2797 	g_list_free(list);
2798 }
2799 
purple_group_on_account(PurpleGroup * g,PurpleAccount * account)2800 gboolean purple_group_on_account(PurpleGroup *g, PurpleAccount *account)
2801 {
2802 	PurpleBlistNode *cnode;
2803 	for (cnode = ((PurpleBlistNode *)g)->child; cnode; cnode = cnode->next) {
2804 		if (PURPLE_BLIST_NODE_IS_CONTACT(cnode)) {
2805 			if(purple_contact_on_account((PurpleContact *) cnode, account))
2806 				return TRUE;
2807 		} else if (PURPLE_BLIST_NODE_IS_CHAT(cnode)) {
2808 			PurpleChat *chat = (PurpleChat *)cnode;
2809 			if ((!account && purple_account_is_connected(chat->account))
2810 					|| chat->account == account)
2811 				return TRUE;
2812 		}
2813 	}
2814 	return FALSE;
2815 }
2816 
purple_group_get_name(PurpleGroup * group)2817 const char *purple_group_get_name(PurpleGroup *group)
2818 {
2819 	g_return_val_if_fail(group != NULL, NULL);
2820 
2821 	return group->name;
2822 }
2823 
2824 void
purple_blist_request_add_buddy(PurpleAccount * account,const char * username,const char * group,const char * alias)2825 purple_blist_request_add_buddy(PurpleAccount *account, const char *username,
2826 							 const char *group, const char *alias)
2827 {
2828 	PurpleBlistUiOps *ui_ops;
2829 
2830 	ui_ops = purple_blist_get_ui_ops();
2831 
2832 	if (ui_ops != NULL && ui_ops->request_add_buddy != NULL)
2833 		ui_ops->request_add_buddy(account, username, group, alias);
2834 }
2835 
2836 void
purple_blist_request_add_chat(PurpleAccount * account,PurpleGroup * group,const char * alias,const char * name)2837 purple_blist_request_add_chat(PurpleAccount *account, PurpleGroup *group,
2838 							const char *alias, const char *name)
2839 {
2840 	PurpleBlistUiOps *ui_ops;
2841 
2842 	ui_ops = purple_blist_get_ui_ops();
2843 
2844 	if (ui_ops != NULL && ui_ops->request_add_chat != NULL)
2845 		ui_ops->request_add_chat(account, group, alias, name);
2846 }
2847 
2848 void
purple_blist_request_add_group(void)2849 purple_blist_request_add_group(void)
2850 {
2851 	PurpleBlistUiOps *ui_ops;
2852 
2853 	ui_ops = purple_blist_get_ui_ops();
2854 
2855 	if (ui_ops != NULL && ui_ops->request_add_group != NULL)
2856 		ui_ops->request_add_group();
2857 }
2858 
2859 static void
purple_blist_node_destroy(PurpleBlistNode * node)2860 purple_blist_node_destroy(PurpleBlistNode *node)
2861 {
2862 	PurpleBlistUiOps *ui_ops;
2863 	PurpleBlistNode *child, *next_child;
2864 
2865 	ui_ops = purple_blist_get_ui_ops();
2866 	child = node->child;
2867 	while (child) {
2868 		next_child = child->next;
2869 		purple_blist_node_destroy(child);
2870 		child = next_child;
2871 	}
2872 
2873 	/* Allow the UI to free data */
2874 	node->parent = NULL;
2875 	node->child  = NULL;
2876 	node->next   = NULL;
2877 	node->prev   = NULL;
2878 	if (ui_ops && ui_ops->remove)
2879 		ui_ops->remove(purplebuddylist, node);
2880 
2881 	if (PURPLE_BLIST_NODE_IS_BUDDY(node))
2882 		purple_buddy_destroy((PurpleBuddy*)node);
2883 	else if (PURPLE_BLIST_NODE_IS_CHAT(node))
2884 		purple_chat_destroy((PurpleChat*)node);
2885 	else if (PURPLE_BLIST_NODE_IS_CONTACT(node))
2886 		purple_contact_destroy((PurpleContact*)node);
2887 	else if (PURPLE_BLIST_NODE_IS_GROUP(node))
2888 		purple_group_destroy((PurpleGroup*)node);
2889 }
2890 
2891 static void
purple_blist_node_setting_free(gpointer data)2892 purple_blist_node_setting_free(gpointer data)
2893 {
2894 	PurpleValue *value;
2895 
2896 	value = (PurpleValue *)data;
2897 
2898 	purple_value_destroy(value);
2899 }
2900 
purple_blist_node_initialize_settings(PurpleBlistNode * node)2901 static void purple_blist_node_initialize_settings(PurpleBlistNode *node)
2902 {
2903 	if (node->settings)
2904 		return;
2905 
2906 	node->settings = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
2907 			(GDestroyNotify)purple_blist_node_setting_free);
2908 }
2909 
purple_blist_node_remove_setting(PurpleBlistNode * node,const char * key)2910 void purple_blist_node_remove_setting(PurpleBlistNode *node, const char *key)
2911 {
2912 	PurpleBlistUiOps *ops;
2913 	g_return_if_fail(node != NULL);
2914 	g_return_if_fail(node->settings != NULL);
2915 	g_return_if_fail(key != NULL);
2916 
2917 	g_hash_table_remove(node->settings, key);
2918 
2919 	ops = purple_blist_get_ui_ops();
2920 	if (ops && ops->save_node)
2921 		ops->save_node(node);
2922 }
2923 
2924 void
purple_blist_node_set_flags(PurpleBlistNode * node,PurpleBlistNodeFlags flags)2925 purple_blist_node_set_flags(PurpleBlistNode *node, PurpleBlistNodeFlags flags)
2926 {
2927 	g_return_if_fail(node != NULL);
2928 
2929 	node->flags = flags;
2930 }
2931 
2932 PurpleBlistNodeFlags
purple_blist_node_get_flags(PurpleBlistNode * node)2933 purple_blist_node_get_flags(PurpleBlistNode *node)
2934 {
2935 	g_return_val_if_fail(node != NULL, 0);
2936 
2937 	return node->flags;
2938 }
2939 
2940 PurpleBlistNodeType
purple_blist_node_get_type(PurpleBlistNode * node)2941 purple_blist_node_get_type(PurpleBlistNode *node)
2942 {
2943 	g_return_val_if_fail(node != NULL, PURPLE_BLIST_OTHER_NODE);
2944 	return node->type;
2945 }
2946 
2947 void
purple_blist_node_set_bool(PurpleBlistNode * node,const char * key,gboolean data)2948 purple_blist_node_set_bool(PurpleBlistNode* node, const char *key, gboolean data)
2949 {
2950 	PurpleValue *value;
2951 	PurpleBlistUiOps *ops;
2952 
2953 	g_return_if_fail(node != NULL);
2954 	g_return_if_fail(node->settings != NULL);
2955 	g_return_if_fail(key != NULL);
2956 
2957 	value = purple_value_new(PURPLE_TYPE_BOOLEAN);
2958 	purple_value_set_boolean(value, data);
2959 
2960 	g_hash_table_replace(node->settings, g_strdup(key), value);
2961 
2962 	ops = purple_blist_get_ui_ops();
2963 	if (ops && ops->save_node)
2964 		ops->save_node(node);
2965 }
2966 
2967 gboolean
purple_blist_node_get_bool(PurpleBlistNode * node,const char * key)2968 purple_blist_node_get_bool(PurpleBlistNode* node, const char *key)
2969 {
2970 	PurpleValue *value;
2971 
2972 	g_return_val_if_fail(node != NULL, FALSE);
2973 	g_return_val_if_fail(node->settings != NULL, FALSE);
2974 	g_return_val_if_fail(key != NULL, FALSE);
2975 
2976 	value = g_hash_table_lookup(node->settings, key);
2977 
2978 	if (value == NULL)
2979 		return FALSE;
2980 
2981 	g_return_val_if_fail(purple_value_get_type(value) == PURPLE_TYPE_BOOLEAN, FALSE);
2982 
2983 	return purple_value_get_boolean(value);
2984 }
2985 
2986 void
purple_blist_node_set_int(PurpleBlistNode * node,const char * key,int data)2987 purple_blist_node_set_int(PurpleBlistNode* node, const char *key, int data)
2988 {
2989 	PurpleValue *value;
2990 	PurpleBlistUiOps *ops;
2991 
2992 	g_return_if_fail(node != NULL);
2993 	g_return_if_fail(node->settings != NULL);
2994 	g_return_if_fail(key != NULL);
2995 
2996 	value = purple_value_new(PURPLE_TYPE_INT);
2997 	purple_value_set_int(value, data);
2998 
2999 	g_hash_table_replace(node->settings, g_strdup(key), value);
3000 
3001 	ops = purple_blist_get_ui_ops();
3002 	if (ops && ops->save_node)
3003 		ops->save_node(node);
3004 }
3005 
3006 int
purple_blist_node_get_int(PurpleBlistNode * node,const char * key)3007 purple_blist_node_get_int(PurpleBlistNode* node, const char *key)
3008 {
3009 	PurpleValue *value;
3010 
3011 	g_return_val_if_fail(node != NULL, 0);
3012 	g_return_val_if_fail(node->settings != NULL, 0);
3013 	g_return_val_if_fail(key != NULL, 0);
3014 
3015 	value = g_hash_table_lookup(node->settings, key);
3016 
3017 	if (value == NULL)
3018 		return 0;
3019 
3020 	g_return_val_if_fail(purple_value_get_type(value) == PURPLE_TYPE_INT, 0);
3021 
3022 	return purple_value_get_int(value);
3023 }
3024 
3025 void
purple_blist_node_set_string(PurpleBlistNode * node,const char * key,const char * data)3026 purple_blist_node_set_string(PurpleBlistNode* node, const char *key, const char *data)
3027 {
3028 	PurpleValue *value;
3029 	PurpleBlistUiOps *ops;
3030 
3031 	g_return_if_fail(node != NULL);
3032 	g_return_if_fail(node->settings != NULL);
3033 	g_return_if_fail(key != NULL);
3034 
3035 	value = purple_value_new(PURPLE_TYPE_STRING);
3036 	purple_value_set_string(value, data);
3037 
3038 	g_hash_table_replace(node->settings, g_strdup(key), value);
3039 
3040 	ops = purple_blist_get_ui_ops();
3041 	if (ops && ops->save_node)
3042 		ops->save_node(node);
3043 }
3044 
3045 const char *
purple_blist_node_get_string(PurpleBlistNode * node,const char * key)3046 purple_blist_node_get_string(PurpleBlistNode* node, const char *key)
3047 {
3048 	PurpleValue *value;
3049 
3050 	g_return_val_if_fail(node != NULL, NULL);
3051 	g_return_val_if_fail(node->settings != NULL, NULL);
3052 	g_return_val_if_fail(key != NULL, NULL);
3053 
3054 	value = g_hash_table_lookup(node->settings, key);
3055 
3056 	if (value == NULL)
3057 		return NULL;
3058 
3059 	g_return_val_if_fail(purple_value_get_type(value) == PURPLE_TYPE_STRING, NULL);
3060 
3061 	return purple_value_get_string(value);
3062 }
3063 
3064 GList *
purple_blist_node_get_extended_menu(PurpleBlistNode * n)3065 purple_blist_node_get_extended_menu(PurpleBlistNode *n)
3066 {
3067 	GList *menu = NULL;
3068 
3069 	g_return_val_if_fail(n != NULL, NULL);
3070 
3071 	purple_signal_emit(purple_blist_get_handle(),
3072 			"blist-node-extended-menu",
3073 			n, &menu);
3074 	return menu;
3075 }
3076 
purple_blist_get_group_size(PurpleGroup * group,gboolean offline)3077 int purple_blist_get_group_size(PurpleGroup *group, gboolean offline)
3078 {
3079 	if (!group)
3080 		return 0;
3081 
3082 	return offline ? group->totalsize : group->currentsize;
3083 }
3084 
purple_blist_get_group_online_count(PurpleGroup * group)3085 int purple_blist_get_group_online_count(PurpleGroup *group)
3086 {
3087 	if (!group)
3088 		return 0;
3089 
3090 	return group->online;
3091 }
3092 
3093 void
purple_blist_set_ui_ops(PurpleBlistUiOps * ops)3094 purple_blist_set_ui_ops(PurpleBlistUiOps *ops)
3095 {
3096 	gboolean overrode = FALSE;
3097 	blist_ui_ops = ops;
3098 
3099 	if (!ops)
3100 		return;
3101 
3102 	if (!ops->save_node) {
3103 		ops->save_node = purple_blist_save_node;
3104 		overrode = TRUE;
3105 	}
3106 	if (!ops->remove_node) {
3107 		ops->remove_node = purple_blist_save_node;
3108 		overrode = TRUE;
3109 	}
3110 	if (!ops->save_account) {
3111 		ops->save_account = purple_blist_save_account;
3112 		overrode = TRUE;
3113 	}
3114 
3115 	if (overrode && (ops->save_node    != purple_blist_save_node ||
3116 	                 ops->remove_node  != purple_blist_save_node ||
3117 	                 ops->save_account != purple_blist_save_account)) {
3118 		purple_debug_warning("blist", "Only some of the blist saving UI ops "
3119 				"were overridden. This probably is not what you want!\n");
3120 	}
3121 }
3122 
3123 PurpleBlistUiOps *
purple_blist_get_ui_ops(void)3124 purple_blist_get_ui_ops(void)
3125 {
3126 	return blist_ui_ops;
3127 }
3128 
3129 
3130 void *
purple_blist_get_handle(void)3131 purple_blist_get_handle(void)
3132 {
3133 	static int handle;
3134 
3135 	return &handle;
3136 }
3137 
3138 void
purple_blist_init(void)3139 purple_blist_init(void)
3140 {
3141 	void *handle = purple_blist_get_handle();
3142 
3143 	purple_signal_register(handle, "buddy-status-changed",
3144 	                     purple_marshal_VOID__POINTER_POINTER_POINTER, NULL,
3145 	                     3,
3146 	                     purple_value_new(PURPLE_TYPE_SUBTYPE,
3147 	                                    PURPLE_SUBTYPE_BLIST_BUDDY),
3148 	                     purple_value_new(PURPLE_TYPE_SUBTYPE,
3149 										PURPLE_SUBTYPE_STATUS),
3150 	                     purple_value_new(PURPLE_TYPE_SUBTYPE,
3151 										PURPLE_SUBTYPE_STATUS));
3152 	purple_signal_register(handle, "buddy-privacy-changed",
3153 	                     purple_marshal_VOID__POINTER, NULL,
3154 	                     1,
3155 	                     purple_value_new(PURPLE_TYPE_SUBTYPE,
3156 	                                    PURPLE_SUBTYPE_BLIST_BUDDY));
3157 
3158 	purple_signal_register(handle, "buddy-idle-changed",
3159 	                     purple_marshal_VOID__POINTER_INT_INT, NULL,
3160 	                     3,
3161 	                     purple_value_new(PURPLE_TYPE_SUBTYPE,
3162 	                                    PURPLE_SUBTYPE_BLIST_BUDDY),
3163 	                     purple_value_new(PURPLE_TYPE_INT),
3164 	                     purple_value_new(PURPLE_TYPE_INT));
3165 
3166 
3167 	purple_signal_register(handle, "buddy-signed-on",
3168 						 purple_marshal_VOID__POINTER, NULL, 1,
3169 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
3170 										PURPLE_SUBTYPE_BLIST_BUDDY));
3171 
3172 	purple_signal_register(handle, "buddy-signed-off",
3173 						 purple_marshal_VOID__POINTER, NULL, 1,
3174 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
3175 										PURPLE_SUBTYPE_BLIST_BUDDY));
3176 
3177 	purple_signal_register(handle, "buddy-got-login-time",
3178 						 purple_marshal_VOID__POINTER, NULL, 1,
3179 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
3180 										PURPLE_SUBTYPE_BLIST_BUDDY));
3181 
3182 	purple_signal_register(handle, "blist-node-added",
3183 						 purple_marshal_VOID__POINTER, NULL, 1,
3184 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
3185 										PURPLE_SUBTYPE_BLIST_NODE));
3186 
3187 	purple_signal_register(handle, "blist-node-removed",
3188 						 purple_marshal_VOID__POINTER, NULL, 1,
3189 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
3190 										PURPLE_SUBTYPE_BLIST_NODE));
3191 
3192 	purple_signal_register(handle, "buddy-added",
3193 						 purple_marshal_VOID__POINTER, NULL, 1,
3194 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
3195 										PURPLE_SUBTYPE_BLIST_BUDDY));
3196 
3197 	purple_signal_register(handle, "buddy-removed",
3198 						 purple_marshal_VOID__POINTER, NULL, 1,
3199 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
3200 										PURPLE_SUBTYPE_BLIST_BUDDY));
3201 
3202 	purple_signal_register(handle, "buddy-removed-from-group",
3203 						 purple_marshal_VOID__POINTER, NULL, 1,
3204 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
3205 										PURPLE_SUBTYPE_BLIST_BUDDY));
3206 
3207 	purple_signal_register(handle, "buddy-icon-changed",
3208 						 purple_marshal_VOID__POINTER, NULL, 1,
3209 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
3210 										PURPLE_SUBTYPE_BLIST_BUDDY));
3211 
3212 	purple_signal_register(handle, "update-idle", purple_marshal_VOID, NULL, 0);
3213 
3214 	purple_signal_register(handle, "blist-node-extended-menu",
3215 			     purple_marshal_VOID__POINTER_POINTER, NULL, 2,
3216 			     purple_value_new(PURPLE_TYPE_SUBTYPE,
3217 					    PURPLE_SUBTYPE_BLIST_NODE),
3218 			     purple_value_new(PURPLE_TYPE_BOXED, "GList **"));
3219 
3220 	purple_signal_register(handle, "blist-node-aliased",
3221 						 purple_marshal_VOID__POINTER_POINTER, NULL, 2,
3222 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
3223 										PURPLE_SUBTYPE_BLIST_NODE),
3224 						 purple_value_new(PURPLE_TYPE_STRING));
3225 
3226 	purple_signal_register(handle, "buddy-caps-changed",
3227 			purple_marshal_VOID__POINTER_INT_INT, NULL,
3228 			3, purple_value_new(PURPLE_TYPE_SUBTYPE,
3229 				PURPLE_SUBTYPE_BLIST_BUDDY),
3230 			purple_value_new(PURPLE_TYPE_INT),
3231 			purple_value_new(PURPLE_TYPE_INT));
3232 
3233 	purple_signal_connect(purple_accounts_get_handle(), "account-created",
3234 			handle,
3235 			PURPLE_CALLBACK(purple_blist_buddies_cache_add_account),
3236 			NULL);
3237 
3238 	purple_signal_connect(purple_accounts_get_handle(), "account-destroying",
3239 			handle,
3240 			PURPLE_CALLBACK(purple_blist_buddies_cache_remove_account),
3241 			NULL);
3242 }
3243 
3244 void
purple_blist_uninit(void)3245 purple_blist_uninit(void)
3246 {
3247 	PurpleBlistNode *node, *next_node;
3248 
3249 	/* This happens if we quit before purple_set_blist is called. */
3250 	if (purplebuddylist == NULL)
3251 		return;
3252 
3253 	if (save_timer != 0) {
3254 		purple_timeout_remove(save_timer);
3255 		save_timer = 0;
3256 		purple_blist_sync();
3257 	}
3258 
3259 	purple_blist_destroy();
3260 
3261 	node = purple_blist_get_root();
3262 	while (node) {
3263 		next_node = node->next;
3264 		purple_blist_node_destroy(node);
3265 		node = next_node;
3266 	}
3267 	purplebuddylist->root = NULL;
3268 
3269 	g_hash_table_destroy(purplebuddylist->buddies);
3270 	g_hash_table_destroy(buddies_cache);
3271 	g_hash_table_destroy(groups_cache);
3272 
3273 	buddies_cache = NULL;
3274 	groups_cache = NULL;
3275 
3276 	PURPLE_DBUS_UNREGISTER_POINTER(purplebuddylist);
3277 	g_free(purplebuddylist);
3278 	purplebuddylist = NULL;
3279 
3280 	purple_signals_disconnect_by_handle(purple_blist_get_handle());
3281 	purple_signals_unregister_by_instance(purple_blist_get_handle());
3282 }
3283