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 }