Gadu-Gadu: refactoring of buddy avatars handling. Fixes #13739, #14305 soc.2012.gg
authorTomasz Wasilczyk <tomkiewicz@cpw.pidgin.im>
Fri, 06 Jul 2012 14:54:00 +0200
branchsoc.2012.gg
changeset5a0c6582d5b1 pushlog
parent 7dd184afae2c
child 57013b9e1a8e
Gadu-Gadu: refactoring of buddy avatars handling. Fixes #13739, #14305
libpurple/protocols/gg/Makefile.am
libpurple/protocols/gg/avatar.c
libpurple/protocols/gg/avatar.h
libpurple/protocols/gg/gg.c
libpurple/protocols/gg/gg.h
libpurple/protocols/gg/image.c
libpurple/protocols/gg/image.h
libpurple/protocols/gg/libgadu-events.c
libpurple/protocols/gg/libgadu-events.h
     1.1 --- a/libpurple/protocols/gg/Makefile.am
     1.2 +++ b/libpurple/protocols/gg/Makefile.am
     1.3 @@ -67,7 +67,11 @@
     1.4  	purplew.h \
     1.5  	purplew.c \
     1.6  	libgaduw.h \
     1.7 -	libgaduw.c
     1.8 +	libgaduw.c \
     1.9 +	avatar.h \
    1.10 +	avatar.c \
    1.11 +	libgadu-events.h \
    1.12 +	libgadu-events.c
    1.13  
    1.14  AM_CFLAGS = $(st)
    1.15  
     2.1 new file mode 100644
     2.2 --- /dev/null
     2.3 +++ b/libpurple/protocols/gg/avatar.c
     2.4 @@ -0,0 +1,254 @@
     2.5 +#include "avatar.h"
     2.6 +
     2.7 +#include <debug.h>
     2.8 +
     2.9 +#include "gg.h"
    2.10 +#include "utils.h"
    2.11 +
    2.12 +// Common
    2.13 +
    2.14 +static inline ggp_avatar_session_data *
    2.15 +ggp_avatar_get_avdata(PurpleConnection *gc);
    2.16 +
    2.17 +static gboolean ggp_avatar_timer_cb(gpointer _gc);
    2.18 +
    2.19 +#define GGP_AVATAR_USERAGENT "GG Client build 11.0.0.7562"
    2.20 +#define GGP_AVATAR_SIZE_MAX 1048576
    2.21 +
    2.22 +// Buddy avatars updating
    2.23 +
    2.24 +typedef struct
    2.25 +{
    2.26 +	uin_t uin;
    2.27 +	time_t timestamp;
    2.28 +	
    2.29 +	PurpleConnection *gc;
    2.30 +	PurpleUtilFetchUrlData *request;
    2.31 +} ggp_avatar_buddy_update_req;
    2.32 +
    2.33 +static gboolean ggp_avatar_buddy_update_next(PurpleConnection *gc);
    2.34 +static void ggp_avatar_buddy_update_received(PurpleUtilFetchUrlData *url_data,
    2.35 +	gpointer _pending_update, const gchar *url_text, gsize len,
    2.36 +	const gchar *error_message);
    2.37 +
    2.38 +#define GGP_AVATAR_BUDDY_URL "http://avatars.gg.pl/%u/s,big"
    2.39 +
    2.40 +/*******************************************************************************
    2.41 + * Common.
    2.42 + ******************************************************************************/
    2.43 +
    2.44 +void ggp_avatar_setup(PurpleConnection *gc)
    2.45 +{
    2.46 +	ggp_avatar_session_data *avdata = ggp_avatar_get_avdata(gc);
    2.47 +
    2.48 +	avdata->pending_updates = NULL;
    2.49 +	avdata->current_update = NULL;
    2.50 +	
    2.51 +	avdata->timer = purple_timeout_add_seconds(1, ggp_avatar_timer_cb, gc);
    2.52 +}
    2.53 +
    2.54 +void ggp_avatar_cleanup(PurpleConnection *gc)
    2.55 +{
    2.56 +	ggp_avatar_session_data *avdata = ggp_avatar_get_avdata(gc);
    2.57 +
    2.58 +	purple_timeout_remove(avdata->timer);
    2.59 +
    2.60 +	if (avdata->current_update != NULL)
    2.61 +	{
    2.62 +		ggp_avatar_buddy_update_req *current_update =
    2.63 +			avdata->current_update;
    2.64 +		
    2.65 +		purple_util_fetch_url_cancel(current_update->request);
    2.66 +		g_free(current_update);
    2.67 +	}
    2.68 +	avdata->current_update = NULL;
    2.69 +
    2.70 +	g_list_free_full(avdata->pending_updates, &g_free);
    2.71 +	avdata->pending_updates = NULL;
    2.72 +}
    2.73 +
    2.74 +static inline ggp_avatar_session_data *
    2.75 +ggp_avatar_get_avdata(PurpleConnection *gc)
    2.76 +{
    2.77 +	GGPInfo *accdata = purple_connection_get_protocol_data(gc);
    2.78 +	return &accdata->avatar_data;
    2.79 +}
    2.80 +
    2.81 +static gboolean ggp_avatar_timer_cb(gpointer _gc)
    2.82 +{
    2.83 +	PurpleConnection *gc = _gc;
    2.84 +	ggp_avatar_session_data *avdata;
    2.85 +	
    2.86 +	g_return_val_if_fail(PURPLE_CONNECTION_IS_VALID(gc), FALSE);
    2.87 +	
    2.88 +	avdata = ggp_avatar_get_avdata(gc);
    2.89 +	if (avdata->current_update != NULL)
    2.90 +	{
    2.91 +		//TODO: verbose mode
    2.92 +		//purple_debug_misc("gg", "ggp_avatar_timer_cb(%p): there is "
    2.93 +		//	"already an update running\n", gc);
    2.94 +		return TRUE;
    2.95 +	}
    2.96 +	
    2.97 +	while (!ggp_avatar_buddy_update_next(gc));
    2.98 +	
    2.99 +	return TRUE;
   2.100 +}
   2.101 +
   2.102 +/*******************************************************************************
   2.103 + * Buddy avatars updating.
   2.104 + ******************************************************************************/
   2.105 +
   2.106 +void ggp_avatar_buddy_update(PurpleConnection *gc, uin_t uin, time_t timestamp)
   2.107 +{
   2.108 +	ggp_avatar_session_data *avdata = ggp_avatar_get_avdata(gc);
   2.109 +	ggp_avatar_buddy_update_req *pending_update =
   2.110 +		g_new(ggp_avatar_buddy_update_req, 1);
   2.111 +
   2.112 +	purple_debug_misc("gg", "ggp_avatar_buddy_update(%p, %u, %lu)\n", gc,
   2.113 +		uin, timestamp);
   2.114 +
   2.115 +	pending_update->uin = uin;
   2.116 +	pending_update->timestamp = timestamp;
   2.117 +
   2.118 +	avdata->pending_updates = g_list_append(avdata->pending_updates,
   2.119 +		pending_update);
   2.120 +}
   2.121 +
   2.122 +void ggp_avatar_buddy_remove(PurpleConnection *gc, uin_t uin)
   2.123 +{
   2.124 +	//TODO: verbose mode
   2.125 +	//purple_debug_misc("gg", "ggp_avatar_buddy_remove(%p, %u) - "
   2.126 +	//	"probably not necessary, thus not implemented\n", gc, uin);
   2.127 +}
   2.128 +
   2.129 +/* return TRUE if avatar update was performed or there is no new requests,
   2.130 +   FALSE if we can request another one immediately */
   2.131 +static gboolean ggp_avatar_buddy_update_next(PurpleConnection *gc)
   2.132 +{
   2.133 +	ggp_avatar_session_data *avdata = ggp_avatar_get_avdata(gc);
   2.134 +	GList *pending_update_it;
   2.135 +	ggp_avatar_buddy_update_req *pending_update;
   2.136 +	PurpleBuddy *buddy;
   2.137 +	PurpleAccount *account = purple_connection_get_account(gc);
   2.138 +	time_t old_timestamp;
   2.139 +	const char *old_timestamp_str;
   2.140 +	gchar *avatar_url;
   2.141 +	
   2.142 +	pending_update_it = g_list_first(avdata->pending_updates);
   2.143 +	if (pending_update_it == NULL)
   2.144 +		return TRUE;
   2.145 +	
   2.146 +	pending_update = pending_update_it->data;
   2.147 +	avdata->pending_updates = g_list_remove(avdata->pending_updates,
   2.148 +		pending_update);
   2.149 +	buddy = purple_find_buddy(account, ggp_uin_to_str(pending_update->uin));
   2.150 +	
   2.151 +	if (!buddy)
   2.152 +	{
   2.153 +		if (ggp_str_to_uin(purple_account_get_username(account)) ==
   2.154 +			pending_update->uin)
   2.155 +		{
   2.156 +			purple_debug_misc("gg",
   2.157 +				"ggp_avatar_buddy_update_next(%p): own "
   2.158 +				"avatar update requested, but we don't have "
   2.159 +				"ourselves on buddy list\n", gc);
   2.160 +		}
   2.161 +		else
   2.162 +		{
   2.163 +			purple_debug_warning("gg",
   2.164 +				"ggp_avatar_buddy_update_next(%p): "
   2.165 +				"%u update requested, but he's not on buddy "
   2.166 +				"list\n", gc, pending_update->uin);
   2.167 +		}
   2.168 +		return FALSE;
   2.169 +	}
   2.170 +
   2.171 +	old_timestamp_str = purple_buddy_icons_get_checksum_for_user(buddy);
   2.172 +	old_timestamp = old_timestamp_str ? g_ascii_strtoull(
   2.173 +		old_timestamp_str, NULL, 10) : 0;
   2.174 +	if (old_timestamp == pending_update->timestamp)
   2.175 +	{
   2.176 +		purple_debug_misc("gg",
   2.177 +			"ggp_avatar_buddy_update_next(%p): "
   2.178 +			"%u have up to date avatar with ts=%lu\n", gc,
   2.179 +			pending_update->uin, pending_update->timestamp);
   2.180 +		return FALSE;
   2.181 +	}
   2.182 +	if (old_timestamp > pending_update->timestamp)
   2.183 +	{
   2.184 +		purple_debug_warning("gg",
   2.185 +			"ggp_avatar_buddy_update_next(%p): "
   2.186 +			"saved timestamp for %u is newer than received "
   2.187 +			"(%lu > %lu)\n", gc, pending_update->uin, old_timestamp,
   2.188 +			pending_update->timestamp);
   2.189 +	}
   2.190 +	
   2.191 +	purple_debug_info("gg",
   2.192 +		"ggp_avatar_buddy_update_next(%p): "
   2.193 +		"updating %u with ts=%lu...\n", gc, pending_update->uin,
   2.194 +		pending_update->timestamp);
   2.195 +
   2.196 +	pending_update->gc = gc;
   2.197 +	avdata->current_update = pending_update;
   2.198 +	avatar_url = g_strdup_printf(GGP_AVATAR_BUDDY_URL, pending_update->uin);
   2.199 +	pending_update->request = purple_util_fetch_url_request(account,
   2.200 +		avatar_url, FALSE, GGP_AVATAR_USERAGENT, TRUE, NULL, FALSE,
   2.201 +		GGP_AVATAR_SIZE_MAX, ggp_avatar_buddy_update_received,
   2.202 +		pending_update);
   2.203 +	g_free(avatar_url);
   2.204 +	
   2.205 +	return TRUE;
   2.206 +}
   2.207 +
   2.208 +static void ggp_avatar_buddy_update_received(PurpleUtilFetchUrlData *url_data,
   2.209 +	gpointer _pending_update, const gchar *url_text, gsize len,
   2.210 +	const gchar *error_message)
   2.211 +{
   2.212 +	ggp_avatar_buddy_update_req *pending_update = _pending_update;
   2.213 +	PurpleBuddy *buddy;
   2.214 +	PurpleAccount *account;
   2.215 +	PurpleConnection *gc = pending_update->gc;
   2.216 +	ggp_avatar_session_data *avdata;
   2.217 +	gchar timestamp_str[20];
   2.218 +
   2.219 +	if (!PURPLE_CONNECTION_IS_VALID(gc))
   2.220 +	{
   2.221 +		g_free(pending_update);
   2.222 +		return;
   2.223 +	}
   2.224 +
   2.225 +	avdata = ggp_avatar_get_avdata(gc);
   2.226 +	g_assert(pending_update == avdata->current_update);
   2.227 +	avdata->current_update = NULL;
   2.228 +
   2.229 +	if (len == 0)
   2.230 +	{
   2.231 +		purple_debug_error("gg", "ggp_avatar_buddy_update_received: bad"
   2.232 +			" response while getting avatar for %u: %s\n",
   2.233 +			pending_update->uin, error_message);
   2.234 +		g_free(pending_update);
   2.235 +		return;
   2.236 +	}
   2.237 +
   2.238 +	account = purple_connection_get_account(gc);
   2.239 +	buddy = purple_find_buddy(account, ggp_uin_to_str(pending_update->uin));
   2.240 +
   2.241 +	if (!buddy)
   2.242 +	{
   2.243 +		purple_debug_warning("gg", "ggp_avatar_buddy_update_received: "
   2.244 +			"buddy %u disappeared\n", pending_update->uin);
   2.245 +		g_free(pending_update);
   2.246 +		return;
   2.247 +	}
   2.248 +
   2.249 +	g_snprintf(timestamp_str, sizeof(timestamp_str), "%lu",
   2.250 +		pending_update->timestamp);
   2.251 +	purple_buddy_icons_set_for_user(account, purple_buddy_get_name(buddy),
   2.252 +		g_memdup(url_text, len), len, timestamp_str);
   2.253 +
   2.254 +	purple_debug_info("gg", "ggp_avatar_buddy_update_received: "
   2.255 +		"got avatar for buddy %u [ts=%lu]\n", pending_update->uin,
   2.256 +		pending_update->timestamp);
   2.257 +	g_free(pending_update);
   2.258 +}
     3.1 new file mode 100644
     3.2 --- /dev/null
     3.3 +++ b/libpurple/protocols/gg/avatar.h
     3.4 @@ -0,0 +1,21 @@
     3.5 +#ifndef _GGP_AVATAR_H
     3.6 +#define _GGP_AVATAR_H
     3.7 +
     3.8 +#include <internal.h>
     3.9 +#include <libgadu.h>
    3.10 +
    3.11 +typedef struct
    3.12 +{
    3.13 +	guint timer;
    3.14 +	GList *pending_updates;
    3.15 +	
    3.16 +	gpointer current_update;
    3.17 +} ggp_avatar_session_data;
    3.18 +
    3.19 +void ggp_avatar_setup(PurpleConnection *gc);
    3.20 +void ggp_avatar_cleanup(PurpleConnection *gc);
    3.21 +
    3.22 +void ggp_avatar_buddy_update(PurpleConnection *gc, uin_t uin, time_t timestamp);
    3.23 +void ggp_avatar_buddy_remove(PurpleConnection *gc, uin_t uin);
    3.24 +
    3.25 +#endif /* _GGP_AVATAR_H */
     4.1 --- a/libpurple/protocols/gg/gg.c
     4.2 +++ b/libpurple/protocols/gg/gg.c
     4.3 @@ -48,6 +48,7 @@
     4.4  #include "account.h"
     4.5  #include "deprecated.h"
     4.6  #include "purplew.h"
     4.7 +#include "libgadu-events.h"
     4.8  
     4.9  /* Prototypes */
    4.10  static void ggp_set_status(PurpleAccount *account, PurpleStatus *status);
    4.11 @@ -481,164 +482,6 @@
    4.12  /* ----- INTERNAL CALLBACKS --------------------------------------------- */
    4.13  /* ---------------------------------------------------------------------- */
    4.14  
    4.15 -struct gg_fetch_avatar_data
    4.16 -{
    4.17 -	PurpleConnection *gc;
    4.18 -	gchar *uin;
    4.19 -	gchar *avatar_url;
    4.20 -};
    4.21 -
    4.22 -
    4.23 -static void gg_fetch_avatar_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data,
    4.24 -               const gchar *data, size_t len, const gchar *error_message) {
    4.25 -	struct gg_fetch_avatar_data *d = user_data;
    4.26 -	PurpleAccount *account;
    4.27 -	PurpleBuddy *buddy;
    4.28 -	gpointer buddy_icon_data;
    4.29 -
    4.30 -	purple_debug_info("gg", "gg_fetch_avatar_cb: got avatar image for %s\n",
    4.31 -		d->uin);
    4.32 -
    4.33 -	/* FIXME: This shouldn't be necessary */
    4.34 -	if (!PURPLE_CONNECTION_IS_VALID(d->gc)) {
    4.35 -		g_free(d->uin);
    4.36 -		g_free(d->avatar_url);
    4.37 -		g_free(d);
    4.38 -		g_return_if_reached();
    4.39 -	}
    4.40 -
    4.41 -	account = purple_connection_get_account(d->gc);
    4.42 -	buddy = purple_find_buddy(account, d->uin);
    4.43 -
    4.44 -	if (buddy == NULL)
    4.45 -		goto out;
    4.46 -
    4.47 -	buddy_icon_data = g_memdup(data, len);
    4.48 -
    4.49 -	purple_buddy_icons_set_for_user(account, purple_buddy_get_name(buddy),
    4.50 -			buddy_icon_data, len, d->avatar_url);
    4.51 -	purple_debug_info("gg", "gg_fetch_avatar_cb: UIN %s should have avatar "
    4.52 -		"now\n", d->uin);
    4.53 -
    4.54 -out:
    4.55 -	g_free(d->uin);
    4.56 -	g_free(d->avatar_url);
    4.57 -	g_free(d);
    4.58 -}
    4.59 -
    4.60 -static void gg_get_avatar_url_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data,
    4.61 -               const gchar *url_text, size_t len, const gchar *error_message) {
    4.62 -	struct gg_fetch_avatar_data *data;
    4.63 -	PurpleConnection *gc = user_data;
    4.64 -	PurpleAccount *account;
    4.65 -	PurpleBuddy *buddy;
    4.66 -	const char *uin;
    4.67 -	const char *is_blank;
    4.68 -	const char *checksum;
    4.69 -
    4.70 -	gchar *bigavatar = NULL;
    4.71 -	xmlnode *xml = NULL;
    4.72 -	xmlnode *xmlnode_users;
    4.73 -	xmlnode *xmlnode_user;
    4.74 -	xmlnode *xmlnode_avatars;
    4.75 -	xmlnode *xmlnode_avatar;
    4.76 -	xmlnode *xmlnode_bigavatar;
    4.77 -
    4.78 -	g_return_if_fail(PURPLE_CONNECTION_IS_VALID(gc));
    4.79 -	account = purple_connection_get_account(gc);
    4.80 -
    4.81 -	if (error_message != NULL)
    4.82 -		purple_debug_error("gg", "gg_get_avatars_cb error: %s\n", error_message);
    4.83 -	else if (len > 0 && url_text && *url_text) {
    4.84 -		xml = xmlnode_from_str(url_text, -1);
    4.85 -		if (xml == NULL)
    4.86 -			goto out;
    4.87 -
    4.88 -		xmlnode_users = xmlnode_get_child(xml, "users");
    4.89 -		if (xmlnode_users == NULL)
    4.90 -			goto out;
    4.91 -
    4.92 -		xmlnode_user = xmlnode_get_child(xmlnode_users, "user");
    4.93 -		if (xmlnode_user == NULL)
    4.94 -			goto out;
    4.95 -
    4.96 -		uin = xmlnode_get_attrib(xmlnode_user, "uin");
    4.97 -
    4.98 -		xmlnode_avatars = xmlnode_get_child(xmlnode_user, "avatars");
    4.99 -		if (xmlnode_avatars == NULL)
   4.100 -			goto out;
   4.101 -
   4.102 -		xmlnode_avatar = xmlnode_get_child(xmlnode_avatars, "avatar");
   4.103 -		if (xmlnode_avatar == NULL)
   4.104 -			goto out;
   4.105 -
   4.106 -		xmlnode_bigavatar = xmlnode_get_child(xmlnode_avatar, "originBigAvatar");
   4.107 -		if (xmlnode_bigavatar == NULL)
   4.108 -			goto out;
   4.109 -
   4.110 -		is_blank = xmlnode_get_attrib(xmlnode_avatar, "blank");
   4.111 -		bigavatar = xmlnode_get_data(xmlnode_bigavatar);
   4.112 -
   4.113 -		purple_debug_info("gg", "gg_get_avatar_url_cb: UIN %s, IS_BLANK %s, "
   4.114 -		                        "URL %s\n",
   4.115 -		                  uin ? uin : "(null)", is_blank ? is_blank : "(null)",
   4.116 -		                  bigavatar ? bigavatar : "(null)");
   4.117 -
   4.118 -		if (uin != NULL && bigavatar != NULL) {
   4.119 -			buddy = purple_find_buddy(account, uin);
   4.120 -			if (buddy == NULL)
   4.121 -				goto out;
   4.122 -
   4.123 -			checksum = purple_buddy_icons_get_checksum_for_user(buddy);
   4.124 -
   4.125 -			if (purple_strequal(is_blank, "1")) {
   4.126 -				purple_buddy_icons_set_for_user(account,
   4.127 -						purple_buddy_get_name(buddy), NULL, 0, NULL);
   4.128 -			} else if (!purple_strequal(checksum, bigavatar)) {
   4.129 -				data = g_new0(struct gg_fetch_avatar_data, 1);
   4.130 -				data->gc = gc;
   4.131 -				data->uin = g_strdup(uin);
   4.132 -				data->avatar_url = g_strdup(bigavatar);
   4.133 -
   4.134 -				purple_debug_info("gg", "gg_get_avatar_url_cb: "
   4.135 -					"requesting avatar for %s\n", uin);
   4.136 -				/* FIXME: This should be cancelled somewhere if not needed. */
   4.137 -				url_data = purple_util_fetch_url_request(account,
   4.138 -						bigavatar, TRUE, "Mozilla/4.0 (compatible; MSIE 5.0)",
   4.139 -						FALSE, NULL, FALSE, -1, gg_fetch_avatar_cb, data);
   4.140 -			}
   4.141 -		}
   4.142 -	}
   4.143 -
   4.144 -out:
   4.145 -	if (xml)
   4.146 -		xmlnode_free(xml);
   4.147 -	g_free(bigavatar);
   4.148 -}
   4.149 -
   4.150 -/**
   4.151 - * Try to update avatar of the buddy.
   4.152 - *
   4.153 - * @param gc     PurpleConnection
   4.154 - * @param uin    UIN of the buddy.
   4.155 - */
   4.156 -static void ggp_update_buddy_avatar(PurpleConnection *gc, uin_t uin)
   4.157 -{
   4.158 -	gchar *avatarurl;
   4.159 -	PurpleUtilFetchUrlData *url_data;
   4.160 -
   4.161 -	purple_debug_info("gg", "ggp_update_buddy_avatar(gc, %u)\n", uin);
   4.162 -
   4.163 -	avatarurl = g_strdup_printf("http://api.gadu-gadu.pl/avatars/%u/0.xml", uin);
   4.164 -
   4.165 -	/* FIXME: This should be cancelled somewhere if not needed. */
   4.166 -	url_data = purple_util_fetch_url_request(
   4.167 -			purple_connection_get_account(gc), avatarurl, TRUE,
   4.168 -			"Mozilla/4.0 (compatible; MSIE 5.5)", FALSE, NULL, FALSE, -1,
   4.169 -			gg_get_avatar_url_cb, gc);
   4.170 -
   4.171 -	g_free(avatarurl);
   4.172 -}
   4.173  
   4.174  /**
   4.175   * Handle change of the status of the buddy.
   4.176 @@ -655,8 +498,6 @@
   4.177  	const char *st;
   4.178  	char *status_msg = NULL;
   4.179  
   4.180 -	ggp_update_buddy_avatar(gc, uin);
   4.181 -
   4.182  	from = g_strdup_printf("%u", uin);
   4.183  
   4.184  	switch (status) {
   4.185 @@ -1170,6 +1011,7 @@
   4.186   * @param data Raw XML contents.
   4.187   *
   4.188   * @see http://toxygen.net/libgadu/protocol/#ch1.13
   4.189 + * @todo: this may not be necessary anymore
   4.190   */
   4.191  static void ggp_xml_event_handler(PurpleConnection *gc, char *data)
   4.192  {
   4.193 @@ -1218,7 +1060,6 @@
   4.194  				purple_debug_info("gg",
   4.195  					"ggp_xml_event_handler: avatar updated (uid: %u)\n",
   4.196  					event_sender);
   4.197 -				ggp_update_buddy_avatar(gc, event_sender);
   4.198  				break;
   4.199  			default:
   4.200  				purple_debug_error("gg",
   4.201 @@ -1354,7 +1195,7 @@
   4.202  			ggp_xml_event_handler(gc, ev->event.xml_event.data);
   4.203  			break;
   4.204  		case GG_EVENT_USER_DATA:
   4.205 -			purple_debug_misc("gg", "GG_EVENT_USER_DATA\n");
   4.206 +			ggp_events_user_data(gc, &ev->event.user_data);
   4.207  			break;
   4.208  		default:
   4.209  			purple_debug_error("gg",
   4.210 @@ -1726,6 +1567,7 @@
   4.211  	purple_connection_set_protocol_data(gc, info);
   4.212  
   4.213  	ggp_image_setup(gc);
   4.214 +	ggp_avatar_setup(gc);
   4.215  	
   4.216  	glp->uin = ggp_str_to_uin(purple_account_get_username(account));
   4.217  	glp->password = ggp_convert_to_cp1250(purple_account_get_password(account));
   4.218 @@ -1839,7 +1681,8 @@
   4.219  		purple_notify_close_with_handle(gc);
   4.220  
   4.221  		ggp_search_destroy(info->searches);
   4.222 -		ggp_image_free(gc);
   4.223 +		ggp_image_cleanup(gc);
   4.224 +		ggp_avatar_cleanup(gc);
   4.225  
   4.226  		if (info->inpa > 0)
   4.227  			purple_input_remove(info->inpa);
     5.1 --- a/libpurple/protocols/gg/gg.h
     5.2 +++ b/libpurple/protocols/gg/gg.h
     5.3 @@ -30,6 +30,7 @@
     5.4  #include "connection.h"
     5.5  
     5.6  #include "image.h"
     5.7 +#include "avatar.h"
     5.8  #include "account.h"
     5.9  
    5.10  
    5.11 @@ -55,6 +56,7 @@
    5.12  	gboolean status_broadcasting; //When TRUE status is visible to all, when FALSE status is visible only to friends.
    5.13  
    5.14  	ggp_image_connection_data image_data;
    5.15 +	ggp_avatar_session_data avatar_data;
    5.16  } GGPInfo;
    5.17  
    5.18  #endif /* _PURPLE_GG_H */
     6.1 --- a/libpurple/protocols/gg/image.c
     6.2 +++ b/libpurple/protocols/gg/image.c
     6.3 @@ -52,7 +52,7 @@
     6.4  		ggp_image_pending_image_free);
     6.5  }
     6.6  
     6.7 -void ggp_image_free(PurpleConnection *gc)
     6.8 +void ggp_image_cleanup(PurpleConnection *gc)
     6.9  {
    6.10  	ggp_image_connection_data *imgdata = ggp_image_get_imgdata(gc);
    6.11  	
     7.1 --- a/libpurple/protocols/gg/image.h
     7.2 +++ b/libpurple/protocols/gg/image.h
     7.3 @@ -20,7 +20,7 @@
     7.4  } ggp_image_prepare_result;
     7.5  
     7.6  void ggp_image_setup(PurpleConnection *gc);
     7.7 -void ggp_image_free(PurpleConnection *gc);
     7.8 +void ggp_image_cleanup(PurpleConnection *gc);
     7.9  
    7.10  const char * ggp_image_pending_placeholder(uint32_t id);
    7.11  
     8.1 new file mode 100644
     8.2 --- /dev/null
     8.3 +++ b/libpurple/protocols/gg/libgadu-events.c
     8.4 @@ -0,0 +1,47 @@
     8.5 +#include "libgadu-events.h"
     8.6 +
     8.7 +#include <debug.h>
     8.8 +
     8.9 +#include "avatar.h"
    8.10 +
    8.11 +void ggp_events_user_data(PurpleConnection *gc, struct gg_event_user_data *data)
    8.12 +{
    8.13 +	int user_idx;
    8.14 +	gboolean is_update;
    8.15 +	
    8.16 +	purple_debug_info("gg", "GG_EVENT_USER_DATA [type=%d, user_count=%d]\n",
    8.17 +		data->type, data->user_count);
    8.18 +	
    8.19 +	/*
    8.20 +	type = 
    8.21 +		1, 3:	user information sent after connecting (divided by
    8.22 +			20 contacts; 3 - last one; 1 - rest of them)
    8.23 +		0: data update
    8.24 +	*/
    8.25 +	is_update = (data->type == 0);
    8.26 +	
    8.27 +	for (user_idx = 0; user_idx < data->user_count; user_idx++)
    8.28 +	{
    8.29 +		struct gg_event_user_data_user *data_user =
    8.30 +			&data->users[user_idx];
    8.31 +		uin_t uin = data_user->uin;
    8.32 +		int attr_idx;
    8.33 +		gboolean got_avatar = FALSE;
    8.34 +		for (attr_idx = 0; attr_idx < data_user->attr_count; attr_idx++)
    8.35 +		{
    8.36 +			struct gg_event_user_data_attr *data_attr =
    8.37 +				&data_user->attrs[attr_idx];
    8.38 +			if (strcmp(data_attr->key, "avatar") == 0)
    8.39 +			{
    8.40 +				time_t timestamp = atoi(data_attr->value);
    8.41 +				if (timestamp <= 0)
    8.42 +					continue;
    8.43 +				got_avatar = TRUE;
    8.44 +				ggp_avatar_buddy_update(gc, uin, timestamp);
    8.45 +			}
    8.46 +		}
    8.47 +		
    8.48 +		if (!is_update && !got_avatar)
    8.49 +			ggp_avatar_buddy_remove(gc, uin);
    8.50 +	}
    8.51 +}
     9.1 new file mode 100644
     9.2 --- /dev/null
     9.3 +++ b/libpurple/protocols/gg/libgadu-events.h
     9.4 @@ -0,0 +1,10 @@
     9.5 +#ifndef _GGP_LIBGADU_EVENTS_H
     9.6 +#define _GGP_LIBGADU_EVENTS_H
     9.7 +
     9.8 +#include <internal.h>
     9.9 +#include <libgadu.h>
    9.10 +
    9.11 +void ggp_events_user_data(PurpleConnection *gc,
    9.12 +	struct gg_event_user_data *data);
    9.13 +
    9.14 +#endif /* _GGP_LIBGADU_EVENTS_H */