* Purple is the legal property of its developers, whose names are too numerous * to list here. Please refer to the COPYRIGHT file distributed with this * Rewritten from scratch during Google Summer of Code 2012 * by Tomek Wasilczyk (http://www.wasilczyk.pl). * Previously implemented by: * - Arkadiusz Miskiewicz <misiek@pld.org.pl> - first implementation (2001); * - Bartosz Oler <bartosz@bzimage.us> - reimplemented during GSoC 2005; * - Krzysztof Klinikowski <grommasher@gmail.com> - some parts (2009-2011). * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA #define GGP_ROSTER_SYNC_SETT "gg-synchronized" #define GGP_ROSTER_DEBUG 0 #define GGP_ROSTER_GROUPID_DEFAULT "00000000-0000-0000-0000-000000000000" #define GGP_ROSTER_GROUPID_BOTS "0b345af6-0001-0000-0000-000000000004" /* TODO: ignored contacts synchronization (?) */ PurpleXmlNode *groups_node, *contacts_node; * Key: (uin_t) user identifier * Value: (PurpleXmlNode*) xml node for contact GHashTable *contact_nodes; * Value: (PurpleXmlNode*) xml node for group * Key: (gchar*) group name * Value: (gchar*) group id * Value: (gchar*) group name GGP_ROSTER_CHANGE_CONTACT_UPDATE, GGP_ROSTER_CHANGE_CONTACT_REMOVE, GGP_ROSTER_CHANGE_GROUP_RENAME, static inline ggp_roster_session_data * ggp_roster_get_rdata(PurpleConnection *gc); static void ggp_roster_content_free(ggp_roster_content *content); static void ggp_roster_change_free(gpointer change); static int ggp_roster_get_version(PurpleConnection *gc); static gboolean ggp_roster_timer_cb(gpointer _gc); static void ggp_roster_dump(ggp_roster_content *content); /* synchronization control */ static gboolean ggp_roster_is_synchronized(PurpleBuddy *buddy); static void ggp_roster_set_synchronized(PurpleConnection *gc, PurpleBuddy *buddy, gboolean synchronized); static gboolean ggp_roster_reply_list_read_group(PurpleXmlNode *node, ggp_roster_content *content); static gboolean ggp_roster_reply_list_read_buddy(PurpleConnection *gc, PurpleXmlNode *node, ggp_roster_content *content, GHashTable *remove_buddies); static void ggp_roster_reply_list(PurpleConnection *gc, uint32_t version, static const gchar * ggp_roster_send_update_group_add( ggp_roster_content *content, PurpleGroup *group); static gboolean ggp_roster_send_update_contact_update(PurpleConnection *gc, ggp_roster_change *change); static gboolean ggp_roster_send_update_contact_remove(PurpleConnection *gc, ggp_roster_change *change); static gboolean ggp_roster_send_update_group_rename(PurpleConnection *gc, ggp_roster_change *change); static void ggp_roster_send_update(PurpleConnection *gc); static void ggp_roster_reply_ack(PurpleConnection *gc, uint32_t version); static void ggp_roster_reply_reject(PurpleConnection *gc, uint32_t version); /******************************************************************************/ static inline ggp_roster_session_data * ggp_roster_get_rdata(PurpleConnection *gc) GGPInfo *accdata = purple_connection_get_protocol_data(gc); return &accdata->roster_data; static void ggp_roster_content_free(ggp_roster_content *content) purple_xmlnode_free(content->xml); if (content->contact_nodes) g_hash_table_destroy(content->contact_nodes); if (content->group_nodes) g_hash_table_destroy(content->group_nodes); g_hash_table_destroy(content->group_ids); if (content->group_names) g_hash_table_destroy(content->group_names); g_free(content->bots_group_id); static void ggp_roster_change_free(gpointer _change) ggp_roster_change *change = _change; if (change->type == GGP_ROSTER_CHANGE_GROUP_RENAME) { g_free(change->data.group_rename.old_name); g_free(change->data.group_rename.new_name); static int ggp_roster_get_version(PurpleConnection *gc) ggp_roster_content *content = ggp_roster_get_rdata(gc)->content; static gboolean ggp_roster_timer_cb(gpointer _gc) PurpleConnection *gc = _gc; PURPLE_ASSERT_CONNECTION_IS_VALID(gc); ggp_roster_send_update(gc); static void ggp_roster_dump(ggp_roster_content *content) g_return_if_fail(content != NULL); g_return_if_fail(content->xml != NULL); str = purple_xmlnode_to_formatted_str(content->xml, &len); purple_debug_misc("gg", "ggp_roster_dump: [%s]\n", str); /******************************************************************************* ******************************************************************************/ gboolean ggp_roster_enabled(void) static gboolean checked = FALSE; enabled = gg_libgadu_check_feature( GG_LIBGADU_FEATURE_USERLIST100); void ggp_roster_setup(PurpleConnection *gc) ggp_roster_session_data *rdata = ggp_roster_get_rdata(gc); rdata->sent_updates = NULL; rdata->pending_updates = NULL; rdata->is_updating = FALSE; if (ggp_roster_enabled()) rdata->timer = g_timeout_add_seconds(2, ggp_roster_timer_cb, gc); void ggp_roster_cleanup(PurpleConnection *gc) ggp_roster_session_data *rdata = ggp_roster_get_rdata(gc); g_source_remove(rdata->timer); ggp_roster_content_free(rdata->content); g_list_free_full(rdata->sent_updates, ggp_roster_change_free); g_list_free_full(rdata->pending_updates, ggp_roster_change_free); /******************************************************************************* * Synchronization control. ******************************************************************************/ static gboolean ggp_roster_is_synchronized(PurpleBuddy *buddy) gboolean ret = purple_blist_node_get_bool(PURPLE_BLIST_NODE(buddy), static void ggp_roster_set_synchronized(PurpleConnection *gc, PurpleBuddy *buddy, gboolean synchronized) ggp_roster_session_data *rdata = ggp_roster_get_rdata(gc); uin_t uin = ggp_str_to_uin(purple_buddy_get_name(buddy)); ggp_roster_change *change; purple_blist_node_set_bool(PURPLE_BLIST_NODE(buddy), GGP_ROSTER_SYNC_SETT, synchronized); change = g_new0(ggp_roster_change, 1); change->type = GGP_ROSTER_CHANGE_CONTACT_UPDATE; g_list_append(rdata->pending_updates, change); void ggp_roster_request_update(PurpleConnection *gc) GGPInfo *accdata = purple_connection_get_protocol_data(gc); int local_version = ggp_roster_get_version(gc); if (!ggp_roster_enabled()) { purple_debug_warning("gg", "ggp_roster_request_update: " purple_debug_info("gg", "ggp_roster_request_update: local=%u\n", gg_userlist100_request(accdata->session, GG_USERLIST100_GET, local_version, GG_USERLIST100_FORMAT_TYPE_GG100, NULL); /******************************************************************************* ******************************************************************************/ void ggp_roster_reply(PurpleConnection *gc, struct gg_event_userlist100_reply *reply) if (GG_USERLIST100_FORMAT_TYPE_GG100 != reply->format_type) { purple_debug_warning("gg", "ggp_roster_reply: " "unsupported format type (%x)\n", reply->format_type); if (reply->type == GG_USERLIST100_REPLY_LIST) ggp_roster_reply_list(gc, reply->version, reply->reply); else if (reply->type == 0x01) /* list up to date (TODO: push to libgadu) */ purple_debug_info("gg", "ggp_roster_reply: list up to date\n"); else if (reply->type == GG_USERLIST100_REPLY_ACK) ggp_roster_reply_ack(gc, reply->version); else if (reply->type == GG_USERLIST100_REPLY_REJECT) ggp_roster_reply_reject(gc, reply->version); purple_debug_error("gg", "ggp_roster_reply: " "unsupported reply (%x)\n", reply->type); void ggp_roster_version(PurpleConnection *gc, struct gg_event_userlist100_version *version) int local_version = ggp_roster_get_version(gc); int remote_version = version->version; purple_debug_info("gg", "ggp_roster_version: local=%u, remote=%u\n", local_version, remote_version); if (local_version < remote_version) ggp_roster_request_update(gc); /******************************************************************************* ******************************************************************************/ void ggp_roster_alias_buddy(PurpleConnection *gc, const char *who, g_return_if_fail(who != NULL); if (!ggp_roster_enabled()) purple_debug_misc("gg", "ggp_roster_alias_buddy(\"%s\", \"%s\")\n", buddy = purple_blist_find_buddy(purple_connection_get_account(gc), who); g_return_if_fail(buddy != NULL); ggp_roster_set_synchronized(gc, buddy, FALSE); void ggp_roster_group_buddy(PurpleConnection *gc, const char *who, const char *old_group, const char *new_group) ggp_roster_session_data *rdata = ggp_roster_get_rdata(gc); ggp_roster_change *change; if (!ggp_roster_enabled()) purple_debug_misc("gg", "ggp_roster_group_buddy: " "who=\"%s\", group=\"%s\" -> \"%s\")\n", who, old_group, new_group); /* purple_blist_find_buddy(..., who) is not accessible at this moment */ change = g_new0(ggp_roster_change, 1); change->type = GGP_ROSTER_CHANGE_CONTACT_UPDATE; change->data.uin = ggp_str_to_uin(who); rdata->pending_updates = g_list_append(rdata->pending_updates, change); void ggp_roster_rename_group(PurpleConnection *gc, const char *old_name, PurpleGroup *group, GList *moved_buddies) ggp_roster_session_data *rdata = ggp_roster_get_rdata(gc); ggp_roster_change *change; if (!ggp_roster_enabled()) change = g_new0(ggp_roster_change, 1); change->type = GGP_ROSTER_CHANGE_GROUP_RENAME; change->data.group_rename.old_name = g_strdup(old_name); change->data.group_rename.new_name = g_strdup(purple_group_get_name(group)); rdata->pending_updates = g_list_append(rdata->pending_updates, change); void ggp_roster_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group, const char *message) g_return_if_fail(gc != NULL); g_return_if_fail(buddy != NULL); if (!ggp_roster_enabled()) ggp_roster_set_synchronized(gc, buddy, FALSE); void ggp_roster_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, ggp_roster_session_data *rdata = ggp_roster_get_rdata(gc); ggp_roster_change *change; if (!ggp_roster_enabled()) change = g_new0(ggp_roster_change, 1); change->type = GGP_ROSTER_CHANGE_CONTACT_REMOVE; change->data.uin = ggp_str_to_uin(purple_buddy_get_name(buddy)); rdata->pending_updates = g_list_append(rdata->pending_updates, change); /******************************************************************************* ******************************************************************************/ static gboolean ggp_roster_reply_list_read_group(PurpleXmlNode *node, ggp_roster_content *content) gboolean succ = TRUE, is_bot, is_default; succ &= ggp_xml_get_string(node, "Id", &id); succ &= ggp_xml_get_string(node, "Name", &name); succ &= ggp_xml_get_bool(node, "IsRemovable", &removable); g_return_val_if_reached(FALSE); is_bot = (strcmp(id, GGP_ROSTER_GROUPID_BOTS) == 0 || g_strcmp0(name, "Pomocnicy") == 0); is_default = (strcmp(id, GGP_ROSTER_GROUPID_DEFAULT) == 0 || g_strcmp0(name, PURPLE_BLIST_DEFAULT_GROUP_NAME) == 0 || g_strcmp0(name, "[default]") == 0); if (!content->bots_group_id && is_bot) content->bots_group_id = g_strdup(id); if (!removable || is_bot || is_default) { g_hash_table_insert(content->group_nodes, g_strdup(id), node); g_hash_table_insert(content->group_ids, g_strdup(name), g_strdup(id)); g_hash_table_insert(content->group_names, id, name); static gboolean ggp_roster_reply_list_read_buddy(PurpleConnection *gc, PurpleXmlNode *node, ggp_roster_content *content, GHashTable *remove_buddies) gchar *alias, *group_name = NULL; PurpleXmlNode *group_list, *group_elem; PurpleBuddy *buddy = NULL; PurpleGroup *group = NULL; PurpleGroup *currentGroup; PurpleAccount *account = purple_connection_get_account(gc); succ &= ggp_xml_get_string(node, "ShowName", &alias); succ &= ggp_xml_get_uint(node, "GGNumber", &uin); group_list = purple_xmlnode_get_child(node, "Groups"); succ &= (group_list != NULL); g_return_val_if_reached(FALSE); g_hash_table_insert(content->contact_nodes, GINT_TO_POINTER(uin), node); /* check, if alias is set */ strcmp(alias, ggp_uin_to_str(uin)) == 0) /* getting (eventually creating) group */ group_elem = purple_xmlnode_get_child(group_list, "GroupId"); while (group_elem != NULL) { if (!ggp_xml_get_string(group_elem, NULL, &id)) isbot = (0 == g_strcmp0(id, content->bots_group_id)); group_name = g_hash_table_lookup(content->group_names, id); /* we don't want to import bots; * they are inserted to roster by default group_elem = purple_xmlnode_get_next_twin(group_elem); group = purple_blist_find_group(group_name); group = purple_group_new(group_name); purple_blist_add_group(group, NULL); /* add buddy, if doesn't exists */ buddy = purple_blist_find_buddy(account, ggp_uin_to_str(uin)); g_hash_table_remove(remove_buddies, GINT_TO_POINTER(uin)); purple_debug_info("gg", "ggp_roster_reply_list_read_buddy: " "adding %u (%s) to buddy list\n", uin, alias); buddy = purple_buddy_new(account, ggp_uin_to_str(uin), alias); purple_blist_add_buddy(buddy, NULL, group, NULL); ggp_roster_set_synchronized(gc, buddy, TRUE); /* buddy exists, but is not synchronized - local list has priority */ if (!ggp_roster_is_synchronized(buddy)) { purple_debug_misc("gg", "ggp_roster_reply_list_read_buddy: " "ignoring not synchronized %u (%s)\n", uin, purple_buddy_get_name(buddy)); currentGroup = ggp_purplew_buddy_get_group_only(buddy); (0 != g_strcmp0(alias, purple_buddy_get_alias_only(buddy))); if (currentGroup == group && !alias_changed) { purple_debug_misc("gg", "ggp_roster_reply_list_read_buddy: " "updating %u (%s) - alias=\"%s\"->\"%s\", group=%p->%p (%s)\n", uin, purple_buddy_get_name(buddy), purple_buddy_get_alias(buddy), alias, currentGroup, group, group_name); purple_buddy_set_local_alias(buddy, alias); if (currentGroup != group) purple_blist_add_buddy(buddy, NULL, group, NULL); static void ggp_roster_reply_list(PurpleConnection *gc, uint32_t version, ggp_roster_session_data *rdata = ggp_roster_get_rdata(gc); PurpleXmlNode *xml, *xml_it; GHashTable *remove_buddies; GList *update_buddies = NULL, *local_groups, *it, *table_values; ggp_roster_content *content; g_return_if_fail(gc != NULL); g_return_if_fail(data != NULL); account = purple_connection_get_account(gc); purple_debug_info("gg", "ggp_roster_reply_list: got list, version=%u\n", xml = purple_xmlnode_from_str(data, -1); purple_debug_warning("gg", "ggp_roster_reply_list: " ggp_roster_content_free(rdata->content); rdata->is_updating = TRUE; content = g_new0(ggp_roster_content, 1); content->version = version; content->contact_nodes = g_hash_table_new(NULL, NULL); content->group_nodes = g_hash_table_new_full( g_str_hash, g_str_equal, g_free, NULL); content->group_ids = g_hash_table_new_full( g_str_hash, g_str_equal, g_free, g_free); content->group_names = g_hash_table_new_full( g_str_hash, g_str_equal, g_free, g_free); ggp_roster_dump(content); content->groups_node = purple_xmlnode_get_child(xml, "Groups"); if (content->groups_node == NULL) { ggp_roster_content_free(content); xml_it = purple_xmlnode_get_child(content->groups_node, "Group"); if (!ggp_roster_reply_list_read_group(xml_it, content)) { ggp_roster_content_free(content); xml_it = purple_xmlnode_get_next_twin(xml_it); /* dumping current group list */ local_groups = ggp_purplew_account_get_groups(account, TRUE); /* dumping current buddy list * - remove synchronized ones, if not found in list at server * - upload not synchronized ones local_buddies = purple_blist_find_buddies(account, NULL); remove_buddies = g_hash_table_new(g_direct_hash, g_direct_equal); PurpleBuddy *buddy = local_buddies->data; uin_t uin = ggp_str_to_uin(purple_buddy_get_name(buddy)); g_slist_delete_link(local_buddies, local_buddies); if (ggp_roster_is_synchronized(buddy)) g_hash_table_insert(remove_buddies, GINT_TO_POINTER(uin), buddy); update_buddies = g_list_append(update_buddies, buddy); content->contacts_node = purple_xmlnode_get_child(xml, "Contacts"); if (content->contacts_node == NULL) { g_hash_table_destroy(remove_buddies); g_list_free(update_buddies); ggp_roster_content_free(content); xml_it = purple_xmlnode_get_child(content->contacts_node, "Contact"); if (!ggp_roster_reply_list_read_buddy(gc, xml_it, content, g_hash_table_destroy(remove_buddies); g_list_free(update_buddies); ggp_roster_content_free(content); xml_it = purple_xmlnode_get_next_twin(xml_it); /* removing buddies, which are not present in roster */ table_values = g_hash_table_get_values(remove_buddies); it = g_list_first(table_values); PurpleBuddy *buddy = it->data; if (!ggp_roster_is_synchronized(buddy)) purple_debug_info("gg", "ggp_roster_reply_list: " "removing %s from buddy list\n", purple_buddy_get_name(buddy)); purple_blist_remove_buddy(buddy); g_list_free(table_values); g_hash_table_destroy(remove_buddies); /* remove groups, which are empty, but had contacts before it = g_list_first(local_groups); PurpleGroup *group = it->data; if (purple_counting_node_get_total_size(PURPLE_COUNTING_NODE(group)) != 0) purple_debug_info("gg", "ggp_roster_reply_list: " "removing group %s\n", purple_group_get_name(group)); purple_blist_remove_group(group); g_list_free(local_groups); /* adding not synchronized buddies */ it = g_list_first(update_buddies); PurpleBuddy *buddy = it->data; uin_t uin = ggp_str_to_uin(purple_buddy_get_name(buddy)); ggp_roster_change *change; purple_debug_misc("gg", "ggp_roster_reply_list: " "adding change of %u for roster\n", uin); change = g_new0(ggp_roster_change, 1); change->type = GGP_ROSTER_CHANGE_CONTACT_UPDATE; g_list_append(rdata->pending_updates, change); g_list_free(update_buddies); rdata->content = content; rdata->is_updating = FALSE; purple_debug_info("gg", "ggp_roster_reply_list: " "import done, version=%u\n", version); /******************************************************************************* ******************************************************************************/ static const gchar * ggp_roster_send_update_group_add( ggp_roster_content *content, PurpleGroup *group) const char *id_existing, *group_name; PurpleXmlNode *group_node; group_name = purple_group_get_name(group); g_hash_table_lookup(content->group_ids, group_name); id_existing = GGP_ROSTER_GROUPID_DEFAULT; purple_debug_info("gg", "ggp_roster_send_update_group_add: adding %s\n", purple_group_get_name(group)); id = purple_uuid_random(); group_node = purple_xmlnode_new_child(content->groups_node, "Group"); succ &= ggp_xml_set_string(group_node, "Id", id); succ &= ggp_xml_set_string(group_node, "Name", group_name); succ &= ggp_xml_set_string(group_node, "IsExpanded", "true"); succ &= ggp_xml_set_string(group_node, "IsRemovable", "true"); content->needs_update = TRUE; g_hash_table_insert(content->group_ids, g_strdup(group_name), g_hash_table_replace(content->group_nodes, id, group_node); g_return_val_if_fail(succ, NULL); static gboolean ggp_roster_send_update_contact_update(PurpleConnection *gc, ggp_roster_change *change) PurpleAccount *account = purple_connection_get_account(gc); ggp_roster_content *content = ggp_roster_get_rdata(gc)->content; uin_t uin = change->data.uin; PurpleXmlNode *buddy_node, *contact_groups; g_return_val_if_fail(change->type == GGP_ROSTER_CHANGE_CONTACT_UPDATE, buddy = purple_blist_find_buddy(account, ggp_uin_to_str(uin)); buddy_node = g_hash_table_lookup(content->contact_nodes, group_id = ggp_roster_send_update_group_add(content, ggp_purplew_buddy_get_group_only(buddy)); if (buddy_node) { /* update existing */ purple_debug_misc("gg", "ggp_roster_send_update_contact_update:" " updating %u...\n", uin); succ &= ggp_xml_set_string(buddy_node, "ShowName", purple_buddy_get_alias(buddy)); contact_groups = purple_xmlnode_get_child(buddy_node, "Groups"); g_assert(contact_groups); ggp_xmlnode_remove_children(contact_groups); succ &= ggp_xml_set_string(contact_groups, "GroupId", group_id); g_return_val_if_fail(succ, FALSE); purple_debug_misc("gg", "ggp_roster_send_update_contact_update: " buddy_node = purple_xmlnode_new_child(content->contacts_node, "Contact"); succ &= ggp_xml_set_string(buddy_node, "Guid", purple_uuid_random()); succ &= ggp_xml_set_uint(buddy_node, "GGNumber", uin); succ &= ggp_xml_set_string(buddy_node, "ShowName", purple_buddy_get_alias(buddy)); contact_groups = purple_xmlnode_new_child(buddy_node, "Groups"); g_assert(contact_groups); succ &= ggp_xml_set_string(contact_groups, "GroupId", group_id); purple_xmlnode_new_child(buddy_node, "Avatars"); succ &= ggp_xml_set_bool(buddy_node, "FlagBuddy", TRUE); succ &= ggp_xml_set_bool(buddy_node, "FlagNormal", TRUE); succ &= ggp_xml_set_bool(buddy_node, "FlagFriend", TRUE); /* we don't use Guid, so update is not needed * content->needs_update = TRUE; g_hash_table_insert(content->contact_nodes, GINT_TO_POINTER(uin), g_return_val_if_fail(succ, FALSE); static gboolean ggp_roster_send_update_contact_remove(PurpleConnection *gc, ggp_roster_change *change) PurpleAccount *account = purple_connection_get_account(gc); ggp_roster_content *content = ggp_roster_get_rdata(gc)->content; uin_t uin = change->data.uin; PurpleXmlNode *buddy_node; g_return_val_if_fail(change->type == GGP_ROSTER_CHANGE_CONTACT_REMOVE, buddy = purple_blist_find_buddy(account, ggp_uin_to_str(uin)); purple_debug_info("gg", "ggp_roster_send_update_contact_remove:" " contact %u re-added\n", uin); buddy_node = g_hash_table_lookup(content->contact_nodes, if (!buddy_node) /* already removed */ purple_debug_info("gg", "ggp_roster_send_update_contact_remove: " purple_xmlnode_free(buddy_node); g_hash_table_remove(content->contact_nodes, GINT_TO_POINTER(uin)); static gboolean ggp_roster_send_update_group_rename(PurpleConnection *gc, ggp_roster_change *change) PurpleAccount *account = purple_connection_get_account(gc); ggp_roster_content *content = ggp_roster_get_rdata(gc)->content; const char *old_name = change->data.group_rename.old_name; const char *new_name = change->data.group_rename.new_name; PurpleXmlNode *group_node; g_return_val_if_fail(change->type == GGP_ROSTER_CHANGE_GROUP_RENAME, purple_debug_misc("gg", "ggp_roster_send_update_group_rename: " "\"%s\"->\"%s\"\n", old_name, new_name); /* moving to or from default group instead of renaming it */ if (0 == g_strcmp0(old_name, PURPLE_BLIST_DEFAULT_GROUP_NAME) || 0 == g_strcmp0(new_name, PURPLE_BLIST_DEFAULT_GROUP_NAME)) group = purple_blist_find_group(new_name); purple_debug_info("gg", "ggp_roster_send_update_group_rename: " "invalidating buddies in default group\n"); group_buddies = ggp_purplew_group_get_buddies(group, account); ggp_roster_set_synchronized(gc, group_buddies->data, group_buddies = g_list_delete_link(group_buddies, group_id = g_hash_table_lookup(content->group_ids, old_name); purple_debug_info("gg", "ggp_roster_send_update_group_rename: " "%s is not present at roster\n", old_name); group_node = g_hash_table_lookup(content->group_nodes, group_id); purple_debug_error("gg", "ggp_roster_send_update_group_rename: " "node for %s not found, id=%s\n", old_name, group_id); g_hash_table_remove(content->group_ids, old_name); g_hash_table_remove(content->group_ids, old_name); g_hash_table_insert(content->group_ids, g_strdup(new_name), g_hash_table_insert(content->group_nodes, g_strdup(group_id), return ggp_xml_set_string(group_node, "Name", new_name); static void ggp_roster_send_update(PurpleConnection *gc) GGPInfo *accdata = purple_connection_get_protocol_data(gc); ggp_roster_session_data *rdata = ggp_roster_get_rdata(gc); ggp_roster_content *content = rdata->content; /* an update is running now */ /* no pending updates found */ if (!rdata->pending_updates) purple_debug_info("gg", "ggp_roster_send_update: " "pending updates found\n"); rdata->sent_updates = rdata->pending_updates; rdata->pending_updates = NULL; updates_it = g_list_first(rdata->sent_updates); ggp_roster_change *change = updates_it->data; updates_it = g_list_next(updates_it); if (change->type == GGP_ROSTER_CHANGE_CONTACT_UPDATE) succ = ggp_roster_send_update_contact_update(gc, else if (change->type == GGP_ROSTER_CHANGE_CONTACT_REMOVE) succ = ggp_roster_send_update_contact_remove(gc, else if (change->type == GGP_ROSTER_CHANGE_GROUP_RENAME) succ = ggp_roster_send_update_group_rename(gc, change); purple_debug_fatal("gg", "ggp_roster_send_update: " ggp_roster_dump(content); str = purple_xmlnode_to_str(content->xml, &len); gg_userlist100_request(accdata->session, GG_USERLIST100_PUT, content->version, GG_USERLIST100_FORMAT_TYPE_GG100, str); static void ggp_roster_reply_ack(PurpleConnection *gc, uint32_t version) PurpleAccount *account = purple_connection_get_account(gc); ggp_roster_session_data *rdata = ggp_roster_get_rdata(gc); ggp_roster_content *content = rdata->content; purple_debug_info("gg", "ggp_roster_reply_ack: version=%u\n", version); /* set synchronization flag for all buddies, that were updated at roster */ updates_it = g_list_first(rdata->sent_updates); ggp_roster_change *change = updates_it->data; updates_it = g_list_next(updates_it); if (change->type != GGP_ROSTER_CHANGE_CONTACT_UPDATE) buddy = purple_blist_find_buddy(account, ggp_uin_to_str(change->data.uin)); ggp_roster_set_synchronized(gc, buddy, TRUE); /* we need to remove "synchronized" flag for all contacts, that have * beed modified between roster update start and now updates_it = g_list_first(rdata->pending_updates); ggp_roster_change *change = updates_it->data; updates_it = g_list_next(updates_it); if (change->type != GGP_ROSTER_CHANGE_CONTACT_UPDATE) buddy = purple_blist_find_buddy(account, ggp_uin_to_str(change->data.uin)); if (buddy && ggp_roster_is_synchronized(buddy)) ggp_roster_set_synchronized(gc, buddy, FALSE); g_list_free_full(rdata->sent_updates, ggp_roster_change_free); rdata->sent_updates = NULL; /* bump roster version or update it, if needed */ g_return_if_fail(content != NULL); if (content->needs_update) { ggp_roster_content_free(rdata->content); /* we have to wait for gg_event_userlist100_version * ggp_roster_request_update(gc); content->version = version; static void ggp_roster_reply_reject(PurpleConnection *gc, uint32_t version) ggp_roster_session_data *rdata = ggp_roster_get_rdata(gc); purple_debug_info("gg", "ggp_roster_reply_reject: version=%u\n", g_return_if_fail(rdata->sent_updates); rdata->pending_updates = g_list_concat(rdata->pending_updates, rdata->sent_updates = NULL; ggp_roster_content_free(rdata->content); ggp_roster_request_update(gc); /******************************************************************************/