Index: backends/rb-player.c
===================================================================
RCS file: /cvs/gnome/rhythmbox/backends/rb-player.c,v
retrieving revision 1.3
diff -u -p -r1.3 rb-player.c
--- backends/rb-player.c	14 Jul 2006 12:22:40 -0000	1.3
+++ backends/rb-player.c	29 Jul 2006 04:12:15 -0000
@@ -31,6 +31,7 @@ enum {
 	BUFFERING,
 	ERROR,
 	TICK,
+	EVENT,
 	LAST_SIGNAL
 };
 
@@ -87,6 +88,15 @@ rb_player_interface_init (RBPlayerIface 
 			      G_TYPE_NONE,
 			      1,
 			      G_TYPE_UINT);
+	signals[EVENT] =
+		g_signal_new ("event",
+			      G_TYPE_FROM_INTERFACE (iface),
+			      G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+			      G_STRUCT_OFFSET (RBPlayerIface, event),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__VOID,
+			      G_TYPE_NONE,
+			      0);
 }
 
 GType
@@ -247,6 +257,12 @@ void
 _rb_player_emit_tick (RBPlayer *player, long elapsed)
 {
 	g_signal_emit (player, signals[TICK], 0, elapsed);
+}
+
+void
+_rb_player_emit_event (RBPlayer *player, const char *what)
+{
+	g_signal_emit (player, signals[EVENT], g_quark_from_string (what));
 }
 
 GQuark
Index: backends/rb-player.h
===================================================================
RCS file: /cvs/gnome/rhythmbox/backends/rb-player.h,v
retrieving revision 1.13
diff -u -p -r1.13 rb-player.h
--- backends/rb-player.h	14 Jul 2006 12:22:40 -0000	1.13
+++ backends/rb-player.h	29 Jul 2006 04:12:15 -0000
@@ -88,6 +88,7 @@ typedef struct
 						 GError *error);
 	void		(*tick)            	(RBPlayer *player,
 						 long elapsed);
+	void		(*event)		(RBPlayer *player);
 } RBPlayerIface;
 
 GType		rb_player_get_type   (void);
@@ -119,6 +120,7 @@ void	_rb_player_emit_info (RBPlayer *pla
 void	_rb_player_emit_buffering (RBPlayer *player, guint progress);
 void	_rb_player_emit_error (RBPlayer *player, GError *error);
 void	_rb_player_emit_tick (RBPlayer *player, long elapsed);
+void	_rb_player_emit_event (RBPlayer *player, const char *what);
 
 G_END_DECLS
 
Index: backends/gstreamer/rb-player-gst.c
===================================================================
RCS file: /cvs/gnome/rhythmbox/backends/gstreamer/rb-player-gst.c,v
retrieving revision 1.65
diff -u -p -r1.65 rb-player-gst.c
--- backends/gstreamer/rb-player-gst.c	14 Jul 2006 12:23:50 -0000	1.65
+++ backends/gstreamer/rb-player-gst.c	29 Jul 2006 04:12:15 -0000
@@ -105,6 +105,7 @@ typedef enum
 	ERROR,
 	TICK,
 	BUFFERING,
+	EVENT,
 } RBPlayerGstSignalType;
 
 typedef struct
@@ -256,6 +257,9 @@ emit_signal_idle (RBPlayerGstSignal *sig
 		_rb_player_emit_buffering (RB_PLAYER (signal->object), g_value_get_uint (signal->info));
 		signal->object->priv->idle_buffering_id = 0;
 		break;
+	case EVENT:
+		_rb_player_emit_event (RB_PLAYER (signal->object), g_value_get_string (signal->info));
+		break;
 	}
 
 	return FALSE;
@@ -488,6 +492,21 @@ rb_player_gst_bus_cb (GstBus * bus, GstM
 		g_value_set_uint (signal->info, (guint)progress);
 		g_idle_add ((GSourceFunc) emit_signal_idle, signal);
 		break;
+	}
+	case GST_MESSAGE_APPLICATION: {
+		RBPlayerGstSignal *signal;
+		const GstStructure *structure;
+
+		structure = gst_message_get_structure (message);
+		signal = g_new0 (RBPlayerGstSignal, 1);
+		signal->type = EVENT;
+
+		g_object_ref (G_OBJECT (mp));
+		signal->object = mp;
+		signal->info = g_new0 (GValue, 1);
+		g_value_init (signal->info, G_TYPE_STRING);
+		g_value_set_string (signal->info, gst_structure_get_name (structure));
+		g_idle_add ((GSourceFunc) emit_signal_idle, signal);
 	}
 	default:
 		break;
Index: plugins/audioscrobbler/Makefile.am
===================================================================
RCS file: /cvs/gnome/rhythmbox/plugins/audioscrobbler/Makefile.am,v
retrieving revision 1.6
diff -u -p -r1.6 Makefile.am
--- plugins/audioscrobbler/Makefile.am	28 May 2006 11:59:30 -0000	1.6
+++ plugins/audioscrobbler/Makefile.am	29 Jul 2006 04:12:17 -0000
@@ -4,7 +4,13 @@ plugin_LTLIBRARIES = libaudioscrobbler.l
 libaudioscrobbler_la_SOURCES = \
 	rb-audioscrobbler-plugin.c			\
 	rb-audioscrobbler.c				\
-	rb-audioscrobbler.h
+	rb-audioscrobbler.h				\
+	rb-lastfm-source.c				\
+	rb-lastfm-source.h
+	
+if USE_GSTREAMER_0_10
+libaudioscrobbler_la_SOURCES += rb-lastfm-gst-src.c
+endif
 	
 libaudioscrobbler_la_LDFLAGS = $(PLUGIN_LIBTOOL_FLAGS)
 libaudioscrobbler_la_LIBADD = 				\
Index: plugins/audioscrobbler/rb-audioscrobbler-plugin.c
===================================================================
RCS file: /cvs/gnome/rhythmbox/plugins/audioscrobbler/rb-audioscrobbler-plugin.c,v
retrieving revision 1.5
diff -u -p -r1.5 rb-audioscrobbler-plugin.c
--- plugins/audioscrobbler/rb-audioscrobbler-plugin.c	21 May 2006 01:01:17 -0000	1.5
+++ plugins/audioscrobbler/rb-audioscrobbler-plugin.c	29 Jul 2006 04:12:17 -0000
@@ -30,6 +30,7 @@
 #include <glib-object.h>
 
 #include "rb-audioscrobbler.h"
+#include "rb-lastfm-source.h"
 #include "rb-plugin.h"
 #include "rb-debug.h"
 #include "rb-shell.h"
@@ -48,6 +49,8 @@ typedef struct
 	RBPlugin parent;
 	RBAudioscrobbler *audioscrobbler;
 	GtkWidget *preferences;
+
+	RBSource *lastfm_source;
 } RBAudioscrobblerPlugin;
 
 typedef struct
@@ -129,6 +132,9 @@ impl_activate (RBPlugin *bplugin,
 								proxy_config);
 	}
 	g_object_unref (G_OBJECT (proxy_config));
+
+	plugin->lastfm_source = rb_lastfm_source_new (shell);
+	rb_shell_append_source (shell, plugin->lastfm_source, NULL);
 }
 
 static void
@@ -137,6 +143,9 @@ impl_deactivate	(RBPlugin *bplugin,
 {
 	RBAudioscrobblerPlugin *plugin = RB_AUDIOSCROBBLER_PLUGIN (bplugin);
 
+	rb_source_delete_thyself (plugin->lastfm_source);
+	plugin->lastfm_source = NULL;
+
 	if (plugin->audioscrobbler) {
 		g_object_unref (plugin->audioscrobbler);
 		plugin->audioscrobbler = NULL;
@@ -161,15 +170,11 @@ impl_create_configure_dialog (RBPlugin *
 
 		widget =  rb_audioscrobbler_get_config_widget (plugin->audioscrobbler);
 
-		plugin->preferences = gtk_dialog_new_with_buttons (_("Last.fm Profile Preferences"),
+		plugin->preferences = gtk_dialog_new_with_buttons (_("Audioscrobbler preferences"),
 								   NULL,
 								   GTK_DIALOG_DESTROY_WITH_PARENT,
 								   GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
 								   NULL);
-		gtk_container_set_border_width (GTK_CONTAINER (plugin->preferences), 5);
-		gtk_box_set_spacing (GTK_BOX (GTK_DIALOG (plugin->preferences)->vbox), 2);
-		gtk_dialog_set_has_separator (GTK_DIALOG (plugin->preferences), FALSE);
-		
 		g_signal_connect (G_OBJECT (plugin->preferences),
 				  "response",
 				  G_CALLBACK (preferences_response_cb),
--- /dev/null	2006-05-23 00:25:23.000000000 +1000
+++ plugins/audioscrobbler/rb-lastfm-gst-src.c	2006-07-28 20:45:22.000000000 +1000
@@ -0,0 +1,307 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ *  Copyright (C) 2006  Jonathan Matthew  <jonathan@kaolin.wh9.net>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
+ *
+ */
+
+#include "config.h"
+
+#define _GNU_SOURCE
+#include <string.h>
+
+#include "rb-debug.h"
+
+#include <libsoup/soup.h>
+#include <gst/gst.h>
+
+#define RB_TYPE_LASTFM_SRC (rb_lastfm_src_get_type())
+#define RB_LASTFM_SRC(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),RB_TYPE_LASTFM_SRC,RBLastFMSrc))
+#define RB_LASTFM_SRC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),RB_TYPE_LASTFM_SRC,RBLastFMSrcClass))
+#define RB_IS_LASTFM_SRC(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),RB_TYPE_LASTFM_SRC))
+#define RB_IS_LASTFM_SRC_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass),RB_TYPE_LASTFM_SRC))
+
+typedef struct _RBLastFMSrc RBLastFMSrc;
+typedef struct _RBLastFMSrcClass RBLastFMSrcClass;
+
+struct _RBLastFMSrc
+{
+	GstBin parent;
+
+	char *lastfm_uri;
+	GstElement *http_src;
+	GstPad *ghostpad;
+};
+
+struct _RBLastFMSrcClass
+{
+	GstBinClass parent_class;
+};
+
+enum
+{
+	PROP_0,
+	PROP_URI,
+};
+
+static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
+	GST_PAD_SRC,
+	GST_PAD_ALWAYS,
+	GST_STATIC_CAPS_ANY);
+
+static GstElementDetails rb_lastfm_src_details =
+GST_ELEMENT_DETAILS ("RB Last.FM Source",
+	"Source/File",
+	"Reads a last.fm radio stream",
+	"Jonathan Matthew <jonathan@kaolin.wh9.net>");
+
+static void rb_lastfm_src_uri_handler_init (gpointer g_iface, gpointer iface_data);
+
+static void
+_do_init (GType lastfm_src_type)
+{
+	static const GInterfaceInfo urihandler_info = {
+		rb_lastfm_src_uri_handler_init,
+		NULL,
+		NULL
+	};
+
+	g_type_add_interface_static (lastfm_src_type, GST_TYPE_URI_HANDLER,
+			&urihandler_info);
+}
+
+GST_BOILERPLATE_FULL (RBLastFMSrc, rb_lastfm_src, GstBin, GST_TYPE_BIN, _do_init);
+
+static void
+rb_lastfm_src_base_init (gpointer g_class)
+{
+	GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
+	gst_element_class_add_pad_template (element_class,
+		gst_static_pad_template_get (&srctemplate));
+	gst_element_class_set_details (element_class, &rb_lastfm_src_details);
+}
+
+static void
+rb_lastfm_src_init (RBLastFMSrc *src, RBLastFMSrcClass *klass)
+{
+	rb_debug ("creating rb last.fm src element");
+}
+
+static gboolean
+rb_lastfm_src_pad_probe_cb (GstPad *pad, GstBuffer *buffer, RBLastFMSrc *src)
+{
+	guint8 *data = GST_BUFFER_DATA (buffer);
+	char *s;
+	static char sync[5] = "SYNC";
+
+	s = memmem (data, GST_BUFFER_SIZE (buffer), sync, 4);
+	if (s != NULL) {
+		GstMessage *msg;
+		GstStructure *s;
+
+		rb_debug ("got song change tag");
+		s = gst_structure_new ("rb-lastfm-new-song", NULL);
+		msg = gst_message_new_application (GST_OBJECT (src), s);
+		gst_element_post_message (GST_ELEMENT (src), msg);
+	}
+
+	return TRUE;
+}
+
+static void
+rb_lastfm_src_set_uri (RBLastFMSrc *src, const char *uri)
+{
+	GstPad *pad;
+
+	rb_debug ("stream uri: %s", uri);
+	g_free (src->lastfm_uri);
+	src->lastfm_uri = g_strdup (uri);
+
+	if (src->http_src) {
+		gst_element_remove_pad (GST_ELEMENT (src), src->ghostpad);
+		gst_object_unref (src->ghostpad);
+		src->ghostpad = NULL;
+
+		gst_bin_remove (GST_BIN (src), src->http_src);
+		gst_object_unref (src->http_src);
+		src->http_src = NULL;
+	}
+
+	/* create actual HTTP source */
+	src->http_src = gst_element_make_from_uri (GST_URI_SRC, src->lastfm_uri, NULL);
+	gst_bin_add (GST_BIN (src), src->http_src);
+	gst_object_ref (src->http_src);
+
+	/* create ghost pad */
+	pad = gst_element_get_pad (src->http_src, "src");
+	src->ghostpad = gst_ghost_pad_new ("src", pad);
+	gst_element_add_pad (GST_ELEMENT (src), src->ghostpad);
+	gst_object_ref (src->ghostpad);
+	gst_object_unref (pad);
+
+	/* attach probe */
+	gst_pad_add_buffer_probe (pad, G_CALLBACK (rb_lastfm_src_pad_probe_cb), src);
+}
+
+static void
+rb_lastfm_src_set_property (GObject *object,
+			    guint prop_id,
+			    const GValue *value,
+			    GParamSpec *pspec)
+{
+	RBLastFMSrc *src = RB_LASTFM_SRC (object);
+
+	switch (prop_id) {
+	case PROP_URI:
+		rb_lastfm_src_set_uri (src, g_value_get_string (value));
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+rb_lastfm_src_get_property (GObject *object,
+			    guint prop_id,
+			    GValue *value,
+			    GParamSpec *pspec)
+{
+	RBLastFMSrc *src = RB_LASTFM_SRC (object);
+
+	switch (prop_id) {
+	case PROP_URI:
+		g_value_set_string (value, src->lastfm_uri);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+rb_lastfm_src_finalize (GObject *object)
+{
+	RBLastFMSrc *src;
+	src = RB_LASTFM_SRC (object);
+
+	g_free (src->lastfm_uri);
+
+	if (src->ghostpad)
+		gst_object_unref (src->ghostpad);
+
+	if (src->http_src)
+		gst_object_unref (src->http_src);
+
+	G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+rb_lastfm_src_class_init (RBLastFMSrcClass *klass)
+{
+	GObjectClass *gobject_class;
+	GstElementClass *element_class;
+	
+	gobject_class = G_OBJECT_CLASS (klass);
+	gobject_class->finalize = rb_lastfm_src_finalize;
+	gobject_class->set_property = rb_lastfm_src_set_property;
+	gobject_class->get_property = rb_lastfm_src_get_property;
+
+	element_class = GST_ELEMENT_CLASS (klass);
+
+	g_object_class_install_property (gobject_class,
+					 PROP_URI,
+					 g_param_spec_string ("uri",
+						 	      "uri",
+							      "last.fm stream uri",
+							      NULL,
+							      G_PARAM_READWRITE));
+}
+
+
+/* URI handler interface */	
+
+static guint
+rb_lastfm_src_uri_get_type (void)
+{
+	return GST_URI_SRC;
+}
+
+static gchar **
+rb_lastfm_src_uri_get_protocols (void)
+{
+	static gchar *protocols[] = {"lastfm", NULL};
+	return protocols;
+}
+
+static const gchar *
+rb_lastfm_src_uri_get_uri (GstURIHandler *handler)
+{
+	RBLastFMSrc *src = RB_LASTFM_SRC (handler);
+
+	return src->lastfm_uri;
+}
+
+static gboolean
+rb_lastfm_src_uri_set_uri (GstURIHandler *handler,
+			   const gchar *uri)
+{
+	RBLastFMSrc *src = RB_LASTFM_SRC (handler);
+	char *http_uri;
+
+	if (GST_STATE (src) == GST_STATE_PLAYING || GST_STATE (src) == GST_STATE_PAUSED) {
+		return FALSE;
+	}
+
+	if (g_str_has_prefix (uri, "lastfm://") == FALSE) {
+		return FALSE;
+	}
+
+	http_uri = g_strdup_printf ("http://%s", uri + strlen ("lastfm://"));
+	g_object_set (src, "uri", http_uri, NULL);
+	g_free (http_uri);
+
+	return TRUE;
+}
+
+static void
+rb_lastfm_src_uri_handler_init (gpointer g_iface,
+				gpointer iface_data)
+{
+	GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface;
+
+	iface->get_type = rb_lastfm_src_uri_get_type;
+	iface->get_protocols = rb_lastfm_src_uri_get_protocols;
+	iface->get_uri = rb_lastfm_src_uri_get_uri;
+	iface->set_uri = rb_lastfm_src_uri_set_uri;
+}
+
+static gboolean
+plugin_init (GstPlugin *plugin)
+{
+	gboolean ret = gst_element_register (plugin, "rblastfmsrc", GST_RANK_PRIMARY, RB_TYPE_LASTFM_SRC);
+	return ret;
+}
+
+GST_PLUGIN_DEFINE_STATIC (GST_VERSION_MAJOR,
+			  GST_VERSION_MINOR,
+			  "rblastfm",
+			  "element to access last.fm streams",
+			  plugin_init,
+			  VERSION,
+			  "GPL",
+			  PACKAGE,
+			  "");
--- /dev/null	2006-08-07 11:25:44.665183760 -0400
+++ plugins/audioscrobbler/rb-lastfm-source.c	2006-08-07 18:59:02.000000000 -0400
@@ -0,0 +1,1222 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ * 
+ *  arch-tag: Implementation of last.fm station source object
+ *
+ *  Copyright (C) 2006 Matt Novenstern <fisxoj@gmail.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
+ *
+ */
+
+/*  The author would like to extend thanks to Ian Holmes, author of Last Exit,
+*   an alternative last.fm player written in C#, the code of which was
+*   extraordinarily useful in the creation of this code
+*/
+
+/* TODO LIST
+*  1. Create interface, menu option to add station (artist or tag)
+*  2. if subscriber, make user radio (low priority)
+*/
+
+
+#include <config.h>
+
+#include <string.h>
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <glade/glade.h>
+
+#include <gconf/gconf-value.h>
+
+#include <libsoup/soup.h>
+#include <libsoup/soup-uri.h>
+
+#include "md5.h"
+
+#include "eel-gconf-extensions.h"
+
+#include "rb-proxy-config.h"
+#include "rb-preferences.h"
+
+#include "rb-audioscrobbler.h"
+#include "rb-lastfm-source.h"
+
+#include "rhythmdb-query-model.h"
+#include "rb-glade-helpers.h"
+#include "rb-stock-icons.h"
+#include "rb-entry-view.h"
+#include "rb-property-view.h"
+#include "rb-util.h"
+#include "rb-file-helpers.h"
+#include "rb-preferences.h"
+#include "rb-dialog.h"
+#include "rb-station-properties-dialog.h"
+#include "rb-new-station-dialog.h"
+#include "rb-debug.h"
+#include "eel-gconf-extensions.h"
+#include "rb-shell-player.h"
+
+#define LASTFM_URL "http://ws.audioscrobbler.com"
+#define PLATFORM_STRING "linux"
+#define RB_LASTFM_VERSION "1.1.1"
+#define EXTRA_URI_ENCODE_CHARS	"&+"
+
+
+static void rb_lastfm_source_class_init (RBLastfmSourceClass *klass);
+static void rb_lastfm_source_init (RBLastfmSource *source);
+static GObject *rb_lastfm_source_constructor (GType type, guint n_construct_properties,
+					      GObjectConstructParam *construct_properties);
+static void rb_lastfm_source_finalize (GObject *object);
+static void rb_lastfm_source_set_property (GObject *object,
+			                  guint prop_id,
+			                  const GValue *value,
+			                  GParamSpec *pspec);
+static void rb_lastfm_source_get_property (GObject *object,
+			                  guint prop_id,
+			                  GValue *value,
+			                  GParamSpec *pspec);
+
+static void rb_lastfm_source_songs_view_sort_order_changed_cb (RBEntryView *view,
+							       RBLastfmSource *source);
+static void rb_lastfm_source_do_query (RBLastfmSource *source);
+
+/* source-specific methods */
+static void rb_lastfm_source_do_handshake (RBLastfmSource *source);
+static char* rb_lastfm_source_get_playback_uri (RhythmDBEntry *entry, gpointer data);
+static void rb_lastfm_perform (RBLastfmSource *lastfm,
+			       const char *url,
+			       char *post_data, /* this takes ownership */
+			       SoupMessageCallbackFn response_handler);
+static void rb_lastfm_message_cb (SoupMessage *req, gpointer user_data);
+static void rb_lastfm_change_station (char *station, RBLastfmSource *source);
+
+static void rb_lastfm_proxy_config_changed_cb (RBProxyConfig *config,
+					       RBLastfmSource *source);
+static void rb_lastfm_source_drag_cb (GtkWidget *widget,
+				      GdkDragContext *dc,
+				      gint x, gint y,
+				      GtkSelectionData *selection_data, 
+				      guint info, guint time,
+				      RBLastfmSource *source);
+
+static void rb_lastfm_source_dispose (GObject *object);
+								
+/* RBSource implementation methods */
+static void impl_delete (RBSource *asource);
+static GList *impl_get_ui_actions (RBSource *source);
+static RBEntryView *impl_get_entry_view (RBSource *asource);
+static void impl_get_status (RBSource *asource, char **text, char **progress_text, float *progress);
+static RBSourceEOFType impl_handle_eos (RBSource *asource);
+static gboolean impl_receive_drag (RBSource *source, GtkSelectionData *data);
+
+static void rb_lastfm_source_new_station (char *uri, char *title, RBLastfmSource *source);
+static void rb_lastfm_source_skip_track (GtkAction *action, RBLastfmSource *source);
+static void rb_lastfm_source_love_track (GtkAction *action, RBLastfmSource *source);
+static void rb_lastfm_source_ban_track (GtkAction *action, RBLastfmSource *source);
+static char *rb_lastfm_source_title_from_uri (char *uri);
+static void rb_lastfm_source_add_station_cb (GtkButton *button, gpointer *data);
+static void rb_lastfm_source_success_cb (RBLastfmSource *source);
+
+static void rb_lastfm_source_new_song_cb (GObject *player_backend, RBLastfmSource *source);
+static void rb_lastfm_song_changed_cb (RBShellPlayer *player, RhythmDBEntry *entry, RBLastfmSource *source);
+
+#ifdef HAVE_GSTREAMER_0_10
+/* can't be bothered creating a whole header file just for this: */
+GType rb_lastfm_src_get_type (void);
+#endif
+
+struct RBLastfmSourcePrivate
+{
+	GtkWidget *vbox;
+	GtkWidget *paned;
+	GtkWidget *vbox2;
+	GtkWidget *hbox;
+	/*GtkWidget *tuner;*/
+	GtkWidget *txtbox;
+	GtkWidget *gobutton;
+	GtkWidget *typecombo;
+	GtkWidget *label;
+	RhythmDB *db;
+
+	GtkActionGroup *action_group;
+	
+	RBEntryView *stations;
+
+	RBShellPlayer *shell_player;
+	RhythmDBEntryType entry_type;
+	char *session;
+	
+	gboolean subscriber;
+	char *base_url;
+	char *base_path;
+	char *stream_url;
+	gboolean framehack;
+	char *update_url;
+	gboolean banned;
+	gboolean connected;
+	
+	char *status_msg;
+	
+	enum {
+		OK = 0,
+		HANDSHAKE,
+		COMMUNICATING,
+		SUCCESS,
+		CHANGING_STATION,
+		FAILED,
+		NO_ARTIST,
+		BANNED
+	} status;
+	
+	SoupSession *soup_session;
+	RBProxyConfig *proxy_config;
+};
+
+G_DEFINE_TYPE (RBLastfmSource, rb_lastfm_source, RB_TYPE_SOURCE);
+#define RB_LASTFM_SOURCE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_LASTFM_SOURCE, RBLastfmSourcePrivate))
+
+enum
+{
+	PROP_0,
+	PROP_ENTRY_TYPE,
+	PROP_PROXY_CONFIG
+};
+
+static GtkActionEntry rb_lastfm_source_actions [] =
+{
+	{ "LastfmSkipSong", GTK_STOCK_MEDIA_NEXT, N_("Next Song"), NULL,
+	  N_("Skip the current track"),
+	  G_CALLBACK (rb_lastfm_source_skip_track) },
+	{ "LastfmLoveSong", GTK_STOCK_ADD, N_("Love Song"), NULL,
+	  N_("Mark this song as loved"),
+	  G_CALLBACK (rb_lastfm_source_love_track) },
+	{ "LastfmBanSong", GTK_STOCK_CANCEL, N_("Ban Song"), NULL,
+	  N_("Ban the current track from being played again"),
+	  G_CALLBACK (rb_lastfm_source_ban_track) }
+};
+
+static const GtkTargetEntry lastfm_drag_types[] = {
+	{  "text/plain", 0, 0 },
+	{  "_NETSCAPE_URL", 0, 1 }
+};
+
+static void
+rb_lastfm_source_class_init (RBLastfmSourceClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
+
+	object_class->finalize = rb_lastfm_source_finalize;
+	object_class->dispose = rb_lastfm_source_dispose;
+	object_class->constructor = rb_lastfm_source_constructor;
+
+	object_class->set_property = rb_lastfm_source_set_property;
+	object_class->get_property = rb_lastfm_source_get_property;
+
+	source_class->impl_can_copy = (RBSourceFeatureFunc) rb_false_function;
+	source_class->impl_can_delete = (RBSourceFeatureFunc) rb_false_function;
+	source_class->impl_can_pause = (RBSourceFeatureFunc) rb_false_function;
+	source_class->impl_delete = impl_delete;
+	source_class->impl_get_entry_view = impl_get_entry_view;
+	source_class->impl_get_status = impl_get_status;
+	source_class->impl_get_ui_actions = impl_get_ui_actions;
+	source_class->impl_handle_eos = impl_handle_eos;
+	source_class->impl_receive_drag = impl_receive_drag;
+
+	g_object_class_install_property (object_class,
+					 PROP_ENTRY_TYPE,
+					 g_param_spec_boxed ("entry-type",
+							     "Entry type",
+							     "Type of the entries which should be displayed by this source",
+							     RHYTHMDB_TYPE_ENTRY_TYPE,
+							     G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+	g_object_class_install_property (object_class,
+					 PROP_PROXY_CONFIG,
+					 g_param_spec_object ("proxy-config",
+							      "RBProxyConfig",
+							      "RBProxyConfig object",
+							      RB_TYPE_PROXY_CONFIG,
+							      G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
+	g_type_class_add_private (klass, sizeof (RBLastfmSourcePrivate));
+
+#ifdef HAVE_GSTREAMER_0_10
+	rb_lastfm_src_get_type ();
+#endif
+}
+
+static void
+rb_lastfm_source_init (RBLastfmSource *source)
+{
+	gint size;
+	GdkPixbuf *pixbuf;
+
+	source->priv = RB_LASTFM_SOURCE_GET_PRIVATE (source);
+
+	source->priv->vbox = gtk_vbox_new (FALSE, 5);
+
+	gtk_container_add (GTK_CONTAINER (source), source->priv->vbox);
+	
+	gtk_icon_size_lookup (GTK_ICON_SIZE_LARGE_TOOLBAR, &size, NULL);
+	pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
+					   "stock_channel",
+					   size,
+					   0, NULL);
+	rb_source_set_pixbuf (RB_SOURCE (source), pixbuf);
+	if (pixbuf != NULL) {
+		g_object_unref (pixbuf);
+	}
+	
+}
+
+static void
+rb_lastfm_source_finalize (GObject *object)
+{
+	RBLastfmSource *source;
+
+	g_return_if_fail (object != NULL);
+	g_return_if_fail (RB_IS_LASTFM_SOURCE (object));
+
+	source = RB_LASTFM_SOURCE (object);
+
+	g_return_if_fail (source->priv != NULL);
+
+	rb_debug ("finalizing lastfm source");
+
+	if (source->priv->db) {
+		g_object_unref (source->priv->db);
+		source->priv->db = NULL;
+	}
+	g_object_unref (G_OBJECT (source->priv->proxy_config));
+
+	G_OBJECT_CLASS (rb_lastfm_source_parent_class)->finalize (object);
+}
+
+static GObject *
+rb_lastfm_source_constructor (GType type, guint n_construct_properties,
+			      GObjectConstructParam *construct_properties)
+{
+	RBLastfmSource *source;
+	RBLastfmSourceClass *klass;
+	RBShell *shell;
+	GObject *player_backend;
+	
+	klass = RB_LASTFM_SOURCE_CLASS (g_type_class_peek (RB_TYPE_LASTFM_SOURCE));
+
+	source = RB_LASTFM_SOURCE (G_OBJECT_CLASS (rb_lastfm_source_parent_class)
+			->constructor (type, n_construct_properties, construct_properties));
+
+	g_object_get (G_OBJECT (source), "shell", &shell, NULL);
+	g_object_get (G_OBJECT (shell), 
+		      "db", &source->priv->db, 
+		      "shell-player", &source->priv->shell_player,
+		      NULL);
+	g_object_unref (G_OBJECT (shell));
+
+	/* Set up station tuner */
+	/*source->priv->tuner = gtk_vbox_new (FALSE, 5); */
+	source->priv->vbox2 = gtk_vbox_new (FALSE, 5);
+	source->priv->hbox = gtk_hbox_new (FALSE, 5);
+	
+	source->priv->label = gtk_label_new ("Enter the artist or global tag to build a radio station out of:");
+	
+	source->priv->gobutton = gtk_button_new_with_label ("Add");
+	g_signal_connect_object (G_OBJECT (source->priv->gobutton),
+				 "clicked",
+				 G_CALLBACK (rb_lastfm_source_add_station_cb),
+				 source, 0);
+	source->priv->typecombo = gtk_combo_box_new_text ();
+	gtk_combo_box_append_text (GTK_COMBO_BOX (source->priv->typecombo), "Artist");
+	gtk_combo_box_append_text (GTK_COMBO_BOX (source->priv->typecombo), "Tag");
+	gtk_combo_box_set_active (GTK_COMBO_BOX (source->priv->typecombo), 0);
+	
+	source->priv->txtbox = gtk_entry_new ();
+	
+	gtk_box_pack_end_defaults (GTK_BOX (source->priv->hbox),
+								 GTK_WIDGET (source->priv->gobutton));
+								 
+	gtk_box_pack_end_defaults (GTK_BOX (source->priv->hbox),
+								 GTK_WIDGET (source->priv->txtbox));
+	
+	gtk_box_pack_start_defaults (GTK_BOX (source->priv->hbox),
+								 GTK_WIDGET (source->priv->typecombo));
+	
+	gtk_box_pack_end_defaults (GTK_BOX (source->priv->vbox2),
+								 GTK_WIDGET (source->priv->hbox));
+	
+	gtk_box_pack_end_defaults (GTK_BOX (source->priv->vbox2),
+								 GTK_WIDGET (source->priv->label));
+	
+	/* set up stations view */
+	source->priv->stations = rb_entry_view_new (source->priv->db, 
+						    G_OBJECT (source->priv->shell_player),
+						    NULL,
+						    FALSE, FALSE);
+	/*rb_entry_view_append_column (source->priv->stations, RB_ENTRY_VIEW_COL_TITLE, TRUE);*/
+	rb_entry_view_append_column (source->priv->stations, RB_ENTRY_VIEW_COL_GENRE, TRUE);
+	rb_entry_view_append_column (source->priv->stations, RB_ENTRY_VIEW_COL_RATING, TRUE);
+	rb_entry_view_append_column (source->priv->stations, RB_ENTRY_VIEW_COL_LAST_PLAYED, TRUE);
+	g_signal_connect_object (G_OBJECT (source->priv->stations),
+				 "sort-order-changed",
+				 G_CALLBACK (rb_lastfm_source_songs_view_sort_order_changed_cb),
+				 source, 0);
+
+	/* Drag and drop URIs */
+	g_signal_connect_object (G_OBJECT (source->priv->stations), 
+				 "drag_data_received",
+				 G_CALLBACK (rb_lastfm_source_drag_cb),
+				 source, 0);
+	g_signal_connect_object (G_OBJECT (source->priv->shell_player), 
+				 "playing-song-changed",
+				 G_CALLBACK (rb_lastfm_song_changed_cb),
+				 source, 0);
+	
+	gtk_drag_dest_set (GTK_WIDGET (source->priv->stations),
+			   GTK_DEST_DEFAULT_ALL,
+			   lastfm_drag_types, 2,
+			   GDK_ACTION_COPY | GDK_ACTION_MOVE);
+			  
+	/* Pack the vbox */
+	/*gtk_paned_pack1 (GTK_PANED (source->priv->paned),
+					 GTK_WIDGET (source->priv->tuner), FALSE, FALSE); */
+			   
+	/*source->priv->paned = gtk_vpaned_new ();
+	gtk_paned_pack2 (GTK_PANED (source->priv->paned),
+			 GTK_WIDGET (source->priv->stations), TRUE, FALSE); */
+	
+	gtk_box_pack_start (GTK_BOX (source->priv->vbox), GTK_WIDGET (source->priv->vbox2), FALSE, FALSE, 5);
+	gtk_box_pack_start_defaults (GTK_BOX (source->priv->vbox), GTK_WIDGET (source->priv->stations));
+	
+	
+	gtk_widget_show_all (GTK_WIDGET (source));
+	
+	/* Source type register moved to rb_lastfm_source_new... */
+	/*if (source->priv->entry_type == RHYTHMDB_ENTRY_TYPE_INVALID ||
+	    source->priv->entry_type == NULL)
+		source->priv->entry_type = LASTFM_STATION_ENTRY_TYPE; */
+	/* um, why? */
+	
+	source->priv->entry_type->get_playback_uri = (RhythmDBEntryStringFunc) rb_lastfm_source_get_playback_uri;
+	source->priv->entry_type->get_playback_uri_data = source;
+	source->priv->entry_type->can_sync_metadata = (RhythmDBEntryCanSyncFunc) rb_true_function;
+	source->priv->entry_type->sync_metadata = (RhythmDBEntrySyncFunc) rb_null_function;
+	
+	source->priv->action_group = _rb_source_register_action_group (RB_SOURCE (source),
+								       "LastfmActions",
+								       rb_lastfm_source_actions,
+								       G_N_ELEMENTS (rb_lastfm_source_actions),
+								       source);
+	
+	rb_lastfm_source_do_query (source);
+
+	g_object_get (source->priv->shell_player, "player", &player_backend, NULL);
+	g_signal_connect_object (player_backend, 
+				 "event::rb-lastfm-new-song", 
+				 G_CALLBACK (rb_lastfm_source_new_song_cb),
+				 source,
+				 0);
+
+	return G_OBJECT (source);
+}
+
+static void
+rb_lastfm_source_set_property (GObject *object,
+			      guint prop_id,
+			      const GValue *value,
+			      GParamSpec *pspec)
+{
+	RBLastfmSource *source = RB_LASTFM_SOURCE (object);
+
+	switch (prop_id) {
+	case PROP_ENTRY_TYPE:
+		source->priv->entry_type = g_value_get_boxed (value);
+		break;
+	case PROP_PROXY_CONFIG:
+		source->priv->proxy_config = g_value_get_object (value);
+		g_object_ref (G_OBJECT (source->priv->proxy_config));
+		g_signal_connect_object (G_OBJECT (source->priv->proxy_config),
+					 "config-changed",
+					 G_CALLBACK (rb_lastfm_proxy_config_changed_cb),
+					 source, 0);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+rb_lastfm_source_get_property (GObject *object,
+			      guint prop_id,
+			      GValue *value,
+			      GParamSpec *pspec)
+{
+	RBLastfmSource *source = RB_LASTFM_SOURCE (object);
+
+	switch (prop_id) {
+	case PROP_ENTRY_TYPE:
+		g_value_set_boxed (value, source->priv->entry_type);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static gchar *
+mkmd5 (char *string)
+{
+	md5_state_t md5state;
+	guchar md5pword[16];
+	gchar md5_response[33];
+
+	int j = 0;
+
+	memset (md5_response, 0, sizeof (md5_response));
+
+	md5_init (&md5state);
+	md5_append (&md5state, (unsigned char*)string, strlen (string));
+	md5_finish (&md5state, md5pword);
+
+	for (j = 0; j < 16; j++) {
+		char a[3];
+		sprintf (a, "%02x", md5pword[j]);
+		md5_response[2*j] = a[0];
+		md5_response[2*j+1] = a[1];
+	}
+
+	return (g_strdup (md5_response));
+}
+
+RBSource *
+rb_lastfm_source_new (RBShell *shell)
+{
+	RBSource *source;
+	RBProxyConfig *proxy_config;
+	RhythmDBEntryType type;
+	char *uri;
+	RhythmDB *db;
+	char *username;
+
+	g_object_get (G_OBJECT (shell), "db", &db, NULL);
+	type = rhythmdb_entry_register_type (db, "lastfm-station");
+	type->save_to_disk = TRUE;
+	
+	g_object_get (G_OBJECT (shell), "proxy-config", &proxy_config, NULL);
+	
+	source = RB_SOURCE (g_object_new (RB_TYPE_LASTFM_SOURCE,
+					  "name", _("Last.fm"),
+					  "shell", shell,
+					  "entry-type", type,
+					  "proxy-config", proxy_config,
+					  NULL));
+	rb_shell_register_entry_type_for_source (shell, source, 
+						 type);
+	
+	/* create default neighbour radio station */
+	username = eel_gconf_get_string (CONF_AUDIOSCROBBLER_USERNAME);
+	if (username != NULL) {
+		RhythmDBEntry *entry;
+
+		uri = g_strdup_printf ("lastfm://user/%s/neighbours", username);
+		entry = rhythmdb_entry_lookup_by_location (db, uri);
+		if (entry == NULL) {
+			rb_lastfm_source_new_station (uri, _("Neighbour Radio"), RB_LASTFM_SOURCE (source));
+		} else {
+			rhythmdb_entry_unref (entry);
+		}
+		g_free (uri);
+		g_free (username);
+	}
+
+	/* TODO watch gconf entries, create/update neighbour station */
+	
+	g_object_unref (db);
+	g_object_unref (proxy_config);
+	return source;
+}
+
+static GList*
+impl_get_ui_actions (RBSource *source)
+{
+	GList *actions = NULL;
+	
+	actions = g_list_prepend (actions, g_strdup ("LastfmLoveSong"));
+	actions = g_list_prepend (actions, g_strdup ("LastfmBanSong"));
+	actions = g_list_prepend (actions, g_strdup ("LastfmSkipSong"));
+
+	return actions;
+}
+
+static RBEntryView *
+impl_get_entry_view (RBSource *asource)
+{
+	RBLastfmSource *source = RB_LASTFM_SOURCE (asource);
+
+	return source->priv->stations;
+}
+
+static RBSourceEOFType
+impl_handle_eos (RBSource *asource)
+{
+	return RB_SOURCE_EOF_RETRY;
+}
+
+static void
+impl_get_status (RBSource *asource, char **text, char **progress_text, float *progress)
+{
+	RBLastfmSource *source = RB_LASTFM_SOURCE (asource);
+	
+	if (source->priv->status == SUCCESS) {
+		g_timeout_add (5000, (GSourceFunc) rb_lastfm_source_success_cb, source);
+	}
+
+	/* *text = g_strdup_printf (ngettext ("%d station", "%d stations", num_entries),
+			       num_entries); */
+	*text = g_strdup(source->priv->status_msg);
+	if (source->priv->status == COMMUNICATING) {
+		*progress = -1.0f;
+	} else {
+		*progress = 0;
+	}
+}
+
+static void
+impl_delete (RBSource *asource)
+{
+	RBLastfmSource *source = RB_LASTFM_SOURCE (asource);
+	GList *l;
+
+	for (l = rb_entry_view_get_selected_entries (source->priv->stations); l != NULL; l = g_list_next (l)) {
+		rhythmdb_entry_delete (source->priv->db, l->data);
+	}
+
+	rhythmdb_commit (source->priv->db);
+}
+
+static void
+rb_lastfm_source_songs_view_sort_order_changed_cb (RBEntryView *view,
+						   RBLastfmSource *source)
+{
+	rb_debug ("sort order changed");
+
+	rb_entry_view_resort_model (view);
+}
+
+static void
+rb_lastfm_source_do_query (RBLastfmSource *source)
+{
+	RhythmDBQueryModel *station_query_model = NULL;
+	GPtrArray *query;
+
+	query = rhythmdb_query_parse (source->priv->db,
+				      RHYTHMDB_QUERY_PROP_EQUALS,
+				      RHYTHMDB_PROP_TYPE,
+				      source->priv->entry_type,
+				      RHYTHMDB_QUERY_END);
+	station_query_model = rhythmdb_query_model_new_empty (source->priv->db);
+	rhythmdb_do_full_query_parsed (source->priv->db, 
+				       RHYTHMDB_QUERY_RESULTS (station_query_model), 
+				       query);
+
+	rhythmdb_query_free (query);
+	query = NULL;
+	
+	rb_entry_view_set_model (source->priv->stations, station_query_model);
+	g_object_set (G_OBJECT (source), "query-model", station_query_model, NULL);
+
+	g_object_unref (G_OBJECT (station_query_model));
+}
+
+static void 
+rb_lastfm_source_do_handshake (RBLastfmSource *source)
+{
+	char *password;
+	char *username;
+	char *md5password;
+	char *handshake_url;
+	
+	username = eel_gconf_get_string (CONF_AUDIOSCROBBLER_USERNAME);
+	if (username == NULL) {
+		rb_debug ("no last.fm username");
+		return;
+	}
+
+	password = eel_gconf_get_string (CONF_AUDIOSCROBBLER_PASSWORD);
+	if (password == NULL) {
+		rb_debug ("no last.fm password");
+		return;
+	}
+
+	md5password = mkmd5 (password);
+	g_free (password);
+
+	source->priv->status_msg = _("Performing handshake with server");
+	handshake_url = g_strdup_printf ("%s/radio/handshake.php?version=1.1.1&platform=linux&"
+					 "username=%s&passwordmd5=%s&debug=0&partner=",
+					 LASTFM_URL,
+					 username,
+					 md5password);
+	rb_debug ("Last.fm sending handshake");
+	g_object_ref (source);
+	rb_lastfm_perform (source, handshake_url, NULL, rb_lastfm_message_cb);
+	g_free (handshake_url);
+	g_free (username);
+	g_free (md5password);
+}
+
+static char *
+rb_lastfm_source_get_playback_uri (RhythmDBEntry *entry, gpointer data)
+{
+	RBLastfmSource *source;
+	source = RB_LASTFM_SOURCE (data);
+
+	if (entry == NULL) {
+		rb_debug ("NULL entry");
+	}
+	if (data == NULL) {
+		rb_debug ("NULL source pointer");
+	}
+		
+	if (!source->priv->connected) {
+		rb_lastfm_source_do_handshake (source);
+		return NULL;
+	}
+	
+
+	char *location;
+	location = (char *) rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
+	location = g_strdup_printf ("lastfm://%s", source->priv->stream_url + strlen("http://"));
+	rb_debug ("playback uri: %s", location);
+	return location;
+}
+
+static void
+rb_lastfm_perform (RBLastfmSource *source,
+		   const char *url,
+		   char *post_data,
+		   SoupMessageCallbackFn response_handler)
+{
+	SoupMessage *msg;
+	msg = soup_message_new ("GET", url);
+	
+	if (msg == NULL)
+		return;
+	
+	soup_message_set_http_version (msg, SOUP_HTTP_1_1); // SOUP_HTTP_1_1 = 1
+	
+	rb_debug ("Last.fm communicating with %s", url);
+	
+	
+	if (post_data != NULL) {
+		rb_debug ("POST data: %s", post_data);
+		soup_message_set_request (msg, 
+					  "application/x-www-form-urlencoded",
+					  SOUP_BUFFER_SYSTEM_OWNED, 
+					  post_data, 
+					  strlen (post_data));
+	}
+
+	/* create soup session, if we haven't got one yet */
+	if (!source->priv->soup_session) {
+		SoupUri *uri;
+		
+		uri = rb_proxy_config_get_libsoup_uri (source->priv->proxy_config);
+		source->priv->soup_session = soup_session_async_new_with_options (
+					"proxy-uri", uri,
+					NULL);
+		if (uri)
+			soup_uri_free (uri);
+	}
+	
+	soup_session_queue_message (source->priv->soup_session,
+				    msg,
+				    (SoupMessageCallbackFn) response_handler,
+				    source);
+	source->priv->status = COMMUNICATING;
+	rb_source_notify_status_changed(RB_SOURCE(source));
+}
+
+static void
+rb_lastfm_message_cb (SoupMessage *req, gpointer user_data)
+{
+	RBLastfmSource *source = RB_LASTFM_SOURCE (user_data);
+	char *body;
+	char **pieces;
+	int i;
+	
+	if ((req->response).body == NULL) {
+		rb_debug ("Lastfm: Server failed to respond");
+		return;
+	}
+	
+	body = g_malloc0 ((req->response).length + 1);
+	memcpy (body, (req->response).body, (req->response).length);
+	
+	rb_debug ("response body: %s", body);
+	
+	if (strstr(body, "ERROR - no such artist") != NULL) {
+		source->priv->status = NO_ARTIST;
+		source->priv->status_msg = _("No such artist.  Check your spelling!");
+	}
+	
+	g_strstrip (body);
+	pieces = g_strsplit (body, "\n", 6);
+	for (i = 0; pieces[i] != NULL; i++) {
+		gchar **values = g_strsplit (pieces[i], "=", 2);
+		if (strcmp (values[0], "session") == 0) {
+			if (strcmp (values[1], "FAILED") == 0) {
+				source->priv->status_msg = _("Handshake failed");
+				source->priv->status = FAILED;
+				rb_debug ("Lastfm failed to connect to the server");
+				return;
+			}
+			source->priv->status_msg = _("Handshake successful");
+			source->priv->status = SUCCESS;
+			source->priv->session = g_strdup (values[1]);
+			rb_debug ("session ID: %s", source->priv->session);
+			source->priv->connected = TRUE;
+		} else if (strcmp (values[0], "stream_url") == 0) {
+			source->priv->stream_url = g_strdup (values[1]);
+			rb_debug ("stream url: %s", source->priv->stream_url);
+		} else if (strcmp (values[0], "subscriber") == 0) {
+			if (strcmp (values[1], "0") == 0) {
+				source->priv->subscriber = FALSE;
+			} else {
+				source->priv->subscriber = TRUE;
+			}
+		} else if (strcmp (values[0], "framehack") ==0 ) {
+			if (strcmp (values[1], "0") == 0) {
+				source->priv->framehack = FALSE;
+			} else {
+				source->priv->framehack = TRUE;
+			}
+		} else if (strcmp (values[0], "base_url") ==0) {
+			source->priv->base_url = g_strdup (values[1]);
+		} else if (strcmp (values[0], "base_path") ==0) {
+			source->priv->base_path = g_strdup (values[1]);
+		} else if (strcmp (values[0], "update_url") ==0) {
+			source->priv->update_url = g_strdup (values[1]);
+		} else if (strcmp (values[0], "banned") ==0) {
+			if (strcmp (values[1], "0") ==0) {
+				source->priv->banned = FALSE;
+			} else {
+				source->priv->status_msg = _("The server marked you as banned, no connection");
+				source->priv->status = BANNED;
+				source->priv->banned = TRUE;
+				source->priv->connected = FALSE;
+			}
+		} else if (strcmp (values[0], "response") == 0) {
+			if (strcmp (values[1], "OK") == 0) {
+				source->priv->status_msg = _("Success!");
+				source->priv->status = SUCCESS;
+				rb_debug ("Successfully communicated");
+				source->priv->connected = TRUE;
+			} else {
+				source->priv->connected = FALSE;
+			}
+		} else if (strcmp (values[0], "stationname") == 0) {
+			gchar **data = g_strsplit (g_strdown(pieces[i - 1]), "=",2);
+			RhythmDBEntry *entry;
+			GValue titlestring = {0,};
+
+			rb_debug ("Received station name from server: %s", values[1]);
+			entry = rhythmdb_entry_lookup_by_location (source->priv->db, data[1]);
+			g_value_init (&titlestring, G_TYPE_STRING);
+			g_value_set_string (&titlestring, values[1]);
+			
+			if (entry == NULL) {
+				entry = rhythmdb_entry_new (source->priv->db, source->priv->entry_type, data[1]);
+				rhythmdb_entry_set (source->priv->db, entry, RHYTHMDB_PROP_GENRE, &titlestring);
+			} else {
+				rhythmdb_entry_set (source->priv->db, entry, RHYTHMDB_PROP_GENRE, &titlestring);
+			}
+			g_value_unset (&titlestring);
+			rhythmdb_commit (source->priv->db);
+			
+		}
+	}
+	rb_source_notify_status_changed (RB_SOURCE (source));
+	g_object_unref (source);
+}
+
+static void 
+rb_lastfm_change_station (char *station, RBLastfmSource *source)
+{
+	if (source->priv->connected != TRUE) {
+		rb_lastfm_source_do_handshake (source);
+		return;
+	}
+	
+	char *url;
+	/*char *safe_station;
+	gchar *base;
+	base = g_strconcat(source->priv->base_url, source->priv->base_path, NULL);*/
+	
+	
+	url = g_strdup_printf("%s/radio/adjust.php?session=%s&url=%s&debug=0",
+		LASTFM_URL,
+		source->priv->session,
+		station);
+	
+	/*g_free (base);*/
+	g_object_ref (source);
+	rb_lastfm_perform (source, url, NULL, rb_lastfm_message_cb);
+	g_free (url);
+	/*g_free (safe_station);*/
+}
+
+static void
+rb_lastfm_proxy_config_changed_cb (RBProxyConfig *config,
+					   RBLastfmSource *source)
+{
+	SoupUri *uri;
+
+	if (source->priv->soup_session) {
+		uri = rb_proxy_config_get_libsoup_uri (config);
+		g_object_set (G_OBJECT (source->priv->soup_session),
+					"proxy-uri", uri,
+					NULL);
+		if (uri)
+			soup_uri_free (uri);
+	}
+}
+
+static void 
+rb_lastfm_source_new_station (char *uri, char *title, RBLastfmSource *source)
+{
+	RhythmDBEntry *entry;
+	GValue v = {0,};
+	
+	rb_debug ("adding lastfm: %s, %s",uri, title);
+	
+	entry = rhythmdb_entry_lookup_by_location (source->priv->db, uri);
+	if (entry) {
+		rb_debug ("uri %s already in db", uri); 
+		return;
+	}
+	
+	entry = rhythmdb_entry_new (source->priv->db, source->priv->entry_type, uri);
+	g_value_init (&v, G_TYPE_STRING);
+	g_value_set_string (&v, title);
+	rhythmdb_entry_set (source->priv->db, entry, RHYTHMDB_PROP_GENRE, &v);
+	g_value_unset (&v);
+
+	g_value_init (&v, G_TYPE_DOUBLE);
+	g_value_set_double (&v, 0.0);
+	rhythmdb_entry_set (source->priv->db, entry, RHYTHMDB_PROP_RATING, &v);
+
+	rhythmdb_commit (source->priv->db);
+}
+
+static void
+rb_lastfm_source_dispose (GObject *object)
+{
+	RBLastfmSource *source;
+
+	source = RB_LASTFM_SOURCE (object);
+
+	if (source->priv->db) {
+		g_object_unref (source->priv->db);
+		source->priv->db = NULL;
+	}
+
+	G_OBJECT_CLASS (rb_lastfm_source_parent_class)->dispose (object);
+}
+
+static void
+rb_lastfm_source_love_track (GtkAction *action, RBLastfmSource *source)
+{
+	char *url;
+	if (source->priv->connected != TRUE) {
+		rb_lastfm_source_do_handshake (source);
+		return;
+	}
+	
+	source->priv->status_msg = g_strdup ("Marking song loved...");
+	
+	url = g_strdup_printf("%s/radio/control.php?session=%s&command=love&debug=0",
+		LASTFM_URL,
+		source->priv->session);
+	g_object_ref (source);
+	rb_lastfm_perform (source, url, NULL, rb_lastfm_message_cb);
+	g_free (url);
+}
+
+static void
+rb_lastfm_source_skip_track (GtkAction *action, RBLastfmSource *source)
+{
+	char *url;
+	if (source->priv->connected != TRUE) {
+		rb_lastfm_source_do_handshake (source);
+		return;
+	}
+	
+	source->priv->status_msg = g_strdup ("Skipping song...");
+
+	url = g_strdup_printf("%s/radio/control.php?session=%s&command=skip&debug=0",
+		LASTFM_URL, 
+		source->priv->session);
+	g_object_ref (source);
+	rb_lastfm_perform (source, url, NULL, rb_lastfm_message_cb);
+	g_free (url);
+}
+
+static void
+rb_lastfm_source_ban_track (GtkAction *action, RBLastfmSource *source)
+{
+	char *url;
+	if (source->priv->connected != TRUE) {
+		rb_lastfm_source_do_handshake (source);
+		return;
+	}
+	
+	source->priv->status_msg = g_strdup ("Banning song...");
+	
+	url = g_strdup_printf("%s/radio/control.php?session=%s&command=ban&debug=0",
+		LASTFM_URL,
+		source->priv->session);
+	g_object_ref (source);
+	rb_lastfm_perform (source, url, NULL, rb_lastfm_message_cb);
+	g_free (url);
+}
+
+
+static void
+rb_lastfm_source_drag_cb (GtkWidget *widget,
+				     GdkDragContext *dc,
+				     gint x, gint y,
+				     GtkSelectionData *selection_data, 
+				     guint info, guint time,
+				     RBLastfmSource *source)
+{
+	impl_receive_drag (RB_SOURCE (source) , selection_data);
+}
+
+static gboolean
+impl_receive_drag (RBSource *asource, GtkSelectionData *selection_data)
+{
+	char *uri;
+	char *title = NULL;
+	RBLastfmSource *source = RB_LASTFM_SOURCE (asource);
+	
+	uri = (char *)selection_data->data;
+	rb_debug ("parsing uri %s", uri);
+	
+	if (strstr(uri, "lastfm://") == NULL)
+		return FALSE;
+	
+	title = rb_lastfm_source_title_from_uri (uri);
+	
+	rb_lastfm_source_new_station (uri, title, source);
+	return TRUE;
+}
+
+
+static char *
+rb_lastfm_source_title_from_uri (char *uri)
+{
+	char *title = NULL;
+	char *unesc_title;
+	gchar **data = g_strsplit (uri, "/", 0);
+	
+	if (strstr (uri, "globaltags") != NULL)
+		title = g_strdup_printf ("Global Tag %s", data[3]);
+	
+	if (title == NULL && strcmp(data[2], "artist") == 0) {
+		/* Check if the station is from an artist page, if not, it is a similar
+		 * artist station, and the server should return a name that change_station
+		 *  will handle for us.
+		 */
+		if (data[4] != NULL) {
+			if (strcmp(data[4], "similarartists") == 0)
+				title = g_strdup_printf ("Artists similar to %s",data[3]);
+			if (strcmp(data[4], "fans") == 0)
+				title = g_strdup_printf ("Artists Liked by fans of %s",data[3]);
+		}
+			
+	}
+	
+	if (title == NULL && strcmp(data[2], "user") == 0) {
+		if (strcmp(data[4], "neighbours") == 0)
+			title = g_strdup_printf ("%s's Neighbour Radio", data[3]);
+		if (strcmp(data[4], "recommended") == 0)
+		title = g_strdup_printf ("%s's Recommended Radio: %s percent", data[3], data[5]);
+		/* subscriber? */
+	}
+
+	if (title == NULL) {
+		title = g_strstrip (uri);
+	}
+	
+	g_strfreev (data);
+	unesc_title = gnome_vfs_unescape_string (title, NULL);
+	g_free (title);
+	return unesc_title;
+}
+
+static void 
+rb_lastfm_source_add_station_cb (GtkButton *button, gpointer *data)
+{
+	RBLastfmSource *source = RB_LASTFM_SOURCE (data);
+	const gchar *add;
+	char *title;
+	char *uri;
+	
+	add = gtk_entry_get_text (GTK_ENTRY (source->priv->txtbox));
+	
+	if (strcmp (gtk_combo_box_get_active_text (GTK_COMBO_BOX (source->priv->typecombo)), "Artist") == 0) {
+		uri = g_strdup_printf ("lastfm://artist/%s/similarartists", add);
+	} else {
+		uri = g_strdup_printf ("lastfm://globaltags/%s", add);
+	}
+	
+	gtk_entry_set_text (GTK_ENTRY (source->priv->txtbox), "");
+	title = rb_lastfm_source_title_from_uri (uri); 
+	rb_lastfm_source_new_station (uri, title, source);
+	
+	g_free(uri);
+	g_free(title);
+}
+
+static void 
+rb_lastfm_source_success_cb (RBLastfmSource *source)
+{
+	if (source->priv->status == SUCCESS) {
+		source->priv->status = OK;
+		source->priv->status_msg = _("Connected");
+	}
+}
+
+static void 
+rb_lastfm_source_metadata_cb (SoupMessage *req, RBLastfmSource *source)
+{
+	char *body;
+	char **pieces;
+	int p;
+	RhythmDBEntry *entry;
+	gboolean commit = FALSE;
+	
+	entry = rb_shell_player_get_playing_entry (source->priv->shell_player);
+	if (entry == NULL || rhythmdb_entry_get_entry_type (entry) != source->priv->entry_type) {
+		rb_debug ("got response to metadata request, but not playing from this source");
+		return;
+	}
+
+	rb_debug ("got response to metadata request");
+	body = g_malloc0 ((req->response).length + 1);
+	memcpy (body, (req->response).body, (req->response).length);
+	
+	g_strstrip (body);
+	pieces = g_strsplit (body, "\n", 0);
+	
+	for (p = 0; pieces[p] != NULL; p++) {
+		gchar **values = g_strsplit (pieces[p], "=", 2);
+		GValue v = {0,};
+
+		if (strcmp (values[0], "station") == 0) {
+		} else if (strcmp (values[0], "station_url") == 0) {
+		} else if (strcmp (values[0], "stationfeed") == 0) {
+		} else if (strcmp (values[0], "stationfeed_url") == 0) {
+		} else if (strcmp (values[0], "artist") == 0) {
+			rb_debug ("artist -> %s", values[1]);
+			g_value_init (&v, G_TYPE_STRING);
+			g_value_set_string (&v, values[1]);
+			rhythmdb_entry_set (source->priv->db, entry, RHYTHMDB_PROP_ARTIST, &v);
+			g_value_unset (&v);
+			commit = TRUE;
+		} else if (strcmp (values[0], "album") == 0) {
+			rb_debug ("album -> %s", values[1]);
+			g_value_init (&v, G_TYPE_STRING);
+			g_value_set_string (&v, values[1]);
+			rhythmdb_entry_set (source->priv->db, entry, RHYTHMDB_PROP_ALBUM, &v);
+			g_value_unset (&v);
+			commit = TRUE;
+		} else if (strcmp (values[0], "track") == 0) {
+			rb_debug ("track -> %s", values[1]);
+			g_value_init (&v, G_TYPE_STRING);
+			g_value_set_string (&v, values[1]);
+			rhythmdb_entry_set (source->priv->db, entry, RHYTHMDB_PROP_TITLE, &v);
+			g_value_unset (&v);
+			commit = TRUE;
+		} else if (strcmp (values[0], "albumcover_small") == 0) {
+		} else if (strcmp (values[0], "albumcover_medium") == 0) {
+		} else if (strcmp (values[0], "albumcover_large") == 0) {
+		} else if (strcmp (values[0], "trackprogress") == 0) {
+		} else if (strcmp (values[0], "trackduration") == 0) {
+		} else if (strcmp (values[0], "artist_url") == 0) {
+		} else if (strcmp (values[0], "album_url") == 0) {
+		} else if (strcmp (values[0], "track_url") == 0) {
+		} else if (strcmp (values[0], "discovery") == 0) {
+		} else {
+			rb_debug ("got unknown value: %s", values[0]);
+		}
+
+		g_strfreev (values);
+	}
+
+	g_strfreev (pieces);
+	g_free (body);
+
+	if (commit)
+		rhythmdb_commit (source->priv->db);
+
+	source->priv->status = SUCCESS;
+	rb_source_notify_status_changed (RB_SOURCE (source));
+}
+
+static void
+rb_lastfm_source_new_song_cb (GObject *player_backend,
+			      RBLastfmSource *source)
+{
+	char *uri;
+	rb_debug ("got new song");
+
+	uri = g_strdup_printf ("http://%s%s/np.php?session=%s&debug=0",
+			       source->priv->base_url,
+			       source->priv->base_path,
+			       source->priv->session);
+	rb_lastfm_perform (source, uri, NULL, (SoupMessageCallbackFn) rb_lastfm_source_metadata_cb);
+	g_free (uri);
+}
+
+static void
+rb_lastfm_song_changed_cb (RBShellPlayer *player,
+				   RhythmDBEntry *entry,
+				   RBLastfmSource *source)
+{
+	RhythmDBEntryType type;
+	RhythmDBEntry *next;
+	GValue v = {0,};
+	char *location;	
+	
+	type = rhythmdb_entry_get_entry_type (entry);
+	
+	next = rb_shell_player_get_playing_entry (source->priv->shell_player);
+	
+	if (rhythmdb_entry_get_entry_type (entry) == source->priv->entry_type) {
+		g_value_init (&v, G_TYPE_STRING);
+		g_value_set_string (&v, "");
+		rhythmdb_entry_set (source->priv->db, entry, RHYTHMDB_PROP_ARTIST, &v);
+		rhythmdb_entry_set (source->priv->db, entry, RHYTHMDB_PROP_TITLE, &v);
+		rhythmdb_entry_set (source->priv->db, entry, RHYTHMDB_PROP_ALBUM, &v);
+		g_value_unset (&v);
+		rhythmdb_commit (source->priv->db);
+	}
+	
+	location = (char *) rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
+	rb_lastfm_change_station (location, source);
+	return;
+}
--- /dev/null	2006-05-23 00:25:23.000000000 +1000
+++ plugins/audioscrobbler/rb-lastfm-source.h	2006-07-28 20:45:22.000000000 +1000
@@ -0,0 +1,61 @@
+/* 
+ *  arch-tag: Header for last.fm station source
+ *
+ *  Copyright (C) 2006 Matt Novenstern  <fisxoj@gmail.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
+ *
+ */
+
+#ifndef __RB_LASTFM_SOURCE_H
+#define __RB_LASTFM_SOURCE_H
+
+#include "rb-shell.h"
+#include "rb-source.h"
+
+G_BEGIN_DECLS
+
+#define RB_TYPE_LASTFM_SOURCE         (rb_lastfm_source_get_type ())
+#define RB_LASTFM_SOURCE(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), RB_TYPE_LASTFM_SOURCE, RBLastfmSource))
+#define RB_LASTFM_SOURCE_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), RB_TYPE_LASTFM_SOURCE, RBLastfmSourceClass))
+#define RB_IS_LASTFM_SOURCE(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), RB_TYPE_LASTFM_SOURCE))
+#define RB_IS_LASTFM_SOURCE_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), RB_TYPE_LASTFM_SOURCE))
+#define RB_LASTFM_SOURCE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), RB_TYPE_LASTFM_SOURCE, RBLastfmSourceClass))
+
+typedef struct RBLastfmSourcePrivate RBLastfmSourcePrivate;
+
+typedef struct
+{
+	RBSource parent;
+
+	RBLastfmSourcePrivate *priv;
+} RBLastfmSource;
+
+typedef struct
+{
+	RBSourceClass parent;
+} RBLastfmSourceClass;
+
+GType		rb_lastfm_source_get_type	(void);
+GType           rb_lastfm_source_register_type    (GTypeModule *module);
+
+RBSource *	rb_lastfm_source_new		(RBShell *shell);
+
+void	lastfm_set_stream_URL 	(gchar URL);
+
+
+G_END_DECLS
+
+#endif /* __RB_LASTFM_SOURCE_H */
