Index: rhythmdb/rhythmdb.h
===================================================================
--- rhythmdb/rhythmdb.h	(revision 4975)
+++ rhythmdb/rhythmdb.h	(working copy)
@@ -49,7 +49,7 @@ typedef struct RhythmDBEntry_ RhythmDBEn
 GType rhythmdb_entry_get_type (void);
 
 #define RHYTHMDB_TYPE_ENTRY      (rhythmdb_entry_get_type ())
-#define RHYTHMDB_ENTRY(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), RHYTHMDB_TYPE_ENTRY, RhythmDBEntry))
+#define RHYTHMDB_ENTRY(o)        ((RhythmDBEntry*)(o))
 
 typedef void (*RhythmDBEntryActionFunc) (RhythmDBEntry *entry, gpointer data);
 typedef char* (*RhythmDBEntryStringFunc) (RhythmDBEntry *entry, gpointer data);
Index: sources/rb-streaming-source.c
===================================================================
--- sources/rb-streaming-source.c	(revision 4975)
+++ sources/rb-streaming-source.c	(working copy)
@@ -473,3 +473,8 @@ playing_entry_changed_cb (RBShellPlayer 
 	g_object_unref (backend);
 }
 
+RhythmDB*
+_rb_streaming_source_get_db (RBStreamingSource *source)
+{
+	return source->priv->db;
+}
Index: sources/rb-streaming-source.h
===================================================================
--- sources/rb-streaming-source.h	(revision 4975)
+++ sources/rb-streaming-source.h	(working copy)
@@ -58,6 +58,8 @@ void		rb_streaming_source_set_streaming_
 void		rb_streaming_source_set_streaming_album (RBStreamingSource *source,
 							 const char *album);
 
+RhythmDB* _rb_streaming_source_get_db (RBStreamingSource *source);
+
 G_END_DECLS
 
 #endif /* __RB_STREAMING_SOURCE_H */
Index: plugins/iradio/rb-iradio-icecast-source.c
===================================================================
--- plugins/iradio/rb-iradio-icecast-source.c	(revision 0)
+++ plugins/iradio/rb-iradio-icecast-source.c	(revision 0)
@@ -0,0 +1,676 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ *  Copyright (C) 2007 James Livingston  <doclivingston@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.
+ *
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <glade/glade.h>
+#include <libgnomevfs/gnome-vfs-uri.h>
+#include <libgnomevfs/gnome-vfs-async-ops.h>
+#include <libxml/tree.h>
+
+#include "rb-iradio-icecast-source.h"
+#include "rb-util.h"
+#include "rb-debug.h"
+
+
+#define IRADIO_ICECAST_SOURCE_ICON "library-internet-radio"
+#define IRADIO_ICECAST_REFRESH_ICON GTK_STOCK_REFRESH
+#define IRADIO_ICECAST_FAVOURITE_ICON GTK_STOCK_ADD
+#define READ_CHUNK_SIZE 8192
+
+struct IcecastParserContext
+{
+	GnomeVFSAsyncHandle *handle;
+	guint in_unknown_elt;
+	xmlParserCtxtPtr xmlctx;
+	GString *buf;
+
+	RBIRadioIcecastSource *source;
+	RhythmDBEntryType entry_type;
+	RhythmDB *db;	
+	GHashTable *table;
+	RhythmDBEntry *entry;
+    
+	enum {
+		ICECAST_PARSER_STATE_START,
+		ICECAST_PARSER_STATE_DIRECTORY,
+		ICECAST_PARSER_STATE_ENTRY,
+		ICECAST_PARSER_STATE_SERVER_NAME,
+		ICECAST_PARSER_STATE_LISTEN_URL,
+		ICECAST_PARSER_STATE_SERVER_TYPE,
+		ICECAST_PARSER_STATE_BITRATE,
+		ICECAST_PARSER_STATE_CHANNELS,
+		ICECAST_PARSER_STATE_SAMPLE_RATE,
+		ICECAST_PARSER_STATE_GENRE,
+		ICECAST_PARSER_STATE_CURRENT_SONG,
+		ICECAST_PARSER_STATE_END,
+	} state;
+
+	char buffer[READ_CHUNK_SIZE];
+};
+
+static void rb_iradio_icecast_source_class_init (RBIRadioIcecastSourceClass *klass);
+static void rb_iradio_icecast_source_init (RBIRadioIcecastSource *source);
+static GObject *rb_iradio_icecast_source_constructor (GType type, guint n_construct_properties,
+						      GObjectConstructParam *construct_properties);
+static void rb_iradio_icecast_source_dispose (GObject *object);
+static void rb_iradio_icecast_source_finalise (GObject *object);
+static void rb_iradio_icecast_source_set_property (GObject *object,
+						   guint prop_id,
+						   const GValue *value,
+						   GParamSpec *pspec);
+static void rb_iradio_icecast_source_get_property (GObject *object,
+						   guint prop_id,
+						   GValue *value,
+						   GParamSpec *pspec);
+
+static void rb_iradio_icecast_source_activate (RBSource *source);
+static GList *rb_iradio_icecast_source_get_ui_actions (RBSource *source);
+static gboolean rb_iradio_icecast_source_show_popup (RBSource *source);
+
+static void rb_iradio_icecast_source_cmd_refresh (GtkAction *action,
+						  RBIRadioIcecastSource *source);
+static void rb_iradio_icecast_source_cmd_add_favourite (GtkAction *action,
+							RBIRadioIcecastSource *source);
+
+static void _icecast_context_free (struct IcecastParserContext *ctx);
+static void rb_iradio_icecast_source_load (RBIRadioIcecastSource *source);
+
+
+#define RB_IRADIO_ICECAST_SOURCE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_IRADIO_ICECAST_SOURCE, RBIRadioIcecastSourcePrivate))
+G_DEFINE_TYPE (RBIRadioIcecastSource, rb_iradio_icecast_source, RB_TYPE_IRADIO_SOURCE)
+
+enum
+{
+	PROP_0,
+	PROP_IRADIO_SOURCE,
+	PROP_ICECAST_URL,
+};
+
+struct RBIRadioIcecastSourcePrivate
+{
+	RBIRadioSource *iradio_source;
+	char *icecast_url;
+
+	struct IcecastParserContext *load_context;
+
+	GtkActionGroup *action_group;
+};
+
+static GtkActionEntry rb_iradio_icecast_source_actions [] =
+{
+	{ "IRadioIcecastRefresh", IRADIO_ICECAST_REFRESH_ICON, N_("Refresh Icecast stations"), NULL,
+	  N_("Refresh list of available stations from Icecast"),
+	  G_CALLBACK (rb_iradio_icecast_source_cmd_refresh) },
+	{ "IRadioIcecastAddFavourite", IRADIO_ICECAST_FAVOURITE_ICON, N_("Add to favourites"), NULL,
+	  N_("Add this station to your favourites list"),
+	  G_CALLBACK (rb_iradio_icecast_source_cmd_add_favourite) },
+};
+
+
+static void
+rb_iradio_icecast_source_class_init (RBIRadioIcecastSourceClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
+
+	object_class->constructor = rb_iradio_icecast_source_constructor;
+	object_class->dispose = rb_iradio_icecast_source_dispose;
+	object_class->finalize = rb_iradio_icecast_source_finalise;
+	object_class->set_property = rb_iradio_icecast_source_set_property;
+	object_class->get_property = rb_iradio_icecast_source_get_property;
+
+	source_class->impl_activate = rb_iradio_icecast_source_activate;
+	source_class->impl_get_ui_actions = rb_iradio_icecast_source_get_ui_actions;
+	source_class->impl_show_popup = rb_iradio_icecast_source_show_popup;
+
+	g_object_class_install_property (object_class,
+					 PROP_IRADIO_SOURCE,
+					 g_param_spec_object ("iradio-source",
+							      "iradio-source",
+							      "the RBIRadioSource parent",
+							      RB_TYPE_IRADIO_SOURCE,
+							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+	g_object_class_install_property (object_class,
+					 PROP_ICECAST_URL,
+					 g_param_spec_string ("icecast-url",
+							      "icecast-url",
+							      "the URL of the icecast xml file",
+							      NULL,
+							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+	g_type_class_add_private (klass, sizeof (RBIRadioIcecastSourcePrivate));
+}
+
+static void
+rb_iradio_icecast_source_init (RBIRadioIcecastSource *source)
+{
+	gint size;
+	GdkPixbuf *pixbuf;
+
+	source->priv = RB_IRADIO_ICECAST_SOURCE_GET_PRIVATE (source);
+
+	gtk_icon_size_lookup (GTK_ICON_SIZE_LARGE_TOOLBAR, &size, NULL);
+	pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
+					   IRADIO_ICECAST_SOURCE_ICON,
+					   size,
+					   0, NULL);
+	rb_source_set_pixbuf (RB_SOURCE (source), pixbuf);
+	if (pixbuf != NULL) {
+		g_object_unref (pixbuf);
+	}
+}
+
+static GObject *
+rb_iradio_icecast_source_constructor (GType type,
+				      guint n_construct_properties,
+				      GObjectConstructParam *construct_properties)
+{
+	RBIRadioIcecastSource *source;
+
+	source = RB_IRADIO_ICECAST_SOURCE (G_OBJECT_CLASS (rb_iradio_icecast_source_parent_class)
+			->constructor (type, n_construct_properties, construct_properties));
+
+	source->priv->action_group = _rb_source_register_action_group (RB_SOURCE (source),
+								       "IRadioIcecastActions",
+								       rb_iradio_icecast_source_actions,
+								       G_N_ELEMENTS (rb_iradio_icecast_source_actions),
+								       source);
+
+	return G_OBJECT (source);
+}
+
+static void
+rb_iradio_icecast_source_dispose (GObject *object)
+{
+	RBIRadioIcecastSource *source;
+
+	source = RB_IRADIO_ICECAST_SOURCE (object);
+
+	if (source->priv->load_context != NULL) {
+		_icecast_context_free (source->priv->load_context);
+	}
+
+	if (source->priv->action_group != NULL) {
+		g_object_unref (source->priv->action_group);
+		source->priv->action_group = NULL;
+	}
+
+	if (source->priv->iradio_source != NULL) {
+		g_object_unref (source->priv->iradio_source);
+		source->priv->iradio_source = NULL;
+	}
+
+	G_OBJECT_CLASS (rb_iradio_icecast_source_parent_class)->dispose (object);
+}
+
+static void
+rb_iradio_icecast_source_finalise (GObject *object)
+{
+	RBIRadioIcecastSource *source;
+
+	source = RB_IRADIO_ICECAST_SOURCE (object);
+
+	g_free (source->priv->icecast_url);
+
+	G_OBJECT_CLASS (rb_iradio_icecast_source_parent_class)->finalize (object);
+}
+
+RBSource *
+rb_iradio_icecast_source_new (RBShell *shell,
+			      RBPlugin *plugin,
+			      RBIRadioSource *iradio_source,
+			      const char *icecast_url)
+{
+	RBSource *source;
+	RhythmDBEntryType entry_type;
+	RhythmDB *db;
+
+	g_object_get (shell, "db", &db, NULL);
+
+	entry_type = rhythmdb_entry_type_get_by_name (db, "iradio-icecast");
+	if (entry_type == RHYTHMDB_ENTRY_TYPE_INVALID) {
+		entry_type = rhythmdb_entry_register_type (db, "iradio-icecast");
+		entry_type->save_to_disk = TRUE;
+		entry_type->category = RHYTHMDB_ENTRY_STREAM;
+		entry_type->can_sync_metadata = (RhythmDBEntryCanSyncFunc) rb_true_function;
+		entry_type->sync_metadata = (RhythmDBEntrySyncFunc) rb_null_function;
+	}
+	g_object_unref (db);
+
+	source = RB_SOURCE (g_object_new (RB_TYPE_IRADIO_ICECAST_SOURCE,
+					  "name", _("Icecast directory"),
+					  "shell", shell,
+					  "entry-type", entry_type,
+					  "plugin", plugin,
+					  "iradio-source", iradio_source,
+					  "icecast-url", icecast_url,
+					  NULL));
+	rb_shell_register_entry_type_for_source (shell, source, entry_type);
+	return source;
+}
+
+static void
+rb_iradio_icecast_source_set_property (GObject *object,
+				       guint prop_id,
+				       const GValue *value,
+				       GParamSpec *pspec)
+{
+	RBIRadioIcecastSource *source = RB_IRADIO_ICECAST_SOURCE (object);
+
+	switch (prop_id) {
+	case PROP_IRADIO_SOURCE:
+		if (source->priv->iradio_source) {
+			g_object_unref (source->priv->iradio_source);
+		}
+		source->priv->iradio_source = RB_IRADIO_SOURCE (g_value_dup_object (value));
+		break;
+	case PROP_ICECAST_URL:
+		g_free (source->priv->icecast_url);
+		source->priv->icecast_url = g_value_dup_string (value);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+
+}
+
+static void
+rb_iradio_icecast_source_get_property (GObject *object,
+				       guint prop_id,
+				       GValue *value,
+				       GParamSpec *pspec)
+{
+	RBIRadioIcecastSource *source = RB_IRADIO_ICECAST_SOURCE (object);
+
+	switch (prop_id) {
+	case PROP_IRADIO_SOURCE:
+		g_value_set_object (value, source->priv->iradio_source);
+		break;
+	case PROP_ICECAST_URL:
+		g_value_set_string (value, source->priv->icecast_url);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+
+
+static void
+_icecast_parser_start_element (struct IcecastParserContext *ctx,
+			       const char *name, const char **attrs)
+{
+	if (ctx->in_unknown_elt) {
+		ctx->in_unknown_elt++;
+		return;
+	}
+
+	rb_debug ("End element: %s state: %d", name, ctx->state);
+
+	switch (ctx->state)
+	{
+	case ICECAST_PARSER_STATE_START:
+		if (strcmp (name, "directory") == 0)
+			ctx->state = ICECAST_PARSER_STATE_DIRECTORY;
+		else
+			rb_debug ("unexpected element '%s' started at top level", name);
+	
+		break;
+
+	case ICECAST_PARSER_STATE_DIRECTORY:
+		if (strcmp (name, "entry") == 0) {
+			ctx->state = ICECAST_PARSER_STATE_ENTRY;
+		} else
+			rb_debug ("unexpected element '%s' started in directory", name);
+		break;
+
+	case ICECAST_PARSER_STATE_ENTRY:
+		if (strcmp (name, "server_name") == 0)
+			ctx->state = ICECAST_PARSER_STATE_SERVER_NAME;
+		else if (strcmp (name, "listen_url") == 0)
+			ctx->state = ICECAST_PARSER_STATE_LISTEN_URL;
+		else if (strcmp (name, "server_type") == 0)
+			ctx->state = ICECAST_PARSER_STATE_SERVER_TYPE;
+		else if (strcmp (name, "bitrate") == 0)
+			ctx->state = ICECAST_PARSER_STATE_BITRATE;
+		else if (strcmp (name, "channels") == 0)
+			ctx->state = ICECAST_PARSER_STATE_CHANNELS;
+		else if (strcmp (name, "samplerate") == 0)
+			ctx->state = ICECAST_PARSER_STATE_SAMPLE_RATE;
+		else if (strcmp (name, "genre") == 0)
+			ctx->state = ICECAST_PARSER_STATE_GENRE;
+		else if (strcmp (name, "current_song") == 0)
+			ctx->state = ICECAST_PARSER_STATE_CURRENT_SONG;
+		else
+			rb_debug ("unknown data element '%s' in entry", name);
+			
+		break;
+
+	case ICECAST_PARSER_STATE_END:
+		rb_debug ("unexpected element '%s' started after directory", name);
+		ctx->in_unknown_elt++;
+		break;
+		
+	case ICECAST_PARSER_STATE_SERVER_NAME:
+	case ICECAST_PARSER_STATE_LISTEN_URL:
+	case ICECAST_PARSER_STATE_SERVER_TYPE:
+	case ICECAST_PARSER_STATE_BITRATE:
+	case ICECAST_PARSER_STATE_CHANNELS:
+	case ICECAST_PARSER_STATE_SAMPLE_RATE:
+	case ICECAST_PARSER_STATE_GENRE:
+	case ICECAST_PARSER_STATE_CURRENT_SONG:
+		rb_debug ("unexpectedly started new element '%s' inside data element", name);
+		ctx->in_unknown_elt++;
+		break;
+	}
+}
+
+static void
+_icecast_parser_set_properties (const char *name, const char *value, struct IcecastParserContext *ctx)
+{
+	GValue val = {0,};
+	RhythmDBPropType prop;
+
+	if (strcmp (name, "listen_url") == 0) {
+		/* this has already been set */
+		return;
+	} else if (strcmp (name, "server_name") == 0) {
+		prop = RHYTHMDB_PROP_TITLE;
+		g_value_init (&val, G_TYPE_STRING);
+		g_value_set_string (&val, value);
+	} else if (strcmp (name, "server_type") == 0) {
+		prop = RHYTHMDB_PROP_MIMETYPE;
+		g_value_init (&val, G_TYPE_STRING);
+		g_value_set_string (&val, value);
+	} else if (strcmp (name, "genre") == 0) {
+		prop = RHYTHMDB_PROP_GENRE;
+		g_value_init (&val, G_TYPE_STRING);
+		g_value_set_string (&val, value);
+	} else if (strcmp (name, "bitrate") == 0) {
+		prop = RHYTHMDB_PROP_BITRATE;
+		g_value_init (&val, G_TYPE_ULONG);
+		g_value_set_ulong (&val, atoi (value));
+	} else if (strcmp (name, "channels") == 0) {
+		/* ignore */
+		return;
+	} else if (strcmp (name, "samplerate") == 0) {
+		/* ignore */
+		return;
+	} else if (strcmp (name, "current_song") == 0) {
+		/* ignore */
+		return;
+	} else {
+		rb_debug ("unknown data element '%s' with value '%s'", name, value);
+		return;
+	}
+
+	rhythmdb_entry_set (ctx->db, ctx->entry,
+			    prop, &val);
+	
+	g_value_unset (&val);
+}
+
+static void
+_icecast_parser_end_element (struct IcecastParserContext *ctx, 
+			     const char *name)
+{
+	rb_debug ("End element: %s state: %d", name, ctx->state);
+
+	if (ctx->in_unknown_elt > 0) {
+		ctx->in_unknown_elt--;
+		rb_debug ("Unknown element");
+		return;
+	}
+	       
+	switch (ctx->state) {
+	case ICECAST_PARSER_STATE_START:
+		ctx->state = ICECAST_PARSER_STATE_END;
+		break;
+	    
+	case ICECAST_PARSER_STATE_DIRECTORY:
+		ctx->state = ICECAST_PARSER_STATE_START;
+		break;
+	
+	case ICECAST_PARSER_STATE_ENTRY:
+	{
+		char *location;
+
+		location = g_hash_table_lookup (ctx->table, "listen_url");
+		if (location) {
+			ctx->entry = rhythmdb_entry_lookup_by_location (ctx->db, location);
+			if (ctx->entry == NULL) {
+				ctx->entry = rhythmdb_entry_new (ctx->db,
+								 ctx->entry_type,
+								 location);
+			}
+
+			g_hash_table_foreach (ctx->table,
+					      (GHFunc)_icecast_parser_set_properties,
+					      ctx);
+
+			rhythmdb_commit (ctx->db);
+		} else {
+			rb_debug ("got icecast entry with no listen_url");
+		}
+		
+		/* purge the table for the next entry*/
+		g_hash_table_foreach_remove (ctx->table, (GHRFunc)rb_true_function, NULL);
+		ctx->state = ICECAST_PARSER_STATE_DIRECTORY;
+		break;
+	}
+
+	case ICECAST_PARSER_STATE_SERVER_NAME:
+	case ICECAST_PARSER_STATE_LISTEN_URL:
+	case ICECAST_PARSER_STATE_SERVER_TYPE:
+	case ICECAST_PARSER_STATE_BITRATE:
+	case ICECAST_PARSER_STATE_GENRE:
+	case ICECAST_PARSER_STATE_CHANNELS:
+	case ICECAST_PARSER_STATE_SAMPLE_RATE:
+	case ICECAST_PARSER_STATE_CURRENT_SONG:
+		/* store the data to be processed when the entry is done */
+		if (ctx->buf->str &&ctx->buf->str[0] )
+			g_hash_table_insert (ctx->table, g_strdup (name), g_strdup (ctx->buf->str));
+
+		g_string_truncate (ctx->buf, 0);
+		ctx->state = ICECAST_PARSER_STATE_ENTRY;
+		break;
+	case ICECAST_PARSER_STATE_END:
+		g_assert_not_reached ();
+		break;
+	}
+}
+static void
+_icecast_parser_characters (struct IcecastParserContext *ctx,
+			    const char *data,
+			    guint len)
+{
+	switch (ctx->state) {
+	case ICECAST_PARSER_STATE_SERVER_NAME:
+	case ICECAST_PARSER_STATE_LISTEN_URL:
+	case ICECAST_PARSER_STATE_SERVER_TYPE:
+	case ICECAST_PARSER_STATE_BITRATE:
+	case ICECAST_PARSER_STATE_CHANNELS:
+	case ICECAST_PARSER_STATE_SAMPLE_RATE:
+	case ICECAST_PARSER_STATE_GENRE:
+	case ICECAST_PARSER_STATE_CURRENT_SONG:
+		g_string_append_len (ctx->buf, data, len);
+		break;
+	case ICECAST_PARSER_STATE_START:
+	case ICECAST_PARSER_STATE_DIRECTORY:
+	case ICECAST_PARSER_STATE_ENTRY:
+	case ICECAST_PARSER_STATE_END:
+		break;
+	default:
+		g_warning ("Unknown podcast parser state: %d", ctx->state);
+		break;
+	}
+}
+
+static void
+_icecast_context_free (struct IcecastParserContext *ctx)
+{
+	ctx->source->priv->load_context = NULL;
+
+	gnome_vfs_async_cancel (ctx->handle);
+	gnome_vfs_async_close (ctx->handle, (GnomeVFSAsyncCloseCallback)rb_null_function, NULL);
+
+	g_free (ctx->xmlctx->sax);
+	ctx->xmlctx->sax = NULL;
+	xmlFreeParserCtxt (ctx->xmlctx);
+
+	g_string_free (ctx->buf, TRUE);
+	g_hash_table_destroy (ctx->table);
+	g_free (ctx);
+}
+
+static void
+_icecast_read_cb (GnomeVFSAsyncHandle *handle, GnomeVFSResult result,
+		  const char *buffer, GnomeVFSFileSize bytes_requested, GnomeVFSFileSize bytes_read,
+		  struct IcecastParserContext *ctx)
+{
+	g_assert (handle == ctx->handle);
+
+	if (result != GNOME_VFS_OK) {
+		/* FIXME: report error */
+		_icecast_context_free (ctx);
+		return;
+	}
+
+	/* process data */
+	if (xmlParseChunk (ctx->xmlctx, buffer, bytes_read, (bytes_read == 0)) != 0) {
+		/* FIXME: report error */
+		_icecast_context_free (ctx);
+		return;
+	}
+
+	if (bytes_read != 0) {
+		gnome_vfs_async_read (handle, ctx->buffer, READ_CHUNK_SIZE, (GnomeVFSAsyncReadCallback)_icecast_read_cb, ctx);
+	}
+}
+
+static void
+_icecast_open_cb (GnomeVFSAsyncHandle *handle, GnomeVFSResult result, struct IcecastParserContext *ctx)
+{
+	g_assert (handle == ctx->handle);
+
+	if (result != GNOME_VFS_OK) {
+		/* FIXME: report error */
+		_icecast_context_free (ctx);
+		return;
+	}
+
+	gnome_vfs_async_read (handle, ctx->buffer, READ_CHUNK_SIZE, (GnomeVFSAsyncReadCallback)_icecast_read_cb, ctx);
+}
+
+
+static void
+rb_iradio_icecast_source_load (RBIRadioIcecastSource *source)
+{
+	xmlSAXHandlerPtr sax_handler = NULL;
+	struct IcecastParserContext *ctx;
+
+	if (source->priv->load_context) {
+		rb_debug ("icecast load already in progress");
+		return;
+	}
+
+	rb_debug ("loading icecast directory from %s", source->priv->icecast_url);
+
+	sax_handler = g_new0 (xmlSAXHandler, 1);
+	sax_handler->startElement = (startElementSAXFunc) _icecast_parser_start_element;
+	sax_handler->endElement = (endElementSAXFunc) _icecast_parser_end_element;
+	sax_handler->characters = (charactersSAXFunc) _icecast_parser_characters;
+	xmlSubstituteEntitiesDefault (1);
+
+	ctx = g_new0 (struct IcecastParserContext, 1);
+	ctx->source = source;
+	g_object_get (source, "entry-type", &ctx->entry_type, NULL);
+	ctx->buf = g_string_sized_new (512);
+	ctx->db = _rb_streaming_source_get_db (RB_STREAMING_SOURCE (source));
+	ctx->table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
+	ctx->xmlctx = xmlCreatePushParserCtxt (sax_handler, ctx, NULL, 0, NULL);
+
+	source->priv->load_context = ctx;
+	gnome_vfs_async_open (&ctx->handle, source->priv->icecast_url,
+			      GNOME_VFS_OPEN_READ, GNOME_VFS_PRIORITY_DEFAULT,
+			      (GnomeVFSAsyncOpenCallback)_icecast_open_cb, ctx);
+}
+
+static void
+rb_iradio_icecast_source_activate (RBSource *source)
+{
+	rb_iradio_icecast_source_load (RB_IRADIO_ICECAST_SOURCE (source));
+}
+
+static GList*
+rb_iradio_icecast_source_get_ui_actions (RBSource *source)
+{
+	GList *actions = NULL;
+
+	/* in the reverse order to what is shown */
+	actions = g_list_prepend (actions, g_strdup ("IRadioIcecastAddFavourite"));
+	actions = g_list_prepend (actions, g_strdup ("IRadioIcecastRefresh"));
+
+	return actions;
+}
+
+static gboolean
+rb_iradio_icecast_source_show_popup (RBSource *source)
+{
+	_rb_source_show_popup (RB_SOURCE (source), "/IRadioIcecastSourcePopup");
+	return TRUE;
+}
+
+static void
+rb_iradio_icecast_source_cmd_refresh (GtkAction *action,
+				      RBIRadioIcecastSource *source)
+{
+	rb_iradio_icecast_source_load (source);
+}
+
+static void
+rb_iradio_icecast_source_cmd_add_favourite (GtkAction *action,
+					    RBIRadioIcecastSource *source)
+{
+	GList *entries, *l;
+
+	entries = rb_entry_view_get_selected_entries (rb_source_get_entry_view (RB_SOURCE (source)));
+
+	for (l = entries; l != NULL; l = g_list_next (l)) {
+		RhythmDBEntry *entry;
+
+		entry = RHYTHMDB_ENTRY (l->data);
+		rb_iradio_source_add_station (source->priv->iradio_source,
+					      rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION),
+					      rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_TITLE),
+					      rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_GENRE));
+	}
+
+	g_list_foreach (entries, (GFunc)rhythmdb_entry_unref, NULL);
+	g_list_free (entries);
+}
+
Index: plugins/iradio/iradio-ui.xml
===================================================================
--- plugins/iradio/iradio-ui.xml	(revision 4975)
+++ plugins/iradio/iradio-ui.xml	(working copy)
@@ -16,4 +16,16 @@
     <separator/>
     <menuitem name="PropertiesIRadioPopup" action="MusicProperties"/>
   </popup>
+
+  <popup name="IRadioIcecastSourcePopup">
+    <menuitem name="IRadioIcecastSrcPopupResfresh" action="IRadioIcecastRefresh"/>
+  </popup>
+
+  <popup name="IRadioIcecastViewPopup">
+    <menuitem name="IRadioPopupFavourite" action="IRadioIcecastAddFavourite"/>
+    <separator/>
+    <menuitem name="DeleteIRadioPopup" action="EditDelete"/>
+    <separator/>
+    <menuitem name="PropertiesIRadioPopup" action="MusicProperties"/>
+  </popup>
 </ui>
Index: plugins/iradio/rb-iradio-plugin.c
===================================================================
--- plugins/iradio/rb-iradio-plugin.c	(revision 4975)
+++ plugins/iradio/rb-iradio-plugin.c	(working copy)
@@ -30,13 +30,17 @@
 #include <glib.h>
 #include <glib-object.h>
 
+#include "rb-iradio-source.h"
+#include "rb-iradio-icecast-source.h"
+
 #include "rb-plugin.h"
 #include "rb-debug.h"
 #include "rb-shell.h"
 #include "rb-dialog.h"
-#include "rb-iradio-source.h"
 #include "rb-file-helpers.h"
 
+#define ICECAST_DIRECTORY_ADDRESS "http://dir.xiph.org/yp.xml"
+
 
 #define RB_TYPE_IRADIO_PLUGIN		(rb_iradio_plugin_get_type ())
 #define RB_IRADIO_PLUGIN(o)		(G_TYPE_CHECK_INSTANCE_CAST ((o), RB_TYPE_IRADIO_PLUGIN, RBIRadioPlugin))
@@ -48,8 +52,11 @@
 typedef struct
 {
 	RBPlugin parent;
-	RBSource *source;
+
 	guint ui_merge_id;
+
+	RBSource *source;
+	RBSource *icecast_source;
 } RBIRadioPlugin;
 
 typedef struct
@@ -97,6 +104,12 @@ impl_activate (RBPlugin *plugin,
 	pi->source = rb_iradio_source_new (shell, plugin);
 	rb_shell_append_source (shell, pi->source, NULL);
 
+	pi->icecast_source = rb_iradio_icecast_source_new (shell,
+							   plugin,
+							   RB_IRADIO_SOURCE (pi->source),
+							   ICECAST_DIRECTORY_ADDRESS);
+	rb_shell_append_source (shell, pi->icecast_source, pi->source);
+
 	g_object_get (shell, "ui-manager", &uimanager, NULL);
 	filename = rb_plugin_find_file (plugin, "iradio-ui.xml");
 	if (filename != NULL) {
Index: plugins/iradio/rb-iradio-icecast-source.h
===================================================================
--- plugins/iradio/rb-iradio-icecast-source.h	(revision 0)
+++ plugins/iradio/rb-iradio-icecast-source.h	(revision 0)
@@ -0,0 +1,59 @@
+/*
+ *  Copyright (C) 2007 James Livingston  <doclivingston@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_IRADIO_ICECAST_SOURCE_H
+#define __RB_IRADIO_ICECAST_SOURCE_H
+
+#include "rb-shell.h"
+#include "rb-plugin.h"
+#include "rb-iradio-source.h"
+
+G_BEGIN_DECLS
+
+#define RB_TYPE_IRADIO_ICECAST_SOURCE         (rb_iradio_icecast_source_get_type ())
+#define RB_IRADIO_ICECAST_SOURCE(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), RB_TYPE_IRADIO_ICECAST_SOURCE, RBIRadioIcecastSource))
+#define RB_IRADIO_ICECAST_SOURCE_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), RB_TYPE_IRADIO_ICECAST_SOURCE, RBIRadioIcecastSourceClass))
+#define RB_IS_IRADIO_ICECAST_SOURCE(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), RB_TYPE_IRADIO_ICECAST_SOURCE))
+#define RB_IS_IRADIO_ICECAST_SOURCE_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), RB_TYPE_IRADIO_ICECAST_SOURCE))
+#define RB_IRADIO_ICECAST_SOURCE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), RB_TYPE_IRADIO_ICECAST_SOURCE, RBIRadioIcecastSourceClass))
+
+typedef struct RBIRadioIcecastSourcePrivate RBIRadioIcecastSourcePrivate;
+
+typedef struct
+{
+	RBIRadioSource parent;
+
+	RBIRadioIcecastSourcePrivate *priv;
+} RBIRadioIcecastSource;
+
+typedef struct
+{
+	RBIRadioSourceClass parent;
+} RBIRadioIcecastSourceClass;
+
+GType		rb_iradio_icecast_source_get_type	(void);
+
+RBSource *	rb_iradio_icecast_source_new		(RBShell *shell,
+							 RBPlugin *plugin,
+							 RBIRadioSource *iradio_source,
+							 const char *icecast_url);
+
+G_END_DECLS
+
+#endif /* __RB_IRADIO_ICECAST_SOURCE_H */
Index: plugins/iradio/rb-iradio-source.c
===================================================================
--- plugins/iradio/rb-iradio-source.c	(revision 4975)
+++ plugins/iradio/rb-iradio-source.c	(working copy)
@@ -133,8 +133,6 @@ static void playing_source_changed_cb (R
 
 struct RBIRadioSourcePrivate
 {
-	RhythmDB *db;
-
 	GtkWidget *vbox;
 	GtkWidget *paned;
 	GtkActionGroup *action_group;
@@ -248,11 +246,6 @@ rb_iradio_source_dispose (GObject *objec
 		source->priv->player = NULL;
 	}
 
-	if (source->priv->db) {
-		g_object_unref (source->priv->db);
-		source->priv->db = NULL;
-	}
-
 	if (source->priv->action_group != NULL) {
 		g_object_unref (source->priv->action_group);
 		source->priv->action_group = NULL;
@@ -271,6 +264,7 @@ rb_iradio_source_constructor (GType type
 {
 	RBIRadioSource *source;
 	RBShell *shell;
+	RhythmDB *db;
 
 	source = RB_IRADIO_SOURCE (G_OBJECT_CLASS (rb_iradio_source_parent_class)
 			->constructor (type, n_construct_properties, construct_properties));
@@ -279,7 +273,6 @@ rb_iradio_source_constructor (GType type
 
 	g_object_get (G_OBJECT (source), "shell", &shell, NULL);
 	g_object_get (G_OBJECT (shell),
-		      "db", &source->priv->db,
 		      "shell-player", &source->priv->player,
 		      NULL);
 	g_object_unref (shell);
@@ -291,7 +284,8 @@ rb_iradio_source_constructor (GType type
 								       source);
 
 	/* set up stations view */
-	source->priv->stations = rb_entry_view_new (source->priv->db, G_OBJECT (source->priv->player),
+	db = _rb_streaming_source_get_db (RB_STREAMING_SOURCE (source));
+	source->priv->stations = rb_entry_view_new (db, G_OBJECT (source->priv->player),
 						    CONF_STATE_IRADIO_SORTING,
 						    FALSE, FALSE);
 
@@ -327,7 +321,7 @@ rb_iradio_source_constructor (GType type
 				 G_CALLBACK (rb_iradio_source_songs_show_popup_cb), source, 0);
 
 	/* set up genre entry view */
-	source->priv->genres = rb_property_view_new (source->priv->db,
+	source->priv->genres = rb_property_view_new (db,
 						     RHYTHMDB_PROP_GENRE,
 						     _("Genre"));
 	g_signal_connect_object (source->priv->genres,
@@ -463,12 +457,14 @@ rb_iradio_source_add_station (RBIRadioSo
 	GValue val = { 0, };
 	char *real_uri = NULL;
 	RhythmDBEntryType entry_type;
+	RhythmDB *db;
 
 	real_uri = guess_uri_scheme (uri);
 	if (real_uri)
 		uri = real_uri;
 
-	entry = rhythmdb_entry_lookup_by_location (source->priv->db, uri);
+	db = _rb_streaming_source_get_db (RB_STREAMING_SOURCE (source));
+	entry = rhythmdb_entry_lookup_by_location (db, uri);
 	if (entry) {
 		rb_debug ("uri %s already in db", uri);
 		g_free (real_uri);
@@ -476,7 +472,7 @@ rb_iradio_source_add_station (RBIRadioSo
 	}
 
 	g_object_get (source, "entry-type", &entry_type, NULL);
-	entry = rhythmdb_entry_new (source->priv->db, entry_type, uri);
+	entry = rhythmdb_entry_new (db, entry_type, uri);
 	g_boxed_free (RHYTHMDB_TYPE_ENTRY_TYPE, entry_type);
 	if (entry == NULL) {
 		g_free (real_uri);
@@ -490,21 +486,21 @@ rb_iradio_source_add_station (RBIRadioSo
 		g_value_take_string (&val,
 				     gnome_vfs_format_uri_for_display (uri));
 
-	rhythmdb_entry_set (source->priv->db, entry, RHYTHMDB_PROP_TITLE, &val);
+	rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_TITLE, &val);
 	g_value_reset (&val);
 
 	if ((!genre) || (strcmp (genre, "") == 0))
 		genre = _("Unknown");
 	g_value_set_string (&val, genre);
-	rhythmdb_entry_set (source->priv->db, entry, RHYTHMDB_PROP_GENRE, &val);
+	rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_GENRE, &val);
 	g_value_unset (&val);
 
 	g_value_init (&val, G_TYPE_DOUBLE);
 	g_value_set_double (&val, 0.0);
-	rhythmdb_entry_set (source->priv->db, entry, RHYTHMDB_PROP_RATING, &val);
+	rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_RATING, &val);
 	g_value_unset (&val);
 
-	rhythmdb_commit (source->priv->db);
+	rhythmdb_commit (db);
 
 	g_free (real_uri);
 }
@@ -575,11 +571,13 @@ impl_delete (RBSource *asource)
 	RBIRadioSource *source = RB_IRADIO_SOURCE (asource);
 	GList *sel;
 	GList *l;
+	RhythmDB *db;
 
+	db = _rb_streaming_source_get_db (RB_STREAMING_SOURCE (source));
 	sel = rb_entry_view_get_selected_entries (source->priv->stations);
 	for (l = sel; l != NULL; l = g_list_next (l)) {
-		rhythmdb_entry_delete (source->priv->db, l->data);
-		rhythmdb_commit (source->priv->db);
+		rhythmdb_entry_delete (db, l->data);
+		rhythmdb_commit (db);
 	}
 
 	g_list_foreach (sel, (GFunc)rhythmdb_entry_unref, NULL);
@@ -739,6 +737,7 @@ rb_iradio_source_do_query (RBIRadioSourc
 	RhythmDBPropertyModel *genre_model;
 	GPtrArray *query;
 	RhythmDBEntryType entry_type;
+	RhythmDB *db;
 
 	/* don't update the selection while we're rebuilding the query */
 	source->priv->setting_new_query = TRUE;
@@ -747,8 +746,9 @@ rb_iradio_source_do_query (RBIRadioSourc
 	 * this is used as the model for the genre view.
 	 */
 
+	db = _rb_streaming_source_get_db (RB_STREAMING_SOURCE (source));
 	g_object_get (source, "entry-type", &entry_type, NULL);
-	query = rhythmdb_query_parse (source->priv->db,
+	query = rhythmdb_query_parse (db,
 				      RHYTHMDB_QUERY_PROP_EQUALS,
 				      RHYTHMDB_PROP_TYPE,
 				      entry_type,
@@ -758,7 +758,7 @@ rb_iradio_source_do_query (RBIRadioSourc
 	if (source->priv->search_text != NULL) {
 		GPtrArray *subquery;
 
-		subquery = rhythmdb_query_parse (source->priv->db,
+		subquery = rhythmdb_query_parse (db,
 						 RHYTHMDB_QUERY_PROP_LIKE,
 						 RHYTHMDB_PROP_GENRE_FOLDED,
 						 source->priv->search_text,
@@ -768,7 +768,7 @@ rb_iradio_source_do_query (RBIRadioSourc
 						 source->priv->search_text,
 						 RHYTHMDB_QUERY_END);
 		rb_debug ("searching for \"%s\"", source->priv->search_text);
-		rhythmdb_query_append (source->priv->db,
+		rhythmdb_query_append (db,
 				       query,
 				       RHYTHMDB_QUERY_SUBQUERY,
 				       subquery,
@@ -779,10 +779,10 @@ rb_iradio_source_do_query (RBIRadioSourc
 
 	genre_model = rb_property_view_get_model (source->priv->genres);
 
-	genre_query_model = rhythmdb_query_model_new_empty (source->priv->db);
+	genre_query_model = rhythmdb_query_model_new_empty (db);
 	g_object_set (genre_model, "query-model", genre_query_model, NULL);
 
-	rhythmdb_do_full_query_parsed (source->priv->db,
+	rhythmdb_do_full_query_parsed (db,
 				       RHYTHMDB_QUERY_RESULTS (genre_query_model),
 				       query);
 
@@ -813,8 +813,8 @@ rb_iradio_source_do_query (RBIRadioSourc
 	if (source->priv->selected_genre != NULL) {
 		rb_debug ("matching on genre \"%s\"", source->priv->selected_genre);
 
-		station_query_model = rhythmdb_query_model_new_empty (source->priv->db);
-		query = rhythmdb_query_parse (source->priv->db,
+		station_query_model = rhythmdb_query_model_new_empty (db);
+		query = rhythmdb_query_parse (db,
 					      RHYTHMDB_QUERY_PROP_EQUALS,
 					      RHYTHMDB_PROP_GENRE,
 					      source->priv->selected_genre,
@@ -1137,8 +1137,9 @@ info_available_cb (RBPlayer *backend,
 	}
 
 	if (set_field && entry_field != 0) {
-		rhythmdb_entry_set (source->priv->db, entry, entry_field, value);
-		rhythmdb_commit (source->priv->db);
+		RhythmDB *db = _rb_streaming_source_get_db (RB_STREAMING_SOURCE (source));
+		rhythmdb_entry_set (db, entry, entry_field, value);
+		rhythmdb_commit (db);
 	}
 
 	g_free (str);
Index: plugins/iradio/Makefile.am
===================================================================
--- plugins/iradio/Makefile.am	(revision 4975)
+++ plugins/iradio/Makefile.am	(working copy)
@@ -4,10 +4,12 @@ plugindir = $(PLUGINDIR)/iradio
 plugin_LTLIBRARIES = libiradio.la
 
 libiradio_la_SOURCES = \
-	rb-iradio-plugin.c					\
-	rb-iradio-source.c		\
-	rb-iradio-source.h		\
-	rb-station-properties-dialog.c	\
+	rb-iradio-plugin.c			\
+	rb-iradio-source.c			\
+	rb-iradio-source.h			\
+	rb-iradio-icecast-source.c		\
+	rb-iradio-icecast-source.h		\
+	rb-station-properties-dialog.c		\
 	rb-station-properties-dialog.h
 
 libiradio_la_LDFLAGS = $(PLUGIN_LIBTOOL_FLAGS)
