[rhythmbox] audioscrobbler: authenticate using web services API



commit 05d987c8b6d52dca975d199027ea2350147dd64c
Author: Jamie Nicol <jamie thenicols net>
Date:   Thu May 27 13:31:55 2010 +0100

    audioscrobbler: authenticate using web services API
    
    Preferences dialog has button to authorise user. When clicked, request
    an "authorisation token". When the token is recieved send the user
    to a url where they can authorise Rhythmbox, and add a timeout
    function to the main loop which will continually check if we have
    been authorised. This will then store a "session key" which is saved
    and reused for future sessions.

 plugins/audioscrobbler/audioscrobbler-prefs.ui     |    5 +-
 plugins/audioscrobbler/rb-audioscrobbler-account.c |  290 +++++++++++++++++++-
 plugins/audioscrobbler/rb-audioscrobbler-account.h |    2 +
 3 files changed, 289 insertions(+), 8 deletions(-)
---
diff --git a/plugins/audioscrobbler/audioscrobbler-prefs.ui b/plugins/audioscrobbler/audioscrobbler-prefs.ui
index 2ac11d3..6cc0eba 100644
--- a/plugins/audioscrobbler/audioscrobbler-prefs.ui
+++ b/plugins/audioscrobbler/audioscrobbler-prefs.ui
@@ -76,13 +76,12 @@
                   </packing>
                 </child>
                 <child>
-                  <object class="GtkLinkButton" id="auth_link">
+                  <object class="GtkButton" id="auth_button">
                     <property name="label" translatable="yes">Allow Rhythmbox to access your profile</property>
                     <property name="visible">True</property>
                     <property name="can_focus">True</property>
                     <property name="receives_default">True</property>
-                    <property name="has_tooltip">True</property>
-                    <property name="relief">none</property>
+                    <signal name="clicked" handler="rb_audioscrobbler_account_auth_button_clicked_cb"/>
                   </object>
                   <packing>
                     <property name="position">1</property>
diff --git a/plugins/audioscrobbler/rb-audioscrobbler-account.c b/plugins/audioscrobbler/rb-audioscrobbler-account.c
index b2fb29c..c34b20e 100644
--- a/plugins/audioscrobbler/rb-audioscrobbler-account.c
+++ b/plugins/audioscrobbler/rb-audioscrobbler-account.c
@@ -29,29 +29,48 @@
 #include <string.h>
 
 #include <gconf/gconf.h>
-#include "eel-gconf-extensions.h"
 
+#include <libsoup/soup.h>
+#include <libsoup/soup-gnome.h>
+
+#include "eel-gconf-extensions.h"
 #include "rb-audioscrobbler-account.h"
 #include "rb-builder-helpers.h"
 #include "rb-debug.h"
+#include "rb-file-helpers.h"
 #include "rb-util.h"
 
+#define LASTFM_API_KEY "0337ff3c59299b6a31d75164041860b6"
+#define LASTFM_API_SECRET "776c85a04a445efa8f9ed7705473c606"
+#define LASTFM_API_URL "http://ws.audioscrobbler.com/2.0/";
+#define LASTFM_AUTH_URL "http://www.last.fm/api/auth/";
+
+#define LASTFM_SESSION_KEY_FILE "session_key"
+#define SESSION_KEY_REQUEST_TIMEOUT 15
 
 struct _RBAudioscrobblerAccountPrivate
 {
 	RBShell *shell;
 
 	/* Authentication info */
-	gchar* username;
+	gchar *username;
+	gchar *auth_token;
+	gchar *session_key;
 
 	/* Widgets for the prefs pane */
 	GtkWidget *config_widget;
 	GtkWidget *username_entry;
 	GtkWidget *username_label;
-	GtkWidget *auth_link;
+	GtkWidget *auth_button;
 
 	/* Preference notifications */
 	guint notification_username_id;
+
+	/* Timeout notifications */
+	guint session_key_timeout_id;
+
+	/* HTTP requests session */
+	SoupSession *soup_session;
 };
 
 #define RB_AUDIOSCROBBLER_ACCOUNT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_AUDIOSCROBBLER_ACCOUNT, RBAudioscrobblerAccountPrivate))
@@ -68,6 +87,8 @@ static void          rb_audioscrobbler_account_dispose (GObject *object);
 static void          rb_audioscrobbler_account_finalize (GObject *object);
 
 static void          rb_audioscrobbler_account_import_settings (RBAudioscrobblerAccount *account);
+static void          rb_audioscrobbler_account_load_session_key (RBAudioscrobblerAccount *account);
+static void          rb_audioscrobbler_account_save_session_key (RBAudioscrobblerAccount *account);
 static void          rb_audioscrobbler_account_preferences_sync (RBAudioscrobblerAccount *account);
 
 static void          rb_audioscrobbler_account_gconf_changed_cb (GConfClient *client,
@@ -75,6 +96,15 @@ static void          rb_audioscrobbler_account_gconf_changed_cb (GConfClient *cl
                                                                  GConfEntry *entry,
                                                                  RBAudioscrobblerAccount *account);
 
+static void          rb_audioscrobbler_account_got_token_cb (SoupSession *session,
+                                                             SoupMessage *msg,
+                                                             gpointer user_data);
+static void          rb_audioscrobbler_account_got_session_key_cb (SoupSession *session,
+                                                                   SoupMessage *msg,
+                                                                   gpointer user_data);
+
+static gboolean      rb_audioscrobbler_account_request_session_key_timeout_cb (gpointer user_data);
+
 enum
 {
 	PROP_0,
@@ -118,14 +148,20 @@ rb_audioscrobbler_account_init (RBAudioscrobblerAccount *account)
 	account->priv = RB_AUDIOSCROBBLER_ACCOUNT_GET_PRIVATE (account);
 
 	account->priv->username = NULL;
+	account->priv->auth_token = NULL;
+	account->priv->session_key = NULL;
 
 	rb_audioscrobbler_account_import_settings (account);
+	if (account->priv->username != NULL)
+		rb_audioscrobbler_account_load_session_key (account);
 
 	account->priv->notification_username_id =
 		eel_gconf_notification_add (CONF_AUDIOSCROBBLER_USERNAME,
 		                            (GConfClientNotifyFunc) rb_audioscrobbler_account_gconf_changed_cb,
 		                            account);
 
+	account->priv->session_key_timeout_id = 0;
+
 	rb_audioscrobbler_account_preferences_sync (account);
 }
 
@@ -141,6 +177,17 @@ rb_audioscrobbler_account_dispose (GObject *object)
 		account->priv->notification_username_id = 0;
 	}
 
+	if (account->priv->session_key_timeout_id != 0) {
+		g_source_remove (account->priv->session_key_timeout_id);
+		account->priv->session_key_timeout_id = 0;
+	}
+
+	if (account->priv->soup_session != NULL) {
+		soup_session_abort (account->priv->soup_session);
+		g_object_unref (account->priv->soup_session);
+		account->priv->soup_session = NULL;
+	}
+
 	G_OBJECT_CLASS (rb_audioscrobbler_account_parent_class)->dispose (object);
 }
 
@@ -152,6 +199,8 @@ rb_audioscrobbler_account_finalize (GObject *object)
 	account = RB_AUDIOSCROBBLER_ACCOUNT (object);
 
 	g_free (account->priv->username);
+	g_free (account->priv->auth_token);
+	g_free (account->priv->session_key);
 
 	G_OBJECT_CLASS (rb_audioscrobbler_account_parent_class)->finalize (object);
 }
@@ -200,6 +249,21 @@ rb_audioscrobbler_account_set_property (GObject *object,
 	}
 }
 
+static gchar *
+mkmd5 (char *string)
+{
+	GChecksum *checksum;
+	gchar *md5_result;
+
+	checksum = g_checksum_new (G_CHECKSUM_MD5);
+	g_checksum_update (checksum, (guchar *)string, -1);
+
+	md5_result = g_strdup (g_checksum_get_string (checksum));
+	g_checksum_free (checksum);
+
+	return md5_result;
+}
+
 static void
 rb_audioscrobbler_account_import_settings (RBAudioscrobblerAccount *account)
 {
@@ -209,6 +273,69 @@ rb_audioscrobbler_account_import_settings (RBAudioscrobblerAccount *account)
 }
 
 static void
+rb_audioscrobbler_account_load_session_key (RBAudioscrobblerAccount *account)
+{
+	/* Attempt to load the saved session key if one exists */
+	const char *rb_data_dir;
+	char *file_path;
+	GFile *file;
+	GInputStream *stream;
+	GDataInputStream *data_stream;
+
+	rb_data_dir = rb_user_data_dir ();
+	if (rb_data_dir != NULL) {
+		file_path = g_build_filename (rb_data_dir, LASTFM_SESSION_KEY_FILE, NULL);
+		file = g_file_new_for_path (file_path);
+		stream = G_INPUT_STREAM (g_file_read (file, NULL, NULL));
+
+		if (stream != NULL) {
+			rb_debug ("loading session key from %s", file_path);
+			data_stream = g_data_input_stream_new (stream);
+			account->priv->session_key =
+				g_data_input_stream_read_line (data_stream, NULL, NULL, NULL);
+			g_object_unref (data_stream);
+			g_object_unref (stream);
+		}
+		g_object_unref (file);
+		g_free (file_path);
+	}
+}
+
+static void
+rb_audioscrobbler_account_save_session_key (RBAudioscrobblerAccount *account)
+{
+	/* Save the current session key to a file */
+	const char *rb_data_dir;
+	char *file_path;
+	GFile *file;
+	GOutputStream *stream;
+	GDataOutputStream *data_stream;
+
+	g_assert (account->priv->session_key != NULL);
+
+	rb_data_dir = rb_user_data_dir ();
+	if (rb_data_dir == NULL)
+		return;
+
+	file_path = g_build_filename (rb_data_dir, LASTFM_SESSION_KEY_FILE, NULL);
+	file = g_file_new_for_path (file_path);
+	stream = G_OUTPUT_STREAM (g_file_replace (file, NULL, FALSE, G_FILE_CREATE_NONE, NULL, NULL));
+
+	if (stream != NULL) {
+		rb_debug ("saving session key to %s", file_path);
+		data_stream = g_data_output_stream_new (stream);
+		g_data_output_stream_put_string (data_stream,
+		                                 account->priv->session_key,
+		                                 NULL,
+		                                 NULL);
+		g_object_unref (data_stream);
+		g_object_unref (stream);
+	}
+	g_object_unref (file);
+	g_free (file_path);
+}
+
+static void
 rb_audioscrobbler_account_preferences_sync (RBAudioscrobblerAccount *account)
 {
 	char *v;
@@ -241,7 +368,7 @@ rb_audioscrobbler_account_get_config_widget (RBAudioscrobblerAccount *account,
 	account->priv->config_widget = GTK_WIDGET (gtk_builder_get_object (builder, "audioscrobbler_vbox"));
 	account->priv->username_entry = GTK_WIDGET (gtk_builder_get_object (builder, "username_entry"));
 	account->priv->username_label = GTK_WIDGET (gtk_builder_get_object (builder, "username_label"));
-	account->priv->auth_link = GTK_WIDGET (gtk_builder_get_object (builder, "auth_link"));
+	account->priv->auth_button = GTK_WIDGET (gtk_builder_get_object (builder, "auth_button"));
 
 	rb_builder_boldify_label (builder, "audioscrobbler_label");
 
@@ -250,6 +377,7 @@ rb_audioscrobbler_account_get_config_widget (RBAudioscrobblerAccount *account,
 	return account->priv->config_widget;
 }
 
+/* GConf callbacks */
 static void
 rb_audioscrobbler_account_gconf_changed_cb (GConfClient *client,
                                             guint cnxn_id,
@@ -283,6 +411,7 @@ rb_audioscrobbler_account_gconf_changed_cb (GConfClient *client,
 	}
 }
 
+/* UI callbacks */
 void
 rb_audioscrobbler_account_username_entry_focus_out_event_cb (GtkWidget *widget,
                                                              RBAudioscrobblerAccount *account)
@@ -295,5 +424,156 @@ void
 rb_audioscrobbler_account_username_entry_activate_cb (GtkEntry *entry,
                                                       RBAudioscrobblerAccount *account)
 {
-	gtk_widget_grab_focus (account->priv->auth_link);
+	gtk_widget_grab_focus (account->priv->auth_button);
+}
+
+void
+rb_audioscrobbler_account_auth_button_clicked_cb (GtkButton *button,
+                                                  RBAudioscrobblerAccount *account)
+{
+	char *sig_arg;
+	char *sig;
+	char *url;
+	SoupMessage *msg;
+
+	/* create soup session, if we haven't got one yet */
+	if (account->priv->soup_session == NULL) {
+		account->priv->soup_session =
+			soup_session_async_new_with_options (SOUP_SESSION_ADD_FEATURE_BY_TYPE,
+                                                             SOUP_TYPE_GNOME_FEATURES_2_26,
+                                                             NULL);
+	}
+
+	/* request a token */
+	sig_arg = g_strdup_printf ("api_key%smethodauth.getToken%s", LASTFM_API_KEY, LASTFM_API_SECRET);
+	sig = mkmd5 (sig_arg);
+	url = g_strdup_printf ("%s?method=auth.getToken&api_key=%s&api_sig=%s",
+	                       LASTFM_API_URL, LASTFM_API_KEY, sig);
+
+	msg = soup_message_new ("GET", url);
+
+	rb_debug ("requesting authorisation token");
+	soup_session_queue_message (account->priv->soup_session,
+	                            msg,
+	                            rb_audioscrobbler_account_got_token_cb,
+	                            account);
+
+	g_free (sig_arg);
+	g_free (sig);
+	g_free (url);
+}
+
+/* Request callbacks */
+static void
+rb_audioscrobbler_account_got_token_cb (SoupSession *session,
+                                        SoupMessage *msg,
+                                        gpointer user_data)
+{
+	RBAudioscrobblerAccount *account = RB_AUDIOSCROBBLER_ACCOUNT (user_data);
+
+	if (SOUP_STATUS_IS_SUCCESSFUL (msg->status_code) && msg->response_body->length != 0) {
+		char **pre_split;
+		char **post_split;
+		char *url;
+
+		/* parse the response */
+		pre_split = g_strsplit (msg->response_body->data, "<token>", -1);
+		post_split = g_strsplit (pre_split[1], "</token>", -1);
+		account->priv->auth_token = g_strdup (post_split[0]);
+		rb_debug ("granted auth token \"%s\"", account->priv->auth_token);
+
+		/* send the user to the web page using the token */
+		url = g_strdup_printf ("%s?api_key=%s&token=%s",
+		                       LASTFM_AUTH_URL,
+		                       LASTFM_API_KEY,
+		                       account->priv->auth_token);
+		rb_debug ("sending user to %s", url);
+		gtk_show_uri (NULL, url, GDK_CURRENT_TIME, NULL);
+
+		/* add timeout which will ask for session key */
+		account->priv->session_key_timeout_id =
+			g_timeout_add_seconds (SESSION_KEY_REQUEST_TIMEOUT,
+			                       rb_audioscrobbler_account_request_session_key_timeout_cb,
+			                       account);
+
+		g_strfreev (pre_split);
+		g_strfreev (post_split);
+		g_free (url);
+	}
+}
+
+static void
+rb_audioscrobbler_account_got_session_key_cb (SoupSession *session,
+                                              SoupMessage *msg,
+                                              gpointer user_data)
+{
+	RBAudioscrobblerAccount *account;
+
+	account = RB_AUDIOSCROBBLER_ACCOUNT (user_data);
+
+	if (SOUP_STATUS_IS_SUCCESSFUL (msg->status_code) && msg->response_body->length != 0) {
+		char **pre_split;
+		char **post_split;
+
+		/* parse the response */
+		pre_split = g_strsplit (msg->response_body->data, "<key>", -1);
+		post_split = g_strsplit (pre_split[1], "</key>", -1);
+		account->priv->session_key = g_strdup (post_split[0]);
+		rb_debug ("granted session key \"%s\"", account->priv->session_key);
+		rb_audioscrobbler_account_save_session_key (account);
+
+		/* remove timeout callback */
+		g_source_remove (account->priv->session_key_timeout_id);
+		account->priv->session_key_timeout_id = 0;
+
+		/* delete authorisation token */
+		g_free (account->priv->auth_token);
+		account->priv->auth_token = NULL;
+
+		g_strfreev (pre_split);
+		g_strfreev (post_split);
+	} else {
+		rb_debug ("error retrieving session key");
+	}
+}
+
+/* Periodically sends a request for the session key */
+static gboolean
+rb_audioscrobbler_account_request_session_key_timeout_cb (gpointer user_data)
+{
+	RBAudioscrobblerAccount *account;
+	char *sig_arg;
+	char *sig;
+	char *url;
+	SoupMessage *msg;
+
+	g_assert (RB_IS_AUDIOSCROBBLER_ACCOUNT (user_data));
+	account = RB_AUDIOSCROBBLER_ACCOUNT (user_data);
+
+	g_assert (account->priv->auth_token != NULL);
+
+	sig_arg = g_strdup_printf ("api_key%smethodauth.getSessiontoken%s%s",
+	                           LASTFM_API_KEY,
+	                           account->priv->auth_token,
+	                           LASTFM_API_SECRET);
+	sig = mkmd5 (sig_arg);
+	url = g_strdup_printf ("%s?method=auth.getSession&api_key=%s&token=%s&api_sig=%s",
+	                       LASTFM_API_URL,
+	                       LASTFM_API_KEY,
+	                       account->priv->auth_token,
+	                       sig);
+
+	msg = soup_message_new ("GET", url);
+
+	rb_debug ("requesting session key");
+	soup_session_queue_message (account->priv->soup_session,
+	                            msg,
+	                            rb_audioscrobbler_account_got_session_key_cb,
+	                            account);
+
+	g_free (sig_arg);
+	g_free (sig);
+	g_free (url);
+
+	return TRUE;
 }
diff --git a/plugins/audioscrobbler/rb-audioscrobbler-account.h b/plugins/audioscrobbler/rb-audioscrobbler-account.h
index 6526b56..6d51053 100644
--- a/plugins/audioscrobbler/rb-audioscrobbler-account.h
+++ b/plugins/audioscrobbler/rb-audioscrobbler-account.h
@@ -69,6 +69,8 @@ void                            rb_audioscrobbler_account_username_entry_focus_o
                                                                                              RBAudioscrobblerAccount *account);
 void                            rb_audioscrobbler_account_username_entry_activate_cb (GtkEntry *entry,
                                                                                       RBAudioscrobblerAccount *account);
+void                            rb_audioscrobbler_account_auth_button_clicked_cb (GtkButton *button,
+                                                                                  RBAudioscrobblerAccount *account);
 
 G_END_DECLS
 



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]