rename accountopt.[ch] to purpleaccountoption.[ch]
[pidgin-git.git] / libpurple / protocols / gg / roster.c
blobf3af2e1a19632993c9fc1d2ecbc4151cf81b5f58
1 /* purple
3 * Purple is the legal property of its developers, whose names are too numerous
4 * to list here. Please refer to the COPYRIGHT file distributed with this
5 * source distribution.
7 * Rewritten from scratch during Google Summer of Code 2012
8 * by Tomek Wasilczyk (http://www.wasilczyk.pl).
10 * Previously implemented by:
11 * - Arkadiusz Miskiewicz <misiek@pld.org.pl> - first implementation (2001);
12 * - Bartosz Oler <bartosz@bzimage.us> - reimplemented during GSoC 2005;
13 * - Krzysztof Klinikowski <grommasher@gmail.com> - some parts (2009-2011).
15 * This program is free software; you can redistribute it and/or modify
16 * it under the terms of the GNU General Public License as published by
17 * the Free Software Foundation; either version 2 of the License, or
18 * (at your option) any later version.
20 * This program is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
25 * You should have received a copy of the GNU General Public License
26 * along with this program; if not, write to the Free Software
27 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
30 #include "roster.h"
32 #include "gg.h"
33 #include "xml.h"
34 #include "utils.h"
35 #include "purplew.h"
37 #include <debug.h>
38 #include <glibcompat.h>
40 #define GGP_ROSTER_SYNC_SETT "gg-synchronized"
41 #define GGP_ROSTER_DEBUG 0
42 #define GGP_ROSTER_GROUPID_DEFAULT "00000000-0000-0000-0000-000000000000"
43 #define GGP_ROSTER_GROUPID_BOTS "0b345af6-0001-0000-0000-000000000004"
45 /* TODO: ignored contacts synchronization (?) */
47 typedef struct
49 int version;
51 PurpleXmlNode *xml;
53 PurpleXmlNode *groups_node, *contacts_node;
55 /**
56 * Key: (uin_t) user identifier
57 * Value: (PurpleXmlNode*) xml node for contact
59 GHashTable *contact_nodes;
61 /**
62 * Key: (gchar*) group id
63 * Value: (PurpleXmlNode*) xml node for group
65 GHashTable *group_nodes;
67 /**
68 * Key: (gchar*) group name
69 * Value: (gchar*) group id
71 GHashTable *group_ids;
73 /**
74 * Key: (gchar*) group id
75 * Value: (gchar*) group name
77 GHashTable *group_names;
79 gchar *bots_group_id;
81 gboolean needs_update;
82 } ggp_roster_content;
84 typedef struct
86 enum
88 GGP_ROSTER_CHANGE_CONTACT_UPDATE,
89 GGP_ROSTER_CHANGE_CONTACT_REMOVE,
90 GGP_ROSTER_CHANGE_GROUP_RENAME,
91 } type;
92 union
94 uin_t uin;
95 struct
97 gchar *old_name;
98 gchar *new_name;
99 } group_rename;
100 } data;
101 } ggp_roster_change;
103 static inline ggp_roster_session_data *
104 ggp_roster_get_rdata(PurpleConnection *gc);
105 static void ggp_roster_content_free(ggp_roster_content *content);
106 static void ggp_roster_change_free(gpointer change);
107 static int ggp_roster_get_version(PurpleConnection *gc);
108 static gboolean ggp_roster_timer_cb(gpointer _gc);
109 #if GGP_ROSTER_DEBUG
110 static void ggp_roster_dump(ggp_roster_content *content);
111 #endif
113 /* synchronization control */
114 static gboolean ggp_roster_is_synchronized(PurpleBuddy *buddy);
115 static void ggp_roster_set_synchronized(PurpleConnection *gc,
116 PurpleBuddy *buddy, gboolean synchronized);
118 /* buddy list import */
119 static gboolean ggp_roster_reply_list_read_group(PurpleXmlNode *node,
120 ggp_roster_content *content);
121 static gboolean ggp_roster_reply_list_read_buddy(PurpleConnection *gc,
122 PurpleXmlNode *node, ggp_roster_content *content, GHashTable *remove_buddies);
123 static void ggp_roster_reply_list(PurpleConnection *gc, uint32_t version,
124 const char *reply);
126 /* buddy list export */
127 static const gchar * ggp_roster_send_update_group_add(
128 ggp_roster_content *content, PurpleGroup *group);
129 static gboolean ggp_roster_send_update_contact_update(PurpleConnection *gc,
130 ggp_roster_change *change);
131 static gboolean ggp_roster_send_update_contact_remove(PurpleConnection *gc,
132 ggp_roster_change *change);
133 static gboolean ggp_roster_send_update_group_rename(PurpleConnection *gc,
134 ggp_roster_change *change);
135 static void ggp_roster_send_update(PurpleConnection *gc);
136 static void ggp_roster_reply_ack(PurpleConnection *gc, uint32_t version);
137 static void ggp_roster_reply_reject(PurpleConnection *gc, uint32_t version);
139 /******************************************************************************/
141 static inline ggp_roster_session_data *
142 ggp_roster_get_rdata(PurpleConnection *gc)
144 GGPInfo *accdata = purple_connection_get_protocol_data(gc);
145 return &accdata->roster_data;
148 static void ggp_roster_content_free(ggp_roster_content *content)
150 if (content == NULL)
151 return;
152 if (content->xml)
153 purple_xmlnode_free(content->xml);
154 if (content->contact_nodes)
155 g_hash_table_destroy(content->contact_nodes);
156 if (content->group_nodes)
157 g_hash_table_destroy(content->group_nodes);
158 if (content->group_ids)
159 g_hash_table_destroy(content->group_ids);
160 if (content->group_names)
161 g_hash_table_destroy(content->group_names);
162 g_free(content->bots_group_id);
163 g_free(content);
166 static void ggp_roster_change_free(gpointer _change)
168 ggp_roster_change *change = _change;
170 if (change->type == GGP_ROSTER_CHANGE_GROUP_RENAME) {
171 g_free(change->data.group_rename.old_name);
172 g_free(change->data.group_rename.new_name);
175 g_free(change);
178 static int ggp_roster_get_version(PurpleConnection *gc)
180 ggp_roster_content *content = ggp_roster_get_rdata(gc)->content;
181 if (content == NULL)
182 return 0;
183 return content->version;
186 static gboolean ggp_roster_timer_cb(gpointer _gc)
188 PurpleConnection *gc = _gc;
190 PURPLE_ASSERT_CONNECTION_IS_VALID(gc);
192 ggp_roster_send_update(gc);
194 return TRUE;
197 #if GGP_ROSTER_DEBUG
198 static void ggp_roster_dump(ggp_roster_content *content)
200 char *str;
201 int len;
203 g_return_if_fail(content != NULL);
204 g_return_if_fail(content->xml != NULL);
206 str = purple_xmlnode_to_formatted_str(content->xml, &len);
207 purple_debug_misc("gg", "ggp_roster_dump: [%s]\n", str);
208 g_free(str);
210 #endif
212 /*******************************************************************************
213 * Setup.
214 ******************************************************************************/
216 gboolean ggp_roster_enabled(void)
218 static gboolean checked = FALSE;
219 static gboolean enabled;
221 if (!checked) {
222 enabled = gg_libgadu_check_feature(
223 GG_LIBGADU_FEATURE_USERLIST100);
224 checked = TRUE;
226 return enabled;
229 void ggp_roster_setup(PurpleConnection *gc)
231 ggp_roster_session_data *rdata = ggp_roster_get_rdata(gc);
233 rdata->content = NULL;
234 rdata->sent_updates = NULL;
235 rdata->pending_updates = NULL;
236 rdata->timer = 0;
237 rdata->is_updating = FALSE;
239 if (ggp_roster_enabled())
240 rdata->timer = g_timeout_add_seconds(2,
241 ggp_roster_timer_cb, gc);
244 void ggp_roster_cleanup(PurpleConnection *gc)
246 ggp_roster_session_data *rdata = ggp_roster_get_rdata(gc);
248 if (rdata->timer)
249 g_source_remove(rdata->timer);
250 ggp_roster_content_free(rdata->content);
251 g_list_free_full(rdata->sent_updates, ggp_roster_change_free);
252 g_list_free_full(rdata->pending_updates, ggp_roster_change_free);
255 /*******************************************************************************
256 * Synchronization control.
257 ******************************************************************************/
259 static gboolean ggp_roster_is_synchronized(PurpleBuddy *buddy)
261 gboolean ret = purple_blist_node_get_bool(PURPLE_BLIST_NODE(buddy),
262 GGP_ROSTER_SYNC_SETT);
263 return ret;
266 static void ggp_roster_set_synchronized(PurpleConnection *gc,
267 PurpleBuddy *buddy, gboolean synchronized)
269 ggp_roster_session_data *rdata = ggp_roster_get_rdata(gc);
270 uin_t uin = ggp_str_to_uin(purple_buddy_get_name(buddy));
271 ggp_roster_change *change;
273 purple_blist_node_set_bool(PURPLE_BLIST_NODE(buddy),
274 GGP_ROSTER_SYNC_SETT, synchronized);
275 if (!synchronized) {
276 change = g_new0(ggp_roster_change, 1);
277 change->type = GGP_ROSTER_CHANGE_CONTACT_UPDATE;
278 change->data.uin = uin;
279 rdata->pending_updates =
280 g_list_append(rdata->pending_updates, change);
284 void ggp_roster_request_update(PurpleConnection *gc)
286 GGPInfo *accdata = purple_connection_get_protocol_data(gc);
287 int local_version = ggp_roster_get_version(gc);
289 if (!ggp_roster_enabled()) {
290 purple_debug_warning("gg", "ggp_roster_request_update: "
291 "feature disabled\n");
292 return;
295 purple_debug_info("gg", "ggp_roster_request_update: local=%u\n",
296 local_version);
298 gg_userlist100_request(accdata->session, GG_USERLIST100_GET,
299 local_version, GG_USERLIST100_FORMAT_TYPE_GG100, NULL);
302 /*******************************************************************************
303 * Libgadu callbacks.
304 ******************************************************************************/
306 void ggp_roster_reply(PurpleConnection *gc,
307 struct gg_event_userlist100_reply *reply)
309 if (GG_USERLIST100_FORMAT_TYPE_GG100 != reply->format_type) {
310 purple_debug_warning("gg", "ggp_roster_reply: "
311 "unsupported format type (%x)\n", reply->format_type);
312 return;
315 if (reply->type == GG_USERLIST100_REPLY_LIST)
316 ggp_roster_reply_list(gc, reply->version, reply->reply);
317 else if (reply->type == 0x01) /* list up to date (TODO: push to libgadu) */
318 purple_debug_info("gg", "ggp_roster_reply: list up to date\n");
319 else if (reply->type == GG_USERLIST100_REPLY_ACK)
320 ggp_roster_reply_ack(gc, reply->version);
321 else if (reply->type == GG_USERLIST100_REPLY_REJECT)
322 ggp_roster_reply_reject(gc, reply->version);
323 else
324 purple_debug_error("gg", "ggp_roster_reply: "
325 "unsupported reply (%x)\n", reply->type);
328 void ggp_roster_version(PurpleConnection *gc,
329 struct gg_event_userlist100_version *version)
331 int local_version = ggp_roster_get_version(gc);
332 int remote_version = version->version;
334 purple_debug_info("gg", "ggp_roster_version: local=%u, remote=%u\n",
335 local_version, remote_version);
337 if (local_version < remote_version)
338 ggp_roster_request_update(gc);
341 /*******************************************************************************
342 * Libpurple callbacks.
343 ******************************************************************************/
345 void ggp_roster_alias_buddy(PurpleConnection *gc, const char *who,
346 const char *alias)
348 PurpleBuddy *buddy;
350 g_return_if_fail(who != NULL);
352 if (!ggp_roster_enabled())
353 return;
355 purple_debug_misc("gg", "ggp_roster_alias_buddy(\"%s\", \"%s\")\n",
356 who, alias);
358 buddy = purple_blist_find_buddy(purple_connection_get_account(gc), who);
359 g_return_if_fail(buddy != NULL);
361 ggp_roster_set_synchronized(gc, buddy, FALSE);
364 void ggp_roster_group_buddy(PurpleConnection *gc, const char *who,
365 const char *old_group, const char *new_group)
367 ggp_roster_session_data *rdata = ggp_roster_get_rdata(gc);
368 ggp_roster_change *change;
370 if (!ggp_roster_enabled())
371 return;
372 if (rdata->is_updating)
373 return;
375 purple_debug_misc("gg", "ggp_roster_group_buddy: "
376 "who=\"%s\", group=\"%s\" -> \"%s\")\n",
377 who, old_group, new_group);
379 /* purple_blist_find_buddy(..., who) is not accessible at this moment */
380 change = g_new0(ggp_roster_change, 1);
381 change->type = GGP_ROSTER_CHANGE_CONTACT_UPDATE;
382 change->data.uin = ggp_str_to_uin(who);
383 rdata->pending_updates = g_list_append(rdata->pending_updates, change);
386 void ggp_roster_rename_group(PurpleConnection *gc, const char *old_name,
387 PurpleGroup *group, GList *moved_buddies)
389 ggp_roster_session_data *rdata = ggp_roster_get_rdata(gc);
390 ggp_roster_change *change;
392 if (!ggp_roster_enabled())
393 return;
395 change = g_new0(ggp_roster_change, 1);
396 change->type = GGP_ROSTER_CHANGE_GROUP_RENAME;
397 change->data.group_rename.old_name = g_strdup(old_name);
398 change->data.group_rename.new_name =
399 g_strdup(purple_group_get_name(group));
400 rdata->pending_updates = g_list_append(rdata->pending_updates, change);
403 void ggp_roster_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy,
404 PurpleGroup *group, const char *message)
406 g_return_if_fail(gc != NULL);
407 g_return_if_fail(buddy != NULL);
409 if (!ggp_roster_enabled())
410 return;
412 ggp_roster_set_synchronized(gc, buddy, FALSE);
415 void ggp_roster_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy,
416 PurpleGroup *group)
418 ggp_roster_session_data *rdata = ggp_roster_get_rdata(gc);
419 ggp_roster_change *change;
421 if (!ggp_roster_enabled())
422 return;
424 change = g_new0(ggp_roster_change, 1);
425 change->type = GGP_ROSTER_CHANGE_CONTACT_REMOVE;
426 change->data.uin = ggp_str_to_uin(purple_buddy_get_name(buddy));
427 rdata->pending_updates = g_list_append(rdata->pending_updates, change);
430 /*******************************************************************************
431 * Buddy list import.
432 ******************************************************************************/
434 static gboolean ggp_roster_reply_list_read_group(PurpleXmlNode *node,
435 ggp_roster_content *content)
437 char *name, *id;
438 gboolean removable;
439 gboolean succ = TRUE, is_bot, is_default;
441 succ &= ggp_xml_get_string(node, "Id", &id);
442 succ &= ggp_xml_get_string(node, "Name", &name);
443 succ &= ggp_xml_get_bool(node, "IsRemovable", &removable);
445 if (!succ) {
446 g_free(id);
447 g_free(name);
448 g_return_val_if_reached(FALSE);
451 is_bot = (strcmp(id, GGP_ROSTER_GROUPID_BOTS) == 0 ||
452 g_strcmp0(name, "Pomocnicy") == 0);
453 is_default = (strcmp(id, GGP_ROSTER_GROUPID_DEFAULT) == 0 ||
454 g_strcmp0(name, PURPLE_BLIST_DEFAULT_GROUP_NAME) == 0 ||
455 g_strcmp0(name, "[default]") == 0);
457 if (!content->bots_group_id && is_bot)
458 content->bots_group_id = g_strdup(id);
460 if (!removable || is_bot || is_default) {
461 g_free(id);
462 g_free(name);
463 return TRUE;
466 g_hash_table_insert(content->group_nodes, g_strdup(id), node);
467 g_hash_table_insert(content->group_ids, g_strdup(name), g_strdup(id));
468 g_hash_table_insert(content->group_names, id, name);
470 return TRUE;
473 static gboolean ggp_roster_reply_list_read_buddy(PurpleConnection *gc,
474 PurpleXmlNode *node, ggp_roster_content *content, GHashTable *remove_buddies)
476 gchar *alias, *group_name = NULL;
477 uin_t uin;
478 gboolean succ = TRUE;
479 PurpleXmlNode *group_list, *group_elem;
480 PurpleBuddy *buddy = NULL;
481 PurpleGroup *group = NULL;
482 PurpleGroup *currentGroup;
483 gboolean alias_changed;
484 PurpleAccount *account = purple_connection_get_account(gc);
486 succ &= ggp_xml_get_string(node, "ShowName", &alias);
487 succ &= ggp_xml_get_uint(node, "GGNumber", &uin);
489 group_list = purple_xmlnode_get_child(node, "Groups");
490 succ &= (group_list != NULL);
492 if (!succ) {
493 g_free(alias);
494 g_return_val_if_reached(FALSE);
497 g_hash_table_insert(content->contact_nodes, GINT_TO_POINTER(uin), node);
499 /* check, if alias is set */
500 if (*alias == '\0' ||
501 strcmp(alias, ggp_uin_to_str(uin)) == 0)
503 g_free(alias);
504 alias = NULL;
507 /* getting (eventually creating) group */
508 group_elem = purple_xmlnode_get_child(group_list, "GroupId");
509 while (group_elem != NULL) {
510 gchar *id;
511 gboolean isbot;
513 if (!ggp_xml_get_string(group_elem, NULL, &id))
514 continue;
515 isbot = (0 == g_strcmp0(id, content->bots_group_id));
516 group_name = g_hash_table_lookup(content->group_names, id);
517 g_free(id);
519 /* we don't want to import bots;
520 * they are inserted to roster by default
522 if (isbot) {
523 g_free(alias);
524 return TRUE;
527 if (group_name != NULL)
528 break;
530 group_elem = purple_xmlnode_get_next_twin(group_elem);
532 if (group_name) {
533 group = purple_blist_find_group(group_name);
534 if (!group) {
535 group = purple_group_new(group_name);
536 purple_blist_add_group(group, NULL);
540 /* add buddy, if doesn't exists */
541 buddy = purple_blist_find_buddy(account, ggp_uin_to_str(uin));
542 g_hash_table_remove(remove_buddies, GINT_TO_POINTER(uin));
543 if (!buddy) {
544 purple_debug_info("gg", "ggp_roster_reply_list_read_buddy: "
545 "adding %u (%s) to buddy list\n", uin, alias);
546 buddy = purple_buddy_new(account, ggp_uin_to_str(uin), alias);
547 purple_blist_add_buddy(buddy, NULL, group, NULL);
548 ggp_roster_set_synchronized(gc, buddy, TRUE);
550 g_free(alias);
551 return TRUE;
554 /* buddy exists, but is not synchronized - local list has priority */
555 if (!ggp_roster_is_synchronized(buddy)) {
556 purple_debug_misc("gg", "ggp_roster_reply_list_read_buddy: "
557 "ignoring not synchronized %u (%s)\n",
558 uin, purple_buddy_get_name(buddy));
559 g_free(alias);
560 return TRUE;
563 currentGroup = ggp_purplew_buddy_get_group_only(buddy);
564 alias_changed =
565 (0 != g_strcmp0(alias, purple_buddy_get_alias_only(buddy)));
567 if (currentGroup == group && !alias_changed) {
568 g_free(alias);
569 return TRUE;
572 purple_debug_misc("gg", "ggp_roster_reply_list_read_buddy: "
573 "updating %u (%s) - alias=\"%s\"->\"%s\", group=%p->%p (%s)\n",
574 uin, purple_buddy_get_name(buddy),
575 purple_buddy_get_alias(buddy), alias,
576 currentGroup, group, group_name);
577 if (alias_changed)
578 purple_buddy_set_local_alias(buddy, alias);
579 if (currentGroup != group)
580 purple_blist_add_buddy(buddy, NULL, group, NULL);
582 g_free(alias);
583 return TRUE;
586 static void ggp_roster_reply_list(PurpleConnection *gc, uint32_t version,
587 const char *data)
589 ggp_roster_session_data *rdata = ggp_roster_get_rdata(gc);
590 PurpleXmlNode *xml, *xml_it;
591 PurpleAccount *account;
592 GSList *local_buddies;
593 GHashTable *remove_buddies;
594 GList *update_buddies = NULL, *local_groups, *it, *table_values;
595 ggp_roster_content *content;
597 g_return_if_fail(gc != NULL);
598 g_return_if_fail(data != NULL);
600 account = purple_connection_get_account(gc);
602 purple_debug_info("gg", "ggp_roster_reply_list: got list, version=%u\n",
603 version);
605 xml = purple_xmlnode_from_str(data, -1);
606 if (xml == NULL) {
607 purple_debug_warning("gg", "ggp_roster_reply_list: "
608 "invalid xml\n");
609 return;
612 ggp_roster_content_free(rdata->content);
613 rdata->content = NULL;
614 rdata->is_updating = TRUE;
615 content = g_new0(ggp_roster_content, 1);
616 content->version = version;
617 content->xml = xml;
618 content->contact_nodes = g_hash_table_new(NULL, NULL);
619 content->group_nodes = g_hash_table_new_full(
620 g_str_hash, g_str_equal, g_free, NULL);
621 content->group_ids = g_hash_table_new_full(
622 g_str_hash, g_str_equal, g_free, g_free);
623 content->group_names = g_hash_table_new_full(
624 g_str_hash, g_str_equal, g_free, g_free);
626 #if GGP_ROSTER_DEBUG
627 ggp_roster_dump(content);
628 #endif
630 /* reading groups */
631 content->groups_node = purple_xmlnode_get_child(xml, "Groups");
632 if (content->groups_node == NULL) {
633 ggp_roster_content_free(content);
634 g_return_if_reached();
636 xml_it = purple_xmlnode_get_child(content->groups_node, "Group");
637 while (xml_it != NULL) {
638 if (!ggp_roster_reply_list_read_group(xml_it, content)) {
639 ggp_roster_content_free(content);
640 g_return_if_reached();
643 xml_it = purple_xmlnode_get_next_twin(xml_it);
646 /* dumping current group list */
647 local_groups = ggp_purplew_account_get_groups(account, TRUE);
649 /* dumping current buddy list
650 * we will:
651 * - remove synchronized ones, if not found in list at server
652 * - upload not synchronized ones
654 local_buddies = purple_blist_find_buddies(account, NULL);
655 remove_buddies = g_hash_table_new(g_direct_hash, g_direct_equal);
656 while (local_buddies) {
657 PurpleBuddy *buddy = local_buddies->data;
658 uin_t uin = ggp_str_to_uin(purple_buddy_get_name(buddy));
659 local_buddies =
660 g_slist_delete_link(local_buddies, local_buddies);
662 if (!uin)
663 continue;
665 if (ggp_roster_is_synchronized(buddy))
666 g_hash_table_insert(remove_buddies,
667 GINT_TO_POINTER(uin), buddy);
668 else
669 update_buddies = g_list_append(update_buddies, buddy);
672 /* reading buddies */
673 content->contacts_node = purple_xmlnode_get_child(xml, "Contacts");
674 if (content->contacts_node == NULL) {
675 g_hash_table_destroy(remove_buddies);
676 g_list_free(update_buddies);
677 ggp_roster_content_free(content);
678 g_return_if_reached();
680 xml_it = purple_xmlnode_get_child(content->contacts_node, "Contact");
681 while (xml_it != NULL) {
682 if (!ggp_roster_reply_list_read_buddy(gc, xml_it, content,
683 remove_buddies))
685 g_hash_table_destroy(remove_buddies);
686 g_list_free(update_buddies);
687 ggp_roster_content_free(content);
688 g_return_if_reached();
691 xml_it = purple_xmlnode_get_next_twin(xml_it);
694 /* removing buddies, which are not present in roster */
695 table_values = g_hash_table_get_values(remove_buddies);
696 it = g_list_first(table_values);
697 while (it) {
698 PurpleBuddy *buddy = it->data;
699 it = g_list_next(it);
700 if (!ggp_roster_is_synchronized(buddy))
701 continue;
702 purple_debug_info("gg", "ggp_roster_reply_list: "
703 "removing %s from buddy list\n",
704 purple_buddy_get_name(buddy));
705 purple_blist_remove_buddy(buddy);
707 g_list_free(table_values);
708 g_hash_table_destroy(remove_buddies);
710 /* remove groups, which are empty, but had contacts before
711 * synchronization
713 it = g_list_first(local_groups);
714 while (it) {
715 PurpleGroup *group = it->data;
716 it = g_list_next(it);
717 if (purple_counting_node_get_total_size(PURPLE_COUNTING_NODE(group)) != 0)
718 continue;
719 purple_debug_info("gg", "ggp_roster_reply_list: "
720 "removing group %s\n", purple_group_get_name(group));
721 purple_blist_remove_group(group);
723 g_list_free(local_groups);
725 /* adding not synchronized buddies */
726 it = g_list_first(update_buddies);
727 while (it) {
728 PurpleBuddy *buddy = it->data;
729 uin_t uin = ggp_str_to_uin(purple_buddy_get_name(buddy));
730 ggp_roster_change *change;
732 it = g_list_next(it);
733 g_assert(uin > 0);
735 purple_debug_misc("gg", "ggp_roster_reply_list: "
736 "adding change of %u for roster\n", uin);
737 change = g_new0(ggp_roster_change, 1);
738 change->type = GGP_ROSTER_CHANGE_CONTACT_UPDATE;
739 change->data.uin = uin;
740 rdata->pending_updates =
741 g_list_append(rdata->pending_updates, change);
743 g_list_free(update_buddies);
745 rdata->content = content;
746 rdata->is_updating = FALSE;
747 purple_debug_info("gg", "ggp_roster_reply_list: "
748 "import done, version=%u\n", version);
751 /*******************************************************************************
752 * Buddy list export.
753 ******************************************************************************/
755 static const gchar * ggp_roster_send_update_group_add(
756 ggp_roster_content *content, PurpleGroup *group)
758 gchar *id;
759 const char *id_existing, *group_name;
760 PurpleXmlNode *group_node;
761 gboolean succ = TRUE;
763 if (group) {
764 group_name = purple_group_get_name(group);
765 id_existing =
766 g_hash_table_lookup(content->group_ids, group_name);
767 } else
768 id_existing = GGP_ROSTER_GROUPID_DEFAULT;
769 if (id_existing)
770 return id_existing;
772 purple_debug_info("gg", "ggp_roster_send_update_group_add: adding %s\n",
773 purple_group_get_name(group));
775 id = purple_uuid_random();
777 group_node = purple_xmlnode_new_child(content->groups_node, "Group");
778 succ &= ggp_xml_set_string(group_node, "Id", id);
779 succ &= ggp_xml_set_string(group_node, "Name", group_name);
780 succ &= ggp_xml_set_string(group_node, "IsExpanded", "true");
781 succ &= ggp_xml_set_string(group_node, "IsRemovable", "true");
782 content->needs_update = TRUE;
784 g_hash_table_insert(content->group_ids, g_strdup(group_name),
785 g_strdup(id));
786 g_hash_table_replace(content->group_nodes, id, group_node);
788 g_return_val_if_fail(succ, NULL);
790 return id;
793 static gboolean ggp_roster_send_update_contact_update(PurpleConnection *gc,
794 ggp_roster_change *change)
796 PurpleAccount *account = purple_connection_get_account(gc);
797 ggp_roster_content *content = ggp_roster_get_rdata(gc)->content;
798 uin_t uin = change->data.uin;
799 PurpleBuddy *buddy;
800 PurpleXmlNode *buddy_node, *contact_groups;
801 gboolean succ = TRUE;
802 const char *group_id;
804 g_return_val_if_fail(change->type == GGP_ROSTER_CHANGE_CONTACT_UPDATE,
805 FALSE);
807 buddy = purple_blist_find_buddy(account, ggp_uin_to_str(uin));
808 if (!buddy)
809 return TRUE;
810 buddy_node = g_hash_table_lookup(content->contact_nodes,
811 GINT_TO_POINTER(uin));
813 group_id = ggp_roster_send_update_group_add(content,
814 ggp_purplew_buddy_get_group_only(buddy));
816 if (buddy_node) { /* update existing */
817 purple_debug_misc("gg", "ggp_roster_send_update_contact_update:"
818 " updating %u...\n", uin);
820 succ &= ggp_xml_set_string(buddy_node, "ShowName",
821 purple_buddy_get_alias(buddy));
823 contact_groups = purple_xmlnode_get_child(buddy_node, "Groups");
824 g_assert(contact_groups);
825 ggp_xmlnode_remove_children(contact_groups);
826 succ &= ggp_xml_set_string(contact_groups, "GroupId", group_id);
828 g_return_val_if_fail(succ, FALSE);
830 return TRUE;
833 /* add new */
834 purple_debug_misc("gg", "ggp_roster_send_update_contact_update: "
835 "adding %u...\n", uin);
836 buddy_node = purple_xmlnode_new_child(content->contacts_node, "Contact");
837 succ &= ggp_xml_set_string(buddy_node, "Guid", purple_uuid_random());
838 succ &= ggp_xml_set_uint(buddy_node, "GGNumber", uin);
839 succ &= ggp_xml_set_string(buddy_node, "ShowName",
840 purple_buddy_get_alias(buddy));
842 contact_groups = purple_xmlnode_new_child(buddy_node, "Groups");
843 g_assert(contact_groups);
844 succ &= ggp_xml_set_string(contact_groups, "GroupId", group_id);
846 purple_xmlnode_new_child(buddy_node, "Avatars");
847 succ &= ggp_xml_set_bool(buddy_node, "FlagBuddy", TRUE);
848 succ &= ggp_xml_set_bool(buddy_node, "FlagNormal", TRUE);
849 succ &= ggp_xml_set_bool(buddy_node, "FlagFriend", TRUE);
851 /* we don't use Guid, so update is not needed
852 * content->needs_update = TRUE;
855 g_hash_table_insert(content->contact_nodes, GINT_TO_POINTER(uin),
856 buddy_node);
858 g_return_val_if_fail(succ, FALSE);
860 return TRUE;
863 static gboolean ggp_roster_send_update_contact_remove(PurpleConnection *gc,
864 ggp_roster_change *change)
866 PurpleAccount *account = purple_connection_get_account(gc);
867 ggp_roster_content *content = ggp_roster_get_rdata(gc)->content;
868 uin_t uin = change->data.uin;
869 PurpleBuddy *buddy;
870 PurpleXmlNode *buddy_node;
872 g_return_val_if_fail(change->type == GGP_ROSTER_CHANGE_CONTACT_REMOVE,
873 FALSE);
875 buddy = purple_blist_find_buddy(account, ggp_uin_to_str(uin));
876 if (buddy) {
877 purple_debug_info("gg", "ggp_roster_send_update_contact_remove:"
878 " contact %u re-added\n", uin);
879 return TRUE;
882 buddy_node = g_hash_table_lookup(content->contact_nodes,
883 GINT_TO_POINTER(uin));
884 if (!buddy_node) /* already removed */
885 return TRUE;
887 purple_debug_info("gg", "ggp_roster_send_update_contact_remove: "
888 "removing %u\n", uin);
889 purple_xmlnode_free(buddy_node);
890 g_hash_table_remove(content->contact_nodes, GINT_TO_POINTER(uin));
892 return TRUE;
895 static gboolean ggp_roster_send_update_group_rename(PurpleConnection *gc,
896 ggp_roster_change *change)
898 PurpleAccount *account = purple_connection_get_account(gc);
899 ggp_roster_content *content = ggp_roster_get_rdata(gc)->content;
900 const char *old_name = change->data.group_rename.old_name;
901 const char *new_name = change->data.group_rename.new_name;
902 PurpleXmlNode *group_node;
903 const char *group_id;
905 g_return_val_if_fail(change->type == GGP_ROSTER_CHANGE_GROUP_RENAME,
906 FALSE);
908 purple_debug_misc("gg", "ggp_roster_send_update_group_rename: "
909 "\"%s\"->\"%s\"\n", old_name, new_name);
911 /* moving to or from default group instead of renaming it */
912 if (0 == g_strcmp0(old_name, PURPLE_BLIST_DEFAULT_GROUP_NAME) ||
913 0 == g_strcmp0(new_name, PURPLE_BLIST_DEFAULT_GROUP_NAME))
915 PurpleGroup *group;
916 GList *group_buddies;
917 group = purple_blist_find_group(new_name);
918 if (!group)
919 return TRUE;
920 purple_debug_info("gg", "ggp_roster_send_update_group_rename: "
921 "invalidating buddies in default group\n");
922 group_buddies = ggp_purplew_group_get_buddies(group, account);
923 while (group_buddies) {
924 ggp_roster_set_synchronized(gc, group_buddies->data,
925 FALSE);
926 group_buddies = g_list_delete_link(group_buddies,
927 group_buddies);
929 return TRUE;
931 group_id = g_hash_table_lookup(content->group_ids, old_name);
932 if (!group_id) {
933 purple_debug_info("gg", "ggp_roster_send_update_group_rename: "
934 "%s is not present at roster\n", old_name);
935 return TRUE;
938 group_node = g_hash_table_lookup(content->group_nodes, group_id);
939 if (!group_node) {
940 purple_debug_error("gg", "ggp_roster_send_update_group_rename: "
941 "node for %s not found, id=%s\n", old_name, group_id);
942 g_hash_table_remove(content->group_ids, old_name);
943 return TRUE;
946 g_hash_table_remove(content->group_ids, old_name);
947 g_hash_table_insert(content->group_ids, g_strdup(new_name),
948 g_strdup(group_id));
949 g_hash_table_insert(content->group_nodes, g_strdup(group_id),
950 group_node);
951 return ggp_xml_set_string(group_node, "Name", new_name);
954 static void ggp_roster_send_update(PurpleConnection *gc)
956 GGPInfo *accdata = purple_connection_get_protocol_data(gc);
957 ggp_roster_session_data *rdata = ggp_roster_get_rdata(gc);
958 ggp_roster_content *content = rdata->content;
959 GList *updates_it;
960 gchar *str;
961 int len;
963 /* an update is running now */
964 if (rdata->sent_updates)
965 return;
967 /* no pending updates found */
968 if (!rdata->pending_updates)
969 return;
971 /* not initialized */
972 if (!content)
973 return;
975 purple_debug_info("gg", "ggp_roster_send_update: "
976 "pending updates found\n");
978 rdata->sent_updates = rdata->pending_updates;
979 rdata->pending_updates = NULL;
981 updates_it = g_list_first(rdata->sent_updates);
982 while (updates_it) {
983 ggp_roster_change *change = updates_it->data;
984 gboolean succ = FALSE;
985 updates_it = g_list_next(updates_it);
987 if (change->type == GGP_ROSTER_CHANGE_CONTACT_UPDATE)
988 succ = ggp_roster_send_update_contact_update(gc,
989 change);
990 else if (change->type == GGP_ROSTER_CHANGE_CONTACT_REMOVE)
991 succ = ggp_roster_send_update_contact_remove(gc,
992 change);
993 else if (change->type == GGP_ROSTER_CHANGE_GROUP_RENAME)
994 succ = ggp_roster_send_update_group_rename(gc, change);
995 else
996 purple_debug_fatal("gg", "ggp_roster_send_update: "
997 "not handled\n");
998 g_return_if_fail(succ);
1001 #if GGP_ROSTER_DEBUG
1002 ggp_roster_dump(content);
1003 #endif
1005 str = purple_xmlnode_to_str(content->xml, &len);
1006 gg_userlist100_request(accdata->session, GG_USERLIST100_PUT,
1007 content->version, GG_USERLIST100_FORMAT_TYPE_GG100, str);
1008 g_free(str);
1011 static void ggp_roster_reply_ack(PurpleConnection *gc, uint32_t version)
1013 PurpleAccount *account = purple_connection_get_account(gc);
1014 ggp_roster_session_data *rdata = ggp_roster_get_rdata(gc);
1015 ggp_roster_content *content = rdata->content;
1016 GList *updates_it;
1018 purple_debug_info("gg", "ggp_roster_reply_ack: version=%u\n", version);
1020 /* set synchronization flag for all buddies, that were updated at roster */
1021 updates_it = g_list_first(rdata->sent_updates);
1022 while (updates_it) {
1023 ggp_roster_change *change = updates_it->data;
1024 PurpleBuddy *buddy;
1025 updates_it = g_list_next(updates_it);
1027 if (change->type != GGP_ROSTER_CHANGE_CONTACT_UPDATE)
1028 continue;
1030 buddy = purple_blist_find_buddy(account,
1031 ggp_uin_to_str(change->data.uin));
1032 if (buddy)
1033 ggp_roster_set_synchronized(gc, buddy, TRUE);
1036 /* we need to remove "synchronized" flag for all contacts, that have
1037 * beed modified between roster update start and now
1039 updates_it = g_list_first(rdata->pending_updates);
1040 while (updates_it) {
1041 ggp_roster_change *change = updates_it->data;
1042 PurpleBuddy *buddy;
1043 updates_it = g_list_next(updates_it);
1045 if (change->type != GGP_ROSTER_CHANGE_CONTACT_UPDATE)
1046 continue;
1048 buddy = purple_blist_find_buddy(account,
1049 ggp_uin_to_str(change->data.uin));
1050 if (buddy && ggp_roster_is_synchronized(buddy))
1051 ggp_roster_set_synchronized(gc, buddy, FALSE);
1054 g_list_free_full(rdata->sent_updates, ggp_roster_change_free);
1055 rdata->sent_updates = NULL;
1057 /* bump roster version or update it, if needed */
1058 g_return_if_fail(content != NULL);
1059 if (content->needs_update) {
1060 ggp_roster_content_free(rdata->content);
1061 rdata->content = NULL;
1062 /* we have to wait for gg_event_userlist100_version
1063 * ggp_roster_request_update(gc);
1066 else
1067 content->version = version;
1070 static void ggp_roster_reply_reject(PurpleConnection *gc, uint32_t version)
1072 ggp_roster_session_data *rdata = ggp_roster_get_rdata(gc);
1074 purple_debug_info("gg", "ggp_roster_reply_reject: version=%u\n",
1075 version);
1077 g_return_if_fail(rdata->sent_updates);
1079 rdata->pending_updates = g_list_concat(rdata->pending_updates,
1080 rdata->sent_updates);
1081 rdata->sent_updates = NULL;
1083 ggp_roster_content_free(rdata->content);
1084 rdata->content = NULL;
1085 ggp_roster_request_update(gc);
1088 /******************************************************************************/