Blob Blame History Raw
Index: podcast/test-podcast-parse.c
===================================================================
--- podcast/test-podcast-parse.c	(revision 5413)
+++ podcast/test-podcast-parse.c	(working copy)
@@ -66,7 +66,6 @@
 	g_date_strftime (datebuf, 1024, "%F %T", &date);
 
 	g_print ("Podcast title: %s\n", data->title);
-	g_print ("Summary: %s\n", data->summary);
 	g_print ("Description: %s\n", data->description);
 	g_print ("Author: %s\n", data->author);
 	g_print ("Date: %s\n", datebuf);
Index: podcast/rb-podcast-manager.c
===================================================================
--- podcast/rb-podcast-manager.c	(revision 5413)
+++ podcast/rb-podcast-manager.c	(working copy)
@@ -816,7 +816,7 @@
 	RBPodcastThreadInfo *info;
 	gchar *valid_url;
 
-	if (g_str_has_prefix (url, "feed://")) {
+	if (g_str_has_prefix (url, "feed://") || g_str_has_prefix (url, "itpc://")) {
 		char *tmp;
 
 		tmp = g_strdup_printf ("http://%s", url + strlen ("feed://"));
@@ -898,7 +899,7 @@
 {
 	RBPodcastChannel *feed = g_new0 (RBPodcastChannel, 1);
 
-	if (rb_podcast_parse_load_feed (feed, info->url)) {
+	if (rb_podcast_parse_load_feed (feed, info->url) && (feed->is_opml == FALSE)) {
 		RBPodcastManagerParseResult *result;
 
 		result = g_new0 (RBPodcastManagerParseResult, 1);
@@ -910,6 +911,16 @@
 				 (GSourceFunc) rb_podcast_manager_parse_complete_cb,
 				 result,
 				 (GDestroyNotify) rb_podcast_manager_free_parse_result);
+	} else if (feed->is_opml) {
+		GList *l;
+
+		rb_debug ("Loading OPML feeds from %s", info->url);
+
+		for (l = feed->posts; l != NULL; l = l->next) {
+			RBPodcastItem *item = l->data;
+			rb_podcast_manager_subscribe_feed (info->pd, item->url);
+		}
+		rb_podcast_parse_channel_free (feed);
 	}
 
 	g_object_unref (info->pd);
@@ -1496,8 +1507,6 @@
 {
 	GValue description_val = { 0, };
 	GValue title_val = { 0, };
-	GValue subtitle_val = { 0, };
-	GValue summary_val = { 0, };
 	GValue lang_val = { 0, };
 	GValue copyright_val = { 0, };
 	GValue image_val = { 0, };
@@ -1568,13 +1577,6 @@
 	rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_ARTIST, &author_val);
 	g_value_unset (&author_val);
 
-	if (data->subtitle) {
-		g_value_init (&subtitle_val, G_TYPE_STRING);
-		g_value_set_string (&subtitle_val, (gchar *) data->subtitle);
-		rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_SUBTITLE, &subtitle_val);
-		g_value_unset (&subtitle_val);
-	}
-
 	if (data->description) {
 		g_value_init (&description_val, G_TYPE_STRING);
 		g_value_set_string (&description_val, (gchar *) data->description);
@@ -1582,13 +1584,6 @@
 		g_value_unset (&description_val);
 	}
 
-	if (data->summary) {
-		g_value_init (&summary_val, G_TYPE_STRING);
-		g_value_set_string (&summary_val, (gchar *) data->summary);
-		rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_SUMMARY, &summary_val);
-		g_value_unset (&summary_val);
-	}
-
 	if (data->lang) {
 		g_value_init (&lang_val, G_TYPE_STRING);
 		g_value_set_string (&lang_val, (gchar *) data->lang);
Index: podcast/rb-podcast-parse.c
===================================================================
--- podcast/rb-podcast-parse.c	(revision 5413)
+++ podcast/rb-podcast-parse.c	(working copy)
@@ -22,427 +22,116 @@
 
 #include "config.h"
 
-#define _XOPEN_SOURCE
-#define __EXTENSIONS__  /* get strptime */
 #include <string.h>
-#include <time.h>
 
-#include <libxml/entities.h>
-#include <libxml/SAX.h>
-#include <libxml/parserInternals.h>
+#include <totem-pl-parser.h>
 #include <libgnomevfs/gnome-vfs.h>
 #include <glib/gi18n.h>
 #include <gtk/gtk.h>
 
 #include "rb-debug.h"
 #include "rb-podcast-parse.h"
+#include "rb-file-helpers.h"
 
-#define BUFFER_SIZE 256
-
-struct RBPoadcastLoadContext
-{
-	guint in_unknown_elt;
-	xmlParserCtxtPtr xmlctx;
-	GString *prop_value;
-	RBPodcastChannel *channel_data;
-	RBPodcastItem *item_data;
-
-	enum {
-		RB_PODCAST_PARSER_STATE_START,
-		RB_PODCAST_PARSER_STATE_RSS,
-		RB_PODCAST_PARSER_STATE_CHANNEL,
-		RB_PODCAST_PARSER_STATE_CHANNEL_PROPERTY,
-		RB_PODCAST_PARSER_STATE_IMG,
-		RB_PODCAST_PARSER_STATE_IMG_PROPERTY,
-		RB_PODCAST_PARSER_STATE_ITEM,
-		RB_PODCAST_PARSER_STATE_ITEM_PROPERTY,
-		RB_PODCAST_PARSER_STATE_END,
-	} state;
-};
-
-static gboolean rb_validate_channel_propert (const char *name);
-static gboolean rb_validate_item_propert (const char *name);
-static uintmax_t rb_podcast_parse_date (const char* date_str);
-static gulong rb_podcast_parse_time (const char *time_str);
-static void rb_podcast_parser_start_element (struct RBPoadcastLoadContext* ctx, const char *name, const char **attrs);
-static void rb_podcast_parser_end_element (struct RBPoadcastLoadContext* ctx, const char *name);
-static void rb_podcast_parser_characters (struct RBPoadcastLoadContext* ctx, const char *data, guint len);
-static void rb_set_channel_value (struct RBPoadcastLoadContext* ctx, const char* name, const char* value);
-static void rb_set_item_value (struct RBPoadcastLoadContext* ctx, const char* name, const char* value);
-
-static RBPodcastItem *
-rb_podcast_initializa_item ()
-{
-	RBPodcastItem *data = g_new0 (RBPodcastItem, 1);
-	return data;
-}
-
 static void
-rb_set_channel_value (struct RBPoadcastLoadContext *ctx,
-		      const char *name,
-		      const char *value)
+playlist_metadata_foreach (const char *key,
+			   const char *value,
+			   gpointer data)
 {
-	xmlChar *dvalue;
+	RBPodcastChannel *channel = (RBPodcastChannel *) data;
 
-	if (value == NULL)
-		return;
-
-	if (name == NULL)
-		return;
-
-	dvalue = xmlCharStrdup (value);
-	g_strstrip ((char *)dvalue);
-
-	if (!strcmp (name, "title")) {
-		ctx->channel_data->title = dvalue;
-	} else if (!strcmp (name, "language")) {
-		ctx->channel_data->lang = dvalue;
-	} else if (!strcmp (name, "itunes:subtitle")) {
-		ctx->channel_data->subtitle = dvalue;
-	} else if (!strcmp (name, "itunes:summary")) {
-		ctx->channel_data->summary = dvalue;
-	} else if (!strcmp (name, "description")) {
-		ctx->channel_data->description = dvalue;
-	} else if (!strcmp (name, "generator")) {
-		if (ctx->channel_data->author == NULL)
-			ctx->channel_data->author = dvalue;
-	} else if (!strcmp (name, "itunes:author")) {
-		g_free (ctx->channel_data->author);
-		ctx->channel_data->author = dvalue;
-	} else if (!strcmp (name, "webMaster")) {
-		ctx->channel_data->contact = dvalue;
-	} else if (!strcmp (name, "pubDate")) {
-		ctx->channel_data->pub_date = rb_podcast_parse_date ((char *)dvalue);
-		g_free (dvalue);
-	} else if (!strcmp (name, "copyright")) {
-		ctx->channel_data->copyright = dvalue;
-	} else if (!strcmp (name, "img")) {
-		ctx->channel_data->img = dvalue;
-	} else {
-		g_free (dvalue);
+	if (strcmp (key, TOTEM_PL_PARSER_FIELD_TITLE) == 0) {
+		channel->title = g_strdup (value);
+	} else if (strcmp (key, TOTEM_PL_PARSER_FIELD_LANGUAGE) == 0) {
+		channel->lang = g_strdup (value);
+	} else if (strcmp (key, TOTEM_PL_PARSER_FIELD_DESCRIPTION) == 0) {
+		channel->description = g_strdup (value);
+	} else if (strcmp (key, TOTEM_PL_PARSER_FIELD_AUTHOR) == 0) {
+		channel->author = g_strdup (value);
+	} else if (strcmp (key, TOTEM_PL_PARSER_FIELD_CONTACT) == 0) {
+		channel->contact = g_strdup (value);
+	} else if (strcmp (key, TOTEM_PL_PARSER_FIELD_IMAGE_URL) == 0) {
+		channel->img = g_strdup (value);
+	} else if (strcmp (key, TOTEM_PL_PARSER_FIELD_PUB_DATE) == 0) {
+		channel->pub_date = totem_pl_parser_parse_date (value, FALSE);
+	} else if (strcmp (key, TOTEM_PL_PARSER_FIELD_COPYRIGHT) == 0) {
+		channel->copyright = g_strdup (value);
 	}
 }
 
 static void
-rb_set_item_value (struct RBPoadcastLoadContext *ctx,
-		   const char *name,
-		   const char *value)
+playlist_started (TotemPlParser *parser,
+		  const char *uri,
+		  GHashTable *metadata,
+		  gpointer data)
 {
-	xmlChar *dvalue;
-
-	dvalue = xmlCharStrdup (value);
-	g_strstrip ((char *)dvalue);
-
-	if (!strcmp (name, "title")) {
-		ctx->item_data->title = dvalue;
-	} else if (!strcmp (name, "url")) {
-		ctx->item_data->url = dvalue;
-	} else if (!strcmp (name, "pubDate")) {
-		ctx->item_data->pub_date = rb_podcast_parse_date ((char *)dvalue);
-		g_free (dvalue);
-	} else if (!strcmp (name, "description")) {
-		ctx->item_data->description = dvalue;
-	} else if (!strcmp (name, "author")) {
-		ctx->item_data->author = dvalue;
-	} else if (!strcmp (name, "itunes:duration")) {
-		ctx->item_data->duration = rb_podcast_parse_time ((char *)dvalue);
-		g_free (dvalue);
-	} else if (!strcmp (name, "length")) {
-		ctx->item_data->filesize = g_ascii_strtoull ((char *)dvalue, NULL, 10);
-	} else {
-		g_free (dvalue);
-	}
+	g_hash_table_foreach (metadata, (GHFunc) playlist_metadata_foreach, data);
 }
 
 static void
-rb_insert_item (struct RBPoadcastLoadContext *ctx)
+playlist_ended (TotemPlParser *parser,
+		const char *uri,
+		gpointer data)
 {
-	RBPodcastItem *data = ctx->item_data;
+	RBPodcastChannel *channel = (RBPodcastChannel *) data;
 
-	rb_debug ("Inserting item as post");
-
-	if (!data->url) {
-		rb_debug ("Item does not have a URL, skipping");
-		return;
-	}
-
-	ctx->channel_data->posts = g_list_prepend (ctx->channel_data->posts, ctx->item_data);
+	channel->posts = g_list_reverse (channel->posts);
 }
 
-static gboolean
-rb_validate_channel_propert (const char *name)
-{
-	if (name == NULL) {
-		return FALSE;
-	}
-
-	if (!strcmp (name, "title") ||
-	    !strcmp (name, "language") ||
-	    !strcmp (name, "itunes:subtitle") ||
-	    !strcmp (name, "itunes:summary") ||
-	    !strcmp (name, "description") ||
-	    !strcmp (name, "generator") ||
-	    !strcmp (name, "itunes:author") ||
-	    !strcmp (name, "webMaster") ||
-	    !strcmp (name, "lastBuildDate") ||
-	    !strcmp (name, "pubDate") ||
-	    !strcmp (name, "copyright")) {
-		return TRUE;
-	} else {
-		return FALSE;
-	}
-
-}
-
-static gboolean
-rb_validate_item_propert (const char *name)
-{
-	if (name == NULL) {
-		return FALSE;
-	}
-
-	if (!strcmp (name, "title") ||
-	    !strcmp (name, "url") ||
-	    !strcmp (name, "pubDate") ||
-	    !strcmp (name, "description") ||
-	    !strcmp (name, "author") ||
-	    !strcmp (name, "itunes:duration") ) {
-
-		return TRUE;
-	} else {
-		return FALSE;
-	}
-}
-
 static void
-rb_podcast_parser_start_element (struct RBPoadcastLoadContext *ctx,
-				 const char *name,
-				 const char **attrs)
+entry_metadata_foreach (const char *key,
+			const char *value,
+			gpointer data)
 {
+	RBPodcastItem *item = (RBPodcastItem *) data;
 
-	rb_debug ("Start element: %s state: %d", name, ctx->state);
-
-	switch (ctx->state) {
-        case RB_PODCAST_PARSER_STATE_START:
-		{
-			if (!strcmp (name, "rss")) {
-				ctx->state = RB_PODCAST_PARSER_STATE_RSS;
-			} else {
-				ctx->in_unknown_elt++;
-			}
-
-			break;
-		}
-
-        case RB_PODCAST_PARSER_STATE_RSS:
-		{
-			if (!strcmp (name, "channel")) {
-				ctx->state = RB_PODCAST_PARSER_STATE_CHANNEL;
-			} else {
-				ctx->in_unknown_elt++;
-			}
-
-			break;
-		}
-
-        case RB_PODCAST_PARSER_STATE_CHANNEL:
-		{
-			if (strcmp (name, "image") == 0)
-			{
-				ctx->state = RB_PODCAST_PARSER_STATE_IMG;
-			} else if (strcmp (name, "itunes:image") == 0) {
-				for (; attrs && *attrs; attrs +=2) {
-					if (!strcmp (*attrs, "href")) {
-						const char *href_value = *(attrs + 1);
-						rb_set_channel_value (ctx, "img", href_value);
-					}
-				}
-
-				ctx->state = RB_PODCAST_PARSER_STATE_IMG;
-
-			} else if (!strcmp (name, "item")) {
-				ctx->item_data = rb_podcast_initializa_item ();
-				ctx->state = RB_PODCAST_PARSER_STATE_ITEM;
-			} else if (!rb_validate_channel_propert (name)) {
-				rb_debug ("Unknown property");
-				ctx->in_unknown_elt++;
-			} else {
-				ctx->state = RB_PODCAST_PARSER_STATE_CHANNEL_PROPERTY;
-			}
-
-			break;
-		}
-
-        case RB_PODCAST_PARSER_STATE_ITEM:
-		{
-			if (!strcmp (name, "enclosure")) {
-				for (; *attrs; attrs +=2) {
-					if (!strcmp (*attrs, "url")) {
-						const char *url_value = *(attrs + 1);
-						rb_set_item_value (ctx, "url", url_value);
-					} else if (!strcmp (*attrs, "length")) {
-						const char *length_value = *(attrs + 1);
-						rb_set_item_value (ctx, "length", length_value);
-					}
-				}
-
-				ctx->state = RB_PODCAST_PARSER_STATE_ITEM_PROPERTY;
-
-			} else if (!rb_validate_item_propert (name)) {
-				ctx->in_unknown_elt++;
-			} else {
-				ctx->state = RB_PODCAST_PARSER_STATE_ITEM_PROPERTY;
-			}
-
-			break;
-		}
-
-        case RB_PODCAST_PARSER_STATE_IMG:
-		{
-			if (strcmp (name, "url") != 0) {
-				ctx->in_unknown_elt++;
-			} else {
-				ctx->state = RB_PODCAST_PARSER_STATE_IMG_PROPERTY;
-			}
-
-			break;
-		}
-
-        case RB_PODCAST_PARSER_STATE_CHANNEL_PROPERTY:
-        case RB_PODCAST_PARSER_STATE_ITEM_PROPERTY:
-        case RB_PODCAST_PARSER_STATE_IMG_PROPERTY:
-		rb_debug ("nested element inside property; treating as unknown");
-		ctx->in_unknown_elt++;
-		break;
-
-        case RB_PODCAST_PARSER_STATE_END:
-		break;
-	default:
-		g_warning ("Unknown podcast parser state: %d", ctx->state);
-		break;
+	if (strcmp (key, TOTEM_PL_PARSER_FIELD_TITLE) == 0) {
+		item->title = g_strdup (value);
+	} else if (strcmp (key, TOTEM_PL_PARSER_FIELD_URL) == 0) {
+		item->url = g_strdup (value);
+	} else if (strcmp (key, TOTEM_PL_PARSER_FIELD_DESCRIPTION) == 0) {
+		item->description = g_strdup (value);
+	} else if (strcmp (key, TOTEM_PL_PARSER_FIELD_AUTHOR) == 0) {
+		item->author = g_strdup (value);
+	} else if (strcmp (key, TOTEM_PL_PARSER_FIELD_PUB_DATE) == 0) {
+		item->pub_date = totem_pl_parser_parse_date (value, FALSE);
+	} else if (strcmp (key, TOTEM_PL_PARSER_FIELD_DURATION) == 0) {
+		item->duration = totem_pl_parser_parse_duration (value, FALSE);
+	} else if (strcmp (key, TOTEM_PL_PARSER_FIELD_FILESIZE) == 0) {
+		item->filesize = g_ascii_strtoull (value, NULL, 10);
 	}
 }
 
 static void
-rb_podcast_parser_end_element (struct RBPoadcastLoadContext *ctx,
-			       const char *name)
+entry_parsed (TotemPlParser *parser,
+	      const char *uri,
+	      GHashTable *metadata,
+	      gpointer data)
 {
-	rb_debug ("End element: %s state: %d", name, ctx->state);
+	RBPodcastChannel *channel = (RBPodcastChannel *) data;
+	RBPodcastItem *item;
 
-	if (ctx->in_unknown_elt > 0) {
-		ctx->in_unknown_elt--;
-		rb_debug ("Unknown element");
-		return;
-	}
-
-	switch (ctx->state) {
-        case RB_PODCAST_PARSER_STATE_START:
-		ctx->state = RB_PODCAST_PARSER_STATE_END;
-		break;
-
-        case RB_PODCAST_PARSER_STATE_RSS:
-		ctx->state = RB_PODCAST_PARSER_STATE_START;
-		break;
-
-        case RB_PODCAST_PARSER_STATE_CHANNEL:
-		ctx->state = RB_PODCAST_PARSER_STATE_RSS;
-		break;
-
-        case RB_PODCAST_PARSER_STATE_CHANNEL_PROPERTY:
-		{
-			rb_set_channel_value (ctx, name, ctx->prop_value->str);
-			ctx->state = RB_PODCAST_PARSER_STATE_CHANNEL;
-			g_string_truncate (ctx->prop_value, 0);
-			break;
-		}
-
-        case RB_PODCAST_PARSER_STATE_ITEM:
-		{
-			rb_insert_item (ctx);
-			ctx->state = RB_PODCAST_PARSER_STATE_CHANNEL;
-			break;
-		}
-
-        case RB_PODCAST_PARSER_STATE_ITEM_PROPERTY:
-		{
-			rb_set_item_value (ctx, name, ctx->prop_value->str);
-			ctx->state = RB_PODCAST_PARSER_STATE_ITEM;
-			g_string_truncate (ctx->prop_value, 0);
-			break;
-		}
-
-        case RB_PODCAST_PARSER_STATE_IMG_PROPERTY:
-		{
-			rb_set_channel_value (ctx, "img", ctx->prop_value->str);
-			ctx->state = RB_PODCAST_PARSER_STATE_IMG;
-			g_string_truncate (ctx->prop_value, 0);
-			break;
-		}
-
-        case RB_PODCAST_PARSER_STATE_IMG:
-		ctx->state = RB_PODCAST_PARSER_STATE_CHANNEL;
-		break;
-
-        case RB_PODCAST_PARSER_STATE_END:
-		break;
-
-	default:
-		g_warning ("Unknown podcast parser state: %d", ctx->state);
-		break;
-	}
+	item = g_new0 (RBPodcastItem, 1);
+	g_hash_table_foreach (metadata, (GHFunc) entry_metadata_foreach, item);
+	channel->posts = g_list_prepend (channel->posts, item);
 }
 
-static void
-rb_podcast_parser_characters (struct RBPoadcastLoadContext *ctx,
-			      const char *data,
-			      guint len)
-{
-	switch (ctx->state) {
-        case RB_PODCAST_PARSER_STATE_CHANNEL_PROPERTY:
-        case RB_PODCAST_PARSER_STATE_ITEM_PROPERTY:
-        case RB_PODCAST_PARSER_STATE_IMG_PROPERTY:
-		g_string_append_len (ctx->prop_value, data, len);
-           	break;
-        case RB_PODCAST_PARSER_STATE_START:
-        case RB_PODCAST_PARSER_STATE_IMG:
-        case RB_PODCAST_PARSER_STATE_RSS:
-        case RB_PODCAST_PARSER_STATE_CHANNEL:
-        case RB_PODCAST_PARSER_STATE_ITEM:
-        case RB_PODCAST_PARSER_STATE_END:
-		break;
-	default:
-		g_warning ("Unknown podcast parser state: %d", ctx->state);
-		break;
-	}
-}
-
 gboolean
 rb_podcast_parse_load_feed (RBPodcastChannel *data,
 			    const char *file_name)
 {
-	xmlParserCtxtPtr parser;
-	xmlSAXHandlerPtr sax_handler = NULL;
 	GnomeVFSResult result;
 	GnomeVFSFileInfo *info;
-	gint file_size;
-	gchar *buffer = NULL;
-	const char *query_string;
+	TotemPlParser *plparser;
 
-	struct RBPoadcastLoadContext *ctx = NULL;
+	data->url = g_strdup (file_name);
 
-	data->url = xmlCharStrdup (file_name);
-
-	/* if the URL has a .rss or .xml extension (before the query string),
+	/* if the URL has a .rss, .xml or .atom extension (before the query string),
 	 * don't bother checking the MIME type.
 	 */
-	query_string = strchr (file_name, '?');
-	if (query_string == NULL) {
-		query_string = file_name + strlen (file_name);
-	}
-
-	if (strncmp (query_string - 4, ".rss", 4) == 0 ||
-	    strncmp (query_string - 4, ".xml", 4) == 0) {
-		rb_debug ("not checking mime type for %s", file_name);
+	if (rb_uri_could_be_podcast (file_name, &data->is_opml)) {
+		rb_debug ("not checking mime type for %s (should be %s file)", file_name,
+			  data->is_opml ? "OPML" : "Podcast");
 	} else {
 		gboolean invalid_mime_type;
 
@@ -451,22 +140,33 @@
 
 		result = gnome_vfs_get_file_info (file_name, info, GNOME_VFS_FILE_INFO_DEFAULT);
 
+		if ((result != GNOME_VFS_OK)) {
+			if (info->mime_type != NULL) {
+				rb_debug ("Invalid mime-type in podcast feed %s", info->mime_type);
+			} else {
+				rb_debug ("Couldn't get mime type for %s: %s", file_name,
+					  gnome_vfs_result_to_string (result));
+			}
+			gnome_vfs_file_info_unref (info);
+			return TRUE;
+		}
+
 		if (info != NULL
 		    && info->mime_type != NULL
 		    && strstr (info->mime_type, "html") == NULL
 		    && strstr (info->mime_type, "xml") == NULL
-		    && strstr (info->mime_type, "rss") == NULL) {
+		    && strstr (info->mime_type, "rss") == NULL
+		    && strstr (info->mime_type, "opml") == NULL) {
 			invalid_mime_type = TRUE;
+		} else if (info != NULL
+			   && info->mime_type != NULL
+			   && strstr (info->mime_type, "opml") != NULL) {
+			data->is_opml = TRUE;
+			invalid_mime_type = FALSE;
 		} else {
 			invalid_mime_type = FALSE;
 		}
 
-		if ((result != GNOME_VFS_OK)) {
-			rb_debug ("Invalid mime-type in podcast feed %s", info->mime_type);
-			gnome_vfs_file_info_unref (info);
-			return TRUE;
-		}
-
 		if (invalid_mime_type) {
 			GtkWidget *dialog;
 
@@ -492,173 +192,22 @@
 			return FALSE;
 	}
 
-	/* first download file by gnome_vfs for use gnome network configuration */
-	rb_debug ("reading podcast feed %s", file_name);
-	result = gnome_vfs_read_entire_file (file_name, &file_size, &buffer);
-	if (result != GNOME_VFS_OK)
-		return TRUE;
+	plparser = totem_pl_parser_new ();
+	g_object_set (plparser, "recurse", FALSE, NULL);
+	g_signal_connect (G_OBJECT (plparser), "entry-parsed", G_CALLBACK (entry_parsed), data);
+	g_signal_connect (G_OBJECT (plparser), "playlist-started", G_CALLBACK (playlist_started), data);
+	g_signal_connect (G_OBJECT (plparser), "playlist-ended", G_CALLBACK (playlist_ended), data);
 
-	/* initializing parse */
-	sax_handler = g_new0 (xmlSAXHandler, 1);
-	sax_handler->startElement = (startElementSAXFunc) rb_podcast_parser_start_element;
-	sax_handler->endElement = (endElementSAXFunc) rb_podcast_parser_end_element;
-	sax_handler->characters = (charactersSAXFunc) rb_podcast_parser_characters;
-	xmlSubstituteEntitiesDefault (1);
-
-	ctx = g_new0 (struct RBPoadcastLoadContext, 1);
-	ctx->in_unknown_elt = 0;
-	ctx->channel_data = data;
-	ctx->prop_value = g_string_sized_new (512);
-
-	parser = xmlCreateMemoryParserCtxt (buffer, file_size);
-	if (parser == NULL) {
-		g_free (sax_handler);
-		g_free (buffer);
-		g_string_free (ctx->prop_value, TRUE);
-		g_free (ctx);
+	if (totem_pl_parser_parse (plparser, file_name, FALSE) != TOTEM_PL_PARSER_RESULT_SUCCESS) {
+		rb_debug ("Parsing %s as a Podcast failed", file_name);
+		g_object_unref (plparser);
 		return FALSE;
 	}
+	rb_debug ("Parsing %s as a Podcast succeeded", file_name);
 
-	ctx->xmlctx = parser;
-	parser->userData = ctx;
-	parser->sax = sax_handler;
-	xmlParseDocument (parser);
-
-	g_free (sax_handler);
-	parser->sax = NULL;
-	xmlFreeParserCtxt (parser);
-
-	g_free (buffer);
-	g_string_free (ctx->prop_value, TRUE);
-	g_free (ctx);
-
-	data->posts = g_list_reverse (data->posts);
 	return TRUE;
 }
 
-static uintmax_t
-rb_podcast_parse_date (const char *date_str)
-{
-	struct tm tm;
-	char *result;
-
-	/* RFC 2822 date format */
-	result = strptime (date_str, "%a, %d %b %Y %T", &tm);
-
-	/* same as above, but without comma */
-	if (result == NULL) {
-		memset (&tm, 0, sizeof (struct tm));
-		result = strptime (date_str, "%a %d %b %Y %T", &tm);
-	}
-
-	/* close-to-RFC 2822, but with extra 0 */
-	if (result == NULL) {
-		memset (&tm, 0, sizeof (struct tm));
-		result = strptime (date_str, "%a, %d %b %Y 0%T", &tm);
-	}
-
-	/* close-to-RFC 2822, but with no seconds */
-	if (result == NULL) {
-		memset (&tm, 0, sizeof (struct tm));
-		result = strptime (date_str, "%a, %d %b %Y %R", &tm);
-	}
-
-	/* format without weekday */
-	if (result == NULL) {
-		memset (&tm, 0, sizeof (struct tm));
-		result = strptime (date_str, "%d %b %Y %T", &tm);
-	}
-
-	/* reversed day and long month */
-	if (result == NULL) {
-		memset (&tm, 0, sizeof (struct tm));
-		result = strptime (date_str, "%a, %B %d %Y %T", &tm);
-	}
-
-	/* ISO date like */
-	if (result == NULL) {
-		memset (&tm, 0, sizeof (struct tm));
-		result = strptime (date_str, "%Y-%m-%d %T", &tm);
-	}
-
-	/* ISO date like without timezone */
-	if (result == NULL) {
-	memset (&tm, 0, sizeof (struct tm));
-		result = strptime (date_str, "%Y-%m-%d", &tm);
-	}
-
-	/* Broken weekday short names */
-	if (result == NULL) {
-		char *tmp;
-
-		/* strip off the erroneous weekday */
-		tmp = strstr (date_str, ",");
-		if (tmp != NULL) {
-			tmp++;
-			memset (&tm, 0, sizeof (struct tm));
-			result = strptime (tmp, "%d %b %Y %T", &tm);
-		}
-	}
-
-	/* format with timezone offset from GMT */
-	if (result == NULL) {
-		memset (&tm, 0, sizeof (struct tm));
-		result = strptime (date_str, "%a %b %d %T %z %Y", &tm);
-	}
-
-	/* format with timezone name */
-	if (result == NULL) {
-		char *tmp;
-
-		memset (&tm, 0, sizeof (struct tm));
-
-		/* match first part of time string */
-		result = strptime (date_str, "%a %b %d %T ", &tm);
-
-		/* look for anything with a timezone name-like format
-		   i.e. at least one all caps alphabetical character */
-		if (result != NULL) {
-			size_t n;
-
-			n = strspn(result, "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
-			tmp = result+n;
-
-			/* make sure there was at least one character that matched */
-			if ((tmp != NULL) && n > 0)
-				/* remaining part must be the year */
-				result = strptime (tmp, "%Y", &tm);
-			else
-				result = NULL;
-		}
-	}
-
-	if (result == NULL) {
-		rb_debug ("unable to convert date string %s", date_str);
-	}
-
-	return (uintmax_t) ( (result==NULL) ? 0 : mktime (&tm) );
-}
-
-static gulong
-rb_podcast_parse_time (const char *time_str)
-{
-	struct tm tm;
-	char *result;
-
-	memset (&tm, 0, sizeof (struct tm));
-	result = strptime (time_str, "%H:%M:%S", &tm);
-	if (result == NULL) {
-		memset (&tm, 0, sizeof (struct tm));
-		result = strptime (time_str, "%M:%S", &tm);
-	}
-	if (result == NULL) {
-		memset (&tm, 0, sizeof (struct tm));
-		rb_debug ("unable to convert duration string %s", time_str);
-	}
-
-	return ((tm.tm_hour * 60 + tm.tm_min) * 60 + tm.tm_sec);
-}
-
 void
 rb_podcast_parse_channel_free (RBPodcastChannel *data)
 {
@@ -671,8 +220,6 @@
 	g_free (data->url);
 	g_free (data->title);
 	g_free (data->lang);
-	g_free (data->subtitle);
-	g_free (data->summary);
 	g_free (data->description);
 	g_free (data->author);
 	g_free (data->contact);
Index: podcast/Makefile.am
===================================================================
--- podcast/Makefile.am	(revision 5413)
+++ podcast/Makefile.am	(working copy)
@@ -6,6 +6,9 @@
 	rb-podcast-parse.c				\
 	rb-podcast-parse.h
 
+librbpodcast_parse_la_LIBADD =				\
+	$(top_builddir)/lib/librb.la
+
 librbpodcast_la_SOURCES =				\
 	rb-feed-podcast-properties-dialog.c		\
 	rb-feed-podcast-properties-dialog.h		\
@@ -23,7 +26,8 @@
 	test-podcast-parse.c
 test_podcast_parse_LDADD =				\
 	librbpodcast_parse.la				\
-	$(RHYTHMBOX_LIBS)
+	$(RHYTHMBOX_LIBS)				\
+	$(TOTEM_PLPARSER_LIBS)
 
 INCLUDES =						\
 	-DGNOMELOCALEDIR=\""$(datadir)/locale"\"        \
@@ -36,7 +40,8 @@
 	-I$(top_srcdir)/metadata			\
 	-I$(top_srcdir)/library				\
 	-I$(top_builddir)/lib 				\
-	$(RHYTHMBOX_CFLAGS)
+	$(RHYTHMBOX_CFLAGS)				\
+	$(TOTEM_PLPARSER_CFLAGS)
 
 librbpodcast_la_LDFLAGS = -export-dynamic
 
Index: podcast/rb-podcast-parse.h
===================================================================
--- podcast/rb-podcast-parse.h	(revision 5413)
+++ podcast/rb-podcast-parse.h	(working copy)
@@ -23,36 +23,34 @@
 #define RB_PODCAST_PARSE_H
 
 #include <glib.h>
-#include <libxml/xmlstring.h>
-#include <inttypes.h>
 
 typedef struct
 {
-	xmlChar* title;
-	xmlChar* url;
-	xmlChar* description;
-	xmlChar* author;
-	uintmax_t pub_date;
+	char* title;
+	char* url;
+	char* description;
+	char* author;
+	guint64 pub_date;
 	gulong duration;
 	guint64 filesize;
-}RBPodcastItem;
+} RBPodcastItem;
 
 typedef struct
 {
-	xmlChar* url;
-	xmlChar* title;
-	xmlChar* lang;
-    	xmlChar* subtitle;
-    	xmlChar* summary;
-	xmlChar* description;
-	xmlChar* author;
-	xmlChar* contact;
-	xmlChar* img;
-	uintmax_t pub_date;
-    	xmlChar* copyright;
+	char* url;
+	char* title;
+	char* lang;
+    	char* description;
+	char* author;
+	char* contact;
+	char* img;
+	guint64 pub_date;
+    	char* copyright;
 
+    	gboolean is_opml;
+
 	GList *posts;
-}RBPodcastChannel;
+} RBPodcastChannel;
 
 gboolean rb_podcast_parse_load_feed	(RBPodcastChannel *data, const char *file_name);
 void rb_podcast_parse_channel_free 	(RBPodcastChannel *data);
Index: configure.ac
===================================================================
--- configure.ac	(revision 5413)
+++ configure.ac	(working copy)
@@ -34,7 +34,7 @@
 LIBGPOD_REQS=0.4
 MUSICBRAINZ_REQS=2.1.0
 NCB_MIN_REQS=2.9.0
-TOTEM_PLPARSER_REQS=1.1.5
+TOTEM_PLPARSER_REQS=2.21.0
 VALA_REQS=0.0.8
 
 AC_MSG_CHECKING([for GNU extension fwrite_unlocked])
Index: lib/rb-file-helpers.c
===================================================================
--- lib/rb-file-helpers.c	(revision 5413)
+++ lib/rb-file-helpers.c	(working copy)
@@ -774,6 +774,65 @@
 	return g_utf8_strrchr (text_uri, -1, GNOME_VFS_URI_PATH_CHR)[1] == '.';
 }
 
+gboolean
+rb_uri_could_be_podcast (const char *uri, gboolean *is_opml)
+{
+	const char *query_string;
+
+	if (is_opml != NULL)
+		*is_opml = FALSE;
+
+	/* Check the scheme is a possible one first */
+	if (g_str_has_prefix (uri, "http") == FALSE &&
+	    g_str_has_prefix (uri, "itpc:") == FALSE &&
+	    g_str_has_prefix (uri, "itms:") == FALSE) {
+	    	rb_debug ("'%s' can't be a Podcast or OPML file, not the right scheme", uri);
+	    	return FALSE;
+	}
+
+	/* Now, check whether the iTunes Music Store link
+	 * is a podcast */
+	if (g_str_has_prefix (uri, "itms:") != FALSE
+	    && strstr (uri, "phobos.apple.com") != NULL
+	    && strstr (uri, "viewPodcast") != NULL)
+		return TRUE;
+
+	query_string = strchr (uri, '?');
+	if (query_string == NULL) {
+		query_string = uri + strlen (uri);
+	}
+
+	/* FIXME hacks */
+	if (strstr (uri, "rss") != NULL ||
+	    strstr (uri, "atom") != NULL ||
+	    strstr (uri, "feed") != NULL) {
+	    	rb_debug ("'%s' should be Podcast file, HACK", uri);
+	    	return TRUE;
+	} else if (strstr (uri, "opml") != NULL) {
+		rb_debug ("'%s' should be an OPML file, HACK", uri);
+		if (is_opml != NULL)
+			*is_opml = TRUE;
+		return TRUE;
+	}
+
+	if (strncmp (query_string - 4, ".rss", 4) == 0 ||
+	    strncmp (query_string - 4, ".xml", 4) == 0 ||
+	    strncmp (query_string - 5, ".atom", 5) == 0 ||
+	    strncmp (uri, "itpc", 4) == 0 ||
+	    (strstr (uri, "phobos.apple.com/") != NULL && strstr (uri, "viewPodcast") != NULL) ||
+	    strstr (uri, "itunes.com/podcast") != NULL) {
+	    	rb_debug ("'%s' should be Podcast file", uri);
+	    	return TRUE;
+	} else if (strncmp (query_string - 5, ".opml", 5) == 0) {
+		rb_debug ("'%s' should be an OPML file", uri);
+		if (is_opml != NULL)
+			*is_opml = TRUE;
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
 char *
 rb_uri_make_hidden (const char *text_uri)
 {
Index: lib/rb-file-helpers.h
===================================================================
--- lib/rb-file-helpers.h	(revision 5413)
+++ lib/rb-file-helpers.h	(working copy)
@@ -44,6 +44,7 @@
 gboolean	rb_uri_is_writable	(const char *uri);
 gboolean	rb_uri_is_local		(const char *uri);
 gboolean	rb_uri_is_hidden	(const char *uri);
+gboolean	rb_uri_could_be_podcast (const char *uri, gboolean *is_opml);
 char *		rb_uri_make_hidden      (const char *uri);
 char *		rb_uri_get_dir_name	(const char *uri);
 char *		rb_uri_get_short_path_name (const char *uri);
Index: shell/rb-shell.c
===================================================================
--- shell/rb-shell.c	(revision 5413)
+++ shell/rb-shell.c	(working copy)
@@ -3202,20 +3205,11 @@
 	gboolean source_is_entry;
 } PlaylistParseData;
 
-#if TOTEM_PL_PARSER_CHECK_VERSION(2,19,0)
 static void
 handle_playlist_entry_cb (TotemPlParser *playlist,
 			  const char *uri,
 			  GHashTable *metadata,
 			  PlaylistParseData *data)
-#else
-static void
-handle_playlist_entry_cb (TotemPlParser *playlist,
-			  const char *uri,
-			  const char *title,
-			  const char *genre,
-			  PlaylistParseData *data)
-#endif /* TOTEM_PL_PARSER_CHECK_VERSION */
 {
 	RBSource *source;
 
@@ -3264,6 +3258,14 @@
 	entry = rhythmdb_entry_lookup_by_location (shell->priv->db, uri);
 	playlist_source = NULL;
 
+	/* If the URI points to a Podcast, pass it on to
+	 * the Podcast source */
+	if (rb_uri_could_be_podcast (uri, NULL)) {
+		rb_podcast_source_add_feed (shell->priv->podcast_source, uri);
+		rb_shell_select_source (shell, RB_SOURCE (shell->priv->podcast_source));
+		return TRUE;
+	}
+
 	if (entry == NULL) {
 		TotemPlParser *parser;
 		TotemPlParserResult result;
@@ -3277,15 +3279,9 @@
 		rb_debug ("adding uri %s, play %d", uri, play);
 		parser = totem_pl_parser_new ();
 
-#if TOTEM_PL_PARSER_CHECK_VERSION(2,19,0)
 		g_signal_connect_data (G_OBJECT (parser), "entry-parsed",
 				       G_CALLBACK (handle_playlist_entry_cb),
 				       &data, NULL, 0);
-#else
-		g_signal_connect_data (G_OBJECT (parser), "entry",
-				       G_CALLBACK (handle_playlist_entry_cb),
-				       &data, NULL, 0);
-#endif /* TOTEM_PL_PARSER_CHECK_VERSION */
 
 		totem_pl_parser_add_ignored_mimetype (parser, "x-directory/normal");
 		if (g_object_class_find_property (G_OBJECT_GET_CLASS (parser), "recurse"))
Index: shell/rb-playlist-manager.c
===================================================================
--- shell/rb-playlist-manager.c	(revision 5413)
+++ shell/rb-playlist-manager.c	(working copy)
@@ -502,20 +502,11 @@
 	return quark;
 }
 
-#if TOTEM_PL_PARSER_CHECK_VERSION(2,19,0)
 static void
 handle_playlist_entry_cb (TotemPlParser *playlist,
 			  const char *uri_maybe,
 			  GHashTable *metadata,
 			  RBPlaylistManager *mgr)
-#else
-static void
-handle_playlist_entry_cb (TotemPlParser *playlist,
-			  const char *uri_maybe,
-			  const char *title,
-			  const char *genre,
-			  RBPlaylistManager *mgr)
-#endif /* TOTEM_PL_PARSER_CHECK_VERSION */
 {
 	char *uri;
 #if TOTEM_PL_PARSER_CHECK_VERSION(2,19,0)
@@ -550,19 +541,29 @@
 }
 
 static void
-playlist_load_start_cb (TotemPlParser *parser, const char *title, RBPlaylistManager *mgr)
+playlist_load_started_cb (TotemPlParser *parser, const char *uri, GHashTable *metadata, RBPlaylistManager *mgr)
 {
-	rb_debug ("loading new playlist %s", title);
+	const char *title;
 
+	rb_debug ("loading new playlist %s", uri);
+
+	title = g_hash_table_lookup (metadata, TOTEM_PL_PARSER_FIELD_TITLE);
+	if (title == NULL)
+		title = _("Unnamed playlist");
+
 	mgr->priv->loading_playlist =
 			RB_STATIC_PLAYLIST_SOURCE (rb_playlist_manager_new_playlist (mgr, title, FALSE));
 }
 
 static void
-playlist_load_end_cb (TotemPlParser *parser, const char *title, RBPlaylistManager *mgr)
+playlist_load_ended_cb (TotemPlParser *parser, const char *uri, GHashTable *metadata, RBPlaylistManager *mgr)
 {
-	rb_debug ("finished loading playlist %s", title);
+	const char *title;
 
+	rb_debug ("finished loading playlist %s", uri);
+
+	title = g_hash_table_lookup (metadata, TOTEM_PL_PARSER_FIELD_TITLE);
+
 	if (title) {
 		g_object_set (mgr->priv->loading_playlist, "name", title, NULL);
 		mgr->priv->loading_playlist = NULL;
@@ -591,22 +592,16 @@
 	{
 		TotemPlParser *parser = totem_pl_parser_new ();
 
-#if TOTEM_PL_PARSER_CHECK_VERSION(2,19,0)
 		g_signal_connect_object (parser, "entry-parsed",
 					 G_CALLBACK (handle_playlist_entry_cb),
 					 mgr, 0);
-#else
-		g_signal_connect_object (parser, "entry",
-					 G_CALLBACK (handle_playlist_entry_cb),
-					 mgr, 0);
-#endif /* TOTEM_PL_PARSER_CHECK_VERSION */
 
-		g_signal_connect_object (parser, "playlist-start",
-					 G_CALLBACK (playlist_load_start_cb),
+		g_signal_connect_object (parser, "playlist-started",
+					 G_CALLBACK (playlist_load_started_cb),
 					 mgr, 0);
 
-		g_signal_connect_object (parser, "playlist-end",
-					 G_CALLBACK (playlist_load_end_cb),
+		g_signal_connect_object (parser, "playlist-ended",
+					 G_CALLBACK (playlist_load_ended_cb),
 					 mgr, 0);
 
 		if (g_object_class_find_property (G_OBJECT_GET_CLASS (parser), "recurse"))
Index: shell/rb-shell-player.c
===================================================================
--- shell/rb-shell-player.c	(revision 5413)
+++ shell/rb-shell-player.c	(working copy)
@@ -1264,20 +1264,11 @@
 	PlaybackStartType play_type;
 } OpenLocationThreadData;
 
-#if TOTEM_PL_PARSER_CHECK_VERSION(2,19,0)
 static void
 playlist_entry_cb (TotemPlParser *playlist,
 		   const char *uri,
 		   GHashTable *metadata,
 		   RBShellPlayer *player)
-#else
-static void
-playlist_entry_cb (TotemPlParser *playlist,
-		   const char *uri,
-		   const char *title,
-		   const char *genre,
-		   RBShellPlayer *player)
-#endif
 {
 	rb_debug ("adding stream url %s", uri);
 	g_queue_push_tail (player->priv->playlist_urls, g_strdup (uri));
@@ -1291,15 +1282,9 @@
 
 	playlist = totem_pl_parser_new ();
 
-#if TOTEM_PL_PARSER_CHECK_VERSION(2,19,0)
 	g_signal_connect_data (G_OBJECT (playlist), "entry-parsed",
 			       G_CALLBACK (playlist_entry_cb),
 			       data->player, NULL, 0);
-#else
-	g_signal_connect_data (G_OBJECT (playlist), "entry",
-			       G_CALLBACK (playlist_entry_cb),
-			       data->player, NULL, 0);
-#endif /* TOTEM_PL_PARSER_CHECK_VERSION */
 
 	totem_pl_parser_add_ignored_mimetype (playlist, "x-directory/normal");
 
Index: data/rhythmbox.schemas
===================================================================
--- data/rhythmbox.schemas	(revision 5413)
+++ data/rhythmbox.schemas	(working copy)
@@ -136,9 +136,9 @@
 	<long>Main window X position.</long>
         </locale>
       </schema>
- 
 
-<schema>
+
+      <schema>
         <key>/schemas/apps/rhythmbox/state/window_position_y</key>
         <applyto>/apps/rhythmbox/state/window_position_y</applyto>
         <owner>rhythmbox</owner>
@@ -150,10 +150,7 @@
         </locale>
       </schema>
  
-
-
-
-<schema>
+      <schema>
         <key>/schemas/apps/rhythmbox/state/window_height</key>
         <applyto>/apps/rhythmbox/state/window_height</applyto>
         <owner>rhythmbox</owner>
@@ -439,6 +436,106 @@
         </locale>
       </schema>
       <schema>
+        <key>/schemas/desktop/gnome/url-handlers/itpc/command</key>
+	<applyto>/desktop/gnome/url-handlers/itpc/command</applyto>
+        <owner>rhythmbox</owner>
+        <type>string</type>
+        <default>rhythmbox "%s"</default>
+        <locale name="C">
+	  <short>The command to handle ITPC scheme URLs</short>
+	  <long>The command to handle ITPC scheme URLs.</long>
+        </locale>
+      </schema>
+      <schema>
+        <key>/schemas/desktop/gnome/url-handlers/itpc/needs_terminal</key>
+	<applyto>/desktop/gnome/url-handlers/itpc/needs_terminal</applyto>
+        <owner>rhythmbox</owner>
+        <type>bool</type>
+        <default>false</default>
+        <locale name="C">
+	  <short>Whether command to handle ITPC scheme URLs needs a terminal</short>
+	  <long>Whether command to handle ITPC scheme URLs needs a terminal.</long>
+        </locale>
+      </schema>
+      <schema>
+        <key>/schemas/desktop/gnome/url-handlers/itpc/enabled</key>
+	<applyto>/desktop/gnome/url-handlers/itpc/enabled</applyto>
+        <owner>rhythmbox</owner>
+        <type>bool</type>
+        <default>true</default>
+        <locale name="C">
+	  <short>Whether command to handle ITPC scheme URLs is enabled</short>
+	  <long>Whether command to handle ITPC scheme URLs is enabled.</long>
+        </locale>
+      </schema>
+      <schema>
+        <key>/schemas/desktop/gnome/url-handlers/itms/command</key>
+	<applyto>/desktop/gnome/url-handlers/itms/command</applyto>
+        <owner>rhythmbox</owner>
+        <type>string</type>
+        <default>rhythmbox "%s"</default>
+        <locale name="C">
+	  <short>The command to handle ITMS scheme URLs</short>
+	  <long>The command to handle ITMS scheme URLs.</long>
+        </locale>
+      </schema>
+      <schema>
+        <key>/schemas/desktop/gnome/url-handlers/itms/needs_terminal</key>
+	<applyto>/desktop/gnome/url-handlers/itms/needs_terminal</applyto>
+        <owner>rhythmbox</owner>
+        <type>bool</type>
+        <default>false</default>
+        <locale name="C">
+	  <short>Whether command to handle ITMS scheme URLs needs a terminal</short>
+	  <long>Whether command to handle ITMS scheme URLs needs a terminal.</long>
+        </locale>
+      </schema>
+      <schema>
+        <key>/schemas/desktop/gnome/url-handlers/itms/enabled</key>
+	<applyto>/desktop/gnome/url-handlers/itms/enabled</applyto>
+        <owner>rhythmbox</owner>
+        <type>bool</type>
+        <default>true</default>
+        <locale name="C">
+	  <short>Whether command to handle ITMS scheme URLs is enabled</short>
+	  <long>Whether command to handle ITMS scheme URLs is enabled.</long>
+        </locale>
+      </schema>
+      <schema>
+        <key>/schemas/desktop/gnome/url-handlers/feed/command</key>
+	<applyto>/desktop/gnome/url-handlers/feed/command</applyto>
+        <owner>rhythmbox</owner>
+        <type>string</type>
+        <default>rhythmbox "%s"</default>
+        <locale name="C">
+	  <short>The command to handle FEED scheme URLs</short>
+	  <long>The command to handle FEED scheme URLs.</long>
+        </locale>
+      </schema>
+      <schema>
+        <key>/schemas/desktop/gnome/url-handlers/feed/needs_terminal</key>
+	<applyto>/desktop/gnome/url-handlers/feed/needs_terminal</applyto>
+        <owner>rhythmbox</owner>
+        <type>bool</type>
+        <default>false</default>
+        <locale name="C">
+	  <short>Whether command to handle FEED scheme URLs needs a terminal</short>
+	  <long>Whether command to handle FEED scheme URLs needs a terminal.</long>
+        </locale>
+      </schema>
+      <schema>
+        <key>/schemas/desktop/gnome/url-handlers/feed/enabled</key>
+	<applyto>/desktop/gnome/url-handlers/feed/enabled</applyto>
+        <owner>rhythmbox</owner>
+        <type>bool</type>
+        <default>true</default>
+        <locale name="C">
+	  <short>Whether command to handle FEED scheme URLs is enabled</short>
+	  <long>Whether command to handle FEED scheme URLs is enabled.</long>
+        </locale>
+      </schema>
+
+      <schema>
         <key>/schemas/apps/rhythmbox/state/podcast/show_browser</key>
         <applyto>/apps/rhythmbox/state/podcast/show_browser</applyto>
         <owner>rhythmbox</owner>