summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTomasz Wasilczyk <tomkiewicz@cpw.pidgin.im>2012-07-06 14:54:00 +0200
committerTomasz Wasilczyk <tomkiewicz@cpw.pidgin.im>2012-07-06 14:54:00 +0200
commit7ee95edf88533667cf19b1d0b6e6b63f395b18df (patch)
treef10a4bfc0d3a263d6a870dcaa5b0390766f70810
parente217fe038c3a1af9de719875fedee2f1b49e1044 (diff)
downloadpidgin-7ee95edf88533667cf19b1d0b6e6b63f395b18df.tar.gz
Gadu-Gadu: refactoring of buddy avatars handling. Fixes #13739, #14305
-rw-r--r--libpurple/protocols/gg/Makefile.am6
-rw-r--r--libpurple/protocols/gg/avatar.c254
-rw-r--r--libpurple/protocols/gg/avatar.h21
-rw-r--r--libpurple/protocols/gg/gg.c169
-rw-r--r--libpurple/protocols/gg/gg.h2
-rw-r--r--libpurple/protocols/gg/image.c2
-rw-r--r--libpurple/protocols/gg/image.h2
-rw-r--r--libpurple/protocols/gg/libgadu-events.c47
-rw-r--r--libpurple/protocols/gg/libgadu-events.h10
9 files changed, 347 insertions, 166 deletions
diff --git a/libpurple/protocols/gg/Makefile.am b/libpurple/protocols/gg/Makefile.am
index b46cd40709..080820e139 100644
--- a/libpurple/protocols/gg/Makefile.am
+++ b/libpurple/protocols/gg/Makefile.am
@@ -67,7 +67,11 @@ GGSOURCES = \
purplew.h \
purplew.c \
libgaduw.h \
- libgaduw.c
+ libgaduw.c \
+ avatar.h \
+ avatar.c \
+ libgadu-events.h \
+ libgadu-events.c
AM_CFLAGS = $(st)
diff --git a/libpurple/protocols/gg/avatar.c b/libpurple/protocols/gg/avatar.c
new file mode 100644
index 0000000000..ba4401e5cc
--- /dev/null
+++ b/libpurple/protocols/gg/avatar.c
@@ -0,0 +1,254 @@
+#include "avatar.h"
+
+#include <debug.h>
+
+#include "gg.h"
+#include "utils.h"
+
+// Common
+
+static inline ggp_avatar_session_data *
+ggp_avatar_get_avdata(PurpleConnection *gc);
+
+static gboolean ggp_avatar_timer_cb(gpointer _gc);
+
+#define GGP_AVATAR_USERAGENT "GG Client build 11.0.0.7562"
+#define GGP_AVATAR_SIZE_MAX 1048576
+
+// Buddy avatars updating
+
+typedef struct
+{
+ uin_t uin;
+ time_t timestamp;
+
+ PurpleConnection *gc;
+ PurpleUtilFetchUrlData *request;
+} ggp_avatar_buddy_update_req;
+
+static gboolean ggp_avatar_buddy_update_next(PurpleConnection *gc);
+static void ggp_avatar_buddy_update_received(PurpleUtilFetchUrlData *url_data,
+ gpointer _pending_update, const gchar *url_text, gsize len,
+ const gchar *error_message);
+
+#define GGP_AVATAR_BUDDY_URL "http://avatars.gg.pl/%u/s,big"
+
+/*******************************************************************************
+ * Common.
+ ******************************************************************************/
+
+void ggp_avatar_setup(PurpleConnection *gc)
+{
+ ggp_avatar_session_data *avdata = ggp_avatar_get_avdata(gc);
+
+ avdata->pending_updates = NULL;
+ avdata->current_update = NULL;
+
+ avdata->timer = purple_timeout_add_seconds(1, ggp_avatar_timer_cb, gc);
+}
+
+void ggp_avatar_cleanup(PurpleConnection *gc)
+{
+ ggp_avatar_session_data *avdata = ggp_avatar_get_avdata(gc);
+
+ purple_timeout_remove(avdata->timer);
+
+ if (avdata->current_update != NULL)
+ {
+ ggp_avatar_buddy_update_req *current_update =
+ avdata->current_update;
+
+ purple_util_fetch_url_cancel(current_update->request);
+ g_free(current_update);
+ }
+ avdata->current_update = NULL;
+
+ g_list_free_full(avdata->pending_updates, &g_free);
+ avdata->pending_updates = NULL;
+}
+
+static inline ggp_avatar_session_data *
+ggp_avatar_get_avdata(PurpleConnection *gc)
+{
+ GGPInfo *accdata = purple_connection_get_protocol_data(gc);
+ return &accdata->avatar_data;
+}
+
+static gboolean ggp_avatar_timer_cb(gpointer _gc)
+{
+ PurpleConnection *gc = _gc;
+ ggp_avatar_session_data *avdata;
+
+ g_return_val_if_fail(PURPLE_CONNECTION_IS_VALID(gc), FALSE);
+
+ avdata = ggp_avatar_get_avdata(gc);
+ if (avdata->current_update != NULL)
+ {
+ //TODO: verbose mode
+ //purple_debug_misc("gg", "ggp_avatar_timer_cb(%p): there is "
+ // "already an update running\n", gc);
+ return TRUE;
+ }
+
+ while (!ggp_avatar_buddy_update_next(gc));
+
+ return TRUE;
+}
+
+/*******************************************************************************
+ * Buddy avatars updating.
+ ******************************************************************************/
+
+void ggp_avatar_buddy_update(PurpleConnection *gc, uin_t uin, time_t timestamp)
+{
+ ggp_avatar_session_data *avdata = ggp_avatar_get_avdata(gc);
+ ggp_avatar_buddy_update_req *pending_update =
+ g_new(ggp_avatar_buddy_update_req, 1);
+
+ purple_debug_misc("gg", "ggp_avatar_buddy_update(%p, %u, %lu)\n", gc,
+ uin, timestamp);
+
+ pending_update->uin = uin;
+ pending_update->timestamp = timestamp;
+
+ avdata->pending_updates = g_list_append(avdata->pending_updates,
+ pending_update);
+}
+
+void ggp_avatar_buddy_remove(PurpleConnection *gc, uin_t uin)
+{
+ //TODO: verbose mode
+ //purple_debug_misc("gg", "ggp_avatar_buddy_remove(%p, %u) - "
+ // "probably not necessary, thus not implemented\n", gc, uin);
+}
+
+/* return TRUE if avatar update was performed or there is no new requests,
+ FALSE if we can request another one immediately */
+static gboolean ggp_avatar_buddy_update_next(PurpleConnection *gc)
+{
+ ggp_avatar_session_data *avdata = ggp_avatar_get_avdata(gc);
+ GList *pending_update_it;
+ ggp_avatar_buddy_update_req *pending_update;
+ PurpleBuddy *buddy;
+ PurpleAccount *account = purple_connection_get_account(gc);
+ time_t old_timestamp;
+ const char *old_timestamp_str;
+ gchar *avatar_url;
+
+ pending_update_it = g_list_first(avdata->pending_updates);
+ if (pending_update_it == NULL)
+ return TRUE;
+
+ pending_update = pending_update_it->data;
+ avdata->pending_updates = g_list_remove(avdata->pending_updates,
+ pending_update);
+ buddy = purple_find_buddy(account, ggp_uin_to_str(pending_update->uin));
+
+ if (!buddy)
+ {
+ if (ggp_str_to_uin(purple_account_get_username(account)) ==
+ pending_update->uin)
+ {
+ purple_debug_misc("gg",
+ "ggp_avatar_buddy_update_next(%p): own "
+ "avatar update requested, but we don't have "
+ "ourselves on buddy list\n", gc);
+ }
+ else
+ {
+ purple_debug_warning("gg",
+ "ggp_avatar_buddy_update_next(%p): "
+ "%u update requested, but he's not on buddy "
+ "list\n", gc, pending_update->uin);
+ }
+ return FALSE;
+ }
+
+ old_timestamp_str = purple_buddy_icons_get_checksum_for_user(buddy);
+ old_timestamp = old_timestamp_str ? g_ascii_strtoull(
+ old_timestamp_str, NULL, 10) : 0;
+ if (old_timestamp == pending_update->timestamp)
+ {
+ purple_debug_misc("gg",
+ "ggp_avatar_buddy_update_next(%p): "
+ "%u have up to date avatar with ts=%lu\n", gc,
+ pending_update->uin, pending_update->timestamp);
+ return FALSE;
+ }
+ if (old_timestamp > pending_update->timestamp)
+ {
+ purple_debug_warning("gg",
+ "ggp_avatar_buddy_update_next(%p): "
+ "saved timestamp for %u is newer than received "
+ "(%lu > %lu)\n", gc, pending_update->uin, old_timestamp,
+ pending_update->timestamp);
+ }
+
+ purple_debug_info("gg",
+ "ggp_avatar_buddy_update_next(%p): "
+ "updating %u with ts=%lu...\n", gc, pending_update->uin,
+ pending_update->timestamp);
+
+ pending_update->gc = gc;
+ avdata->current_update = pending_update;
+ avatar_url = g_strdup_printf(GGP_AVATAR_BUDDY_URL, pending_update->uin);
+ pending_update->request = purple_util_fetch_url_request(account,
+ avatar_url, FALSE, GGP_AVATAR_USERAGENT, TRUE, NULL, FALSE,
+ GGP_AVATAR_SIZE_MAX, ggp_avatar_buddy_update_received,
+ pending_update);
+ g_free(avatar_url);
+
+ return TRUE;
+}
+
+static void ggp_avatar_buddy_update_received(PurpleUtilFetchUrlData *url_data,
+ gpointer _pending_update, const gchar *url_text, gsize len,
+ const gchar *error_message)
+{
+ ggp_avatar_buddy_update_req *pending_update = _pending_update;
+ PurpleBuddy *buddy;
+ PurpleAccount *account;
+ PurpleConnection *gc = pending_update->gc;
+ ggp_avatar_session_data *avdata;
+ gchar timestamp_str[20];
+
+ if (!PURPLE_CONNECTION_IS_VALID(gc))
+ {
+ g_free(pending_update);
+ return;
+ }
+
+ avdata = ggp_avatar_get_avdata(gc);
+ g_assert(pending_update == avdata->current_update);
+ avdata->current_update = NULL;
+
+ if (len == 0)
+ {
+ purple_debug_error("gg", "ggp_avatar_buddy_update_received: bad"
+ " response while getting avatar for %u: %s\n",
+ pending_update->uin, error_message);
+ g_free(pending_update);
+ return;
+ }
+
+ account = purple_connection_get_account(gc);
+ buddy = purple_find_buddy(account, ggp_uin_to_str(pending_update->uin));
+
+ if (!buddy)
+ {
+ purple_debug_warning("gg", "ggp_avatar_buddy_update_received: "
+ "buddy %u disappeared\n", pending_update->uin);
+ g_free(pending_update);
+ return;
+ }
+
+ g_snprintf(timestamp_str, sizeof(timestamp_str), "%lu",
+ pending_update->timestamp);
+ purple_buddy_icons_set_for_user(account, purple_buddy_get_name(buddy),
+ g_memdup(url_text, len), len, timestamp_str);
+
+ purple_debug_info("gg", "ggp_avatar_buddy_update_received: "
+ "got avatar for buddy %u [ts=%lu]\n", pending_update->uin,
+ pending_update->timestamp);
+ g_free(pending_update);
+}
diff --git a/libpurple/protocols/gg/avatar.h b/libpurple/protocols/gg/avatar.h
new file mode 100644
index 0000000000..310f37115b
--- /dev/null
+++ b/libpurple/protocols/gg/avatar.h
@@ -0,0 +1,21 @@
+#ifndef _GGP_AVATAR_H
+#define _GGP_AVATAR_H
+
+#include <internal.h>
+#include <libgadu.h>
+
+typedef struct
+{
+ guint timer;
+ GList *pending_updates;
+
+ gpointer current_update;
+} ggp_avatar_session_data;
+
+void ggp_avatar_setup(PurpleConnection *gc);
+void ggp_avatar_cleanup(PurpleConnection *gc);
+
+void ggp_avatar_buddy_update(PurpleConnection *gc, uin_t uin, time_t timestamp);
+void ggp_avatar_buddy_remove(PurpleConnection *gc, uin_t uin);
+
+#endif /* _GGP_AVATAR_H */
diff --git a/libpurple/protocols/gg/gg.c b/libpurple/protocols/gg/gg.c
index a6acd39cf2..fd8ab55f9f 100644
--- a/libpurple/protocols/gg/gg.c
+++ b/libpurple/protocols/gg/gg.c
@@ -48,6 +48,7 @@
#include "account.h"
#include "deprecated.h"
#include "purplew.h"
+#include "libgadu-events.h"
/* Prototypes */
static void ggp_set_status(PurpleAccount *account, PurpleStatus *status);
@@ -481,164 +482,6 @@ static void ggp_rem_deny(PurpleConnection *gc, const char *who)
/* ----- INTERNAL CALLBACKS --------------------------------------------- */
/* ---------------------------------------------------------------------- */
-struct gg_fetch_avatar_data
-{
- PurpleConnection *gc;
- gchar *uin;
- gchar *avatar_url;
-};
-
-
-static void gg_fetch_avatar_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data,
- const gchar *data, size_t len, const gchar *error_message) {
- struct gg_fetch_avatar_data *d = user_data;
- PurpleAccount *account;
- PurpleBuddy *buddy;
- gpointer buddy_icon_data;
-
- purple_debug_info("gg", "gg_fetch_avatar_cb: got avatar image for %s\n",
- d->uin);
-
- /* FIXME: This shouldn't be necessary */
- if (!PURPLE_CONNECTION_IS_VALID(d->gc)) {
- g_free(d->uin);
- g_free(d->avatar_url);
- g_free(d);
- g_return_if_reached();
- }
-
- account = purple_connection_get_account(d->gc);
- buddy = purple_find_buddy(account, d->uin);
-
- if (buddy == NULL)
- goto out;
-
- buddy_icon_data = g_memdup(data, len);
-
- purple_buddy_icons_set_for_user(account, purple_buddy_get_name(buddy),
- buddy_icon_data, len, d->avatar_url);
- purple_debug_info("gg", "gg_fetch_avatar_cb: UIN %s should have avatar "
- "now\n", d->uin);
-
-out:
- g_free(d->uin);
- g_free(d->avatar_url);
- g_free(d);
-}
-
-static void gg_get_avatar_url_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data,
- const gchar *url_text, size_t len, const gchar *error_message) {
- struct gg_fetch_avatar_data *data;
- PurpleConnection *gc = user_data;
- PurpleAccount *account;
- PurpleBuddy *buddy;
- const char *uin;
- const char *is_blank;
- const char *checksum;
-
- gchar *bigavatar = NULL;
- xmlnode *xml = NULL;
- xmlnode *xmlnode_users;
- xmlnode *xmlnode_user;
- xmlnode *xmlnode_avatars;
- xmlnode *xmlnode_avatar;
- xmlnode *xmlnode_bigavatar;
-
- g_return_if_fail(PURPLE_CONNECTION_IS_VALID(gc));
- account = purple_connection_get_account(gc);
-
- if (error_message != NULL)
- purple_debug_error("gg", "gg_get_avatars_cb error: %s\n", error_message);
- else if (len > 0 && url_text && *url_text) {
- xml = xmlnode_from_str(url_text, -1);
- if (xml == NULL)
- goto out;
-
- xmlnode_users = xmlnode_get_child(xml, "users");
- if (xmlnode_users == NULL)
- goto out;
-
- xmlnode_user = xmlnode_get_child(xmlnode_users, "user");
- if (xmlnode_user == NULL)
- goto out;
-
- uin = xmlnode_get_attrib(xmlnode_user, "uin");
-
- xmlnode_avatars = xmlnode_get_child(xmlnode_user, "avatars");
- if (xmlnode_avatars == NULL)
- goto out;
-
- xmlnode_avatar = xmlnode_get_child(xmlnode_avatars, "avatar");
- if (xmlnode_avatar == NULL)
- goto out;
-
- xmlnode_bigavatar = xmlnode_get_child(xmlnode_avatar, "originBigAvatar");
- if (xmlnode_bigavatar == NULL)
- goto out;
-
- is_blank = xmlnode_get_attrib(xmlnode_avatar, "blank");
- bigavatar = xmlnode_get_data(xmlnode_bigavatar);
-
- purple_debug_info("gg", "gg_get_avatar_url_cb: UIN %s, IS_BLANK %s, "
- "URL %s\n",
- uin ? uin : "(null)", is_blank ? is_blank : "(null)",
- bigavatar ? bigavatar : "(null)");
-
- if (uin != NULL && bigavatar != NULL) {
- buddy = purple_find_buddy(account, uin);
- if (buddy == NULL)
- goto out;
-
- checksum = purple_buddy_icons_get_checksum_for_user(buddy);
-
- if (purple_strequal(is_blank, "1")) {
- purple_buddy_icons_set_for_user(account,
- purple_buddy_get_name(buddy), NULL, 0, NULL);
- } else if (!purple_strequal(checksum, bigavatar)) {
- data = g_new0(struct gg_fetch_avatar_data, 1);
- data->gc = gc;
- data->uin = g_strdup(uin);
- data->avatar_url = g_strdup(bigavatar);
-
- purple_debug_info("gg", "gg_get_avatar_url_cb: "
- "requesting avatar for %s\n", uin);
- /* FIXME: This should be cancelled somewhere if not needed. */
- url_data = purple_util_fetch_url_request(account,
- bigavatar, TRUE, "Mozilla/4.0 (compatible; MSIE 5.0)",
- FALSE, NULL, FALSE, -1, gg_fetch_avatar_cb, data);
- }
- }
- }
-
-out:
- if (xml)
- xmlnode_free(xml);
- g_free(bigavatar);
-}
-
-/**
- * Try to update avatar of the buddy.
- *
- * @param gc PurpleConnection
- * @param uin UIN of the buddy.
- */
-static void ggp_update_buddy_avatar(PurpleConnection *gc, uin_t uin)
-{
- gchar *avatarurl;
- PurpleUtilFetchUrlData *url_data;
-
- purple_debug_info("gg", "ggp_update_buddy_avatar(gc, %u)\n", uin);
-
- avatarurl = g_strdup_printf("http://api.gadu-gadu.pl/avatars/%u/0.xml", uin);
-
- /* FIXME: This should be cancelled somewhere if not needed. */
- url_data = purple_util_fetch_url_request(
- purple_connection_get_account(gc), avatarurl, TRUE,
- "Mozilla/4.0 (compatible; MSIE 5.5)", FALSE, NULL, FALSE, -1,
- gg_get_avatar_url_cb, gc);
-
- g_free(avatarurl);
-}
/**
* Handle change of the status of the buddy.
@@ -655,8 +498,6 @@ static void ggp_generic_status_handler(PurpleConnection *gc, uin_t uin,
const char *st;
char *status_msg = NULL;
- ggp_update_buddy_avatar(gc, uin);
-
from = g_strdup_printf("%u", uin);
switch (status) {
@@ -1170,6 +1011,7 @@ static void ggp_typing_notification_handler(PurpleConnection *gc, uin_t uin, int
* @param data Raw XML contents.
*
* @see http://toxygen.net/libgadu/protocol/#ch1.13
+ * @todo: this may not be necessary anymore
*/
static void ggp_xml_event_handler(PurpleConnection *gc, char *data)
{
@@ -1218,7 +1060,6 @@ static void ggp_xml_event_handler(PurpleConnection *gc, char *data)
purple_debug_info("gg",
"ggp_xml_event_handler: avatar updated (uid: %u)\n",
event_sender);
- ggp_update_buddy_avatar(gc, event_sender);
break;
default:
purple_debug_error("gg",
@@ -1354,7 +1195,7 @@ static void ggp_callback_recv(gpointer _gc, gint fd, PurpleInputCondition cond)
ggp_xml_event_handler(gc, ev->event.xml_event.data);
break;
case GG_EVENT_USER_DATA:
- purple_debug_misc("gg", "GG_EVENT_USER_DATA\n");
+ ggp_events_user_data(gc, &ev->event.user_data);
break;
default:
purple_debug_error("gg",
@@ -1726,6 +1567,7 @@ static void ggp_login(PurpleAccount *account)
purple_connection_set_protocol_data(gc, info);
ggp_image_setup(gc);
+ ggp_avatar_setup(gc);
glp->uin = ggp_str_to_uin(purple_account_get_username(account));
glp->password = ggp_convert_to_cp1250(purple_account_get_password(account));
@@ -1839,7 +1681,8 @@ static void ggp_close(PurpleConnection *gc)
purple_notify_close_with_handle(gc);
ggp_search_destroy(info->searches);
- ggp_image_free(gc);
+ ggp_image_cleanup(gc);
+ ggp_avatar_cleanup(gc);
if (info->inpa > 0)
purple_input_remove(info->inpa);
diff --git a/libpurple/protocols/gg/gg.h b/libpurple/protocols/gg/gg.h
index cd1718ddac..f931da6731 100644
--- a/libpurple/protocols/gg/gg.h
+++ b/libpurple/protocols/gg/gg.h
@@ -30,6 +30,7 @@
#include "connection.h"
#include "image.h"
+#include "avatar.h"
#include "account.h"
@@ -55,6 +56,7 @@ typedef struct {
gboolean status_broadcasting; //When TRUE status is visible to all, when FALSE status is visible only to friends.
ggp_image_connection_data image_data;
+ ggp_avatar_session_data avatar_data;
} GGPInfo;
#endif /* _PURPLE_GG_H */
diff --git a/libpurple/protocols/gg/image.c b/libpurple/protocols/gg/image.c
index 39fb7a38b0..f74eed5c99 100644
--- a/libpurple/protocols/gg/image.c
+++ b/libpurple/protocols/gg/image.c
@@ -52,7 +52,7 @@ void ggp_image_setup(PurpleConnection *gc)
ggp_image_pending_image_free);
}
-void ggp_image_free(PurpleConnection *gc)
+void ggp_image_cleanup(PurpleConnection *gc)
{
ggp_image_connection_data *imgdata = ggp_image_get_imgdata(gc);
diff --git a/libpurple/protocols/gg/image.h b/libpurple/protocols/gg/image.h
index 1404952e80..50732ac69a 100644
--- a/libpurple/protocols/gg/image.h
+++ b/libpurple/protocols/gg/image.h
@@ -20,7 +20,7 @@ typedef enum
} ggp_image_prepare_result;
void ggp_image_setup(PurpleConnection *gc);
-void ggp_image_free(PurpleConnection *gc);
+void ggp_image_cleanup(PurpleConnection *gc);
const char * ggp_image_pending_placeholder(uint32_t id);
diff --git a/libpurple/protocols/gg/libgadu-events.c b/libpurple/protocols/gg/libgadu-events.c
new file mode 100644
index 0000000000..19e31ad7bf
--- /dev/null
+++ b/libpurple/protocols/gg/libgadu-events.c
@@ -0,0 +1,47 @@
+#include "libgadu-events.h"
+
+#include <debug.h>
+
+#include "avatar.h"
+
+void ggp_events_user_data(PurpleConnection *gc, struct gg_event_user_data *data)
+{
+ int user_idx;
+ gboolean is_update;
+
+ purple_debug_info("gg", "GG_EVENT_USER_DATA [type=%d, user_count=%d]\n",
+ data->type, data->user_count);
+
+ /*
+ type =
+ 1, 3: user information sent after connecting (divided by
+ 20 contacts; 3 - last one; 1 - rest of them)
+ 0: data update
+ */
+ is_update = (data->type == 0);
+
+ for (user_idx = 0; user_idx < data->user_count; user_idx++)
+ {
+ struct gg_event_user_data_user *data_user =
+ &data->users[user_idx];
+ uin_t uin = data_user->uin;
+ int attr_idx;
+ gboolean got_avatar = FALSE;
+ for (attr_idx = 0; attr_idx < data_user->attr_count; attr_idx++)
+ {
+ struct gg_event_user_data_attr *data_attr =
+ &data_user->attrs[attr_idx];
+ if (strcmp(data_attr->key, "avatar") == 0)
+ {
+ time_t timestamp = atoi(data_attr->value);
+ if (timestamp <= 0)
+ continue;
+ got_avatar = TRUE;
+ ggp_avatar_buddy_update(gc, uin, timestamp);
+ }
+ }
+
+ if (!is_update && !got_avatar)
+ ggp_avatar_buddy_remove(gc, uin);
+ }
+}
diff --git a/libpurple/protocols/gg/libgadu-events.h b/libpurple/protocols/gg/libgadu-events.h
new file mode 100644
index 0000000000..d7ae8b9f83
--- /dev/null
+++ b/libpurple/protocols/gg/libgadu-events.h
@@ -0,0 +1,10 @@
+#ifndef _GGP_LIBGADU_EVENTS_H
+#define _GGP_LIBGADU_EVENTS_H
+
+#include <internal.h>
+#include <libgadu.h>
+
+void ggp_events_user_data(PurpleConnection *gc,
+ struct gg_event_user_data *data);
+
+#endif /* _GGP_LIBGADU_EVENTS_H */