mark PurpleImageClass as private
[pidgin-git.git] / libpurple / protocols / gg / avatar.c
blob5ee4e7c1b7e4d66e1be03e5852f868aa39f62e6c
1 /* purple
3 * Purple is the legal property of its developers, whose names are too numerous
4 * to list here. Please refer to the COPYRIGHT file distributed with this
5 * source distribution.
7 * Rewritten from scratch during Google Summer of Code 2012
8 * by Tomek Wasilczyk (http://www.wasilczyk.pl).
10 * Previously implemented by:
11 * - Arkadiusz Miskiewicz <misiek@pld.org.pl> - first implementation (2001);
12 * - Bartosz Oler <bartosz@bzimage.us> - reimplemented during GSoC 2005;
13 * - Krzysztof Klinikowski <grommasher@gmail.com> - some parts (2009-2011).
15 * This program is free software; you can redistribute it and/or modify
16 * it under the terms of the GNU General Public License as published by
17 * the Free Software Foundation; either version 2 of the License, or
18 * (at your option) any later version.
20 * This program is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
25 * You should have received a copy of the GNU General Public License
26 * along with this program; if not, write to the Free Software
27 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
30 #include "avatar.h"
32 #include <debug.h>
33 #include <glibcompat.h>
34 #include <http.h>
36 #include "gg.h"
37 #include "utils.h"
38 #include "oauth/oauth-purple.h"
40 /* Common */
42 static inline ggp_avatar_session_data *
43 ggp_avatar_get_avdata(PurpleConnection *gc);
45 static gboolean ggp_avatar_timer_cb(gpointer _gc);
47 #define GGP_AVATAR_USERAGENT "GG Client build 11.0.0.7562"
48 #define GGP_AVATAR_SIZE_MAX 1048576
50 /* Buddy avatars updating */
52 typedef struct
54 uin_t uin;
55 time_t timestamp;
57 PurpleConnection *gc;
58 PurpleHttpConnection *request;
59 } ggp_avatar_buddy_update_req;
61 static gboolean ggp_avatar_buddy_update_next(PurpleConnection *gc);
62 static void ggp_avatar_buddy_update_received(PurpleHttpConnection *http_conn,
63 PurpleHttpResponse *response, gpointer user_data);
65 #define GGP_AVATAR_BUDDY_URL "http://avatars.gg.pl/%u/s,big"
67 /* Own avatar setting */
69 typedef struct
71 PurpleImage *img;
72 } ggp_avatar_own_data;
74 static void ggp_avatar_own_got_token(PurpleConnection *gc, const gchar *token,
75 gpointer img);
76 static void ggp_avatar_own_sent(PurpleHttpConnection *http_conn,
77 PurpleHttpResponse *response, gpointer user_data);
79 #define GGP_AVATAR_RESPONSE_MAX 10240
81 /*******************************************************************************
82 * Common.
83 ******************************************************************************/
85 void ggp_avatar_setup(PurpleConnection *gc)
87 ggp_avatar_session_data *avdata = ggp_avatar_get_avdata(gc);
89 avdata->pending_updates = NULL;
90 avdata->current_update = NULL;
91 avdata->own_data = g_new0(ggp_avatar_own_data, 1);
93 avdata->timer = g_timeout_add_seconds(1, ggp_avatar_timer_cb, gc);
96 void ggp_avatar_cleanup(PurpleConnection *gc)
98 ggp_avatar_session_data *avdata = ggp_avatar_get_avdata(gc);
100 g_source_remove(avdata->timer);
102 if (avdata->current_update != NULL) {
103 ggp_avatar_buddy_update_req *current_update =
104 avdata->current_update;
106 purple_http_conn_cancel(current_update->request);
107 g_free(current_update);
109 avdata->current_update = NULL;
111 g_free(avdata->own_data);
113 g_list_free_full(avdata->pending_updates, &g_free);
114 avdata->pending_updates = NULL;
117 static inline ggp_avatar_session_data *
118 ggp_avatar_get_avdata(PurpleConnection *gc)
120 GGPInfo *accdata = purple_connection_get_protocol_data(gc);
121 return &accdata->avatar_data;
124 static gboolean ggp_avatar_timer_cb(gpointer _gc)
126 PurpleConnection *gc = _gc;
127 ggp_avatar_session_data *avdata;
129 PURPLE_ASSERT_CONNECTION_IS_VALID(gc);
131 avdata = ggp_avatar_get_avdata(gc);
132 if (avdata->current_update != NULL) {
133 if (purple_debug_is_verbose()) {
134 purple_debug_misc("gg", "ggp_avatar_timer_cb(%p): "
135 "there is already an update running\n", gc);
137 return TRUE;
140 while (!ggp_avatar_buddy_update_next(gc));
142 return TRUE;
145 /*******************************************************************************
146 * Buddy avatars updating.
147 ******************************************************************************/
149 void ggp_avatar_buddy_update(PurpleConnection *gc, uin_t uin, time_t timestamp)
151 ggp_avatar_session_data *avdata = ggp_avatar_get_avdata(gc);
152 ggp_avatar_buddy_update_req *pending_update =
153 g_new(ggp_avatar_buddy_update_req, 1); /* TODO: leak? */
155 if (purple_debug_is_verbose()) {
156 purple_debug_misc("gg", "ggp_avatar_buddy_update(%p, %u, %lu)\n", gc,
157 uin, timestamp);
160 pending_update->uin = uin;
161 pending_update->timestamp = timestamp;
163 avdata->pending_updates = g_list_append(avdata->pending_updates,
164 pending_update);
167 void ggp_avatar_buddy_remove(PurpleConnection *gc, uin_t uin)
169 if (purple_debug_is_verbose()) {
170 purple_debug_misc("gg", "ggp_avatar_buddy_remove(%p, %u)\n", gc, uin);
173 purple_buddy_icons_set_for_user(purple_connection_get_account(gc),
174 ggp_uin_to_str(uin), NULL, 0, NULL);
177 /* return TRUE if avatar update was performed or there is no new requests,
178 FALSE if we can request another one immediately */
179 static gboolean ggp_avatar_buddy_update_next(PurpleConnection *gc)
181 PurpleHttpRequest *req;
182 ggp_avatar_session_data *avdata = ggp_avatar_get_avdata(gc);
183 GList *pending_update_it;
184 ggp_avatar_buddy_update_req *pending_update;
185 PurpleBuddy *buddy;
186 PurpleAccount *account = purple_connection_get_account(gc);
187 time_t old_timestamp;
188 const char *old_timestamp_str;
190 pending_update_it = g_list_first(avdata->pending_updates);
191 if (pending_update_it == NULL)
192 return TRUE;
194 pending_update = pending_update_it->data;
195 avdata->pending_updates = g_list_remove(avdata->pending_updates,
196 pending_update);
197 buddy = purple_blist_find_buddy(account, ggp_uin_to_str(pending_update->uin));
199 if (!buddy) {
200 if (ggp_str_to_uin(purple_account_get_username(account)) ==
201 pending_update->uin)
203 purple_debug_misc("gg",
204 "ggp_avatar_buddy_update_next(%p): own "
205 "avatar update requested, but we don't have "
206 "ourselves on buddy list\n", gc);
207 } else {
208 purple_debug_warning("gg",
209 "ggp_avatar_buddy_update_next(%p): "
210 "%u update requested, but he's not on buddy "
211 "list\n", gc, pending_update->uin);
213 return FALSE;
216 old_timestamp_str = purple_buddy_icons_get_checksum_for_user(buddy);
217 old_timestamp = old_timestamp_str ? g_ascii_strtoull(
218 old_timestamp_str, NULL, 10) : 0;
219 if (old_timestamp == pending_update->timestamp) {
220 if (purple_debug_is_verbose()) {
221 purple_debug_misc("gg",
222 "ggp_avatar_buddy_update_next(%p): "
223 "%u have up to date avatar with ts=%lu\n", gc,
224 pending_update->uin, pending_update->timestamp);
226 return FALSE;
228 if (old_timestamp > pending_update->timestamp) {
229 purple_debug_warning("gg",
230 "ggp_avatar_buddy_update_next(%p): "
231 "saved timestamp for %u is newer than received "
232 "(%lu > %lu)\n", gc, pending_update->uin, old_timestamp,
233 pending_update->timestamp);
236 purple_debug_info("gg",
237 "ggp_avatar_buddy_update_next(%p): "
238 "updating %u with ts=%lu...\n", gc, pending_update->uin,
239 pending_update->timestamp);
241 pending_update->gc = gc;
242 avdata->current_update = pending_update;
244 req = purple_http_request_new(NULL);
245 purple_http_request_set_url_printf(req, GGP_AVATAR_BUDDY_URL,
246 pending_update->uin);
247 purple_http_request_header_set(req, "User-Agent", GGP_AVATAR_USERAGENT);
248 purple_http_request_set_max_len(req, GGP_AVATAR_SIZE_MAX);
249 pending_update->request = purple_http_request(gc, req,
250 ggp_avatar_buddy_update_received, pending_update);
251 purple_http_request_unref(req);
253 return TRUE;
256 static void ggp_avatar_buddy_update_received(PurpleHttpConnection *http_conn,
257 PurpleHttpResponse *response, gpointer _pending_update)
259 ggp_avatar_buddy_update_req *pending_update = _pending_update;
260 PurpleBuddy *buddy;
261 PurpleAccount *account;
262 PurpleConnection *gc = pending_update->gc;
263 ggp_avatar_session_data *avdata;
264 gchar timestamp_str[20];
265 const gchar *got_data;
266 size_t got_len;
268 PURPLE_ASSERT_CONNECTION_IS_VALID(gc);
270 avdata = ggp_avatar_get_avdata(gc);
271 g_assert(pending_update == avdata->current_update);
272 avdata->current_update = NULL;
274 if (!purple_http_response_is_successful(response)) {
275 purple_debug_error("gg", "ggp_avatar_buddy_update_received: bad"
276 " response while getting avatar for %u: %s\n",
277 pending_update->uin,
278 purple_http_response_get_error(response));
279 g_free(pending_update);
280 return;
283 account = purple_connection_get_account(gc);
284 buddy = purple_blist_find_buddy(account, ggp_uin_to_str(pending_update->uin));
286 if (!buddy) {
287 purple_debug_warning("gg", "ggp_avatar_buddy_update_received: "
288 "buddy %u disappeared\n", pending_update->uin);
289 g_free(pending_update);
290 return;
293 g_snprintf(timestamp_str, sizeof(timestamp_str), "%lu",
294 pending_update->timestamp);
295 got_data = purple_http_response_get_data(response, &got_len);
296 purple_buddy_icons_set_for_user(account, purple_buddy_get_name(buddy),
297 g_memdup(got_data, got_len), got_len, timestamp_str);
299 purple_debug_info("gg", "ggp_avatar_buddy_update_received: "
300 "got avatar for buddy %u [ts=%lu]\n", pending_update->uin,
301 pending_update->timestamp);
302 g_free(pending_update);
305 /*******************************************************************************
306 * Own avatar setting.
307 ******************************************************************************/
310 * TODO: use new, GG11 method, when IMToken will be provided by libgadu.
312 * POST https://avatars.mpa.gg.pl/avatars/user,<uin>/0
313 * Authorization: IMToken 0123456789abcdef0123456789abcdef01234567
314 * photo=<avatar content>
317 void ggp_avatar_own_set(PurpleConnection *gc, PurpleImage *img)
319 ggp_avatar_own_data *own_data;
321 PURPLE_ASSERT_CONNECTION_IS_VALID(gc);
323 purple_debug_info("gg", "ggp_avatar_own_set(%p, %p)", gc, img);
325 own_data = ggp_avatar_get_avdata(gc)->own_data;
327 if (img == NULL) {
328 purple_debug_warning("gg", "ggp_avatar_own_set: avatar removing"
329 " is probably not possible within old protocol");
330 return;
333 own_data->img = img;
335 ggp_oauth_request(gc, ggp_avatar_own_got_token, img, NULL, NULL);
338 static void
339 ggp_avatar_own_got_token(PurpleConnection *gc, const gchar *token,
340 gpointer _img)
342 PurpleHttpRequest *req;
343 PurpleImage *img = _img;
344 ggp_avatar_own_data *own_data = ggp_avatar_get_avdata(gc)->own_data;
345 gchar *img_data, *img_data_e, *request_data;
346 PurpleAccount *account = purple_connection_get_account(gc);
347 uin_t uin = ggp_str_to_uin(purple_account_get_username(account));
349 if (img != own_data->img) {
350 purple_debug_warning("gg", "ggp_avatar_own_got_token: "
351 "avatar was changed in meantime\n");
352 return;
354 own_data->img = NULL;
356 img_data = g_base64_encode(purple_image_get_data(img),
357 purple_image_get_data_size(img));
358 img_data_e = g_uri_escape_string(img_data, NULL, FALSE);
359 g_free(img_data);
360 request_data = g_strdup_printf("uin=%d&photo=%s", uin, img_data_e);
361 g_free(img_data_e);
363 purple_debug_misc("gg", "ggp_avatar_own_got_token: "
364 "uploading new avatar...\n");
366 req = purple_http_request_new("http://avatars.nowe.gg/upload");
367 purple_http_request_set_max_len(req, GGP_AVATAR_RESPONSE_MAX);
368 purple_http_request_set_method(req, "POST");
369 purple_http_request_header_set(req, "Authorization", token);
370 purple_http_request_header_set(req, "From", "avatars to avatars");
371 purple_http_request_header_set(req, "Content-Type",
372 "application/x-www-form-urlencoded");
373 purple_http_request_set_contents(req, request_data, -1);
374 purple_http_request(gc, req, ggp_avatar_own_sent, NULL);
375 purple_http_request_unref(req);
377 g_free(request_data);
380 static void ggp_avatar_own_sent(PurpleHttpConnection *http_conn,
381 PurpleHttpResponse *response, gpointer user_data)
383 PurpleConnection *gc =
384 purple_http_conn_get_purple_connection(http_conn);
386 PURPLE_ASSERT_CONNECTION_IS_VALID(gc);
388 if (!purple_http_response_is_successful(response)) {
389 purple_debug_error("gg", "ggp_avatar_own_sent: "
390 "avatar not sent. %s\n",
391 purple_http_response_get_error(response));
392 return;
394 purple_debug_info("gg", "ggp_avatar_own_sent: %s\n",
395 purple_http_response_get_data(response, NULL));