hythmbox-2.98/plugins/audioscrobbler/rb-audioscrobbler-account.c

No issues found

  1 /*
  2  * rb-audioscrobbler-account.c
  3  *
  4  * Copyright (C) 2010 Jamie Nicol <jamie@thenicols.net>
  5  *
  6  * This program is free software; you can redistribute it and/or modify
  7  * it under the terms of the GNU General Public License as published by
  8  * the Free Software Foundation; either version 2, or (at your option)
  9  * any later version.
 10  *
 11  * The Rhythmbox authors hereby grant permission for non-GPL compatible
 12  * GStreamer plugins to be used and distributed together with GStreamer
 13  * and Rhythmbox. This permission is above and beyond the permissions granted
 14  * by the GPL license by which Rhythmbox is covered. If you modify this code
 15  * you may extend this exception to your version of the code, but you are not
 16  * obligated to do so. If you do not wish to do so, delete this exception
 17  * statement from your version.
 18  *
 19  * This program is distributed in the hope that it will be useful,
 20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 22  * GNU General Public License for more details.
 23  *
 24  * You should have received a copy of the GNU General Public License
 25  * along with this program; if not, write to the Free Software
 26  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
 27  */
 28 
 29 #include <string.h>
 30 
 31 #include <glib/gi18n.h>
 32 
 33 #include <libsoup/soup.h>
 34 #include <libsoup/soup-gnome.h>
 35 #include <json-glib/json-glib.h>
 36 
 37 #include "rb-audioscrobbler-account.h"
 38 #include "rb-builder-helpers.h"
 39 #include "rb-debug.h"
 40 #include "rb-file-helpers.h"
 41 #include "rb-util.h"
 42 
 43 #define SESSION_SETTINGS_FILE "sessions"
 44 #define SESSION_KEY_REQUEST_TIMEOUT 5
 45 
 46 struct _RBAudioscrobblerAccountPrivate
 47 {
 48 	RBAudioscrobblerService *service;
 49 
 50 	/* Authentication info */
 51 	gchar *username;
 52 	gchar *auth_token;
 53 	gchar *session_key;
 54 	RBAudioscrobblerAccountLoginStatus login_status;
 55 
 56 	/* Widgets for the prefs pane */
 57 	GtkWidget *config_widget;
 58 	GtkWidget *login_status_label;
 59 	GtkWidget *auth_button;
 60 
 61 	/* Timeout notifications */
 62 	guint session_key_timeout_id;
 63 
 64 	/* HTTP requests session */
 65 	SoupSession *soup_session;
 66 };
 67 
 68 #define RB_AUDIOSCROBBLER_ACCOUNT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_AUDIOSCROBBLER_ACCOUNT, RBAudioscrobblerAccountPrivate))
 69 
 70 static void          rb_audioscrobbler_account_class_init (RBAudioscrobblerAccountClass *klass);
 71 static void          rb_audioscrobbler_account_init (RBAudioscrobblerAccount *account);
 72 static void          rb_audioscrobbler_account_constructed (GObject *object);
 73 static void          rb_audioscrobbler_account_dispose (GObject *object);
 74 static void          rb_audioscrobbler_account_finalize (GObject *object);
 75 static void	     rb_audioscrobbler_account_get_property (GObject *object,
 76                                                              guint prop_id,
 77                                                              GValue *value,
 78                                                              GParamSpec *pspec);
 79 static void	     rb_audioscrobbler_account_set_property (GObject *object,
 80                                                              guint prop_id,
 81                                                              const GValue *value,
 82                                                              GParamSpec *pspec);
 83 
 84 /* load/save session to file to avoid having to reauthenticate */
 85 static void          load_session_settings (RBAudioscrobblerAccount *account);
 86 static void          save_session_settings (RBAudioscrobblerAccount *account);
 87 
 88 /* private functions used in authentication process */
 89 static void          cancel_session (RBAudioscrobblerAccount *account);
 90 static void          request_token (RBAudioscrobblerAccount *account);
 91 static void          got_token_cb (SoupSession *session,
 92                                    SoupMessage *msg,
 93                                    gpointer user_data);
 94 static gboolean      request_session_key_timeout_cb (gpointer user_data);
 95 static void          got_session_key_cb (SoupSession *session,
 96                                          SoupMessage *msg,
 97                                          gpointer user_data);
 98 enum
 99 {
100 	PROP_0,
101 	PROP_SERVICE,
102 	PROP_USERNAME,
103 	PROP_SESSION_KEY,
104 	PROP_LOGIN_STATUS
105 };
106 
107 enum
108 {
109 	LOGIN_STATUS_CHANGED,
110 	LAST_SIGNAL
111 };
112 
113 static guint rb_audioscrobbler_account_signals[LAST_SIGNAL] = { 0 };
114 
115 G_DEFINE_DYNAMIC_TYPE (RBAudioscrobblerAccount, rb_audioscrobbler_account, G_TYPE_OBJECT)
116 
117 RBAudioscrobblerAccount *
118 rb_audioscrobbler_account_new (RBAudioscrobblerService *service)
119 {
120 	return g_object_new (RB_TYPE_AUDIOSCROBBLER_ACCOUNT,
121                              "service", service,
122 	                     NULL);
123 }
124 
125 static void
126 rb_audioscrobbler_account_class_init (RBAudioscrobblerAccountClass *klass)
127 {
128 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
129 
130 	object_class->constructed = rb_audioscrobbler_account_constructed;
131 	object_class->dispose = rb_audioscrobbler_account_dispose;
132 	object_class->finalize = rb_audioscrobbler_account_finalize;
133 
134 	object_class->get_property = rb_audioscrobbler_account_get_property;
135 	object_class->set_property = rb_audioscrobbler_account_set_property;
136 
137 	g_object_class_install_property (object_class,
138 	                                 PROP_SERVICE,
139 	                                 g_param_spec_object ("service",
140 	                                                      "Service",
141 	                                                      "Audioscrobbler service the account is with",
142 	                                                      RB_TYPE_AUDIOSCROBBLER_SERVICE,
143                                                               G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
144 
145 	g_object_class_install_property (object_class,
146 	                                 PROP_USERNAME,
147 	                                 g_param_spec_string ("username",
148 	                                                      "Username",
149 	                                                      "Username",
150 	                                                      NULL,
151                                                               G_PARAM_READABLE));
152 
153 	g_object_class_install_property (object_class,
154 	                                 PROP_SESSION_KEY,
155 	                                 g_param_spec_string ("session-key",
156 	                                                      "Session Key",
157 	                                                      "Session key used to authenticate the user",
158 	                                                      NULL,
159                                                               G_PARAM_READABLE));
160 
161 	g_object_class_install_property (object_class,
162 	                                 PROP_LOGIN_STATUS,
163 	                                 g_param_spec_enum ("login-status",
164 	                                                    "Login Status",
165 	                                                    "Login status",
166 	                                                    RB_TYPE_AUDIOSCROBBLER_ACCOUNT_LOGIN_STATUS,
167 	                                                    RB_AUDIOSCROBBLER_ACCOUNT_LOGIN_STATUS_LOGGED_OUT,
168                                                             G_PARAM_READABLE));
169 
170 	/**
171 	 * RBAudioscrobblerAccount::login-status-changed:
172 	 * @account: the #RBAudioscrobblerAccount
173 	 * @status: new status
174 	 *
175 	 * Emitted after the login status of the account has changed.
176 	 */
177 	rb_audioscrobbler_account_signals[LOGIN_STATUS_CHANGED] =
178 		g_signal_new ("login-status-changed",
179 			      G_OBJECT_CLASS_TYPE (object_class),
180 			      G_SIGNAL_RUN_LAST,
181 			      G_STRUCT_OFFSET (RBAudioscrobblerAccountClass, login_status_changed),
182 			      NULL, NULL,
183 			      g_cclosure_marshal_VOID__ENUM,
184 			      G_TYPE_NONE,
185 			      1,
186 			      RB_TYPE_AUDIOSCROBBLER_ACCOUNT_LOGIN_STATUS);
187 
188 	g_type_class_add_private (klass, sizeof (RBAudioscrobblerAccountPrivate));
189 }
190 
191 static void
192 rb_audioscrobbler_account_class_finalize (RBAudioscrobblerAccountClass *klass)
193 {
194 }
195 
196 static void
197 rb_audioscrobbler_account_init (RBAudioscrobblerAccount *account)
198 {
199 	account->priv = RB_AUDIOSCROBBLER_ACCOUNT_GET_PRIVATE (account);
200 
201 	account->priv->username = NULL;
202 	account->priv->auth_token = NULL;
203 	account->priv->session_key = NULL;
204 	account->priv->login_status = RB_AUDIOSCROBBLER_ACCOUNT_LOGIN_STATUS_LOGGED_OUT;
205 
206 	account->priv->session_key_timeout_id = 0;
207 }
208 
209 static void
210 rb_audioscrobbler_account_constructed (GObject *object)
211 {
212 	RBAudioscrobblerAccount *account;
213 
214 	RB_CHAIN_GOBJECT_METHOD (rb_audioscrobbler_account_parent_class, constructed, object);
215 	account = RB_AUDIOSCROBBLER_ACCOUNT (object);
216 
217 	load_session_settings (account);
218 }
219 
220 static void
221 rb_audioscrobbler_account_dispose (GObject *object)
222 {
223 	RBAudioscrobblerAccount *account = RB_AUDIOSCROBBLER_ACCOUNT (object);
224 
225 	if (account->priv->service != NULL) {
226 		g_object_unref (account->priv->service);
227 		account->priv->service = NULL;
228 	}
229 
230 	if (account->priv->session_key_timeout_id != 0) {
231 		g_source_remove (account->priv->session_key_timeout_id);
232 		account->priv->session_key_timeout_id = 0;
233 	}
234 
235 	if (account->priv->soup_session != NULL) {
236 		soup_session_abort (account->priv->soup_session);
237 		g_object_unref (account->priv->soup_session);
238 		account->priv->soup_session = NULL;
239 	}
240 
241 	G_OBJECT_CLASS (rb_audioscrobbler_account_parent_class)->dispose (object);
242 }
243 
244 static void
245 rb_audioscrobbler_account_finalize (GObject *object)
246 {
247 	RBAudioscrobblerAccount *account = RB_AUDIOSCROBBLER_ACCOUNT (object);
248 
249 	g_free (account->priv->username);
250 	g_free (account->priv->auth_token);
251 	g_free (account->priv->session_key);
252 
253 	G_OBJECT_CLASS (rb_audioscrobbler_account_parent_class)->finalize (object);
254 }
255 
256 static void
257 rb_audioscrobbler_account_get_property (GObject *object,
258                                         guint prop_id,
259                                         GValue *value,
260                                         GParamSpec *pspec)
261 {
262 	RBAudioscrobblerAccount *account = RB_AUDIOSCROBBLER_ACCOUNT (object);
263 
264 	switch (prop_id) {
265 	case PROP_USERNAME:
266 		g_value_set_string (value, rb_audioscrobbler_account_get_username (account));
267 		break;
268 	case PROP_SESSION_KEY:
269 		g_value_set_string (value, rb_audioscrobbler_account_get_session_key (account));
270 		break;
271 	case PROP_LOGIN_STATUS:
272 		g_value_set_enum (value, rb_audioscrobbler_account_get_login_status (account));
273 		break;
274 	default:
275 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
276 		break;
277 	}
278 }
279 
280 static void
281 rb_audioscrobbler_account_set_property (GObject *object,
282                                         guint prop_id,
283                                         const GValue *value,
284                                         GParamSpec *pspec)
285 {
286 	RBAudioscrobblerAccount *account = RB_AUDIOSCROBBLER_ACCOUNT (object);
287 	switch (prop_id) {
288 	case PROP_SERVICE:
289 		account->priv->service = g_value_dup_object (value);
290 		break;
291 	default:
292 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
293 		break;
294 	}
295 }
296 
297 const char *
298 rb_audioscrobbler_account_get_username (RBAudioscrobblerAccount *account)
299 {
300 	return account->priv->username;
301 }
302 
303 const char *
304 rb_audioscrobbler_account_get_session_key (RBAudioscrobblerAccount *account)
305 {
306 	return account->priv->session_key;
307 }
308 
309 RBAudioscrobblerAccountLoginStatus
310 rb_audioscrobbler_account_get_login_status (RBAudioscrobblerAccount *account)
311 {
312 	return account->priv->login_status;
313 }
314 
315 static void
316 load_session_settings (RBAudioscrobblerAccount *account)
317 {
318 	/* Attempt to load the saved session */
319 	const char *rb_data_dir;
320 	char *file_path;
321 	GKeyFile *key_file;
322 	char *service_name;
323 
324 	rb_data_dir = rb_user_data_dir ();
325 	if (rb_data_dir == NULL) {
326 		rb_debug ("error loading session: could not find data dir");
327 		return;
328 	}
329 
330 	file_path = g_build_filename (rb_data_dir, "audioscrobbler", SESSION_SETTINGS_FILE, NULL);
331 	key_file = g_key_file_new ();
332 	g_key_file_load_from_file (key_file, file_path, G_KEY_FILE_NONE, NULL);
333 
334 	/* get the service name */
335 	g_object_get (account->priv->service, "name", &service_name, NULL);
336 
337 	account->priv->username = g_key_file_get_string (key_file, service_name, "username", NULL);
338 	account->priv->session_key = g_key_file_get_string (key_file, service_name, "session_key", NULL);
339 
340 	g_free (file_path);
341 	g_key_file_free (key_file);
342 	g_free (service_name);
343 
344 	if (account->priv->username != NULL && account->priv->session_key != NULL) {
345 		rb_debug ("loaded session: username=\"%s\", session key=\"%s\"",
346 			          account->priv->username,
347 			          account->priv->session_key);
348 
349 		account->priv->login_status = RB_AUDIOSCROBBLER_ACCOUNT_LOGIN_STATUS_LOGGED_IN;
350 		g_signal_emit (account, rb_audioscrobbler_account_signals[LOGIN_STATUS_CHANGED],
351 		               0, account->priv->login_status);
352 	} else {
353 		rb_debug ("there is no session to load");
354 
355 		/* free both incase only one of them did not load */
356 		g_free (account->priv->username);
357 		account->priv->username = NULL;
358 		g_free (account->priv->session_key);
359 		account->priv->session_key = NULL;
360 
361 		account->priv->login_status = RB_AUDIOSCROBBLER_ACCOUNT_LOGIN_STATUS_LOGGED_OUT;
362 		g_signal_emit (account, rb_audioscrobbler_account_signals[LOGIN_STATUS_CHANGED],
363 		               0, account->priv->login_status);
364 	}
365 }
366 
367 static void
368 save_session_settings (RBAudioscrobblerAccount *account)
369 {
370 	/* Save the current session */
371 	const char *rb_data_dir;
372 	char *file_path;
373 	GKeyFile *key_file;
374 	char *service_name;
375 	char *data;
376 	gsize data_length;
377 	GFile *out_file;
378 	GError *error;
379 
380 	rb_data_dir = rb_user_data_dir ();
381 	if (rb_data_dir == NULL) {
382 		rb_debug ("error saving session: could not find data dir");
383 		return;
384 	}
385 
386 	file_path = g_build_filename (rb_data_dir, "audioscrobbler", SESSION_SETTINGS_FILE, NULL);
387 	key_file = g_key_file_new ();
388 	/* load existing file contents. errors wont matter, just means file doesn't exist yet */
389 	g_key_file_load_from_file (key_file, file_path, G_KEY_FILE_KEEP_COMMENTS, NULL);
390 
391 	/* get the service name */
392 	g_object_get (account->priv->service, "name", &service_name, NULL);
393 
394 	/* set the new data */
395 	if (account->priv->username != NULL && account->priv->session_key != NULL) {
396 		g_key_file_set_string (key_file, service_name, "username", account->priv->username);
397 		g_key_file_set_string (key_file, service_name, "session_key", account->priv->session_key);
398 	} else {
399 		g_key_file_remove_group (key_file, service_name, NULL);
400 	}
401 	g_free (service_name);
402 
403 	data = g_key_file_to_data (key_file, &data_length, NULL);
404 	g_key_file_free (key_file);
405 
406 	/* write data to the file */
407 	out_file = g_file_new_for_path (file_path);
408 	g_free (file_path);
409 
410 	error = NULL;
411 	g_file_replace_contents (out_file, data, data_length, NULL, FALSE, G_FILE_CREATE_NONE, NULL, NULL, &error);
412 	if (error != NULL) {
413 		rb_debug ("error saving session: %s", error->message);
414 		g_error_free (error);
415 	} else {
416 		rb_debug ("successfully saved session");
417 	}
418 
419 	g_free (data);
420 	g_object_unref (out_file);
421 }
422 
423 void
424 rb_audioscrobbler_account_authenticate (RBAudioscrobblerAccount *account)
425 {
426 	/* begin the web services authentication process */
427 	if (account->priv->login_status != RB_AUDIOSCROBBLER_ACCOUNT_LOGIN_STATUS_LOGGED_OUT) {
428 		rb_debug ("logging out before starting auth process");
429 		rb_audioscrobbler_account_logout (account);
430 	}
431 
432 	/* request an authentication token */
433 	request_token (account);
434 }
435 
436 void
437 rb_audioscrobbler_account_logout (RBAudioscrobblerAccount *account)
438 {
439 	cancel_session (account);
440 	save_session_settings (account);
441 
442 	account->priv->login_status = RB_AUDIOSCROBBLER_ACCOUNT_LOGIN_STATUS_LOGGED_OUT;
443 	g_signal_emit (account, rb_audioscrobbler_account_signals[LOGIN_STATUS_CHANGED],
444 	               0, account->priv->login_status);
445 }
446 
447 void
448 rb_audioscrobbler_account_notify_of_auth_error (RBAudioscrobblerAccount *account)
449 {
450 	/* After a session has been granted, no authentication methods will be called
451 	 * therefore we must rely on other classes which call other methods (submissions,
452 	 * radio, etc) to notify us when there is an authentication error
453 	 */
454 
455 	cancel_session (account);
456 	save_session_settings (account);
457 
458 	account->priv->login_status = RB_AUDIOSCROBBLER_ACCOUNT_LOGIN_STATUS_AUTH_ERROR;
459 	g_signal_emit (account, rb_audioscrobbler_account_signals[LOGIN_STATUS_CHANGED],
460 	               0, account->priv->login_status);
461 }
462 
463 static void
464 cancel_session (RBAudioscrobblerAccount *account)
465 {
466 	/* cancels the current session, freeing the username,
467 	 * session key, auth token. removing timeout callbacks etc.
468 	 * Basically log out without setting state to logged out:
469 	 * eg error states will also want to cancel the session
470 	 */
471 	g_free (account->priv->username);
472 	account->priv->username = NULL;
473 
474 	g_free (account->priv->auth_token);
475 	account->priv->auth_token = NULL;
476 
477 	g_free (account->priv->session_key);
478 	account->priv->session_key = NULL;
479 
480 	if (account->priv->session_key_timeout_id != 0) {
481 		g_source_remove (account->priv->session_key_timeout_id);
482 		account->priv->session_key_timeout_id = 0;
483 	}
484 }
485 
486 static void
487 request_token (RBAudioscrobblerAccount *account)
488 {
489 	/* requests an authentication token
490 	 * first stage of the authentication process
491 	 */
492 	char *sig_arg;
493 	char *sig;
494 	char *url;
495 	SoupMessage *msg;
496 
497 	/* create the soup session, if we haven't got one yet */
498 	if (account->priv->soup_session == NULL) {
499 		account->priv->soup_session =
500 			soup_session_async_new_with_options (SOUP_SESSION_ADD_FEATURE_BY_TYPE,
501 		                                             SOUP_TYPE_GNOME_FEATURES_2_26,
502 		                                             NULL);
503 	}
504 
505 	/* create the request */
506 	sig_arg = g_strdup_printf ("api_key%smethodauth.getToken%s",
507 	                           rb_audioscrobbler_service_get_api_key (account->priv->service),
508 	                           rb_audioscrobbler_service_get_api_secret (account->priv->service));
509 	sig = g_compute_checksum_for_string (G_CHECKSUM_MD5, sig_arg, -1);
510 	url = g_strdup_printf ("%s?method=auth.getToken&api_key=%s&api_sig=%s&format=json",
511 			       rb_audioscrobbler_service_get_api_url (account->priv->service),
512 	                       rb_audioscrobbler_service_get_api_key (account->priv->service),
513 	                       sig);
514 
515 	msg = soup_message_new ("GET", url);
516 
517 	/* send the request */
518 	rb_debug ("requesting authorisation token");
519 	soup_session_queue_message (account->priv->soup_session,
520 			            msg,
521 			            got_token_cb,
522 			            account);
523 
524 	/* update status */
525 	account->priv->login_status = RB_AUDIOSCROBBLER_ACCOUNT_LOGIN_STATUS_LOGGING_IN;
526 	g_signal_emit (account, rb_audioscrobbler_account_signals[LOGIN_STATUS_CHANGED],
527 	               0, account->priv->login_status);
528 
529 	g_free (sig_arg);
530 	g_free (sig);
531 	g_free (url);
532 }
533 
534 static void
535 got_token_cb (SoupSession *session, SoupMessage *msg, gpointer user_data)
536 {
537 	/* parses the authentication token from the response
538 	 */
539 	RBAudioscrobblerAccount *account;
540 	JsonParser *parser;
541 
542 	account = RB_AUDIOSCROBBLER_ACCOUNT (user_data);
543 
544 	parser = json_parser_new ();
545 
546 	if (msg->response_body->data != NULL &&
547 	    json_parser_load_from_data (parser, msg->response_body->data, msg->response_body->length, NULL)) {
548 		JsonObject *root_object;
549 
550 		root_object = json_node_get_object (json_parser_get_root (parser));
551 		if (json_object_has_member (root_object, "token")) {
552 			char *url;
553 
554 			account->priv->auth_token = g_strdup (json_object_get_string_member (root_object, "token"));
555 			rb_debug ("granted auth token \"%s\"", account->priv->auth_token);
556 
557 			/* send the user to the web page using the token */
558 			url = g_strdup_printf ("%s?api_key=%s&token=%s",
559 				               rb_audioscrobbler_service_get_auth_url (account->priv->service),
560 				               rb_audioscrobbler_service_get_api_key (account->priv->service),
561 				               account->priv->auth_token);
562 			rb_debug ("sending user to %s", url);
563 			gtk_show_uri (NULL, url, GDK_CURRENT_TIME, NULL);
564 
565 			/* add timeout which will ask for session key */
566 			account->priv->session_key_timeout_id =
567 				g_timeout_add_seconds (SESSION_KEY_REQUEST_TIMEOUT,
568 					               request_session_key_timeout_cb,
569 					               account);
570 
571 			g_free (url);
572 		} else {
573 			rb_debug ("error retrieving auth token: %s",
574 			          json_object_get_string_member (root_object, "message"));
575 
576 			/* go back to being logged out */
577 			rb_audioscrobbler_account_logout (account);
578 		}
579 	} else {
580 		/* treat as connection error */
581 		rb_debug ("empty or invalid response retrieving auth token. treating as connection error");
582 
583 		cancel_session (account);
584 
585 		account->priv->login_status = RB_AUDIOSCROBBLER_ACCOUNT_LOGIN_STATUS_CONNECTION_ERROR;
586 		g_signal_emit (account, rb_audioscrobbler_account_signals[LOGIN_STATUS_CHANGED],
587 		               0, account->priv->login_status);
588 	}
589 
590 	g_object_unref (parser);
591 }
592 
593 static gboolean
594 request_session_key_timeout_cb (gpointer user_data)
595 {
596 	/* Periodically sends a request for the session key */
597 	RBAudioscrobblerAccount *account;
598 	char *sig_arg;
599 	char *sig;
600 	char *url;
601 	SoupMessage *msg;
602 
603 	g_assert (RB_IS_AUDIOSCROBBLER_ACCOUNT (user_data));
604 	account = RB_AUDIOSCROBBLER_ACCOUNT (user_data);
605 
606 	/* create the request */
607 	sig_arg = g_strdup_printf ("api_key%smethodauth.getSessiontoken%s%s",
608 	                           rb_audioscrobbler_service_get_api_key (account->priv->service),
609 	                           account->priv->auth_token,
610 	                           rb_audioscrobbler_service_get_api_secret (account->priv->service));
611 	sig = g_compute_checksum_for_string (G_CHECKSUM_MD5, sig_arg, -1);
612 	url = g_strdup_printf ("%s?method=auth.getSession&api_key=%s&token=%s&api_sig=%s&format=json",
613 	                       rb_audioscrobbler_service_get_api_url (account->priv->service),
614 	                       rb_audioscrobbler_service_get_api_key (account->priv->service),
615 	                       account->priv->auth_token,
616 	                       sig);
617 
618 	msg = soup_message_new ("GET", url);
619 
620 	/* send the request */
621 	rb_debug ("requesting session key");
622 	soup_session_queue_message (account->priv->soup_session,
623 	                            msg,
624 	                            got_session_key_cb,
625 	                            account);
626 
627 	g_free (sig_arg);
628 	g_free (sig);
629 	g_free (url);
630 
631 	return TRUE;
632 }
633 
634 static void
635 got_session_key_cb (SoupSession *session, SoupMessage *msg, gpointer user_data)
636 {
637 	/* parses the session details from the response.
638 	 * if successful then authentication is complete.
639 	 * if the error is that the token has not been authenticated
640 	 * then keep trying.
641 	 * on other errors stop trying and go to logged out state.
642 	 */
643 	RBAudioscrobblerAccount *account;
644 	JsonParser *parser;
645 
646 	g_assert (RB_IS_AUDIOSCROBBLER_ACCOUNT (user_data));
647 	account = RB_AUDIOSCROBBLER_ACCOUNT (user_data);
648 
649 	parser = json_parser_new ();
650 
651 	if (msg->response_body->data != NULL &&
652 	    json_parser_load_from_data (parser, msg->response_body->data, msg->response_body->length, NULL)) {
653 		JsonObject *root_object;
654 
655 		root_object = json_node_get_object (json_parser_get_root (parser));
656 		if (json_object_has_member (root_object, "session")) {
657 			JsonObject *session_object;
658 
659 			/* cancel the old session (and remove timeout) */
660 			cancel_session (account);
661 
662 			session_object = json_object_get_object_member (root_object, "session");
663 			account->priv->username = g_strdup (json_object_get_string_member (session_object, "name"));
664 			account->priv->session_key = g_strdup (json_object_get_string_member (session_object, "key"));
665 
666 			rb_debug ("granted session key \"%s\" for user \"%s\"",
667 				  account->priv->session_key,
668 				  account->priv->username);
669 
670 			/* save our session for future use */
671 			save_session_settings (account);
672 
673 			/* update status */
674 			account->priv->login_status = RB_AUDIOSCROBBLER_ACCOUNT_LOGIN_STATUS_LOGGED_IN;
675 			g_signal_emit (account, rb_audioscrobbler_account_signals[LOGIN_STATUS_CHANGED],
676 				       0, account->priv->login_status);
677 		} else {
678 			int code;
679 			const char *message;
680 
681 			code = json_object_get_int_member (root_object, "error");
682 			message = json_object_get_string_member (root_object, "message");
683 
684 			switch (code) {
685 			case 14:
686 				rb_debug ("auth token has not been authorised yet. will try again");
687 				break;
688 			default:
689 				/* some other error. most likely 4 (invalid token) or 15 (token has expired)
690 				 * whatever it is, we wont be retrieving a session key from it
691 				 */
692 				rb_debug ("error retrieving session key: %s", message);
693 
694 				/* go back to being logged out */
695 				rb_audioscrobbler_account_logout (account);
696 				break;
697 			}
698 		}
699 
700 	} else {
701 		/* treat as connection error */
702 		rb_debug ("empty or invalid response retrieving session key. treating as connection error");
703 
704 		cancel_session (account);
705 
706 		account->priv->login_status = RB_AUDIOSCROBBLER_ACCOUNT_LOGIN_STATUS_CONNECTION_ERROR;
707 		g_signal_emit (account, rb_audioscrobbler_account_signals[LOGIN_STATUS_CHANGED],
708 		               0, account->priv->login_status);
709 	}
710 
711 	g_object_unref (parser);
712 }
713 
714 /* This should really be standard. */
715 #define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }
716 
717 GType
718 rb_audioscrobbler_account_login_status_get_type (void)
719 {
720 	static GType etype = 0;
721 
722 	if (etype == 0)	{
723 		static const GEnumValue values[] = {
724 			ENUM_ENTRY (RB_AUDIOSCROBBLER_ACCOUNT_LOGIN_STATUS_LOGGED_OUT, "Logged out"),
725 			ENUM_ENTRY (RB_AUDIOSCROBBLER_ACCOUNT_LOGIN_STATUS_LOGGING_IN, "Logging in"),
726 			ENUM_ENTRY (RB_AUDIOSCROBBLER_ACCOUNT_LOGIN_STATUS_LOGGED_IN, "Logged in"),
727 			ENUM_ENTRY (RB_AUDIOSCROBBLER_ACCOUNT_LOGIN_STATUS_AUTH_ERROR, "Authentication Error"),
728 			ENUM_ENTRY (RB_AUDIOSCROBBLER_ACCOUNT_LOGIN_STATUS_CONNECTION_ERROR, "Connection Error"),
729 			{ 0, 0, 0 }
730 		};
731 
732 		etype = g_enum_register_static ("RBAudioscrobblerAccountLoginStatus", values);
733 	}
734 
735 	return etype;
736 }
737 
738 void
739 _rb_audioscrobbler_account_register_type (GTypeModule *module)
740 {
741 	rb_audioscrobbler_account_register_type (module);
742 }