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