libpurple/buddylist.c

changeset 42727
67132affc27c
parent 42726
226a7ec46dde
child 42728
e8a4f48d595f
equal deleted inserted replaced
42726:226a7ec46dde 42727:67132affc27c
1 /*
2 * Purple - Internet Messaging Library
3 * Copyright (C) Pidgin Developers <devel@pidgin.im>
4 *
5 * Purple is the legal property of its developers, whose names are too numerous
6 * to list here. Please refer to the COPYRIGHT file distributed with this
7 * source distribution.
8 *
9 * This library is free software; you can redistribute it and/or modify it
10 * under the terms of the GNU General Public License as published by the Free
11 * Software Foundation; either version 2 of the License, or (at your option)
12 * any later version.
13 *
14 * This library is distributed in the hope that it will be useful, but WITHOUT
15 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
17 * more details.
18 *
19 * You should have received a copy of the GNU General Public License along with
20 * this library; if not, see <https://www.gnu.org/licenses/>.
21 */
22
23 #include <glib/gi18n-lib.h>
24
25 #include "buddylist.h"
26 #include "debug.h"
27 #include "notify.h"
28 #include "prefs.h"
29 #include "purpleaccount.h"
30 #include "purpleaccountmanager.h"
31 #include "purpleprivate.h"
32 #include "purpleprotocol.h"
33 #include "purpleprotocolclient.h"
34 #include "purpleconversation.h"
35 #include "signals.h"
36 #include "util.h"
37 #include "xmlnode.h"
38
39 /* Private data for a buddy list. */
40 typedef struct {
41 PurpleBlistNode *root;
42 GHashTable *buddies; /* Every buddy in this list */
43 } PurpleBuddyListPrivate;
44
45 static GType buddy_list_type = G_TYPE_INVALID;
46 static PurpleBuddyList *purplebuddylist = NULL;
47
48 G_DEFINE_TYPE_WITH_PRIVATE(PurpleBuddyList, purple_buddy_list, G_TYPE_OBJECT);
49
50 /*
51 * A hash table used for efficient lookups of buddies by name.
52 * PurpleAccount* => GHashTable*, with the inner hash table being
53 * struct _purple_hbuddy => PurpleBuddy*
54 */
55 static GHashTable *buddies_cache = NULL;
56
57 /*
58 * A hash table used for efficient lookups of groups by name.
59 * UTF-8 collate-key => PurpleGroup*.
60 */
61 static GHashTable *groups_cache = NULL;
62
63 static guint save_timer = 0;
64 static gboolean blist_loaded = FALSE;
65 static gchar *localized_default_group_name = NULL;
66
67 /*********************************************************************
68 * Private utility functions *
69 *********************************************************************/
70
71 static gchar *
72 purple_blist_fold_name(const gchar *name)
73 {
74 gchar *res, *tmp;
75
76 if (name == NULL)
77 return NULL;
78
79 tmp = g_utf8_casefold(name, -1);
80 res = g_utf8_collate_key(tmp, -1);
81 g_free(tmp);
82
83 return res;
84 }
85
86 static PurpleBlistNode *purple_blist_get_last_sibling(PurpleBlistNode *node)
87 {
88 PurpleBlistNode *n = node;
89 if (!n)
90 return NULL;
91 while (n->next)
92 n = n->next;
93 return n;
94 }
95
96 PurpleBlistNode *_purple_blist_get_last_child(PurpleBlistNode *node)
97 {
98 if (!node)
99 return NULL;
100 return purple_blist_get_last_sibling(node->child);
101 }
102
103 struct _list_account_buddies {
104 GSList *list;
105 PurpleAccount *account;
106 };
107
108 struct _purple_hbuddy {
109 char *name;
110 PurpleAccount *account;
111 PurpleBlistNode *group;
112 };
113
114 /* This function must not use purple_normalize */
115 static guint _purple_blist_hbuddy_hash(struct _purple_hbuddy *hb)
116 {
117 return g_str_hash(hb->name) ^ g_direct_hash(hb->group) ^ g_direct_hash(hb->account);
118 }
119
120 /* This function must not use purple_normalize */
121 static guint _purple_blist_hbuddy_equal(struct _purple_hbuddy *hb1, struct _purple_hbuddy *hb2)
122 {
123 return (hb1->group == hb2->group &&
124 hb1->account == hb2->account &&
125 purple_strequal(hb1->name, hb2->name));
126 }
127
128 static void _purple_blist_hbuddy_free_key(struct _purple_hbuddy *hb)
129 {
130 g_free(hb->name);
131 g_free(hb);
132 }
133
134 static void
135 purple_blist_buddies_cache_add_account(PurpleAccount *account)
136 {
137 GHashTable *account_buddies = g_hash_table_new_full((GHashFunc)_purple_blist_hbuddy_hash,
138 (GEqualFunc)_purple_blist_hbuddy_equal,
139 (GDestroyNotify)_purple_blist_hbuddy_free_key, NULL);
140 g_hash_table_insert(buddies_cache, account, account_buddies);
141 }
142
143 static void
144 purple_buddy_list_account_added_cb(G_GNUC_UNUSED PurpleAccountManager *manager,
145 PurpleAccount *account,
146 G_GNUC_UNUSED gpointer data)
147 {
148 purple_blist_buddies_cache_add_account(account);
149 }
150
151 static void
152 purple_blist_buddies_cache_remove_account(const PurpleAccount *account)
153 {
154 g_hash_table_remove(buddies_cache, account);
155 }
156
157 static void
158 purple_buddy_list_account_removed_cb(G_GNUC_UNUSED PurpleAccountManager *manager,
159 PurpleAccount *account,
160 G_GNUC_UNUSED gpointer data)
161 {
162 purple_blist_buddies_cache_remove_account(account);
163 }
164
165 /*********************************************************************
166 * Writing to disk *
167 *********************************************************************/
168
169 static void
170 value_to_xmlnode(gpointer key, gpointer hvalue, gpointer user_data)
171 {
172 const char *name;
173 GValue *value;
174 PurpleXmlNode *node, *child;
175 char buf[21];
176
177 name = (const char *)key;
178 value = (GValue *)hvalue;
179 node = (PurpleXmlNode *)user_data;
180
181 g_return_if_fail(value != NULL);
182
183 child = purple_xmlnode_new_child(node, "setting");
184 purple_xmlnode_set_attrib(child, "name", name);
185
186 if (G_VALUE_HOLDS_INT(value)) {
187 purple_xmlnode_set_attrib(child, "type", "int");
188 g_snprintf(buf, sizeof(buf), "%d", g_value_get_int(value));
189 purple_xmlnode_insert_data(child, buf, -1);
190 }
191 else if (G_VALUE_HOLDS_STRING(value)) {
192 purple_xmlnode_set_attrib(child, "type", "string");
193 purple_xmlnode_insert_data(child, g_value_get_string(value), -1);
194 }
195 else if (G_VALUE_HOLDS_BOOLEAN(value)) {
196 purple_xmlnode_set_attrib(child, "type", "bool");
197 g_snprintf(buf, sizeof(buf), "%d", g_value_get_boolean(value));
198 purple_xmlnode_insert_data(child, buf, -1);
199 }
200 }
201
202 static PurpleXmlNode *
203 contact_to_xmlnode(PurpleMetaContact *contact)
204 {
205 PurpleXmlNode *node;
206 gchar *alias;
207
208 node = purple_xmlnode_new("contact");
209 g_object_get(contact, "alias", &alias, NULL);
210
211 if (alias != NULL)
212 {
213 purple_xmlnode_set_attrib(node, "alias", alias);
214 }
215
216 /* Write contact settings */
217 g_hash_table_foreach(purple_blist_node_get_settings(PURPLE_BLIST_NODE(contact)),
218 value_to_xmlnode, node);
219
220 g_free(alias);
221 return node;
222 }
223
224 static PurpleXmlNode *
225 group_to_xmlnode(PurpleGroup *group)
226 {
227 PurpleXmlNode *node, *child;
228 PurpleBlistNode *cnode;
229
230 node = purple_xmlnode_new("group");
231 if (group != purple_blist_get_default_group())
232 purple_xmlnode_set_attrib(node, "name", purple_group_get_name(group));
233
234 /* Write settings */
235 g_hash_table_foreach(purple_blist_node_get_settings(PURPLE_BLIST_NODE(group)),
236 value_to_xmlnode, node);
237
238 /* Write contacts and chats */
239 for (cnode = PURPLE_BLIST_NODE(group)->child; cnode != NULL; cnode = cnode->next)
240 {
241 if (purple_blist_node_is_transient(cnode))
242 continue;
243 if (PURPLE_IS_META_CONTACT(cnode))
244 {
245 child = contact_to_xmlnode(PURPLE_META_CONTACT(cnode));
246 purple_xmlnode_insert_child(node, child);
247 }
248 }
249
250 return node;
251 }
252
253 static PurpleXmlNode *
254 blist_to_xmlnode(void) {
255 PurpleXmlNode *node, *child, *grandchild;
256 PurpleBlistNode *gnode;
257 const gchar *localized_default;
258
259 node = purple_xmlnode_new("purple");
260 purple_xmlnode_set_attrib(node, "version", "1.0");
261
262 /* Write groups */
263 child = purple_xmlnode_new_child(node, "blist");
264
265 localized_default = localized_default_group_name;
266 if (!purple_strequal(_("Buddies"), "Buddies"))
267 localized_default = _("Buddies");
268 if (localized_default != NULL) {
269 purple_xmlnode_set_attrib(child,
270 "localized-default-group", localized_default);
271 }
272
273 for (gnode = purple_blist_get_default_root(); gnode != NULL;
274 gnode = gnode->next) {
275 if (purple_blist_node_is_transient(gnode))
276 continue;
277 if (PURPLE_IS_GROUP(gnode))
278 {
279 grandchild = group_to_xmlnode(PURPLE_GROUP(gnode));
280 purple_xmlnode_insert_child(child, grandchild);
281 }
282 }
283
284 return node;
285 }
286
287 static void
288 purple_blist_sync(void)
289 {
290 PurpleXmlNode *node;
291 char *data;
292
293 if (!blist_loaded)
294 {
295 purple_debug_error("buddylist", "Attempted to save buddy list before it "
296 "was read!\n");
297 return;
298 }
299
300 node = blist_to_xmlnode();
301 data = purple_xmlnode_to_formatted_str(node, NULL);
302 purple_util_write_data_to_config_file("blist.xml", data, -1);
303 g_free(data);
304 purple_xmlnode_free(node);
305 }
306
307 static gboolean
308 save_cb(G_GNUC_UNUSED gpointer data)
309 {
310 purple_blist_sync();
311 save_timer = 0;
312 return FALSE;
313 }
314
315 static void
316 purple_blist_real_schedule_save(void)
317 {
318 if (save_timer == 0)
319 save_timer = g_timeout_add_seconds(5, save_cb, NULL);
320 }
321
322 static void
323 purple_blist_real_save_account(G_GNUC_UNUSED PurpleBuddyList *list,
324 G_GNUC_UNUSED PurpleAccount *account)
325 {
326 #if 1
327 purple_blist_real_schedule_save();
328 #else
329 if (account != NULL) {
330 /* Save the buddies and privacy data for this account */
331 } else {
332 /* Save all buddies and privacy data */
333 }
334 #endif
335 }
336
337 static void
338 purple_blist_real_save_node(G_GNUC_UNUSED PurpleBuddyList *list,
339 G_GNUC_UNUSED PurpleBlistNode *node)
340 {
341 purple_blist_real_schedule_save();
342 }
343
344 void
345 purple_blist_schedule_save(void)
346 {
347 PurpleBuddyListClass *klass = NULL;
348
349 g_return_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist));
350
351 klass = PURPLE_BUDDY_LIST_GET_CLASS(purplebuddylist);
352
353 /* Save everything */
354 if (klass && klass->save_account) {
355 klass->save_account(purplebuddylist, NULL);
356 }
357 }
358
359 /*********************************************************************
360 * Reading from disk *
361 *********************************************************************/
362
363 static void
364 parse_setting(PurpleBlistNode *node, PurpleXmlNode *setting)
365 {
366 const char *name = purple_xmlnode_get_attrib(setting, "name");
367 const char *type = purple_xmlnode_get_attrib(setting, "type");
368 char *value = purple_xmlnode_get_data(setting);
369
370 if (!value)
371 return;
372
373 if (!type || purple_strequal(type, "string"))
374 purple_blist_node_set_string(node, name, value);
375 else if (purple_strequal(type, "bool"))
376 purple_blist_node_set_bool(node, name, atoi(value));
377 else if (purple_strequal(type, "int"))
378 purple_blist_node_set_int(node, name, atoi(value));
379
380 g_free(value);
381 }
382
383 static void
384 parse_contact(PurpleGroup *group, PurpleXmlNode *cnode)
385 {
386 PurpleMetaContact *contact = purple_meta_contact_new();
387 PurpleXmlNode *x;
388 const char *alias;
389
390 purple_blist_add_contact(contact, group,
391 _purple_blist_get_last_child((PurpleBlistNode*)group));
392
393 if ((alias = purple_xmlnode_get_attrib(cnode, "alias"))) {
394 purple_meta_contact_set_alias(contact, alias);
395 }
396
397 for (x = cnode->child; x; x = x->next) {
398 if (x->type != PURPLE_XMLNODE_TYPE_TAG)
399 continue;
400 if (purple_strequal(x->name, "setting"))
401 parse_setting(PURPLE_BLIST_NODE(contact), x);
402 }
403
404 /* if the contact is empty, don't keep it around. it causes problems */
405 if (!PURPLE_BLIST_NODE(contact)->child)
406 purple_blist_remove_contact(contact);
407 }
408
409 static void
410 parse_group(PurpleXmlNode *groupnode)
411 {
412 const char *name = purple_xmlnode_get_attrib(groupnode, "name");
413 PurpleGroup *group;
414 PurpleXmlNode *cnode;
415
416 group = purple_group_new(name);
417 purple_blist_add_group(group, purple_blist_get_last_sibling(
418 purple_blist_get_default_root()));
419
420 for (cnode = groupnode->child; cnode; cnode = cnode->next) {
421 if (cnode->type != PURPLE_XMLNODE_TYPE_TAG)
422 continue;
423 if (purple_strequal(cnode->name, "setting"))
424 parse_setting((PurpleBlistNode*)group, cnode);
425 else if (purple_strequal(cnode->name, "contact") ||
426 purple_strequal(cnode->name, "person"))
427 parse_contact(group, cnode);
428 }
429 }
430
431 static void
432 load_blist(void)
433 {
434 PurpleXmlNode *purple, *blist;
435
436 blist_loaded = TRUE;
437
438 purple = purple_util_read_xml_from_config_file("blist.xml", _("buddy list"));
439
440 if(purple == NULL) {
441 return;
442 }
443
444 blist = purple_xmlnode_get_child(purple, "blist");
445 if(blist) {
446 PurpleXmlNode *groupnode;
447
448 localized_default_group_name = g_strdup(
449 purple_xmlnode_get_attrib(blist,
450 "localized-default-group"));
451
452 for(groupnode = purple_xmlnode_get_child(blist, "group"); groupnode != NULL;
453 groupnode = purple_xmlnode_get_next_twin(groupnode)) {
454 parse_group(groupnode);
455 }
456 } else {
457 g_free(localized_default_group_name);
458 localized_default_group_name = NULL;
459 }
460
461 purple_xmlnode_free(purple);
462 }
463
464 /*****************************************************************************
465 * Public API functions *
466 *****************************************************************************/
467
468 void
469 purple_blist_set_ui(GType type)
470 {
471 g_return_if_fail(g_type_is_a(type, PURPLE_TYPE_BUDDY_LIST) ||
472 type == G_TYPE_INVALID);
473 buddy_list_type = type;
474 }
475
476 void
477 purple_blist_boot(void)
478 {
479 GListModel *manager_model = NULL;
480 PurpleBuddyList *gbl = g_object_new(buddy_list_type, NULL);
481 guint n_items = 0;
482
483 buddies_cache = g_hash_table_new_full(g_direct_hash, g_direct_equal,
484 NULL, (GDestroyNotify)g_hash_table_destroy);
485
486 groups_cache = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
487
488 manager_model = purple_account_manager_get_default_as_model();
489 n_items = g_list_model_get_n_items(manager_model);
490 for(guint index = 0; index < n_items; index++) {
491 PurpleAccount *account = g_list_model_get_item(manager_model, index);
492 purple_blist_buddies_cache_add_account(account);
493 g_object_unref(account);
494 }
495
496 purplebuddylist = gbl;
497
498 load_blist();
499 }
500
501 PurpleBuddyList *
502 purple_blist_get_default(void)
503 {
504 return purplebuddylist;
505 }
506
507 PurpleBlistNode *
508 purple_blist_get_default_root(void)
509 {
510 if (purplebuddylist) {
511 PurpleBuddyListPrivate *priv =
512 purple_buddy_list_get_instance_private(purplebuddylist);
513 return priv->root;
514 }
515 return NULL;
516 }
517
518 PurpleBlistNode *
519 purple_blist_get_root(PurpleBuddyList *list)
520 {
521 PurpleBuddyListPrivate *priv = NULL;
522
523 g_return_val_if_fail(PURPLE_IS_BUDDY_LIST(list), NULL);
524 priv = purple_buddy_list_get_instance_private(list);
525
526 return priv->root;
527 }
528
529 static void
530 append_buddy(G_GNUC_UNUSED gpointer key, gpointer value, gpointer user_data)
531 {
532 GSList **list = user_data;
533 *list = g_slist_prepend(*list, value);
534 }
535
536 GSList *
537 purple_blist_get_buddies(void)
538 {
539 PurpleBuddyListPrivate *priv;
540 GSList *buddies = NULL;
541
542 if (!purplebuddylist)
543 return NULL;
544
545 priv = purple_buddy_list_get_instance_private(purplebuddylist);
546 g_hash_table_foreach(priv->buddies, append_buddy, &buddies);
547 return buddies;
548 }
549
550 void
551 purple_blist_show(void)
552 {
553 PurpleBuddyListClass *klass = NULL;
554
555 g_return_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist));
556 klass = PURPLE_BUDDY_LIST_GET_CLASS(purplebuddylist);
557
558 if (klass && klass->show) {
559 klass->show(purplebuddylist);
560 }
561 }
562
563 void purple_blist_set_visible(gboolean show)
564 {
565 PurpleBuddyListClass *klass = NULL;
566
567 g_return_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist));
568 klass = PURPLE_BUDDY_LIST_GET_CLASS(purplebuddylist);
569
570 if (klass && klass->set_visible) {
571 klass->set_visible(purplebuddylist, show);
572 }
573 }
574
575 void purple_blist_update_groups_cache(PurpleGroup *group, const char *new_name)
576 {
577 gchar* key;
578
579 key = purple_blist_fold_name(purple_group_get_name(group));
580 g_hash_table_remove(groups_cache, key);
581 g_free(key);
582
583 g_hash_table_insert(groups_cache,
584 purple_blist_fold_name(new_name), group);
585 }
586
587 void purple_blist_add_contact(PurpleMetaContact *contact, PurpleGroup *group, PurpleBlistNode *node)
588 {
589 PurpleBuddyListClass *klass = NULL;
590 PurpleGroup *g;
591 PurpleBlistNode *gnode, *cnode, *bnode;
592 PurpleCountingNode *contact_counter, *group_counter;
593
594 g_return_if_fail(PURPLE_IS_META_CONTACT(contact));
595 g_return_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist));
596
597 if (PURPLE_BLIST_NODE(contact) == node)
598 return;
599
600 klass = PURPLE_BUDDY_LIST_GET_CLASS(purplebuddylist);
601
602 if (node && (PURPLE_IS_META_CONTACT(node)))
603 g = PURPLE_GROUP(node->parent);
604 else if (group)
605 g = group;
606 else
607 g = purple_blist_get_default_group();
608
609 gnode = (PurpleBlistNode*)g;
610 cnode = (PurpleBlistNode*)contact;
611
612 if (cnode->parent) {
613 if (cnode->parent->child == cnode)
614 cnode->parent->child = cnode->next;
615 if (cnode->prev)
616 cnode->prev->next = cnode->next;
617 if (cnode->next)
618 cnode->next->prev = cnode->prev;
619
620 contact_counter = PURPLE_COUNTING_NODE(contact);
621 group_counter = PURPLE_COUNTING_NODE(cnode->parent);
622
623 if (purple_counting_node_get_online_count(contact_counter) > 0)
624 purple_counting_node_change_online_count(group_counter, -1);
625 if (purple_counting_node_get_current_size(contact_counter) > 0)
626 purple_counting_node_change_current_size(group_counter, -1);
627 purple_counting_node_change_total_size(group_counter, -1);
628
629 if (klass && klass->remove) {
630 klass->remove(purplebuddylist, cnode);
631 }
632
633 if (klass && klass->remove_node) {
634 klass->remove_node(purplebuddylist, cnode);
635 }
636 }
637
638 if (node && (PURPLE_IS_META_CONTACT(node))) {
639 if (node->next)
640 node->next->prev = cnode;
641 cnode->next = node->next;
642 cnode->prev = node;
643 cnode->parent = node->parent;
644 node->next = cnode;
645 } else {
646 if (gnode->child)
647 gnode->child->prev = cnode;
648 cnode->prev = NULL;
649 cnode->next = gnode->child;
650 gnode->child = cnode;
651 cnode->parent = gnode;
652 }
653
654 contact_counter = PURPLE_COUNTING_NODE(contact);
655 group_counter = PURPLE_COUNTING_NODE(g);
656
657 if (purple_counting_node_get_online_count(contact_counter) > 0)
658 purple_counting_node_change_online_count(group_counter, +1);
659 if (purple_counting_node_get_current_size(contact_counter) > 0)
660 purple_counting_node_change_current_size(group_counter, +1);
661 purple_counting_node_change_total_size(group_counter, +1);
662
663 if (klass && klass->save_node) {
664 if (cnode->child) {
665 klass->save_node(purplebuddylist, cnode);
666 }
667 for (bnode = cnode->child; bnode; bnode = bnode->next) {
668 klass->save_node(purplebuddylist, bnode);
669 }
670 }
671
672 if (klass && klass->update) {
673 if (cnode->child) {
674 klass->update(purplebuddylist, cnode);
675 }
676
677 for (bnode = cnode->child; bnode; bnode = bnode->next) {
678 klass->update(purplebuddylist, bnode);
679 }
680 }
681 }
682
683 void purple_blist_add_group(PurpleGroup *group, PurpleBlistNode *node)
684 {
685 PurpleBuddyListClass *klass = NULL;
686 PurpleBuddyListPrivate *priv = NULL;
687 PurpleBlistNode *gnode = (PurpleBlistNode*)group;
688 gchar* key;
689
690 g_return_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist));
691 g_return_if_fail(PURPLE_IS_GROUP(group));
692
693 klass = PURPLE_BUDDY_LIST_GET_CLASS(purplebuddylist);
694 priv = purple_buddy_list_get_instance_private(purplebuddylist);
695
696 /* if we're moving to overtop of ourselves, do nothing */
697 if (gnode == node) {
698 if (!priv->root) {
699 node = NULL;
700 } else {
701 return;
702 }
703 }
704
705 if (purple_blist_find_group(purple_group_get_name(group))) {
706 /* This is just being moved */
707
708 if (klass && klass->remove) {
709 klass->remove(purplebuddylist,
710 (PurpleBlistNode *)group);
711 }
712
713 if (gnode == priv->root) {
714 priv->root = gnode->next;
715 }
716 if (gnode->prev)
717 gnode->prev->next = gnode->next;
718 if (gnode->next)
719 gnode->next->prev = gnode->prev;
720 } else {
721 key = purple_blist_fold_name(purple_group_get_name(group));
722 g_hash_table_insert(groups_cache, key, group);
723 }
724
725 if (node && PURPLE_IS_GROUP(node)) {
726 gnode->next = node->next;
727 gnode->prev = node;
728 if (node->next)
729 node->next->prev = gnode;
730 node->next = gnode;
731 } else {
732 if (priv->root) {
733 priv->root->prev = gnode;
734 }
735 gnode->next = priv->root;
736 gnode->prev = NULL;
737 priv->root = gnode;
738 }
739
740 if (klass && klass->save_node) {
741 klass->save_node(purplebuddylist, gnode);
742 for (node = gnode->child; node; node = node->next) {
743 klass->save_node(purplebuddylist, node);
744 }
745 }
746
747 if (klass && klass->update) {
748 klass->update(purplebuddylist, gnode);
749 for (node = gnode->child; node; node = node->next) {
750 klass->update(purplebuddylist, node);
751 }
752 }
753
754 purple_signal_emit(purple_blist_get_handle(), "blist-node-added",
755 gnode);
756 }
757
758 void purple_blist_remove_contact(PurpleMetaContact *contact)
759 {
760 PurpleBuddyListClass *klass = NULL;
761 PurpleBlistNode *node, *gnode;
762 PurpleGroup *group;
763
764 g_return_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist));
765 g_return_if_fail(PURPLE_IS_META_CONTACT(contact));
766
767 klass = PURPLE_BUDDY_LIST_GET_CLASS(purplebuddylist);
768 node = (PurpleBlistNode *)contact;
769 gnode = node->parent;
770 group = PURPLE_GROUP(gnode);
771
772 /* Remove the node from its parent */
773 if (gnode->child == node)
774 gnode->child = node->next;
775 if (node->prev)
776 node->prev->next = node->next;
777 if (node->next)
778 node->next->prev = node->prev;
779 purple_counting_node_change_total_size(PURPLE_COUNTING_NODE(group), -1);
780
781 /* Update the UI */
782 if (klass && klass->remove) {
783 klass->remove(purplebuddylist, node);
784 }
785
786 if (klass && klass->remove_node) {
787 klass->remove_node(purplebuddylist, node);
788 }
789
790 purple_signal_emit(purple_blist_get_handle(), "blist-node-removed",
791 PURPLE_BLIST_NODE(contact));
792
793 /* Delete the node */
794 g_object_unref(contact);
795 }
796
797 void purple_blist_remove_group(PurpleGroup *group)
798 {
799 PurpleAccountManager *manager = NULL;
800 PurpleBuddyListClass *klass = NULL;
801 PurpleBuddyListPrivate *priv = NULL;
802 PurpleBlistNode *node;
803 GList *accounts = NULL;
804 gchar* key;
805
806 g_return_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist));
807 g_return_if_fail(PURPLE_IS_GROUP(group));
808
809 if (group == purple_blist_get_default_group())
810 purple_debug_warning("buddylist", "cannot remove default group");
811
812 klass = PURPLE_BUDDY_LIST_GET_CLASS(purplebuddylist);
813 priv = purple_buddy_list_get_instance_private(purplebuddylist);
814 node = (PurpleBlistNode *)group;
815
816 /* Make sure the group is empty */
817 if (node->child)
818 return;
819
820 /* Remove the node from its parent */
821 if (priv->root == node) {
822 priv->root = node->next;
823 }
824 if (node->prev)
825 node->prev->next = node->next;
826 if (node->next)
827 node->next->prev = node->prev;
828
829 key = purple_blist_fold_name(purple_group_get_name(group));
830 g_hash_table_remove(groups_cache, key);
831 g_free(key);
832
833 /* Update the UI */
834 if (klass && klass->remove) {
835 klass->remove(purplebuddylist, node);
836 }
837
838 if (klass && klass->remove_node) {
839 klass->remove_node(purplebuddylist, node);
840 }
841
842 purple_signal_emit(purple_blist_get_handle(), "blist-node-removed",
843 PURPLE_BLIST_NODE(group));
844
845 /* Remove the group from all accounts that are online */
846 manager = purple_account_manager_get_default();
847 accounts = purple_account_manager_get_connected(manager);
848 while(accounts != NULL) {
849 purple_account_remove_group(accounts->data, group);
850
851 accounts = g_list_delete_link(accounts, accounts);
852 }
853
854 /* Delete the node */
855 g_object_unref(group);
856 }
857
858 PurpleGroup *purple_blist_find_group(const char *name)
859 {
860 gchar* key;
861 PurpleGroup *group;
862
863 g_return_val_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist), NULL);
864
865 if (name == NULL || name[0] == '\0')
866 name = PURPLE_BLIST_DEFAULT_GROUP_NAME;
867 if (purple_strequal(name, "Buddies"))
868 name = PURPLE_BLIST_DEFAULT_GROUP_NAME;
869 if (purple_strequal(name, localized_default_group_name))
870 name = PURPLE_BLIST_DEFAULT_GROUP_NAME;
871
872 key = purple_blist_fold_name(name);
873 group = g_hash_table_lookup(groups_cache, key);
874 g_free(key);
875
876 return group;
877 }
878
879 PurpleGroup *
880 purple_blist_get_default_group(void)
881 {
882 PurpleGroup *group;
883
884 group = purple_blist_find_group(PURPLE_BLIST_DEFAULT_GROUP_NAME);
885 if (!group) {
886 group = purple_group_new(PURPLE_BLIST_DEFAULT_GROUP_NAME);
887 purple_blist_add_group(group, NULL);
888 }
889
890 return group;
891 }
892
893 void
894 purple_blist_walk(PurpleBlistWalkFunc group_func,
895 PurpleBlistWalkFunc meta_contact_func,
896 PurpleBlistWalkFunc contact_func,
897 gpointer data)
898 {
899 PurpleBlistNode *group = NULL, *meta_contact = NULL, *contact = NULL;
900
901 for (group = purple_blist_get_default_root(); group != NULL;
902 group = group->next) {
903 if(group_func != NULL) {
904 group_func(group, data);
905 }
906
907 for(meta_contact = group->child; meta_contact != NULL; meta_contact = meta_contact->next) {
908 if(PURPLE_IS_META_CONTACT(meta_contact)) {
909 if(meta_contact_func != NULL) {
910 meta_contact_func(meta_contact, data);
911 }
912
913 if(contact_func != NULL) {
914 for(contact = meta_contact->child; contact != NULL; contact = contact->next) {
915 contact_func(contact, data);
916 }
917 }
918 }
919 }
920 }
921 }
922
923 const gchar *
924 purple_blist_get_default_group_name(void) {
925 return _("Buddies");
926 }
927
928
929 void
930 purple_blist_request_add_buddy(PurpleAccount *account, const char *username,
931 const char *group, const char *alias)
932 {
933 PurpleBuddyListClass *klass = NULL;
934
935 g_return_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist));
936
937 klass = PURPLE_BUDDY_LIST_GET_CLASS(purplebuddylist);
938 if (klass != NULL && klass->request_add_buddy != NULL) {
939 klass->request_add_buddy(purplebuddylist, account, username,
940 group, alias);
941 }
942 }
943
944 void
945 purple_blist_request_add_group(void)
946 {
947 PurpleBuddyListClass *klass = NULL;
948
949 g_return_if_fail(PURPLE_IS_BUDDY_LIST(purplebuddylist));
950
951 klass = PURPLE_BUDDY_LIST_GET_CLASS(purplebuddylist);
952 if (klass != NULL && klass->request_add_group != NULL) {
953 klass->request_add_group(purplebuddylist);
954 }
955 }
956
957 void
958 purple_blist_new_node(PurpleBuddyList *list, PurpleBlistNode *node)
959 {
960 PurpleBuddyListClass *klass = NULL;
961
962 g_return_if_fail(PURPLE_IS_BUDDY_LIST(list));
963
964 klass = PURPLE_BUDDY_LIST_GET_CLASS(list);
965 if (klass && klass->new_node) {
966 klass->new_node(list, node);
967 }
968 }
969
970 void
971 purple_blist_update_node(PurpleBuddyList *list, PurpleBlistNode *node)
972 {
973 PurpleBuddyListClass *klass = NULL;
974
975 g_return_if_fail(PURPLE_IS_BUDDY_LIST(list));
976
977 klass = PURPLE_BUDDY_LIST_GET_CLASS(list);
978 if (klass && klass->update) {
979 klass->update(list, node);
980 }
981 }
982
983 void
984 purple_blist_save_node(PurpleBuddyList *list, PurpleBlistNode *node)
985 {
986 PurpleBuddyListClass *klass = NULL;
987
988 g_return_if_fail(PURPLE_IS_BUDDY_LIST(list));
989
990 klass = PURPLE_BUDDY_LIST_GET_CLASS(list);
991 if (klass && klass->save_node) {
992 klass->save_node(list, node);
993 }
994 }
995
996 void
997 purple_blist_save_account(PurpleBuddyList *list, PurpleAccount *account)
998 {
999 PurpleBuddyListClass *klass = NULL;
1000
1001 /* XXX: There's a chicken and egg problem with the accounts api, where
1002 * it'll call this function before purple_blist_init is called, this will
1003 * cause the following g_return_if_fail to fail, and muck up the logs. We
1004 * need to find a better fix for this, but this gets rid of it for now.
1005 */
1006 if(G_UNLIKELY(list == NULL && purplebuddylist == NULL)) {
1007 return;
1008 }
1009
1010 g_return_if_fail(PURPLE_IS_BUDDY_LIST(list));
1011
1012 klass = PURPLE_BUDDY_LIST_GET_CLASS(list);
1013 if (klass && klass->save_account) {
1014 klass->save_account(list, account);
1015 }
1016 }
1017
1018 const gchar *
1019 _purple_blist_get_localized_default_group_name(void)
1020 {
1021 return localized_default_group_name;
1022 }
1023
1024 void *
1025 purple_blist_get_handle(void)
1026 {
1027 static int handle;
1028
1029 return &handle;
1030 }
1031
1032 void
1033 purple_blist_init(void)
1034 {
1035 void *handle = purple_blist_get_handle();
1036
1037 /* Set a default, which can't be done as a static initializer. */
1038 buddy_list_type = PURPLE_TYPE_BUDDY_LIST;
1039
1040 purple_signal_register(handle, "blist-node-added",
1041 purple_marshal_VOID__POINTER, G_TYPE_NONE, 1,
1042 PURPLE_TYPE_BLIST_NODE);
1043
1044 purple_signal_register(handle, "blist-node-removed",
1045 purple_marshal_VOID__POINTER, G_TYPE_NONE, 1,
1046 PURPLE_TYPE_BLIST_NODE);
1047
1048 purple_signal_register(handle, "update-idle", purple_marshal_VOID,
1049 G_TYPE_NONE, 0);
1050
1051 purple_signal_register(handle, "blist-node-extended-menu",
1052 purple_marshal_VOID__POINTER_POINTER, G_TYPE_NONE, 2,
1053 PURPLE_TYPE_BLIST_NODE,
1054 G_TYPE_POINTER); /* (GList **) */
1055
1056 purple_signal_register(handle, "blist-node-aliased",
1057 purple_marshal_VOID__POINTER_POINTER, G_TYPE_NONE, 2,
1058 PURPLE_TYPE_BLIST_NODE, G_TYPE_STRING);
1059 }
1060
1061 static void
1062 blist_node_destroy(PurpleBuddyListClass *klass, PurpleBuddyList *list,
1063 PurpleBlistNode *node)
1064 {
1065 PurpleBlistNode *child, *next_child;
1066
1067 child = node->child;
1068 while (child) {
1069 next_child = child->next;
1070 blist_node_destroy(klass, list, child);
1071 child = next_child;
1072 }
1073
1074 /* Allow the UI to free data */
1075 node->parent = NULL;
1076 node->child = NULL;
1077 node->next = NULL;
1078 node->prev = NULL;
1079 if (klass && klass->remove) {
1080 klass->remove(list, node);
1081 }
1082
1083 g_object_unref(node);
1084 }
1085
1086 void
1087 purple_blist_uninit(void)
1088 {
1089 /* This happens if we quit before purple_set_blist is called. */
1090 if (purplebuddylist == NULL)
1091 return;
1092
1093 if(save_timer != 0) {
1094 g_clear_handle_id(&save_timer, g_source_remove);
1095 purple_blist_sync();
1096 }
1097
1098 purple_debug_info("buddylist", "Destroying");
1099
1100 g_clear_pointer(&buddies_cache, g_hash_table_destroy);
1101 g_clear_pointer(&groups_cache, g_hash_table_destroy);
1102
1103 g_clear_object(&purplebuddylist);
1104
1105 g_clear_pointer(&localized_default_group_name, g_free);
1106
1107 purple_signals_disconnect_by_handle(purple_blist_get_handle());
1108 purple_signals_unregister_by_instance(purple_blist_get_handle());
1109 }
1110
1111 /**************************************************************************
1112 * GObject code
1113 **************************************************************************/
1114
1115 /* GObject initialization function */
1116 static void
1117 purple_buddy_list_init(PurpleBuddyList *blist)
1118 {
1119 PurpleBuddyListPrivate *priv = NULL;
1120 PurpleAccountManager *manager = NULL;
1121
1122 priv = purple_buddy_list_get_instance_private(blist);
1123
1124 priv->buddies = g_hash_table_new_full(
1125 (GHashFunc)_purple_blist_hbuddy_hash,
1126 (GEqualFunc)_purple_blist_hbuddy_equal,
1127 (GDestroyNotify)_purple_blist_hbuddy_free_key, NULL);
1128
1129 manager = purple_account_manager_get_default();
1130 g_signal_connect_object(manager, "added",
1131 G_CALLBACK(purple_buddy_list_account_added_cb),
1132 blist, 0);
1133 g_signal_connect_object(manager, "removed",
1134 G_CALLBACK(purple_buddy_list_account_removed_cb),
1135 blist, 0);
1136 }
1137
1138 /* GObject finalize function */
1139 static void
1140 purple_buddy_list_finalize(GObject *object)
1141 {
1142 PurpleBuddyList *list = PURPLE_BUDDY_LIST(object);
1143 PurpleBuddyListClass *klass = PURPLE_BUDDY_LIST_GET_CLASS(list);
1144 PurpleBuddyListPrivate *priv =
1145 purple_buddy_list_get_instance_private(list);
1146 PurpleBlistNode *node, *next_node;
1147
1148 g_hash_table_destroy(priv->buddies);
1149
1150 node = priv->root;
1151 while (node) {
1152 next_node = node->next;
1153 blist_node_destroy(klass, list, node);
1154 node = next_node;
1155 }
1156 priv->root = NULL;
1157
1158 G_OBJECT_CLASS(purple_buddy_list_parent_class)->finalize(object);
1159 }
1160
1161 /* Class initializer function */
1162 static void purple_buddy_list_class_init(PurpleBuddyListClass *klass)
1163 {
1164 GObjectClass *obj_class = G_OBJECT_CLASS(klass);
1165
1166 obj_class->finalize = purple_buddy_list_finalize;
1167
1168 klass->save_node = purple_blist_real_save_node;
1169 klass->remove_node = purple_blist_real_save_node;
1170 klass->save_account = purple_blist_real_save_account;
1171 }

mercurial