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
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
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 (?) */
53 PurpleXmlNode
*groups_node
, *contacts_node
;
56 * Key: (uin_t) user identifier
57 * Value: (PurpleXmlNode*) xml node for contact
59 GHashTable
*contact_nodes
;
62 * Key: (gchar*) group id
63 * Value: (PurpleXmlNode*) xml node for group
65 GHashTable
*group_nodes
;
68 * Key: (gchar*) group name
69 * Value: (gchar*) group id
71 GHashTable
*group_ids
;
74 * Key: (gchar*) group id
75 * Value: (gchar*) group name
77 GHashTable
*group_names
;
81 gboolean needs_update
;
88 GGP_ROSTER_CHANGE_CONTACT_UPDATE
,
89 GGP_ROSTER_CHANGE_CONTACT_REMOVE
,
90 GGP_ROSTER_CHANGE_GROUP_RENAME
,
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
);
110 static void ggp_roster_dump(ggp_roster_content
*content
);
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
,
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
)
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
);
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
);
178 static int ggp_roster_get_version(PurpleConnection
*gc
)
180 ggp_roster_content
*content
= ggp_roster_get_rdata(gc
)->content
;
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
);
198 static void ggp_roster_dump(ggp_roster_content
*content
)
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
);
212 /*******************************************************************************
214 ******************************************************************************/
216 gboolean
ggp_roster_enabled(void)
218 static gboolean checked
= FALSE
;
219 static gboolean enabled
;
222 enabled
= gg_libgadu_check_feature(
223 GG_LIBGADU_FEATURE_USERLIST100
);
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
;
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
);
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
);
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
);
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");
295 purple_debug_info("gg", "ggp_roster_request_update: local=%u\n",
298 gg_userlist100_request(accdata
->session
, GG_USERLIST100_GET
,
299 local_version
, GG_USERLIST100_FORMAT_TYPE_GG100
, NULL
);
302 /*******************************************************************************
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
);
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
);
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
,
350 g_return_if_fail(who
!= NULL
);
352 if (!ggp_roster_enabled())
355 purple_debug_misc("gg", "ggp_roster_alias_buddy(\"%s\", \"%s\")\n",
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())
372 if (rdata
->is_updating
)
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())
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())
412 ggp_roster_set_synchronized(gc
, buddy
, FALSE
);
415 void ggp_roster_remove_buddy(PurpleConnection
*gc
, PurpleBuddy
*buddy
,
418 ggp_roster_session_data
*rdata
= ggp_roster_get_rdata(gc
);
419 ggp_roster_change
*change
;
421 if (!ggp_roster_enabled())
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 /*******************************************************************************
432 ******************************************************************************/
434 static gboolean
ggp_roster_reply_list_read_group(PurpleXmlNode
*node
,
435 ggp_roster_content
*content
)
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
);
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
) {
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
);
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
;
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
);
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)
507 /* getting (eventually creating) group */
508 group_elem
= purple_xmlnode_get_child(group_list
, "GroupId");
509 while (group_elem
!= NULL
) {
513 if (!ggp_xml_get_string(group_elem
, NULL
, &id
))
515 isbot
= (0 == g_strcmp0(id
, content
->bots_group_id
));
516 group_name
= g_hash_table_lookup(content
->group_names
, id
);
519 /* we don't want to import bots;
520 * they are inserted to roster by default
527 if (group_name
!= NULL
)
530 group_elem
= purple_xmlnode_get_next_twin(group_elem
);
533 group
= purple_blist_find_group(group_name
);
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
));
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
);
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
));
563 currentGroup
= ggp_purplew_buddy_get_group_only(buddy
);
565 (0 != g_strcmp0(alias
, purple_buddy_get_alias_only(buddy
)));
567 if (currentGroup
== group
&& !alias_changed
) {
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
);
578 purple_buddy_set_local_alias(buddy
, alias
);
579 if (currentGroup
!= group
)
580 purple_blist_add_buddy(buddy
, NULL
, group
, NULL
);
586 static void ggp_roster_reply_list(PurpleConnection
*gc
, uint32_t version
,
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",
605 xml
= purple_xmlnode_from_str(data
, -1);
607 purple_debug_warning("gg", "ggp_roster_reply_list: "
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
;
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
);
627 ggp_roster_dump(content
);
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
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
));
660 g_slist_delete_link(local_buddies
, local_buddies
);
665 if (ggp_roster_is_synchronized(buddy
))
666 g_hash_table_insert(remove_buddies
,
667 GINT_TO_POINTER(uin
), buddy
);
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
,
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
);
698 PurpleBuddy
*buddy
= it
->data
;
699 it
= g_list_next(it
);
700 if (!ggp_roster_is_synchronized(buddy
))
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
713 it
= g_list_first(local_groups
);
715 PurpleGroup
*group
= it
->data
;
716 it
= g_list_next(it
);
717 if (purple_counting_node_get_total_size(PURPLE_COUNTING_NODE(group
)) != 0)
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
);
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
);
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 /*******************************************************************************
753 ******************************************************************************/
755 static const gchar
* ggp_roster_send_update_group_add(
756 ggp_roster_content
*content
, PurpleGroup
*group
)
759 const char *id_existing
, *group_name
;
760 PurpleXmlNode
*group_node
;
761 gboolean succ
= TRUE
;
764 group_name
= purple_group_get_name(group
);
766 g_hash_table_lookup(content
->group_ids
, group_name
);
768 id_existing
= GGP_ROSTER_GROUPID_DEFAULT
;
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
),
786 g_hash_table_replace(content
->group_nodes
, id
, group_node
);
788 g_return_val_if_fail(succ
, NULL
);
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
;
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
,
807 buddy
= purple_blist_find_buddy(account
, ggp_uin_to_str(uin
));
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
);
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
),
858 g_return_val_if_fail(succ
, FALSE
);
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
;
870 PurpleXmlNode
*buddy_node
;
872 g_return_val_if_fail(change
->type
== GGP_ROSTER_CHANGE_CONTACT_REMOVE
,
875 buddy
= purple_blist_find_buddy(account
, ggp_uin_to_str(uin
));
877 purple_debug_info("gg", "ggp_roster_send_update_contact_remove:"
878 " contact %u re-added\n", uin
);
882 buddy_node
= g_hash_table_lookup(content
->contact_nodes
,
883 GINT_TO_POINTER(uin
));
884 if (!buddy_node
) /* already removed */
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
));
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
,
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
))
916 GList
*group_buddies
;
917 group
= purple_blist_find_group(new_name
);
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
,
926 group_buddies
= g_list_delete_link(group_buddies
,
931 group_id
= g_hash_table_lookup(content
->group_ids
, old_name
);
933 purple_debug_info("gg", "ggp_roster_send_update_group_rename: "
934 "%s is not present at roster\n", old_name
);
938 group_node
= g_hash_table_lookup(content
->group_nodes
, group_id
);
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
);
946 g_hash_table_remove(content
->group_ids
, old_name
);
947 g_hash_table_insert(content
->group_ids
, g_strdup(new_name
),
949 g_hash_table_insert(content
->group_nodes
, g_strdup(group_id
),
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
;
963 /* an update is running now */
964 if (rdata
->sent_updates
)
967 /* no pending updates found */
968 if (!rdata
->pending_updates
)
971 /* not initialized */
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
);
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
,
990 else if (change
->type
== GGP_ROSTER_CHANGE_CONTACT_REMOVE
)
991 succ
= ggp_roster_send_update_contact_remove(gc
,
993 else if (change
->type
== GGP_ROSTER_CHANGE_GROUP_RENAME
)
994 succ
= ggp_roster_send_update_group_rename(gc
, change
);
996 purple_debug_fatal("gg", "ggp_roster_send_update: "
998 g_return_if_fail(succ
);
1001 #if GGP_ROSTER_DEBUG
1002 ggp_roster_dump(content
);
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
);
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
;
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
;
1025 updates_it
= g_list_next(updates_it
);
1027 if (change
->type
!= GGP_ROSTER_CHANGE_CONTACT_UPDATE
)
1030 buddy
= purple_blist_find_buddy(account
,
1031 ggp_uin_to_str(change
->data
.uin
));
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
;
1043 updates_it
= g_list_next(updates_it
);
1045 if (change
->type
!= GGP_ROSTER_CHANGE_CONTACT_UPDATE
)
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);
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",
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 /******************************************************************************/