[rhythmbox] podcast: add child sources showing new episodes and recent downloads



commit 1cfd8d0af85dbbccdf6fb0292ac61780fa45df50
Author: Jonathan Matthew <jonathan d14n org>
Date:   Sun Oct 10 23:42:28 2010 +1000

    podcast: add child sources showing new episodes and recent downloads
    
    This splits the podcast source in two, into a base class that provides the feed
    and post views and implements the various UI actions, and a subclass that
    contains the parts unique to the main source, mostly the configuration dialog.
    
    The child sources are populated by database queries.  The new episode source
    uses the post-time property and the new download source uses the modification
    time, which previously wasn't set at all for podcast posts, but now is set to
    the current time when the download starts.
    
    The queries are currently hardcoded to match episodes posted and downloaded in
    the last week, but we could provide hour/day/week/month selections if that
    seems useful enough.
    
    The child sources currently use the same gconf keys as the main source, so they
    have the same sort order and browser settings.

 po/POTFILES.in                           |    3 +-
 podcast/Makefile.am                      |    6 +-
 podcast/rb-podcast-main-source.c         |  422 ++++++
 podcast/rb-podcast-main-source.h         |   65 +
 podcast/rb-podcast-manager.c             |   23 +-
 {sources => podcast}/rb-podcast-source.c | 2186 ++++++++++++------------------
 {sources => podcast}/rb-podcast-source.h |   12 +-
 rhythmdb/rhythmdb-property-model.c       |    1 +
 shell/rb-shell.c                         |   16 +-
 sources/Makefile.am                      |    4 +-
 10 files changed, 1387 insertions(+), 1351 deletions(-)
---
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 7955df9..041eb51 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -154,9 +154,11 @@ plugins/visualizer/rb-visualizer-plugin.c
 [type: gettext/glade]plugins/visualizer/visualizer-controls.ui
 [type: gettext/ini]plugins/visualizer/visualizer.rb-plugin.in
 podcast/rb-feed-podcast-properties-dialog.c
+podcast/rb-podcast-main-source.c
 podcast/rb-podcast-manager.c
 podcast/rb-podcast-parse.c
 podcast/rb-podcast-properties-dialog.c
+podcast/rb-podcast-source.c
 remote/dbus/rb-client.c
 rhythmdb/rhythmdb-monitor.c
 rhythmdb/rhythmdb-property-model.c
@@ -188,7 +190,6 @@ sources/rb-media-player-source.c
 sources/rb-missing-files-source.c
 sources/rb-play-queue-source.c
 sources/rb-playlist-source.c
-sources/rb-podcast-source.c
 sources/rb-source.c
 sources/rb-source-group.c
 sources/rb-sourcelist.c
diff --git a/podcast/Makefile.am b/podcast/Makefile.am
index a4c86e1..c68e323 100644
--- a/podcast/Makefile.am
+++ b/podcast/Makefile.am
@@ -17,6 +17,10 @@ librbpodcast_la_SOURCES =				\
 	rb-feed-podcast-properties-dialog.h		\
 	rb-podcast-properties-dialog.c			\
 	rb-podcast-properties-dialog.h			\
+	rb-podcast-main-source.c			\
+	rb-podcast-main-source.h			\
+	rb-podcast-source.c				\
+	rb-podcast-source.h				\
 	rb-podcast-parse.c				\
 	rb-podcast-manager.c				\
 	rb-podcast-entry-types.c
@@ -39,7 +43,7 @@ AM_CFLAGS =						\
 	-I$(top_srcdir)/widgets				\
 	-I$(top_srcdir)/shell				\
 	-I$(top_srcdir)/metadata			\
-	-I$(top_srcdir)/library				\
+	-I$(top_srcdir)/sources				\
 	-I$(top_builddir)/lib 				\
 	$(RHYTHMBOX_CFLAGS)				\
 	$(WEBKIT_CFLAGS)				\
diff --git a/podcast/rb-podcast-main-source.c b/podcast/rb-podcast-main-source.c
new file mode 100644
index 0000000..6741caa
--- /dev/null
+++ b/podcast/rb-podcast-main-source.c
@@ -0,0 +1,422 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ *  Copyright (C) 2010 Jonathan Matthew <jonathan d14n org>
+ *
+ *  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.
+ *
+ *  The Rhythmbox authors hereby grant permission for non-GPL compatible
+ *  GStreamer plugins to be used and distributed together with GStreamer
+ *  and Rhythmbox. This permission is above and beyond the permissions granted
+ *  by the GPL license by which Rhythmbox is covered. If you modify this code
+ *  you may extend this exception to your version of the code, but you are not
+ *  obligated to do so. If you do not wish to do so, delete this exception
+ *  statement from your 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 <glib.h>
+#include <glib/gi18n.h>
+
+#include "rb-podcast-main-source.h"
+#include "rb-podcast-entry-types.h"
+#include "rb-shell.h"
+#include "eel-gconf-extensions.h"
+#include "rb-builder-helpers.h"
+#include "rb-file-helpers.h"
+#include "rb-util.h"
+#include "rb-stock-icons.h"
+
+#define CONF_STATE_PODCAST_PREFIX		CONF_PREFIX "/state/podcast"
+#define CONF_STATE_PODCAST_DOWNLOAD_INTERVAL	CONF_STATE_PODCAST_PREFIX "/download_interval"
+#define CONF_STATE_PODCAST_DOWNLOAD_DIR		CONF_STATE_PODCAST_PREFIX "/download_prefix"
+
+struct _RBPodcastMainSourcePrivate
+{
+	GtkWidget *config_widget;
+};
+
+G_DEFINE_TYPE (RBPodcastMainSource, rb_podcast_main_source, RB_TYPE_PODCAST_SOURCE)
+
+
+RBSource *
+rb_podcast_main_source_new (RBShell *shell, RBPodcastManager *podcast_manager)
+{
+	RBSource *source;
+	RhythmDBQuery *base_query;
+	RhythmDB *db;
+
+	g_object_get (shell, "db", &db, NULL);
+	base_query = rhythmdb_query_parse (db,
+					   RHYTHMDB_QUERY_PROP_EQUALS,
+					   RHYTHMDB_PROP_TYPE,
+					   RHYTHMDB_ENTRY_TYPE_PODCAST_POST,
+					   RHYTHMDB_QUERY_END);
+	g_object_unref (db);
+
+	source = RB_SOURCE (g_object_new (RB_TYPE_PODCAST_MAIN_SOURCE,
+					  "name", _("Podcasts"),
+					  "shell", shell,
+					  "entry-type", RHYTHMDB_ENTRY_TYPE_PODCAST_POST,
+					  "source-group", RB_SOURCE_GROUP_LIBRARY,
+					  "search-type", RB_SOURCE_SEARCH_INCREMENTAL,
+					  "podcast-manager", podcast_manager,
+					  "base-query", base_query,
+					  NULL));
+
+	rhythmdb_query_free (base_query);
+
+	rb_shell_register_entry_type_for_source (shell, source,
+						 RHYTHMDB_ENTRY_TYPE_PODCAST_FEED);
+	rb_shell_register_entry_type_for_source (shell, source,
+						 RHYTHMDB_ENTRY_TYPE_PODCAST_POST);
+
+	return source;
+}
+
+void
+rb_podcast_main_source_add_subsources (RBPodcastMainSource *source)
+{
+	RhythmDBQuery *query;
+	RBSource *podcast_subsource;
+	RBPodcastManager *podcast_mgr;
+	RhythmDB *db;
+	RBShell *shell;
+
+	g_object_get (source,
+		      "shell", &shell,
+		      "podcast-manager", &podcast_mgr,
+		      NULL);
+	g_object_get (shell, "db", &db, NULL);
+
+	query = rhythmdb_query_parse (db,
+				      RHYTHMDB_QUERY_PROP_EQUALS,
+				      RHYTHMDB_PROP_TYPE,
+				      RHYTHMDB_ENTRY_TYPE_PODCAST_POST,
+				      RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN,
+				      RHYTHMDB_PROP_FIRST_SEEN,
+				      3600 * 24 * 7,
+				      RHYTHMDB_QUERY_END);
+
+	podcast_subsource = rb_podcast_source_new (shell,
+						   podcast_mgr,
+						   query,
+						   _("New Episodes"),
+						   RB_STOCK_AUTO_PLAYLIST);
+	rhythmdb_query_free (query);
+	rb_source_set_hidden_when_empty (podcast_subsource, TRUE);
+	rb_shell_append_source (shell, podcast_subsource, RB_SOURCE (source));
+
+	query = rhythmdb_query_parse (db,
+				      RHYTHMDB_QUERY_PROP_EQUALS,
+				      RHYTHMDB_PROP_TYPE,
+				      RHYTHMDB_ENTRY_TYPE_PODCAST_POST,
+				      RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN,
+				      RHYTHMDB_PROP_LAST_SEEN,
+				      3600 * 24 * 7,
+				      RHYTHMDB_QUERY_END);
+
+	podcast_subsource = rb_podcast_source_new (shell,
+						   podcast_mgr,
+						   query,
+						   _("New Downloads"),		/* better name? */
+						   RB_STOCK_AUTO_PLAYLIST);
+	rhythmdb_query_free (query);
+	rb_source_set_hidden_when_empty (podcast_subsource, TRUE);
+	rb_shell_append_source (shell, podcast_subsource, RB_SOURCE (source));
+
+	g_object_unref (db);
+	g_object_unref (shell);
+}
+
+static void
+start_download_cb (RBPodcastManager *pd,
+		   RhythmDBEntry *entry,
+		   RBPodcastMainSource *source)
+{
+	RBShell *shell;
+	char *podcast_name;
+
+	podcast_name = g_markup_escape_text (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_TITLE), -1);
+
+	g_object_get (source, "shell", &shell, NULL);
+	rb_shell_notify_custom (shell, 4000, _("Downloading podcast"), podcast_name, NULL, FALSE);
+	g_object_unref (shell);
+
+	g_free (podcast_name);
+}
+
+static void
+finish_download_cb (RBPodcastManager *pd,
+		    RhythmDBEntry *entry,
+		    RBPodcastMainSource *source)
+{
+	RBShell *shell;
+	char *podcast_name;
+
+	podcast_name = g_markup_escape_text (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_TITLE), -1);
+
+	g_object_get (source, "shell", &shell, NULL);
+	rb_shell_notify_custom (shell, 4000, _("Finished downloading podcast"), podcast_name, NULL, FALSE);
+	g_object_unref (shell);
+
+	g_free (podcast_name);
+}
+
+static void
+feed_updates_available_cb (RBPodcastManager *pd,
+			   RhythmDBEntry *entry,
+			   RBPodcastMainSource *source)
+{
+	RBShell *shell;
+	char *podcast_name;
+
+	podcast_name = g_markup_escape_text (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_TITLE), -1);
+
+	g_object_get (source, "shell", &shell, NULL);
+	rb_shell_notify_custom (shell, 4000, _("New updates available from"), podcast_name, NULL, FALSE);
+	g_object_unref (shell);
+
+	g_free (podcast_name);
+
+}
+
+static gboolean
+feed_error_cb (RBPodcastManager *pd,
+	       const char *error,
+	       gboolean existing,
+	       RBPodcastMainSource *source)
+{
+	GtkWidget *dialog;
+	int result;
+
+	/* if the podcast feed doesn't already exist in the db,
+	 * ask if the user wants to add it anyway; if it already
+	 * exists, there's nothing to do besides reporting the error.
+	 */
+	dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (source))),
+					 GTK_DIALOG_DESTROY_WITH_PARENT,
+					 GTK_MESSAGE_ERROR,
+					 existing ? GTK_BUTTONS_OK : GTK_BUTTONS_YES_NO,
+					 _("Error in podcast"));
+
+	if (existing) {
+		gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+							  "%s", error);
+	} else {
+		gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+							  _("%s. Would you like to add the podcast feed anyway?"), error);
+	}
+
+	gtk_window_set_title (GTK_WINDOW (dialog), "");
+	gtk_container_set_border_width (GTK_CONTAINER (dialog), 6);
+
+	result = gtk_dialog_run (GTK_DIALOG (dialog));
+	gtk_widget_destroy (dialog);
+
+	/* in the existing feed case, the response will be _OK or _NONE.
+	 * we want to return FALSE here in this case, so this check works.
+	 */
+	return (result == GTK_RESPONSE_YES);
+}
+
+static void
+rb_podcast_main_source_btn_file_change_cb (GtkFileChooserButton *widget, const char *key)
+{
+	char *uri;
+	
+	uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (widget));
+	eel_gconf_set_string (key, uri);
+	g_free (uri);
+}
+
+static void
+rb_podcast_main_source_cb_interval_changed_cb (GtkComboBox *box, gpointer cb_data)
+{
+	RBPodcastManager *podcast_mgr;
+
+	guint index = gtk_combo_box_get_active (box);
+	eel_gconf_set_integer (CONF_STATE_PODCAST_DOWNLOAD_INTERVAL,
+			       index);
+
+	g_object_get (cb_data, "podcast-manager", &podcast_mgr, NULL);
+	rb_podcast_manager_start_sync (podcast_mgr);
+	g_object_unref (podcast_mgr);
+}
+
+static GtkWidget *
+impl_get_config_widget (RBSource *asource, RBShellPreferences *prefs)
+{
+	RBPodcastMainSource *source = RB_PODCAST_MAIN_SOURCE (asource);
+	RBPodcastManager *podcast_mgr;
+	GtkBuilder *builder;
+	GtkWidget *cb_update_interval;
+	GtkWidget *btn_file;
+	char *download_dir;
+
+	if (source->priv->config_widget)
+		return source->priv->config_widget;
+
+	builder = rb_builder_load ("podcast-prefs.ui", source);
+	source->priv->config_widget = GTK_WIDGET (gtk_builder_get_object (builder, "podcast_vbox"));
+
+	btn_file = GTK_WIDGET (gtk_builder_get_object (builder, "location_chooser"));
+	gtk_file_chooser_add_shortcut_folder (GTK_FILE_CHOOSER (btn_file),
+					      rb_music_dir (),
+					      NULL);
+
+	g_object_get (source, "podcast-manager", &podcast_mgr, NULL);
+	download_dir = rb_podcast_manager_get_podcast_dir (podcast_mgr);
+	g_object_unref (podcast_mgr);
+
+	gtk_file_chooser_set_current_folder_uri (GTK_FILE_CHOOSER (btn_file),
+						 download_dir);
+	g_free (download_dir);
+
+	g_signal_connect (btn_file,
+			  "selection-changed",
+			  G_CALLBACK (rb_podcast_main_source_btn_file_change_cb),
+			  CONF_STATE_PODCAST_DOWNLOAD_DIR);
+
+	cb_update_interval = GTK_WIDGET (gtk_builder_get_object (builder, "cb_update_interval"));
+	gtk_combo_box_set_active (GTK_COMBO_BOX (cb_update_interval),
+				  eel_gconf_get_integer (CONF_STATE_PODCAST_DOWNLOAD_INTERVAL));
+	g_signal_connect (cb_update_interval,
+			  "changed",
+			  G_CALLBACK (rb_podcast_main_source_cb_interval_changed_cb),
+			  source);
+
+	return source->priv->config_widget;
+}
+
+static guint
+impl_want_uri (RBSource *source, const char *uri)
+{
+	if (g_str_has_prefix (uri, "http://";) == FALSE)
+		return 0;
+
+	if (g_str_has_suffix (uri, ".xml") ||
+	    g_str_has_suffix (uri, ".rss"))
+		return 100;
+
+	return 0;
+}
+
+static void
+impl_add_uri (RBSource *source,
+	      const char *uri,
+	      const char *title,
+	      const char *genre,
+	      RBSourceAddCallback callback,
+	      gpointer data,
+	      GDestroyNotify destroy_data)
+{
+	RBPodcastManager *podcast_mgr;
+
+	g_object_get (source, "podcast-manager", &podcast_mgr, NULL);
+	rb_podcast_manager_subscribe_feed (podcast_mgr, uri, FALSE);
+	g_object_unref (podcast_mgr);
+
+	if (callback != NULL) {
+		callback (source, uri, data);
+		if (destroy_data != NULL) {
+			destroy_data (data);
+		}
+	}
+}
+
+static void
+impl_constructed (GObject *object)
+{
+	RBPodcastMainSource *source;
+	RBPodcastManager *podcast_mgr;
+	GdkPixbuf *pixbuf;
+	gint size;
+	
+	RB_CHAIN_GOBJECT_METHOD (rb_podcast_main_source_parent_class, constructed, object);
+	source = RB_PODCAST_MAIN_SOURCE (object);
+
+	g_object_get (source, "podcast-manager", &podcast_mgr, NULL);
+
+	g_signal_connect_object (podcast_mgr,
+	 		        "start_download",
+			  	G_CALLBACK (start_download_cb),
+			  	source, 0);
+
+	g_signal_connect_object (podcast_mgr,
+			  	"finish_download",
+			  	G_CALLBACK (finish_download_cb),
+			  	source, 0);
+
+	g_signal_connect_object (podcast_mgr,
+			  	"feed_updates_available",
+ 			  	G_CALLBACK (feed_updates_available_cb),
+			  	source, 0);
+
+	g_signal_connect_object (podcast_mgr,
+			  	 "process_error",
+			 	 G_CALLBACK (feed_error_cb),
+			  	 source, 0);
+
+	gtk_icon_size_lookup (RB_SOURCE_ICON_SIZE, &size, NULL);
+	pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
+					   RB_STOCK_PODCAST,
+					   size,
+					   0, NULL);
+
+	if (pixbuf != NULL) {
+		rb_source_set_pixbuf (RB_SOURCE (source), pixbuf);
+		g_object_unref (pixbuf);
+	}
+}
+
+static void
+impl_dispose (GObject *object)
+{
+	RBPodcastMainSource *source;
+
+	source = RB_PODCAST_MAIN_SOURCE (object);
+	if (source->priv->config_widget != NULL) {
+		g_object_unref (source->priv->config_widget);
+		source->priv->config_widget = NULL;
+	}
+
+	G_OBJECT_CLASS (rb_podcast_main_source_parent_class)->dispose (object);
+}
+
+static void
+rb_podcast_main_source_init (RBPodcastMainSource *source)
+{
+	source->priv = G_TYPE_INSTANCE_GET_PRIVATE (source,
+						    RB_TYPE_PODCAST_MAIN_SOURCE,
+						    RBPodcastMainSourcePrivate);
+}
+
+static void
+rb_podcast_main_source_class_init (RBPodcastMainSourceClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
+
+	object_class->dispose = impl_dispose;
+	object_class->constructed = impl_constructed;
+
+	source_class->impl_get_config_widget = impl_get_config_widget;
+	source_class->impl_want_uri = impl_want_uri;
+	source_class->impl_add_uri = impl_add_uri;
+	
+	g_type_class_add_private (klass, sizeof (RBPodcastMainSourcePrivate));
+}
diff --git a/podcast/rb-podcast-main-source.h b/podcast/rb-podcast-main-source.h
new file mode 100644
index 0000000..b84ad0a
--- /dev/null
+++ b/podcast/rb-podcast-main-source.h
@@ -0,0 +1,65 @@
+/*
+ *  Copyright (C) 2010  Jonathan Matthew  <jonathan d14n org>
+ *
+ *  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.
+ *
+ *  The Rhythmbox authors hereby grant permission for non-GPL compatible
+ *  GStreamer plugins to be used and distributed together with GStreamer
+ *  and Rhythmbox. This permission is above and beyond the permissions granted
+ *  by the GPL license by which Rhythmbox is covered. If you modify this code
+ *  you may extend this exception to your version of the code, but you are not
+ *  obligated to do so. If you do not wish to do so, delete this exception
+ *  statement from your 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_PODCAST_MAIN_SOURCE_H
+#define __RB_PODCAST_MAIN_SOURCE_H
+
+#include <podcast/rb-podcast-source.h>
+#include <podcast/rb-podcast-manager.h>
+
+#define RB_TYPE_PODCAST_MAIN_SOURCE         (rb_podcast_main_source_get_type ())
+#define RB_PODCAST_MAIN_SOURCE(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), RB_TYPE_PODCAST_MAIN_SOURCE, RBPodcastMainSource))
+#define RB_PODCAST_MAIN_SOURCE_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), RB_TYPE_PODCAST_MAIN_SOURCE, RBPodcastMainSourceClass))
+#define RB_IS_PODCAST_MAIN_SOURCE(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), RB_TYPE_PODCAST_MAIN_SOURCE))
+#define RB_IS_PODCAST_MAIN_SOURCE_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), RB_TYPE_PODCAST_MAIN_SOURCE))
+#define RB_PODCAST_MAIN_SOURCE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), RB_TYPE_PODCAST_MAIN_SOURCE, RBPodcastMainSourceClass))
+
+typedef struct _RBPodcastMainSource RBPodcastMainSource;
+typedef struct _RBPodcastMainSourceClass RBPodcastMainSourceClass;
+
+typedef struct _RBPodcastMainSourcePrivate RBPodcastMainSourcePrivate;
+
+struct _RBPodcastMainSource
+{
+	RBPodcastSource parent;
+
+	RBPodcastMainSourcePrivate *priv;
+};
+
+struct _RBPodcastMainSourceClass
+{
+	RBPodcastSourceClass parent;
+};
+
+GType		rb_podcast_main_source_get_type 	(void);
+
+RBSource	*rb_podcast_main_source_new 		(RBShell *shell,
+							 RBPodcastManager *podcast_manager);
+
+void		rb_podcast_main_source_add_subsources	(RBPodcastMainSource *source);
+
+#endif /* __RB_PODCAST_MAIN_SOURCE_H */
diff --git a/podcast/rb-podcast-manager.c b/podcast/rb-podcast-manager.c
index a972490..c83f90a 100644
--- a/podcast/rb-podcast-manager.c
+++ b/podcast/rb-podcast-manager.c
@@ -459,15 +459,24 @@ rb_podcast_manager_download_entry (RBPodcastManager *pd,
 	if ((status < RHYTHMDB_PODCAST_STATUS_COMPLETE) ||
 	    (status == RHYTHMDB_PODCAST_STATUS_WAITING)) {
 		RBPodcastManagerInfo *data;
-		if (status < RHYTHMDB_PODCAST_STATUS_COMPLETE) {
-			GValue status_val = { 0, };
-			g_value_init (&status_val, G_TYPE_ULONG);
-			g_value_set_ulong (&status_val, RHYTHMDB_PODCAST_STATUS_WAITING);
-			rhythmdb_entry_set (pd->priv->db, entry, RHYTHMDB_PROP_STATUS, &status_val);
-			g_value_unset (&status_val);
+		GValue val = { 0, };
+		GTimeVal now;
 
-			rhythmdb_commit (pd->priv->db);
+		if (status < RHYTHMDB_PODCAST_STATUS_COMPLETE) {
+			g_value_init (&val, G_TYPE_ULONG);
+			g_value_set_ulong (&val, RHYTHMDB_PODCAST_STATUS_WAITING);
+			rhythmdb_entry_set (pd->priv->db, entry, RHYTHMDB_PROP_STATUS, &val);
+			g_value_unset (&val);
 		}
+
+		/* set last seen time so it shows up in the 'new downloads' subsource */
+		g_value_init (&val, G_TYPE_ULONG);
+		g_get_current_time (&now);
+		g_value_set_ulong (&val, now.tv_sec);
+		rhythmdb_entry_set (pd->priv->db, entry, RHYTHMDB_PROP_LAST_SEEN, &val);
+		g_value_unset (&val);
+		rhythmdb_commit (pd->priv->db);
+
 		rb_debug ("Adding podcast episode %s to download list", get_remote_location (entry));
 
 		data = g_new0 (RBPodcastManagerInfo, 1);
diff --git a/sources/rb-podcast-source.c b/podcast/rb-podcast-source.c
similarity index 61%
rename from sources/rb-podcast-source.c
rename to podcast/rb-podcast-source.c
index 77ae3ab..63a5989 100644
--- a/sources/rb-podcast-source.c
+++ b/podcast/rb-podcast-source.c
@@ -27,13 +27,8 @@
  */
 
 /*
- * SECTION:rb-podcast-source
- * @short_description: source displaying podcast feeds and episodes
- *
- * The podcast source displays podcast episodes in its entry view
- * and podcast feeds in a property view.  It uses a few custom columns
- * to display podcast-specific information: episode download status
- * and an indication of feed parsing errors.
+ * Base source for podcast sources.  This provides the feed
+ * and post views, the search actions, and so on.
  */
 
 #include "config.h"
@@ -51,8 +46,6 @@
 
 #include "rhythmdb.h"
 #include "rhythmdb-query-model.h"
-#include "rb-statusbar.h"
-#include "rb-builder-helpers.h"
 #include "rb-stock-icons.h"
 #include "rb-entry-view.h"
 #include "rb-property-view.h"
@@ -72,221 +65,33 @@
 #include "rb-source-search-basic.h"
 #include "rb-cell-renderer-pixbuf.h"
 
-static void rb_podcast_source_class_init 		(RBPodcastSourceClass *klass);
-
-static void rb_podcast_source_init 			(RBPodcastSource *source);
-
-static void rb_podcast_source_constructed 		(GObject *object);
-
-static void rb_podcast_source_dispose 			(GObject *object);
-
-static void rb_podcast_source_finalize 			(GObject *object);
-
-static void rb_podcast_source_set_property 		(GObject *object,
-			                  		 guint prop_id,
-			                  		 const GValue *value,
-			                  		 GParamSpec *pspec);
-
-static void rb_podcast_source_get_property 		(GObject *object,
-			                  		 guint prop_id,
-			                  		 GValue *value,
-			                  		 GParamSpec *pspec);
-
-static void rb_podcast_source_songs_show_popup_cb 	(RBEntryView *view,
-							 gboolean over_entry,
-						  	 RBPodcastSource *source);
-
-static void rb_podcast_source_feeds_show_popup_cb 	(RBPropertyView *view,
-						  	 RBPodcastSource *source);
-
-static void paned_size_allocate_cb 			(GtkWidget *widget,
-				    			 GtkAllocation *allocation,
-		                    			 RBPodcastSource *source);
-
-static void rb_podcast_source_state_pref_changed 	(GConfClient *client,
-						 	 guint cnxn_id,
-						 	 GConfEntry *entry,
-					 		 RBPodcastSource *source);
-
-static void rb_podcast_source_show_browser 		(RBPodcastSource *source,
-					   		 gboolean show);
-
-static void rb_podcast_source_state_prefs_sync 		(RBPodcastSource *source);
-
-static void feed_select_change_cb 			(RBPropertyView *propview,
-							 GList *feeds,
-			 			       	 RBPodcastSource *podcast_source);
-
-static void rb_podcast_source_posts_view_sort_order_changed_cb (RBEntryView *view,
-								RBPodcastSource *source);
-
-static void rb_podcast_source_download_status_changed_cb(RBPodcastManager *download,
-							 RhythmDBEntry *entry,
-							 gulong status,
-							 RBPodcastSource *source);
-
-static void rb_podcast_source_btn_file_change_cb 	(GtkFileChooserButton *widget,
-							 const char *key);
-
-static void posts_view_drag_data_received_cb 		(GtkWidget *widget,
-					      		 GdkDragContext *dc,
-  				              		 gint x,
-							 gint y,
-				              		 GtkSelectionData *selection_data,
-				              		 guint info,
-							 guint time,
-				              		 RBPodcastSource *source);
-
-static void rb_podcast_source_cmd_download_post		(GtkAction *action,
-							 RBPodcastSource *source);
-static void rb_podcast_source_cmd_cancel_download	(GtkAction *action,
-							 RBPodcastSource *source);
-static void rb_podcast_source_cmd_delete_feed		(GtkAction *action,
-							 RBPodcastSource *source);
-static void rb_podcast_source_cmd_update_feed		(GtkAction *action,
-							 RBPodcastSource *source);
-static void rb_podcast_source_cmd_update_all		(GtkAction *action,
-							 RBPodcastSource *source);
-static void rb_podcast_source_cmd_properties_feed	(GtkAction *action,
-							 RBPodcastSource *source);
-
-static gint rb_podcast_source_post_date_cell_sort_func 	(RhythmDBEntry *a,
-							 RhythmDBEntry *b,
-		                                   	 RhythmDBQueryModel *model);
-
-static gint rb_podcast_source_post_status_cell_sort_func(RhythmDBEntry *a,
-							 RhythmDBEntry *b,
-		                                   	 RhythmDBQueryModel *model);
-
-static gint rb_podcast_source_post_feed_cell_sort_func 	(RhythmDBEntry *a,
-							 RhythmDBEntry *b,
-		                                   	 RhythmDBQueryModel *model);
-
-static void rb_podcast_source_post_status_cell_data_func(GtkTreeViewColumn *column,
-							 GtkCellRenderer *renderer,
-						     	 GtkTreeModel *tree_model,
-							 GtkTreeIter *iter,
-						     	 RBPodcastSource *source);
-
-static void rb_podcast_source_post_date_cell_data_func 	(GtkTreeViewColumn *column,
-							 GtkCellRenderer *renderer,
-		 			     	   	 GtkTreeModel *tree_model,
-							 GtkTreeIter *iter,
-				     	           	 RBPodcastSource *source);
-
-static void rb_podcast_source_post_feed_cell_data_func  (GtkTreeViewColumn *column,
-							 GtkCellRenderer *renderer,
-						     	 GtkTreeModel *tree_model,
-							 GtkTreeIter *iter,
-						     	 RBPodcastSource *source);
-
-static gboolean rb_podcast_source_feed_title_search_func (GtkTreeModel *model,
-							  gint column,
-							  const gchar *key,
-							  GtkTreeIter *iter,
-							  RBPodcastSource *source);
-static void rb_podcast_source_feed_title_cell_data_func (GtkTreeViewColumn *column,
-							 GtkCellRenderer *renderer,
-						     	 GtkTreeModel *tree_model,
-							 GtkTreeIter *iter,
-						     	 RBPodcastSource *source);
-static void rb_podcast_source_feed_error_cell_data_func (GtkTreeViewColumn *column,
-							 GtkCellRenderer *renderer,
-						     	 GtkTreeModel *tree_model,
-							 GtkTreeIter *iter,
-						     	 RBPodcastSource *source);
-
-static void rb_podcast_source_start_download_cb 	(RBPodcastManager *pd,
-							 RhythmDBEntry *entry,
-							 RBPodcastSource *source);
-
-static void rb_podcast_source_finish_download_cb 	(RBPodcastManager *pd,
-							 RhythmDBEntry *entry,
-							 RBPodcastSource *source);
-
-static void rb_podcast_source_feed_updates_available_cb (RBPodcastManager *pd,
-							 RhythmDBEntry *entry,
-							 RBPodcastSource *source);
-
-static gboolean rb_podcast_source_download_process_error_cb (RBPodcastManager *pd,
-							 const char *error,
-							 gboolean existing,
-					  		 RBPodcastSource *source);
-
-static void rb_podcast_source_cb_interval_changed_cb 	(GtkComboBox *box, gpointer cb_data);
-
-static gboolean rb_podcast_source_load_finish_cb  	(gpointer cb_data);
-static RBShell *rb_podcast_source_get_shell		(RBPodcastSource *source);
-static void rb_podcast_source_entry_activated_cb (RBEntryView *view,
-						  RhythmDBEntry *entry,
-						  RBPodcastSource *source);
-static void rb_podcast_source_cmd_new_podcast	 (GtkAction *action,
-						  RBPodcastSource *source);
-static void rb_podcast_source_entry_changed_cb	(RhythmDB *db,
-						 RhythmDBEntry *entry,
-						 GValueArray *changes,
+static void podcast_cmd_new_podcast	 	(GtkAction *action,
+						 RBPodcastSource *source);
+static void podcast_cmd_download_post		(GtkAction *action,
+						 RBPodcastSource *source);
+static void podcast_cmd_cancel_download		(GtkAction *action,
+						 RBPodcastSource *source);
+static void podcast_cmd_delete_feed		(GtkAction *action,
+						 RBPodcastSource *source);
+static void podcast_cmd_update_feed		(GtkAction *action,
 						 RBPodcastSource *source);
-static void rb_podcast_source_pixbuf_clicked_cb	(RBCellRendererPixbuf *renderer,
-						 const char *path,
+static void podcast_cmd_update_all		(GtkAction *action,
+						 RBPodcastSource *source);
+static void podcast_cmd_properties_feed		(GtkAction *action,
 						 RBPodcastSource *source);
 
-/* source methods */
-static char *impl_get_browser_key	 		(RBSource *source);
-static RBEntryView *impl_get_entry_view 		(RBSource *source);
-static void impl_search 				(RBSource *source,
-							 RBSourceSearch *search,
-							 const char *current,
-							 const char *next);
-static void impl_delete 				(RBSource *source);
-static void impl_song_properties 			(RBSource *source);
-static RBSourceEOFType impl_handle_eos 			(RBSource *asource);
-static gboolean impl_show_popup 			(RBSource *source);
-static void rb_podcast_source_do_query			(RBPodcastSource *source);
-static GtkWidget *impl_get_config_widget 		(RBSource *source,
-							 RBShellPreferences *prefs);
-static gboolean impl_receive_drag 			(RBSource *source,
-							 GtkSelectionData *data);
-static gboolean impl_can_add_to_queue			(RBSource *source);
-static void impl_add_to_queue				(RBSource *source, RBSource *queue);
-static GList *impl_get_ui_actions			(RBSource *source);
-static GList *impl_get_search_actions			(RBSource *source);
-static void impl_get_status				(RBSource *source,
-							 char **text,
-							 char **progress_text,
-							 float *progress);
-static guint impl_want_uri				(RBSource *source, const char *uri);
-static void impl_add_uri				(RBSource *source,
-							 const char *uri,
-							 const char *title,
-							 const char *genre,
-							 RBSourceAddCallback callback,
-							 gpointer data,
-							 GDestroyNotify destroy_data);
-static char *impl_get_delete_action			(RBSource *source);
-
-#define CMD_PATH_SHOW_BROWSER "/commands/ShowBrowser"
-#define CMD_PATH_CURRENT_STATION "/commands/CurrentStation"
-#define CMD_PATH_SONG_INFO    "/commands/SongInfo"
-
-#define CONF_UI_PODCAST_DIR 			CONF_PREFIX "/ui/podcast"
-#define CONF_UI_PODCAST_COLUMNS_SETUP 		CONF_PREFIX "/ui/podcast/columns_setup"
 #define CONF_STATE_PODCAST_PREFIX		CONF_PREFIX "/state/podcast"
 #define CONF_STATE_PANED_POSITION 		CONF_STATE_PODCAST_PREFIX "/paned_position"
 #define CONF_STATE_PODCAST_SORTING_POSTS	CONF_STATE_PODCAST_PREFIX "/sorting_posts"
-#define CONF_STATE_PODCAST_SORTING_FEEDS	CONF_STATE_PODCAST_PREFIX "/sorting_feeds"
 #define CONF_STATE_SHOW_BROWSER   		CONF_STATE_PODCAST_PREFIX "/show_browser"
-#define CONF_STATE_PODCAST_DOWNLOAD_DIR		CONF_STATE_PODCAST_PREFIX "/download_prefix"
-#define CONF_STATE_PODCAST_DOWNLOAD_INTERVAL	CONF_STATE_PODCAST_PREFIX "/download_interval"
-#define CONF_STATE_PODCAST_DOWNLOAD_NEXT_TIME	CONF_STATE_PODCAST_PREFIX "/download_next_time"
 
-struct RBPodcastSourcePrivate
+struct _RBPodcastSourcePrivate
 {
 	RhythmDB *db;
 
 	guint prefs_notify_id;
 
 	GtkWidget *vbox;
-	GtkWidget *config_widget;
 	GtkWidget *paned;
 
 	RhythmDBPropertyModel *feed_model;
@@ -295,8 +100,8 @@ struct RBPodcastSourcePrivate
 	GtkActionGroup *action_group;
 
 	GList *selected_feeds;
+	RhythmDBQuery *base_query;
 	RhythmDBQuery *search_query;
-	RhythmDBPropType search_prop;
 	RBSourceSearch *default_search;
 
 	RBPodcastManager *podcast_mgr;
@@ -304,32 +109,30 @@ struct RBPodcastSourcePrivate
 	GdkPixbuf *error_pixbuf;
 };
 
-#define RB_PODCAST_SOURCE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_PODCAST_SOURCE, RBPodcastSourcePrivate))
 
 static GtkActionEntry rb_podcast_source_actions [] =
 {
 	{ "MusicNewPodcast", RB_STOCK_PODCAST_NEW, N_("_New Podcast Feed..."), "<control>P",
 	  N_("Subscribe to a new Podcast Feed"),
-	  G_CALLBACK (rb_podcast_source_cmd_new_podcast) },
-
+	  G_CALLBACK (podcast_cmd_new_podcast) },
 	{ "PodcastSrcDownloadPost", NULL, N_("Download _Episode"), NULL,
 	  N_("Download Podcast Episode"),
-	  G_CALLBACK (rb_podcast_source_cmd_download_post) },
+	  G_CALLBACK (podcast_cmd_download_post) },
 	{ "PodcastSrcCancelDownload", GTK_STOCK_CANCEL, N_("_Cancel Download"), NULL,
 	  N_("Cancel Episode Download"),
-	  G_CALLBACK (rb_podcast_source_cmd_cancel_download) },
+	  G_CALLBACK (podcast_cmd_cancel_download) },
 	{ "PodcastFeedProperties", GTK_STOCK_PROPERTIES, N_("_Properties"), NULL,
 	  N_("Episode Properties"),
-	  G_CALLBACK (rb_podcast_source_cmd_properties_feed) },
+	  G_CALLBACK (podcast_cmd_properties_feed) },
 	{ "PodcastFeedUpdate", GTK_STOCK_REFRESH, N_("_Update Podcast Feed"), NULL,
 	  N_("Update Feed"),
-	  G_CALLBACK (rb_podcast_source_cmd_update_feed) },
+	  G_CALLBACK (podcast_cmd_update_feed) },
 	{ "PodcastFeedDelete", GTK_STOCK_DELETE, N_("_Delete Podcast Feed"), NULL,
 	  N_("Delete Feed"),
-	  G_CALLBACK (rb_podcast_source_cmd_delete_feed) },
+	  G_CALLBACK (podcast_cmd_delete_feed) },
 	{ "PodcastUpdateAllFeeds", GTK_STOCK_REFRESH, N_("_Update All Feeds"), NULL,
 	  N_("Update all feeds"),
-	  G_CALLBACK (rb_podcast_source_cmd_update_all) },
+	  G_CALLBACK (podcast_cmd_update_all) },
 };
 
 static GtkRadioActionEntry rb_podcast_source_radio_actions [] =
@@ -348,638 +151,13 @@ static const GtkTargetEntry posts_view_drag_types[] = {
 enum
 {
 	PROP_0,
-	PROP_PODCAST_MANAGER
+	PROP_PODCAST_MANAGER,
+	PROP_BASE_QUERY
 };
 
 G_DEFINE_TYPE (RBPodcastSource, rb_podcast_source, RB_TYPE_SOURCE)
 
 static void
-rb_podcast_source_class_init (RBPodcastSourceClass *klass)
-{
-
-	GObjectClass *object_class = G_OBJECT_CLASS (klass);
-	RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
-
-	object_class->dispose = rb_podcast_source_dispose;
-	object_class->finalize = rb_podcast_source_finalize;
-	object_class->constructed = rb_podcast_source_constructed;
-
-	object_class->set_property = rb_podcast_source_set_property;
-	object_class->get_property = rb_podcast_source_get_property;
-
-	source_class->impl_add_to_queue = impl_add_to_queue;
-	source_class->impl_can_add_to_queue = impl_can_add_to_queue;
-	source_class->impl_can_browse = (RBSourceFeatureFunc) rb_true_function;
-	source_class->impl_can_copy = (RBSourceFeatureFunc) rb_false_function;
-	source_class->impl_can_cut = (RBSourceFeatureFunc) rb_false_function;
-	source_class->impl_can_delete = (RBSourceFeatureFunc) rb_true_function;
-	source_class->impl_delete = impl_delete;
-	source_class->impl_get_browser_key  = impl_get_browser_key;
-	source_class->impl_get_config_widget = impl_get_config_widget;
-	source_class->impl_get_entry_view = impl_get_entry_view;
-	source_class->impl_get_search_actions = impl_get_search_actions;
-	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;
-	source_class->impl_search = impl_search;
-	source_class->impl_show_popup = impl_show_popup;
-	source_class->impl_song_properties = impl_song_properties;
-	source_class->impl_get_status = impl_get_status;
-	source_class->impl_want_uri = impl_want_uri;
-	source_class->impl_add_uri = impl_add_uri;
-	source_class->impl_get_delete_action = impl_get_delete_action;
-
-	g_object_class_install_property (object_class,
-					 PROP_PODCAST_MANAGER,
-					 g_param_spec_object ("podcast-manager",
-					                      "RBPodcastManager",
-					                      "RBPodcastManager object",
-					                      RB_TYPE_PODCAST_MANAGER,
-					                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
-
-	g_type_class_add_private (klass, sizeof (RBPodcastSourcePrivate));
-}
-
-static void
-rb_podcast_source_init (RBPodcastSource *source)
-{
-	GtkIconTheme *icon_theme;
-	GdkPixbuf *pixbuf;
-	gint       size;
-
-	source->priv = RB_PODCAST_SOURCE_GET_PRIVATE (source);
-
-	source->priv->search_prop = RHYTHMDB_PROP_SEARCH_MATCH;
-	source->priv->selected_feeds = NULL;
-	source->priv->vbox = gtk_vbox_new (FALSE, 5);
-
-	gtk_container_add (GTK_CONTAINER (source), source->priv->vbox);
-
-	gtk_icon_size_lookup (RB_SOURCE_ICON_SIZE, &size, NULL);
-	pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
-					   RB_STOCK_PODCAST,
-					   size,
-					   0, NULL);
-
-	if (pixbuf != NULL) {
-		rb_source_set_pixbuf (RB_SOURCE (source), pixbuf);
-		g_object_unref (pixbuf);
-	}
-
-	icon_theme = gtk_icon_theme_get_default ();
-	source->priv->error_pixbuf = gtk_icon_theme_load_icon (icon_theme,
-							       "dialog-error",
-							       16,
-							       0,
-							       NULL);
-}
-
-static void
-rb_podcast_source_dispose (GObject *object)
-{
-	RBPodcastSource *source;
-
-	rb_debug ("dispose podcast_source");
-	source = RB_PODCAST_SOURCE (object);
-
-	if (source->priv->db != NULL) {
-		g_object_unref (source->priv->db);
-		source->priv->db = NULL;
-	}
-
-	if (source->priv->search_query != NULL) {
-		rhythmdb_query_free (source->priv->search_query);
-		source->priv->search_query = NULL;
-	}
-
-	if (source->priv->action_group != NULL) {
-		g_object_unref (source->priv->action_group);
-		source->priv->action_group = NULL;
-	}
-
-	if (source->priv->podcast_mgr != NULL) {
-		g_object_unref (source->priv->podcast_mgr);
-		source->priv->podcast_mgr = NULL;
-	}
-
-	if (source->priv->error_pixbuf != NULL) {
-		g_object_unref (source->priv->error_pixbuf);
-		source->priv->error_pixbuf = NULL;
-	}
-
-	if (source->priv->prefs_notify_id != 0) {
-		eel_gconf_notification_remove (source->priv->prefs_notify_id);
-		source->priv->prefs_notify_id = 0;
-	}
-
-	G_OBJECT_CLASS (rb_podcast_source_parent_class)->dispose (object);
-}
-
-static void
-rb_podcast_source_finalize (GObject *object)
-{
-	RBPodcastSource *source;
-
-	g_return_if_fail (object != NULL);
-	g_return_if_fail (RB_IS_PODCAST_SOURCE (object));
-
-	source = RB_PODCAST_SOURCE (object);
-
-	g_return_if_fail (source->priv != NULL);
-
-	rb_debug ("finalizing podcast source");
-
-	if (source->priv->selected_feeds) {
-		g_list_foreach (source->priv->selected_feeds, (GFunc) g_free, NULL);
-	        g_list_free (source->priv->selected_feeds);
-	}
-
-	G_OBJECT_CLASS (rb_podcast_source_parent_class)->finalize (object);
-}
-
-static void
-rb_podcast_source_constructed (GObject *object)
-{
-	RBPodcastSource *source;
-	GtkTreeViewColumn *column;
-	GtkCellRenderer *renderer;
-	RBShell *shell;
-	RhythmDBQueryModel *query_model;
-	GPtrArray *query;
-	GtkAction *action;
-
-	RB_CHAIN_GOBJECT_METHOD (rb_podcast_source_parent_class, constructed, object);
-	source = RB_PODCAST_SOURCE (object);
-
-	g_object_get (source, "shell", &shell, NULL);
-	g_object_get (shell, "db", &source->priv->db, NULL);
-
-	source->priv->action_group = _rb_source_register_action_group (RB_SOURCE (source),
-								       "PodcastActions",
-								       rb_podcast_source_actions,
-								       G_N_ELEMENTS (rb_podcast_source_actions),
-								       source);
-
-	action = gtk_action_group_get_action (source->priv->action_group,
-					      "MusicNewPodcast");
-	/* Translators: this is the toolbar button label
-	   for New Podcast Feed action. */
-	g_object_set (G_OBJECT (action), "short-label", C_("Podcast", "New"), NULL);
-
-	action = gtk_action_group_get_action (source->priv->action_group,
-					      "PodcastUpdateAllFeeds");
-	/* Translators: this is the toolbar button label
-	   for Update All Feeds action. */
-	g_object_set (G_OBJECT (action), "short-label", _("Update"), NULL);
-
-	gtk_action_group_add_radio_actions (source->priv->action_group,
-					    rb_podcast_source_radio_actions,
-					    G_N_ELEMENTS (rb_podcast_source_radio_actions),
-					    0,
-					    NULL,
-					    NULL);
-	rb_source_search_basic_create_for_actions (source->priv->action_group,
-						   rb_podcast_source_radio_actions,
-						   G_N_ELEMENTS (rb_podcast_source_radio_actions));
-
-	source->priv->default_search = rb_source_search_basic_new (RHYTHMDB_PROP_SEARCH_MATCH);
-
-	source->priv->paned = gtk_vpaned_new ();
-
-	g_idle_add ((GSourceFunc) rb_podcast_source_load_finish_cb, source);
-
-	source->priv->podcast_mgr = rb_podcast_manager_new (source->priv->db);
-
-	g_object_unref (shell);
-
-	/* set up posts view */
-	source->priv->posts = rb_entry_view_new (source->priv->db,
-						 rb_shell_get_player (shell),
-						 CONF_STATE_PODCAST_SORTING_POSTS,
-						 TRUE, FALSE);
-
-	g_signal_connect_object (source->priv->posts,
-				 "entry-activated",
-				 G_CALLBACK (rb_podcast_source_entry_activated_cb),
-				 source, 0);
-
-	/* Podcast date column */
-	column = gtk_tree_view_column_new ();
-	renderer = gtk_cell_renderer_text_new();
-
-	gtk_tree_view_column_pack_start (column, renderer, TRUE);
-
-	gtk_tree_view_column_set_clickable (column, TRUE);
-	gtk_tree_view_column_set_resizable (column, TRUE);
-	gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
-	{
-		const char *sample_strings[3];
-		sample_strings[0] = _("Date");
-		sample_strings[1] = rb_entry_view_get_time_date_column_sample ();
-		sample_strings[2] = NULL;
-		rb_entry_view_set_fixed_column_width (source->priv->posts, column, renderer, sample_strings);
-	}
-
-	gtk_tree_view_column_set_cell_data_func (column, renderer,
-						 (GtkTreeCellDataFunc) rb_podcast_source_post_date_cell_data_func,
-						 source, NULL);
-
-	rb_entry_view_append_column_custom (source->priv->posts, column,
-					    _("Date"), "Date",
-					    (GCompareDataFunc) rb_podcast_source_post_date_cell_sort_func, 0, NULL);
-
-	rb_entry_view_append_column (source->priv->posts, RB_ENTRY_VIEW_COL_TITLE, TRUE);
-
-	/* COLUMN FEED */
-	column = gtk_tree_view_column_new ();
-	renderer = gtk_cell_renderer_text_new();
-
-	gtk_tree_view_column_pack_start (column, renderer, TRUE);
-
-	gtk_tree_view_column_set_clickable (column, TRUE);
-	gtk_tree_view_column_set_resizable (column, TRUE);
-	gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
-	gtk_tree_view_column_set_expand (column, TRUE);
-
-	gtk_tree_view_column_set_cell_data_func (column, renderer,
-						 (GtkTreeCellDataFunc) rb_podcast_source_post_feed_cell_data_func,
-						 source, NULL);
-
-	rb_entry_view_append_column_custom (source->priv->posts, column,
-					    _("Feed"), "Feed",
-					    (GCompareDataFunc) rb_podcast_source_post_feed_cell_sort_func, 0, NULL);
-
-	rb_entry_view_append_column (source->priv->posts, RB_ENTRY_VIEW_COL_DURATION, FALSE);
-	rb_entry_view_append_column (source->priv->posts, RB_ENTRY_VIEW_COL_RATING, FALSE);
-	rb_entry_view_append_column (source->priv->posts, RB_ENTRY_VIEW_COL_PLAY_COUNT, FALSE);
-	rb_entry_view_append_column (source->priv->posts, RB_ENTRY_VIEW_COL_LAST_PLAYED, FALSE);
-
-	/* Status column */
-	column = gtk_tree_view_column_new ();
-	renderer = gtk_cell_renderer_progress_new();
-
-	gtk_tree_view_column_pack_start (column, renderer, TRUE);
-
-	gtk_tree_view_column_set_clickable (column, TRUE);
-	gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
-
-	{
-		static const char *status_strings[6];
-		status_strings[0] = _("Status");
-		status_strings[1] = _("Downloaded");
-		status_strings[2] = _("Waiting");
-		status_strings[3] = _("Failed");
-		status_strings[4] = "100 %";
-		status_strings[5] = NULL;
-
-		rb_entry_view_set_fixed_column_width (source->priv->posts,
-						      column,
-						      renderer,
-						      status_strings);
-	}
-
-	gtk_tree_view_column_set_cell_data_func (column, renderer,
-						 (GtkTreeCellDataFunc) rb_podcast_source_post_status_cell_data_func,
-						 source, NULL);
-
-	rb_entry_view_append_column_custom (source->priv->posts, column,
-					    _("Status"), "Status",
-					    (GCompareDataFunc) rb_podcast_source_post_status_cell_sort_func, 0, NULL);
-
-	g_signal_connect_object (source->priv->posts,
-				 "sort-order-changed",
-				 G_CALLBACK (rb_podcast_source_posts_view_sort_order_changed_cb),
-				 source, 0);
-
-	g_signal_connect (source->priv->podcast_mgr,
-			  "status_changed",
-			  G_CALLBACK (rb_podcast_source_download_status_changed_cb),
-			  source);
-
-	g_signal_connect_object (source->priv->podcast_mgr,
-			  	 "process_error",
-			 	 G_CALLBACK (rb_podcast_source_download_process_error_cb),
-			  	 source, 0);
-
-	g_signal_connect_object (source->priv->posts,
-				 "size_allocate",
-				 G_CALLBACK (paned_size_allocate_cb),
-				 source, 0);
-
-	g_signal_connect_object (source->priv->posts,
-				 "show_popup",
-				 G_CALLBACK (rb_podcast_source_songs_show_popup_cb),
-				 source, 0);
-
-	/* configure feed view */
-	source->priv->feeds = rb_property_view_new (source->priv->db,
-						    RHYTHMDB_PROP_LOCATION,
-						    _("Feed"));
-	rb_property_view_set_selection_mode (RB_PROPERTY_VIEW (source->priv->feeds),
-					     GTK_SELECTION_MULTIPLE);
-
-	query_model = rhythmdb_query_model_new_empty (source->priv->db);
-	source->priv->feed_model = rb_property_view_get_model (RB_PROPERTY_VIEW (source->priv->feeds));
-	g_object_set (source->priv->feed_model, "query-model", query_model, NULL);
-
-	query = rhythmdb_query_parse (source->priv->db,
-				      RHYTHMDB_QUERY_PROP_EQUALS,
-				      RHYTHMDB_PROP_TYPE,
-				      RHYTHMDB_ENTRY_TYPE_PODCAST_FEED,
-				      RHYTHMDB_QUERY_END);
-	rhythmdb_do_full_query_parsed (source->priv->db,
-				       RHYTHMDB_QUERY_RESULTS (query_model),
-				       query);
-
-	rhythmdb_query_free (query);
-	g_object_unref (query_model);
-
-	/* error indicator column */
-	column = gtk_tree_view_column_new ();
-	renderer = rb_cell_renderer_pixbuf_new ();
-	gtk_tree_view_column_pack_start (column, renderer, TRUE);
-	gtk_tree_view_column_set_cell_data_func (column, renderer,
-						 (GtkTreeCellDataFunc) rb_podcast_source_feed_error_cell_data_func,
-						 source, NULL);
-	gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
-	gtk_tree_view_column_set_fixed_width (column, gdk_pixbuf_get_width (source->priv->error_pixbuf) + 5);
-	gtk_tree_view_column_set_reorderable (column, FALSE);
-	gtk_tree_view_column_set_visible (column, TRUE);
-	rb_property_view_append_column_custom (source->priv->feeds, column);
-	g_signal_connect_object (renderer,
-				 "pixbuf-clicked",
-				 G_CALLBACK (rb_podcast_source_pixbuf_clicked_cb),
-				 source, 0);
-
-	/* redraw error indicator when errors are set or cleared */
-	g_signal_connect_object (source->priv->db,
-				 "entry_changed",
-				 G_CALLBACK (rb_podcast_source_entry_changed_cb),
-				 source, 0);
-
-	/* title column */
-	column = gtk_tree_view_column_new ();
-	renderer = gtk_cell_renderer_text_new ();
-
-	gtk_tree_view_column_pack_start (column, renderer, TRUE);
-
-	gtk_tree_view_column_set_cell_data_func (column,
-						 renderer,
-						 (GtkTreeCellDataFunc) rb_podcast_source_feed_title_cell_data_func,
-						 source, NULL);
-
-	gtk_tree_view_column_set_title (column, _("Feed"));
-	gtk_tree_view_column_set_reorderable (column, FALSE);
-	gtk_tree_view_column_set_visible (column, TRUE);
-	rb_property_view_append_column_custom (source->priv->feeds, column);
-
-	g_signal_connect_object (source->priv->feeds, "show_popup",
-				 G_CALLBACK (rb_podcast_source_feeds_show_popup_cb),
-				 source, 0);
-
-	g_signal_connect_object (source->priv->feeds,
-				 "properties-selected",
-				 G_CALLBACK (feed_select_change_cb),
-				 source, 0);
-
-	rb_property_view_set_search_func (source->priv->feeds,
-					  (GtkTreeViewSearchEqualFunc) rb_podcast_source_feed_title_search_func,
-					  source,
-					  NULL);
-
-	/* set up drag and drop */
-	g_signal_connect_object (source->priv->feeds,
-				 "drag_data_received",
-				 G_CALLBACK (posts_view_drag_data_received_cb),
-				 source, 0);
-
-	gtk_drag_dest_set (GTK_WIDGET (source->priv->feeds),
-			   GTK_DEST_DEFAULT_ALL,
-			   posts_view_drag_types, 2,
-			   GDK_ACTION_COPY | GDK_ACTION_MOVE);
-
-	g_signal_connect_object (G_OBJECT (source->priv->posts),
-				 "drag_data_received",
-				 G_CALLBACK (posts_view_drag_data_received_cb),
-				 source, 0);
-
-	gtk_drag_dest_set (GTK_WIDGET (source->priv->posts),
-			   GTK_DEST_DEFAULT_ALL,
-			   posts_view_drag_types, 2,
-			   GDK_ACTION_COPY | GDK_ACTION_MOVE);
-
-	/* set up properties page */
-	gtk_paned_pack1 (GTK_PANED (source->priv->paned),
-			 GTK_WIDGET (source->priv->feeds), FALSE, FALSE);
-	gtk_paned_pack2 (GTK_PANED (source->priv->paned),
-			 GTK_WIDGET (source->priv->posts), TRUE, FALSE);
-
-	gtk_box_pack_start (GTK_BOX (source->priv->vbox), source->priv->paned, TRUE, TRUE, 0);
-
-	source->priv->prefs_notify_id = eel_gconf_notification_add (CONF_STATE_PODCAST_PREFIX,
-								    (GConfClientNotifyFunc) rb_podcast_source_state_pref_changed,
-								    source);
-
-	gtk_widget_show_all (GTK_WIDGET (source));
-	rb_podcast_source_state_prefs_sync (source);
-
-	rb_podcast_source_do_query (source);
-}
-
-static void
-rb_podcast_source_set_property (GObject *object,
-				guint prop_id,
-				const GValue *value,
-				GParamSpec *pspec)
-{
-	RBPodcastSource *source = RB_PODCAST_SOURCE (object);
-
-	switch (prop_id) {
-	case PROP_PODCAST_MANAGER:
-		source->priv->podcast_mgr = g_value_get_object (value);
-		if (source->priv->podcast_mgr)
-			g_object_ref (source->priv->podcast_mgr);
-		break;
-	default:
-		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-		break;
-	}
-}
-
-static void
-rb_podcast_source_get_property (GObject *object,
-				guint prop_id,
-				GValue *value,
-				GParamSpec *pspec)
-{
-	RBPodcastSource *source = RB_PODCAST_SOURCE (object);
-
-	switch (prop_id) {
-	case PROP_PODCAST_MANAGER:
-		g_value_set_object (value, source->priv->podcast_mgr);
-	        break;
-	default:
-		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-		break;
-	}
-}
-
-/*
- * rb_podcast_source_new:
- * @shell: the #RBShell instance
- *
- * Creates the #RBPodcastSource instance
- *
- * Return value: the #RBPodcastSource
- */
-RBSource *
-rb_podcast_source_new (RBShell *shell)
-{
-	RBSource *source;
-	source = RB_SOURCE (g_object_new (RB_TYPE_PODCAST_SOURCE,
-					  "name", _("Podcasts"),
-					  "shell", shell,
-					  "entry-type", RHYTHMDB_ENTRY_TYPE_PODCAST_POST,
-					  "source-group", RB_SOURCE_GROUP_LIBRARY,
-					  "search-type", RB_SOURCE_SEARCH_INCREMENTAL,
-					  NULL));
-
-	rb_shell_register_entry_type_for_source (shell, source,
-						 RHYTHMDB_ENTRY_TYPE_PODCAST_FEED);
-	rb_shell_register_entry_type_for_source (shell, source,
-						 RHYTHMDB_ENTRY_TYPE_PODCAST_POST);
-
-	return source;
-}
-
-static void
-impl_search (RBSource *asource, RBSourceSearch *search, const char *cur_text, const char *new_text)
-{
-	RBPodcastSource *source = RB_PODCAST_SOURCE (asource);
-
-	if (search == NULL) {
-		search = source->priv->default_search;
-	}
-
-	if (source->priv->search_query != NULL) {
-		rhythmdb_query_free (source->priv->search_query);
-		source->priv->search_query = NULL;
-	}
-	source->priv->search_query = rb_source_search_create_query (search, source->priv->db, new_text);
-	rb_podcast_source_do_query (source);
-
-	rb_source_notify_filter_changed (RB_SOURCE (source));
-}
-
-static RBEntryView *
-impl_get_entry_view (RBSource *asource)
-{
-	RBPodcastSource *source = RB_PODCAST_SOURCE (asource);
-
-	return source->priv->posts;
-}
-
-static RBSourceEOFType
-impl_handle_eos (RBSource *asource)
-{
-	return RB_SOURCE_EOF_STOP;
-}
-
-static char *
-impl_get_browser_key (RBSource *asource)
-{
-	return g_strdup (CONF_STATE_SHOW_BROWSER);
-}
-
-static void
-impl_delete (RBSource *asource)
-{
-	RBPodcastSource *source = RB_PODCAST_SOURCE (asource);
-	GList *entries;
-	GList *l;
-	gint ret;
-	GtkWidget *dialog;
-	GtkWidget *button;
-	GtkWindow *window;
-	RBShell *shell;
-
-	rb_debug ("Delete episode action");
-
-	g_object_get (source, "shell", &shell, NULL);
-	g_object_get (shell, "window", &window, NULL);
-	g_object_unref (shell);
-
-	dialog = gtk_message_dialog_new (window,
-			                 GTK_DIALOG_DESTROY_WITH_PARENT,
-					 GTK_MESSAGE_WARNING,
-					 GTK_BUTTONS_NONE,
-					 _("Delete the podcast episode and downloaded file?"));
-
-	gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
-	                                          _("If you choose to delete the episode and file, "
-						    "they will be permanently lost.  Please note that "
-						    "you can delete the episode but keep the downloaded "
-						    "file by choosing to delete the episode only."));
-
-	gtk_window_set_title (GTK_WINDOW (dialog), "");
-
-	gtk_dialog_add_buttons (GTK_DIALOG (dialog),
-	                        _("Delete _Episode Only"),
-	                        GTK_RESPONSE_NO,
-	                        GTK_STOCK_CANCEL,
-	                        GTK_RESPONSE_CANCEL,
-	                        NULL);
-	button = gtk_dialog_add_button (GTK_DIALOG (dialog),
-	                                _("_Delete Episode And File"),
-			                GTK_RESPONSE_YES);
-
-	gtk_window_set_focus (GTK_WINDOW (dialog), button);
-	gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_YES);
-
-	ret = gtk_dialog_run (GTK_DIALOG (dialog));
-	gtk_widget_destroy (dialog);
-
-	if (ret == GTK_RESPONSE_CANCEL || ret == GTK_RESPONSE_DELETE_EVENT)
-		return;
-
-	entries = rb_entry_view_get_selected_entries (source->priv->posts);
-	for (l = entries; l != NULL; l = g_list_next (l)) {
-		RhythmDBEntry *entry = l->data;
-
-		rb_podcast_manager_cancel_download (source->priv->podcast_mgr, entry);
-		if (ret == GTK_RESPONSE_YES) {
-			rb_podcast_manager_delete_download (source->priv->podcast_mgr, entry);
-		}
-
-		/* set podcast entries to invisible instead of deleted so they will
-		 * not reappear after the podcast has been updated
-		 */
-		GValue v = {0,};
-		g_value_init (&v, G_TYPE_BOOLEAN);
-		g_value_set_boolean (&v, TRUE);
-		rhythmdb_entry_set (source->priv->db, entry, RHYTHMDB_PROP_HIDDEN, &v);
-		g_value_unset (&v);
-	}
-
-	g_list_foreach (entries, (GFunc)rhythmdb_entry_unref, NULL);
-	g_list_free (entries);
-
-	rhythmdb_commit (source->priv->db);
-}
-
-static void
-impl_song_properties (RBSource *asource)
-{
-	RBPodcastSource *source = RB_PODCAST_SOURCE (asource);
-	GtkWidget *dialog = rb_podcast_properties_dialog_new (source->priv->posts);
-	rb_debug ("in song properties");
-	if (dialog)
-		gtk_widget_show_all (dialog);
-	else
-		rb_debug ("no selection!");
-}
-
-static void
 paned_size_allocate_cb (GtkWidget *widget,
 			GtkAllocation *allocation,
 		        RBPodcastSource *source)
@@ -995,43 +173,33 @@ rb_podcast_source_state_prefs_sync (RBPodcastSource *source)
 	rb_debug ("syncing state");
 	gtk_paned_set_position (GTK_PANED (source->priv->paned),
 				eel_gconf_get_integer (CONF_STATE_PANED_POSITION));
-	rb_podcast_source_show_browser (source,
-					eel_gconf_get_boolean (CONF_STATE_SHOW_BROWSER));
+	g_object_set (source->priv->feeds,
+		      "visible", eel_gconf_get_boolean (CONF_STATE_SHOW_BROWSER),
+		      NULL);
 }
 
 static void
-rb_podcast_source_state_pref_changed (GConfClient *client,
-				      guint cnxn_id,
-				      GConfEntry *entry,
-				      RBPodcastSource *source)
+podcast_pref_change_cb (GConfClient *client,
+			guint cnxn_id,
+			GConfEntry *entry,
+			RBPodcastSource *source)
 {
 	rb_debug ("state prefs changed");
 	rb_podcast_source_state_prefs_sync (source);
 }
 
 static void
-rb_podcast_source_posts_view_sort_order_changed_cb (RBEntryView *view,
-						    RBPodcastSource *source)
+podcast_posts_view_sort_order_changed_cb (RBEntryView *view,
+					  RBPodcastSource *source)
 {
 	rb_debug ("sort order changed");
 	rb_entry_view_resort_model (view);
 }
 
 static void
-rb_podcast_source_download_status_changed_cb (RBPodcastManager *download,
-					      RhythmDBEntry *entry,
-					      gulong status,
-					      RBPodcastSource *source)
-{
-	gtk_widget_queue_draw (GTK_WIDGET (source->priv->posts));
-	return;
-
-}
-
-static void
-rb_podcast_source_songs_show_popup_cb (RBEntryView *view,
-				       gboolean over_entry,
-				       RBPodcastSource *source)
+podcast_posts_show_popup_cb (RBEntryView *view,
+			     gboolean over_entry,
+			     RBPodcastSource *source)
 {
 	if (G_OBJECT (source) == NULL) {
 		return;
@@ -1073,8 +241,8 @@ rb_podcast_source_songs_show_popup_cb (RBEntryView *view,
 }
 
 static void
-rb_podcast_source_feeds_show_popup_cb (RBPropertyView *view,
-				       RBPodcastSource *source)
+podcast_feeds_show_popup_cb (RBPropertyView *view,
+			     RBPodcastSource *source)
 {
 	if (G_OBJECT (source) == NULL) {
 		return;
@@ -1104,46 +272,11 @@ rb_podcast_source_feeds_show_popup_cb (RBPropertyView *view,
 	}
 }
 
-static void
-feed_select_change_cb (RBPropertyView *propview,
-		       GList *feeds,
-		       RBPodcastSource *source)
-{
-	if (rb_string_list_equal (feeds, source->priv->selected_feeds))
-		return;
-
-	if (source->priv->selected_feeds) {
-		g_list_foreach (source->priv->selected_feeds, (GFunc) g_free, NULL);
-	        g_list_free (source->priv->selected_feeds);
-	}
-
-	source->priv->selected_feeds = rb_string_list_copy (feeds);
-
-	rb_podcast_source_do_query (source);
-	rb_source_notify_filter_changed (RB_SOURCE (source));
-}
-
-static void
-rb_podcast_source_show_browser (RBPodcastSource *source,
-				gboolean show)
-{
-	g_object_set (source->priv->feeds, "visible", show, NULL);
-}
-
 static GPtrArray *
 construct_query_from_selection (RBPodcastSource *source)
 {
 	GPtrArray *query;
-	RhythmDBEntryType *entry_type;
-
-	g_object_get (source, "entry-type", &entry_type, NULL);
-
-	query = rhythmdb_query_parse (source->priv->db,
-				      RHYTHMDB_QUERY_PROP_EQUALS,
-				      RHYTHMDB_PROP_TYPE,
-				      entry_type,
-				      RHYTHMDB_QUERY_END);
-	g_object_unref (entry_type);
+	query = rhythmdb_query_copy (source->priv->base_query);
 
 	if (source->priv->search_query) {
 		rhythmdb_query_append (source->priv->db,
@@ -1208,63 +341,26 @@ rb_podcast_source_do_query (RBPodcastSource *source)
 	g_object_unref (query_model);
 }
 
-static gboolean
-impl_show_popup (RBSource *source)
+static void
+feed_select_change_cb (RBPropertyView *propview,
+		       GList *feeds,
+		       RBPodcastSource *source)
 {
-	_rb_source_show_popup (RB_SOURCE (source), "/PodcastSourcePopup");
-	return TRUE;
-}
+	if (rb_string_list_equal (feeds, source->priv->selected_feeds))
+		return;
 
-static GtkWidget *
-impl_get_config_widget (RBSource *asource, RBShellPreferences *prefs)
-{
-	RBPodcastSource *source = RB_PODCAST_SOURCE (asource);
-	GtkBuilder *builder;
-	GtkWidget *cb_update_interval;
-	GtkWidget *btn_file;
-	char *download_dir;
-
-	if (source->priv->config_widget)
-		return source->priv->config_widget;
-
-	builder = rb_builder_load ("podcast-prefs.ui", source);
-	source->priv->config_widget = GTK_WIDGET (gtk_builder_get_object (builder, "podcast_vbox"));
-
-	btn_file = GTK_WIDGET (gtk_builder_get_object (builder, "location_chooser"));
-	gtk_file_chooser_add_shortcut_folder (GTK_FILE_CHOOSER (btn_file),
-					      rb_music_dir (),
-					      NULL);
-	download_dir = rb_podcast_manager_get_podcast_dir (source->priv->podcast_mgr);
-	gtk_file_chooser_set_current_folder_uri (GTK_FILE_CHOOSER (btn_file),
-						 download_dir);
-	g_free (download_dir);
-
-	g_signal_connect (btn_file,
-			  "selection-changed",
-			  G_CALLBACK (rb_podcast_source_btn_file_change_cb),
-			  CONF_STATE_PODCAST_DOWNLOAD_DIR);
-
-	cb_update_interval = GTK_WIDGET (gtk_builder_get_object (builder, "cb_update_interval"));
-	gtk_combo_box_set_active (GTK_COMBO_BOX (cb_update_interval),
-				  eel_gconf_get_integer (CONF_STATE_PODCAST_DOWNLOAD_INTERVAL));
-	g_signal_connect (cb_update_interval,
-			  "changed",
-			  G_CALLBACK (rb_podcast_source_cb_interval_changed_cb),
-			  source);
-
-	return source->priv->config_widget;
-}
+	if (source->priv->selected_feeds) {
+		g_list_foreach (source->priv->selected_feeds, (GFunc) g_free, NULL);
+	        g_list_free (source->priv->selected_feeds);
+	}
 
-static void
-rb_podcast_source_btn_file_change_cb (GtkFileChooserButton *widget, const char *key)
-{
-	char *uri;
-	
-	uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (widget));
-	eel_gconf_set_string (key, uri);
-	g_free (uri);
+	source->priv->selected_feeds = rb_string_list_copy (feeds);
+
+	rb_podcast_source_do_query (source);
+	rb_source_notify_filter_changed (RB_SOURCE (source));
 }
 
+
 static void
 posts_view_drag_data_received_cb (GtkWidget *widget,
 				  GdkDragContext *dc,
@@ -1275,26 +371,36 @@ posts_view_drag_data_received_cb (GtkWidget *widget,
 				  guint time,
 				  RBPodcastSource *source)
 {
-	impl_receive_drag (RB_SOURCE (source), selection_data);
+	rb_source_receive_drag (RB_SOURCE (source), selection_data);
 }
 
-/*
- * rb_podcast_source_add_feed:
- * @source: the #RBPodcastSource
- * @uri: the new feed to add
- *
- * Adds a new podcast feed.
- * Simply calls #rb_podcast_manager_subscribe_feed.
- */
-void
-rb_podcast_source_add_feed (RBPodcastSource *source, const char *uri)
+static void
+podcast_location_added_cb (RBURIDialog *dialog,
+			   const char *location,
+			   RBPodcastSource *source)
 {
-	rb_podcast_manager_subscribe_feed (source->priv->podcast_mgr, uri, FALSE);
+	rb_podcast_manager_subscribe_feed (source->priv->podcast_mgr,
+					   location,
+					   FALSE);
+}
+
+static void
+podcast_cmd_new_podcast (GtkAction *action, RBPodcastSource *source)
+{
+	GtkWidget *dialog;
+
+	dialog = rb_uri_dialog_new (_("New Podcast Feed"), _("URL of podcast feed:"));
+	g_signal_connect_object (G_OBJECT (dialog),
+				 "location-added",
+				 G_CALLBACK (podcast_location_added_cb),
+				 source, 0);
+	gtk_dialog_run (GTK_DIALOG (dialog));
+	gtk_widget_destroy (dialog);
 }
 
+
 static void
-rb_podcast_source_cmd_download_post (GtkAction *action,
-				     RBPodcastSource *source)
+podcast_cmd_download_post (GtkAction *action, RBPodcastSource *source)
 {
 	GList *lst;
 	GValue val = {0, };
@@ -1327,8 +433,7 @@ rb_podcast_source_cmd_download_post (GtkAction *action,
 }
 
 static void
-rb_podcast_source_cmd_cancel_download (GtkAction *action,
-				       RBPodcastSource *source)
+podcast_cmd_cancel_download (GtkAction *action, RBPodcastSource *source)
 {
 	GList *lst;
 	GValue val = {0, };
@@ -1361,8 +466,7 @@ rb_podcast_source_cmd_cancel_download (GtkAction *action,
 }
 
 static void
-rb_podcast_source_cmd_delete_feed (GtkAction *action,
-			     	   RBPodcastSource *source)
+podcast_cmd_delete_feed (GtkAction *action, RBPodcastSource *source)
 {
 	GList *feeds, *l;
 	gint ret;
@@ -1425,8 +529,7 @@ rb_podcast_source_cmd_delete_feed (GtkAction *action,
 }
 
 static void
-rb_podcast_source_cmd_properties_feed (GtkAction *action,
-			     	       RBPodcastSource *source)
+podcast_cmd_properties_feed (GtkAction *action, RBPodcastSource *source)
 {
 	RhythmDBEntry *entry;
 	GtkWidget *dialog;
@@ -1448,8 +551,7 @@ rb_podcast_source_cmd_properties_feed (GtkAction *action,
 }
 
 static void
-rb_podcast_source_cmd_update_feed (GtkAction *action,
-			     	   RBPodcastSource *source)
+podcast_cmd_update_feed (GtkAction *action, RBPodcastSource *source)
 {
 	GList *feeds, *l;
 
@@ -1468,21 +570,22 @@ rb_podcast_source_cmd_update_feed (GtkAction *action,
 }
 
 static void
-rb_podcast_source_cmd_update_all (GtkAction *action, RBPodcastSource *source)
+podcast_cmd_update_all (GtkAction *action, RBPodcastSource *source)
 {
 	rb_podcast_manager_update_feeds (source->priv->podcast_mgr);
 }
 
 static void
-rb_podcast_source_post_status_cell_data_func (GtkTreeViewColumn *column,
-					      GtkCellRenderer *renderer,
-				     	      GtkTreeModel *tree_model,
-					      GtkTreeIter *iter,
-				     	      RBPodcastSource *source)
+podcast_post_status_cell_data_func (GtkTreeViewColumn *column,
+				    GtkCellRenderer *renderer,
+				    GtkTreeModel *tree_model,
+				    GtkTreeIter *iter,
+				    RBPodcastSource *source)
 
 {
 	RhythmDBEntry *entry;
 	guint value;
+	char *s;
 
 	gtk_tree_model_get (tree_model, iter, 0, &entry, -1);
 
@@ -1504,32 +607,28 @@ rb_podcast_source_post_status_cell_data_func (GtkTreeViewColumn *column,
 		value = 0;
 		break;
 	default:
-		{
-			char *s;
-
-			value = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_STATUS);
-			s = g_strdup_printf ("%u %%", value);
+		value = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_STATUS);
+		s = g_strdup_printf ("%u %%", value);
 
-			g_object_set (renderer, "text", s, NULL);
-			g_free (s);
-		}
+		g_object_set (renderer, "text", s, NULL);
+		g_free (s);
+		break;
 	}
 
 	g_object_set (renderer, "visible",
 		      rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_STATUS) != RHYTHMDB_PODCAST_STATUS_PAUSED,
+		      "value", value,
 		      NULL);
 
-	g_object_set (renderer, "value", value, NULL);
-
 	rhythmdb_entry_unref (entry);
 }
 
 static void
-rb_podcast_source_post_feed_cell_data_func (GtkTreeViewColumn *column,
-					    GtkCellRenderer *renderer,
-				       	    GtkTreeModel *tree_model,
-					    GtkTreeIter *iter,
-				       	    RBPodcastSource *source)
+podcast_post_feed_cell_data_func (GtkTreeViewColumn *column,
+				  GtkCellRenderer *renderer,
+				  GtkTreeModel *tree_model,
+				  GtkTreeIter *iter,
+				  RBPodcastSource *source)
 
 {
 	RhythmDBEntry *entry;
@@ -1544,11 +643,11 @@ rb_podcast_source_post_feed_cell_data_func (GtkTreeViewColumn *column,
 }
 
 static gboolean
-rb_podcast_source_feed_title_search_func (GtkTreeModel *model,
-					  gint column,
-					  const gchar *key,
-					  GtkTreeIter *iter,
-					  RBPodcastSource *source)
+podcast_feed_title_search_func (GtkTreeModel *model,
+				gint column,
+				const gchar *key,
+				GtkTreeIter *iter,
+				RBPodcastSource *source)
 {
 	char *title;
 	char *fold_key;
@@ -1575,11 +674,11 @@ rb_podcast_source_feed_title_search_func (GtkTreeModel *model,
 }
 
 static void
-rb_podcast_source_feed_title_cell_data_func (GtkTreeViewColumn *column,
-					     GtkCellRenderer *renderer,
-					     GtkTreeModel *tree_model,
-					     GtkTreeIter *iter,
-					     RBPodcastSource *source)
+podcast_feed_title_cell_data_func (GtkTreeViewColumn *column,
+				   GtkCellRenderer *renderer,
+				   GtkTreeModel *tree_model,
+				   GtkTreeIter *iter,
+				   RBPodcastSource *source)
 {
 	char *title;
 	char *str;
@@ -1623,11 +722,11 @@ rb_podcast_source_feed_title_cell_data_func (GtkTreeViewColumn *column,
 }
 
 static void
-rb_podcast_source_feed_error_cell_data_func (GtkTreeViewColumn *column,
-					     GtkCellRenderer *renderer,
-					     GtkTreeModel *tree_model,
-					     GtkTreeIter *iter,
-					     RBPodcastSource *source)
+podcast_feed_error_cell_data_func (GtkTreeViewColumn *column,
+				   GtkCellRenderer *renderer,
+				   GtkTreeModel *tree_model,
+				   GtkTreeIter *iter,
+				   RBPodcastSource *source)
 {
 	char *title;
 	RhythmDBEntry *entry = NULL;
@@ -1649,11 +748,11 @@ rb_podcast_source_feed_error_cell_data_func (GtkTreeViewColumn *column,
 }
 
 static void
-rb_podcast_source_post_date_cell_data_func (GtkTreeViewColumn *column,
-					    GtkCellRenderer *renderer,
-				     	    GtkTreeModel *tree_model,
-					    GtkTreeIter *iter,
-				     	    RBPodcastSource *source)
+podcast_post_date_cell_data_func (GtkTreeViewColumn *column,
+				  GtkCellRenderer *renderer,
+				  GtkTreeModel *tree_model,
+				  GtkTreeIter *iter,
+				  RBPodcastSource *source)
 {
 	RhythmDBEntry *entry;
 	gulong value;
@@ -1674,134 +773,50 @@ rb_podcast_source_post_date_cell_data_func (GtkTreeViewColumn *column,
 	rhythmdb_entry_unref (entry);
 }
 
-static void
-rb_podcast_source_cb_interval_changed_cb (GtkComboBox *box, gpointer cb_data)
-{
-	guint index = gtk_combo_box_get_active (box);
-	eel_gconf_set_integer (CONF_STATE_PODCAST_DOWNLOAD_INTERVAL,
-			       index);
-
-	rb_podcast_manager_start_sync (RB_PODCAST_SOURCE (cb_data)->priv->podcast_mgr);
-}
-
-static gboolean
-rb_podcast_source_load_finish_cb  (gpointer cb_data)
-{
-	RBPodcastSource *source  = RB_PODCAST_SOURCE (cb_data);
-
-	rb_podcast_manager_start_sync (source->priv->podcast_mgr);
-
-	g_signal_connect_object (G_OBJECT (source->priv->podcast_mgr),
-	 		        "start_download",
-			  	G_CALLBACK (rb_podcast_source_start_download_cb),
-			  	source, 0);
-
-	g_signal_connect_object (G_OBJECT (source->priv->podcast_mgr),
-			  	"finish_download",
-			  	G_CALLBACK (rb_podcast_source_finish_download_cb),
-			  	source, 0);
-
-	g_signal_connect_object (G_OBJECT (source->priv->podcast_mgr),
-			  	"feed_updates_available",
- 			  	G_CALLBACK (rb_podcast_source_feed_updates_available_cb),
-			  	source, 0);
 
-	return FALSE;
-}
-
-static gboolean
-impl_receive_drag (RBSource *asource, GtkSelectionData *selection_data)
-{
-	GList *list, *i;
-	RBPodcastSource *source = RB_PODCAST_SOURCE (asource);
-
-	list = rb_uri_list_parse ((const char *) gtk_selection_data_get_data (selection_data));
-
-	for (i = list; i != NULL; i = i->next) {
-		char *uri = NULL;
-
-		uri = i->data;
-		if ((uri != NULL) &&
-		    (!rhythmdb_entry_lookup_by_location (source->priv->db, uri))) {
-			rb_podcast_source_add_feed (source, uri);
-		}
-		
-		if (gtk_selection_data_get_data_type (selection_data) == gdk_atom_intern ("_NETSCAPE_URL", FALSE)) {
-			i = i->next;
-		}
-	}
-
-	rb_list_deep_free (list);
-	return TRUE;
-}
-
-static void
-rb_podcast_source_start_download_cb (RBPodcastManager *pd,
-				     RhythmDBEntry *entry,
-				     RBPodcastSource *source)
-{
-	RBShell *shell = rb_podcast_source_get_shell (source);
-	char *podcast_name;
-
-	podcast_name = g_markup_escape_text (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_TITLE), -1);
-	rb_shell_notify_custom (shell, 4000, _("Downloading podcast"), podcast_name, NULL, FALSE);
-
-	g_object_unref (shell);
-	g_free (podcast_name);
-}
-
-static void
-rb_podcast_source_finish_download_cb (RBPodcastManager *pd,
-				      RhythmDBEntry *entry,
-				      RBPodcastSource *source)
+static gint
+podcast_post_feed_sort_func (RhythmDBEntry *a,
+			     RhythmDBEntry *b,
+			     RhythmDBQueryModel *model)
 {
-	RBShell *shell = rb_podcast_source_get_shell (source);
-	char *podcast_name;
-
-	podcast_name = g_markup_escape_text (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_TITLE), -1);
-	rb_shell_notify_custom (shell, 4000, _("Finished downloading podcast"), podcast_name, NULL, FALSE);
-
-	g_object_unref (shell);
-	g_free (podcast_name);
-}
+	const char *a_str, *b_str;
+	gulong a_val, b_val;
+	gint ret;
 
-static void
-rb_podcast_source_feed_updates_available_cb (RBPodcastManager *pd,
-					     RhythmDBEntry *entry,
-					     RBPodcastSource *source)
-{
-	RBShell *shell = rb_podcast_source_get_shell (source);
-	char *podcast_name;
+	/* feeds */
+	a_str = rhythmdb_entry_get_string (a, RHYTHMDB_PROP_ALBUM_SORT_KEY);
+	b_str = rhythmdb_entry_get_string (b, RHYTHMDB_PROP_ALBUM_SORT_KEY);
 
-	podcast_name = g_markup_escape_text (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_TITLE), -1);
-	rb_shell_notify_custom (shell, 4000, _("New updates available from"), podcast_name, NULL, FALSE);
+	ret = strcmp (a_str, b_str);
+	if (ret != 0)
+		return ret;
 
-	g_object_unref (shell);
-	g_free (podcast_name);
+	a_val = rhythmdb_entry_get_ulong (a, RHYTHMDB_PROP_POST_TIME);
+	b_val = rhythmdb_entry_get_ulong (b, RHYTHMDB_PROP_POST_TIME);
 
-}
+	if (a_val != b_val)
+		return (a_val > b_val) ? 1 : -1;
 
-static RBShell *
-rb_podcast_source_get_shell (RBPodcastSource *source)
-{
-	RBShell *shell;
+	/* titles */
+	a_str = rhythmdb_entry_get_string (a, RHYTHMDB_PROP_TITLE_SORT_KEY);
+	b_str = rhythmdb_entry_get_string (b, RHYTHMDB_PROP_TITLE_SORT_KEY);
 
-	g_object_get (source, "shell", &shell, NULL);
+	ret = strcmp (a_str, b_str);
+	if (ret != 0)
+		return ret;
 
-	return shell;
-}
+	/* location */
+	a_str = rhythmdb_entry_get_string (a, RHYTHMDB_PROP_LOCATION);
+	b_str = rhythmdb_entry_get_string (b, RHYTHMDB_PROP_LOCATION);
 
-void
-rb_podcast_source_shutdown	(RBPodcastSource *source)
-{
-	rb_debug ("podcast source shutdown");
-	rb_podcast_manager_shutdown (source->priv->podcast_mgr);
+	ret = strcmp (a_str, b_str);
+	return ret;
 }
 
 static gint
-rb_podcast_source_post_date_cell_sort_func (RhythmDBEntry *a,
-					    RhythmDBEntry *b,
-					    RhythmDBQueryModel *model)
+podcast_post_date_sort_func (RhythmDBEntry *a,
+			     RhythmDBEntry *b,
+			     RhythmDBQueryModel *model)
 {
 	gulong a_val, b_val;
 	gint ret;
@@ -1812,15 +827,15 @@ rb_podcast_source_post_date_cell_sort_func (RhythmDBEntry *a,
 	if (a_val != b_val)
 		ret = (a_val > b_val) ? 1 : -1;
 	else
-		ret = rb_podcast_source_post_feed_cell_sort_func (a, b, model);
+		ret = podcast_post_feed_sort_func (a, b, model);
 
         return ret;
 }
 
 static gint
-rb_podcast_source_post_status_cell_sort_func (RhythmDBEntry *a,
-					      RhythmDBEntry *b,
-					      RhythmDBQueryModel *model)
+podcast_post_status_sort_func (RhythmDBEntry *a,
+			       RhythmDBEntry *b,
+			       RhythmDBQueryModel *model)
 {
 	gulong a_val, b_val;
 	gint ret;
@@ -1831,107 +846,174 @@ rb_podcast_source_post_status_cell_sort_func (RhythmDBEntry *a,
         if (a_val != b_val)
 		ret = (a_val > b_val) ? 1 : -1;
 	else
-		ret = rb_podcast_source_post_feed_cell_sort_func (a, b, model);
+		ret = podcast_post_feed_sort_func (a, b, model);
 
 	return ret;
 }
 
-static gint
-rb_podcast_source_post_feed_cell_sort_func (RhythmDBEntry *a,
-					    RhythmDBEntry *b,
-					    RhythmDBQueryModel *model)
+
+static void
+episode_activated_cb (RBEntryView *view,
+		      RhythmDBEntry *entry,
+		      RBPodcastSource *source)
 {
-	const char *a_str, *b_str;
-	gulong a_val, b_val;
-	gint ret;
+	GValue val = {0,};
 
-	/* feeds */
-	a_str = rhythmdb_entry_get_string (a, RHYTHMDB_PROP_ALBUM_SORT_KEY);
-	b_str = rhythmdb_entry_get_string (b, RHYTHMDB_PROP_ALBUM_SORT_KEY);
+	/* check to see if it has already been downloaded */
+	if (rb_podcast_manager_entry_downloaded (entry))
+		return;
 
-	ret = strcmp (a_str, b_str);
-	if (ret != 0)
-		return ret;
+	g_value_init (&val, G_TYPE_ULONG);
+	g_value_set_ulong (&val, RHYTHMDB_PODCAST_STATUS_WAITING);
+	rhythmdb_entry_set (source->priv->db, entry, RHYTHMDB_PROP_STATUS, &val);
+	rhythmdb_commit (source->priv->db);
+	g_value_unset (&val);
 
-	a_val = rhythmdb_entry_get_ulong (a, RHYTHMDB_PROP_POST_TIME);
-	b_val = rhythmdb_entry_get_ulong (b, RHYTHMDB_PROP_POST_TIME);
+	rb_podcast_manager_download_entry (source->priv->podcast_mgr, entry);
+}
 
-	if (a_val != b_val)
-		return (a_val > b_val) ? 1 : -1;
+static void
+podcast_entry_changed_cb (RhythmDB *db,
+			  RhythmDBEntry *entry,
+			  GValueArray *changes,
+			  RBPodcastSource *source)
+{
+	RhythmDBEntryType *entry_type;
+	gboolean feed_changed;
+	int i;
 
-	/* titles */
-	a_str = rhythmdb_entry_get_string (a, RHYTHMDB_PROP_TITLE_SORT_KEY);
-	b_str = rhythmdb_entry_get_string (b, RHYTHMDB_PROP_TITLE_SORT_KEY);
+	entry_type = rhythmdb_entry_get_entry_type (entry);
+	if (entry_type != RHYTHMDB_ENTRY_TYPE_PODCAST_FEED)
+		return;
 
-	ret = strcmp (a_str, b_str);
-	if (ret != 0)
-		return ret;
+	feed_changed = FALSE;
+	for (i = 0; i < changes->n_values; i++) {
+		GValue *v = g_value_array_get_nth (changes, i);
+		RhythmDBEntryChange *change = g_value_get_boxed (v);
 
-	/* location */
-	a_str = rhythmdb_entry_get_string (a, RHYTHMDB_PROP_LOCATION);
-	b_str = rhythmdb_entry_get_string (b, RHYTHMDB_PROP_LOCATION);
+		if (change->prop == RHYTHMDB_PROP_PLAYBACK_ERROR) {
+			feed_changed = TRUE;
+			break;
+		}
+	}
 
-	ret = strcmp (a_str, b_str);
-	return ret;
+	if (feed_changed) {
+		const char *loc;
+		GtkTreeIter iter;
+
+		loc = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
+		if (rhythmdb_property_model_iter_from_string (source->priv->feed_model,
+							      loc,
+							      &iter)) {
+			GtkTreePath *path;
+
+			path = gtk_tree_model_get_path (GTK_TREE_MODEL (source->priv->feed_model),
+						        &iter);
+			gtk_tree_model_row_changed (GTK_TREE_MODEL (source->priv->feed_model),
+						    path,
+						    &iter);
+			gtk_tree_path_free (path);
+		}
+	}
 }
 
-static gboolean
-rb_podcast_source_download_process_error_cb (RBPodcastManager *pd,
-					     const char *error,
-					     gboolean existing,
-					     RBPodcastSource *source)
+static void
+podcast_error_pixbuf_clicked_cb (RBCellRendererPixbuf *renderer,
+				 const char *path_string,
+				 RBPodcastSource *source)
 {
-	GtkWidget *dialog;
-	int result;
+	GtkTreePath *path;
+	GtkTreeIter iter;
 
-	/* if the podcast feed doesn't already exist in the db,
-	 * ask if the user wants to add it anyway; if it already
-	 * exists, there's nothing to do besides reporting the error.
-	 */
-	dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (source))),
-					 GTK_DIALOG_DESTROY_WITH_PARENT,
-					 GTK_MESSAGE_ERROR,
-					 existing ? GTK_BUTTONS_OK : GTK_BUTTONS_YES_NO,
-					 _("Error in podcast"));
-
-	if (existing) {
-		gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
-							  "%s", error);
-	} else {
-		gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
-							  _("%s. Would you like to add the podcast feed anyway?"), error);
+	g_return_if_fail (path_string != NULL);
+
+	path = gtk_tree_path_new_from_string (path_string);
+	if (gtk_tree_model_get_iter (GTK_TREE_MODEL (source->priv->feed_model), &iter, path)) {
+		RhythmDBEntry *entry;
+		char *feed_url;
+
+		gtk_tree_model_get (GTK_TREE_MODEL (source->priv->feed_model),
+				    &iter,
+				    RHYTHMDB_PROPERTY_MODEL_COLUMN_TITLE, &feed_url,
+				    -1);
+
+		entry = rhythmdb_entry_lookup_by_location (source->priv->db, feed_url);
+		if (entry != NULL) {
+			const gchar *error;
+
+			error = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_PLAYBACK_ERROR);
+			if (error) {
+				rb_error_dialog (NULL, _("Podcast Error"), "%s", error);
+			}
+		}
+
+		g_free (feed_url);
 	}
 
-	gtk_window_set_title (GTK_WINDOW (dialog), "");
-	gtk_container_set_border_width (GTK_CONTAINER (dialog), 6);
+	gtk_tree_path_free (path);
+}
 
-	result = gtk_dialog_run (GTK_DIALOG (dialog));
-	gtk_widget_destroy (dialog);
+RBSource *
+rb_podcast_source_new (RBShell *shell,
+		       RBPodcastManager *podcast_manager,
+		       RhythmDBQuery *base_query,
+		       const char *name,
+		       const char *icon_name)
+{
+	RBSource *source;
 
-	/* in the existing feed case, the response will be _OK or _NONE.
-	 * we want to return FALSE here in this case, so this check works.
-	 */
-	return (result == GTK_RESPONSE_YES);
+	source = RB_SOURCE (g_object_new (RB_TYPE_PODCAST_SOURCE,
+					  "name", name,
+					  "shell", shell,
+					  "entry-type", RHYTHMDB_ENTRY_TYPE_PODCAST_POST,
+					  "source-group", RB_SOURCE_GROUP_LIBRARY,
+					  "search-type", RB_SOURCE_SEARCH_INCREMENTAL,
+					  "podcast-manager", podcast_manager,
+					  "base-query", base_query,
+					  NULL));
+
+	if (icon_name != NULL) {
+		GdkPixbuf *pixbuf;
+		gint size;
+
+		gtk_icon_size_lookup (RB_SOURCE_ICON_SIZE, &size, NULL);
+		pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
+						   icon_name,
+						   size,
+						   0, NULL);
+
+		if (pixbuf != NULL) {
+			rb_source_set_pixbuf (source, pixbuf);
+			g_object_unref (pixbuf);
+		}
+	}
+
+	return source;
 }
 
 static void
-rb_podcast_source_entry_activated_cb (RBEntryView *view,
-				      RhythmDBEntry *entry,
-				      RBPodcastSource *source)
+impl_add_to_queue (RBSource *source, RBSource *queue)
 {
-	GValue val = {0,};
+	RBEntryView *songs;
+	GList *selection;
+	GList *iter;
 
-	/* check to see if it has already been downloaded */
-	if (rb_podcast_manager_entry_downloaded (entry))
+	songs = rb_source_get_entry_view (source);
+	selection = rb_entry_view_get_selected_entries (songs);
+
+	if (selection == NULL)
 		return;
 
-	g_value_init (&val, G_TYPE_ULONG);
-	g_value_set_ulong (&val, RHYTHMDB_PODCAST_STATUS_WAITING);
-	rhythmdb_entry_set (source->priv->db, entry, RHYTHMDB_PROP_STATUS, &val);
-	rhythmdb_commit (source->priv->db);
-	g_value_unset (&val);
+	for (iter = selection; iter; iter = iter->next) {
+		RhythmDBEntry *entry = (RhythmDBEntry *)iter->data;
+		if (!rb_podcast_manager_entry_downloaded (entry))
+			continue;
+		rb_static_playlist_source_add_entry (RB_STATIC_PLAYLIST_SOURCE (queue),
+						     entry, -1);
+	}
 
-	rb_podcast_manager_download_entry (source->priv->podcast_mgr, entry);
+	g_list_foreach (selection, (GFunc)rhythmdb_entry_unref, NULL);
+	g_list_free (selection);
 }
 
 static gboolean
@@ -1963,39 +1045,93 @@ impl_can_add_to_queue (RBSource *source)
 }
 
 static void
-impl_add_to_queue (RBSource *source, RBSource *queue)
+impl_delete (RBSource *asource)
 {
-	RBEntryView *songs;
-	GList *selection;
-	GList *iter;
+	RBPodcastSource *source = RB_PODCAST_SOURCE (asource);
+	GList *entries;
+	GList *l;
+	gint ret;
+	GtkWidget *dialog;
+	GtkWidget *button;
+	GtkWindow *window;
+	RBShell *shell;
 
-	songs = rb_source_get_entry_view (source);
-	selection = rb_entry_view_get_selected_entries (songs);
+	rb_debug ("Delete episode action");
 
-	if (selection == NULL)
+	g_object_get (source, "shell", &shell, NULL);
+	g_object_get (shell, "window", &window, NULL);
+	g_object_unref (shell);
+
+	dialog = gtk_message_dialog_new (window,
+			                 GTK_DIALOG_DESTROY_WITH_PARENT,
+					 GTK_MESSAGE_WARNING,
+					 GTK_BUTTONS_NONE,
+					 _("Delete the podcast episode and downloaded file?"));
+
+	gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+	                                          _("If you choose to delete the episode and file, "
+						    "they will be permanently lost.  Please note that "
+						    "you can delete the episode but keep the downloaded "
+						    "file by choosing to delete the episode only."));
+
+	gtk_window_set_title (GTK_WINDOW (dialog), "");
+
+	gtk_dialog_add_buttons (GTK_DIALOG (dialog),
+	                        _("Delete _Episode Only"),
+	                        GTK_RESPONSE_NO,
+	                        GTK_STOCK_CANCEL,
+	                        GTK_RESPONSE_CANCEL,
+	                        NULL);
+	button = gtk_dialog_add_button (GTK_DIALOG (dialog),
+	                                _("_Delete Episode And File"),
+			                GTK_RESPONSE_YES);
+
+	gtk_window_set_focus (GTK_WINDOW (dialog), button);
+	gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_YES);
+
+	ret = gtk_dialog_run (GTK_DIALOG (dialog));
+	gtk_widget_destroy (dialog);
+
+	if (ret == GTK_RESPONSE_CANCEL || ret == GTK_RESPONSE_DELETE_EVENT)
 		return;
 
-	for (iter = selection; iter; iter = iter->next) {
-		RhythmDBEntry *entry = (RhythmDBEntry *)iter->data;
-		if (!rb_podcast_manager_entry_downloaded (entry))
-			continue;
-		rb_static_playlist_source_add_entry (RB_STATIC_PLAYLIST_SOURCE (queue),
-						     entry, -1);
+	entries = rb_entry_view_get_selected_entries (source->priv->posts);
+	for (l = entries; l != NULL; l = g_list_next (l)) {
+		RhythmDBEntry *entry = l->data;
+
+		rb_podcast_manager_cancel_download (source->priv->podcast_mgr, entry);
+		if (ret == GTK_RESPONSE_YES) {
+			rb_podcast_manager_delete_download (source->priv->podcast_mgr, entry);
+		}
+
+		/* set podcast entries to invisible instead of deleted so they will
+		 * not reappear after the podcast has been updated
+		 */
+		GValue v = {0,};
+		g_value_init (&v, G_TYPE_BOOLEAN);
+		g_value_set_boolean (&v, TRUE);
+		rhythmdb_entry_set (source->priv->db, entry, RHYTHMDB_PROP_HIDDEN, &v);
+		g_value_unset (&v);
 	}
 
-	g_list_foreach (selection, (GFunc)rhythmdb_entry_unref, NULL);
-	g_list_free (selection);
+	g_list_foreach (entries, (GFunc)rhythmdb_entry_unref, NULL);
+	g_list_free (entries);
+
+	rhythmdb_commit (source->priv->db);
 }
 
-static GList *
-impl_get_ui_actions (RBSource *source)
+static char *
+impl_get_browser_key (RBSource *asource)
 {
-	GList *actions = NULL;
+	return g_strdup (CONF_STATE_SHOW_BROWSER);
+}
 
-	actions = g_list_prepend (actions, g_strdup ("PodcastUpdateAllFeeds"));
-	actions = g_list_prepend (actions, g_strdup ("MusicNewPodcast"));
 
-	return actions;
+static RBEntryView *
+impl_get_entry_view (RBSource *asource)
+{
+	RBPodcastSource *source = RB_PODCAST_SOURCE (asource);
+	return source->priv->posts;
 }
 
 static GList *
@@ -2010,29 +1146,82 @@ impl_get_search_actions (RBSource *source)
 	return actions;
 }
 
-static void
-rb_podcast_source_location_added_cb (RBURIDialog *dialog,
-				     const char *location,
-				     RBPodcastSource *source)
+static GList *
+impl_get_ui_actions (RBSource *source)
+{
+	GList *actions = NULL;
+
+	actions = g_list_prepend (actions, g_strdup ("PodcastUpdateAllFeeds"));
+	actions = g_list_prepend (actions, g_strdup ("MusicNewPodcast"));
+
+	return actions;
+}
+
+static RBSourceEOFType
+impl_handle_eos (RBSource *asource)
 {
-	rb_podcast_manager_subscribe_feed (source->priv->podcast_mgr, location, FALSE);
+	return RB_SOURCE_EOF_STOP;
+}
+
+
+static gboolean
+impl_receive_drag (RBSource *asource, GtkSelectionData *selection_data)
+{
+	GList *list, *i;
+	RBPodcastSource *source = RB_PODCAST_SOURCE (asource);
+
+	list = rb_uri_list_parse ((const char *) gtk_selection_data_get_data (selection_data));
+
+	for (i = list; i != NULL; i = i->next) {
+		char *uri = NULL;
+
+		uri = i->data;
+		if ((uri != NULL) && (!rhythmdb_entry_lookup_by_location (source->priv->db, uri))) {
+			rb_podcast_manager_subscribe_feed (source->priv->podcast_mgr, uri, FALSE);
+		}
+		
+		if (gtk_selection_data_get_data_type (selection_data) == gdk_atom_intern ("_NETSCAPE_URL", FALSE)) {
+			i = i->next;
+		}
+	}
+
+	rb_list_deep_free (list);
+	return TRUE;
 }
 
 static void
-rb_podcast_source_cmd_new_podcast (GtkAction *action,
-				   RBPodcastSource *source)
+impl_search (RBSource *asource, RBSourceSearch *search, const char *cur_text, const char *new_text)
 {
-	GtkWidget *dialog;
+	RBPodcastSource *source = RB_PODCAST_SOURCE (asource);
 
-	rb_debug ("Got new podcast command");
+	if (search == NULL) {
+		search = source->priv->default_search;
+	}
 
-	dialog = rb_uri_dialog_new (_("New Podcast Feed"), _("URL of podcast feed:"));
-	g_signal_connect_object (G_OBJECT (dialog),
-				 "location-added",
-				 G_CALLBACK (rb_podcast_source_location_added_cb),
-				 source, 0);
-	gtk_dialog_run (GTK_DIALOG (dialog));
-	gtk_widget_destroy (dialog);
+	if (source->priv->search_query != NULL) {
+		rhythmdb_query_free (source->priv->search_query);
+		source->priv->search_query = NULL;
+	}
+	source->priv->search_query = rb_source_search_create_query (search, source->priv->db, new_text);
+	rb_podcast_source_do_query (source);
+
+	rb_source_notify_filter_changed (RB_SOURCE (source));
+}
+
+static gboolean
+impl_show_popup (RBSource *source)
+{
+	_rb_source_show_popup (RB_SOURCE (source), "/PodcastSourcePopup");
+	return TRUE;
+}
+
+static void
+impl_song_properties (RBSource *asource)
+{
+	RBPodcastSource *source = RB_PODCAST_SOURCE (asource);
+	GtkWidget *dialog = rb_podcast_properties_dialog_new (source->priv->posts);
+	if (dialog)
+		gtk_widget_show_all (dialog);
 }
 
 static void
@@ -2059,121 +1248,454 @@ impl_get_status (RBSource *source, char **text, char **progress_text, float *pro
 	}
 }
 
-static guint
-impl_want_uri (RBSource *source, const char *uri)
+
+static char *
+impl_get_delete_action (RBSource *source)
 {
-	if (g_str_has_prefix (uri, "http://";) == FALSE)
-		return 0;
+	return g_strdup ("EditDelete");
+}
 
-	if (g_str_has_suffix (uri, ".xml") ||
-	    g_str_has_suffix (uri, ".rss"))
-		return 100;
+static void
+impl_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+	RBPodcastSource *source = RB_PODCAST_SOURCE (object);
 
-	return 0;
+	switch (prop_id) {
+	case PROP_PODCAST_MANAGER:
+		source->priv->podcast_mgr = g_value_dup_object (value);
+		break;
+	case PROP_BASE_QUERY:
+		source->priv->base_query = rhythmdb_query_copy (g_value_get_pointer (value));
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+		break;
+	}
 }
 
 static void
-impl_add_uri (RBSource *asource,
-	      const char *uri,
-	      const char *title,
-	      const char *genre,
-	      RBSourceAddCallback callback,
-	      gpointer data,
-	      GDestroyNotify destroy_data)
+impl_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
 {
-	RBPodcastSource *source = RB_PODCAST_SOURCE (asource);
-	rb_podcast_manager_subscribe_feed (source->priv->podcast_mgr, uri, FALSE);
-	if (callback != NULL) {
-		callback (asource, uri, data);
-		if (destroy_data != NULL) {
-			destroy_data (data);
-		}
+	RBPodcastSource *source = RB_PODCAST_SOURCE (object);
+
+	switch (prop_id) {
+	case PROP_PODCAST_MANAGER:
+		g_value_set_object (value, source->priv->podcast_mgr);
+		break;
+	case PROP_BASE_QUERY:
+		g_value_set_pointer (value, source->priv->base_query);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+		break;
 	}
 }
 
 static void
-rb_podcast_source_entry_changed_cb (RhythmDB *db,
-				    RhythmDBEntry *entry,
-				    GValueArray *changes,
-				    RBPodcastSource *source)
+impl_constructed (GObject *object)
 {
-	RhythmDBEntryType *entry_type;
-	gboolean feed_changed;
-	int i;
+	RBPodcastSource *source;
+	GtkTreeViewColumn *column;
+	GtkCellRenderer *renderer;
+	RBShell *shell;
+	RhythmDBQueryModel *query_model;
+	GtkAction *action;
 
-	entry_type = rhythmdb_entry_get_entry_type (entry);
-	if (entry_type != RHYTHMDB_ENTRY_TYPE_PODCAST_FEED)
-		return;
+	RB_CHAIN_GOBJECT_METHOD (rb_podcast_source_parent_class, constructed, object);
+	source = RB_PODCAST_SOURCE (object);
 
-	feed_changed = FALSE;
-	for (i = 0; i < changes->n_values; i++) {
-		GValue *v = g_value_array_get_nth (changes, i);
-		RhythmDBEntryChange *change = g_value_get_boxed (v);
+	g_object_get (source, "shell", &shell, NULL);
+	g_object_get (shell, "db", &source->priv->db, NULL);
 
-		if (change->prop == RHYTHMDB_PROP_PLAYBACK_ERROR) {
-			feed_changed = TRUE;
-			break;
-		}
+	source->priv->action_group = _rb_source_register_action_group (RB_SOURCE (source),
+								       "PodcastActions",
+								       NULL, 0,
+								       source);
+
+	_rb_action_group_add_source_actions (source->priv->action_group,
+					     G_OBJECT (shell),
+					     rb_podcast_source_actions,
+					     G_N_ELEMENTS (rb_podcast_source_actions));
+
+	action = gtk_action_group_get_action (source->priv->action_group,
+					      "MusicNewPodcast");
+	/* Translators: this is the toolbar button label
+	   for New Podcast Feed action. */
+	g_object_set (action, "short-label", C_("Podcast", "New"), NULL);
+
+	action = gtk_action_group_get_action (source->priv->action_group,
+					      "PodcastUpdateAllFeeds");
+	/* Translators: this is the toolbar button label
+	   for Update All Feeds action. */
+	g_object_set (action, "short-label", _("Update"), NULL);
+
+	if (gtk_action_group_get_action (source->priv->action_group,
+					 rb_podcast_source_radio_actions[0].name) == NULL) {
+		gtk_action_group_add_radio_actions (source->priv->action_group,
+						    rb_podcast_source_radio_actions,
+						    G_N_ELEMENTS (rb_podcast_source_radio_actions),
+						    0,
+						    NULL,
+						    NULL);
+		rb_source_search_basic_create_for_actions (source->priv->action_group,
+							   rb_podcast_source_radio_actions,
+							   G_N_ELEMENTS (rb_podcast_source_radio_actions));
 	}
 
-	if (feed_changed) {
-		const char *loc;
-		GtkTreeIter iter;
+	source->priv->default_search = rb_source_search_basic_new (RHYTHMDB_PROP_SEARCH_MATCH);
 
-		loc = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
-		if (rhythmdb_property_model_iter_from_string (source->priv->feed_model,
-							      loc,
-							      &iter)) {
-			GtkTreePath *path;
+	source->priv->paned = gtk_vpaned_new ();
 
-			path = gtk_tree_model_get_path (GTK_TREE_MODEL (source->priv->feed_model),
-						        &iter);
-			gtk_tree_model_row_changed (GTK_TREE_MODEL (source->priv->feed_model),
-						    path,
-						    &iter);
-			gtk_tree_path_free (path);
-		}
+	g_object_unref (shell);
+
+	/* set up posts view */
+	source->priv->posts = rb_entry_view_new (source->priv->db,
+						 rb_shell_get_player (shell),
+						 CONF_STATE_PODCAST_SORTING_POSTS,
+						 TRUE, FALSE);
+
+	g_signal_connect_object (source->priv->posts,
+				 "entry-activated",
+				 G_CALLBACK (episode_activated_cb),
+				 source, 0);
+
+	/* Podcast date column */
+	column = gtk_tree_view_column_new ();
+	renderer = gtk_cell_renderer_text_new();
+
+	gtk_tree_view_column_pack_start (column, renderer, TRUE);
+
+	gtk_tree_view_column_set_clickable (column, TRUE);
+	gtk_tree_view_column_set_resizable (column, TRUE);
+	gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
+	{
+		const char *sample_strings[3];
+		sample_strings[0] = _("Date");
+		sample_strings[1] = rb_entry_view_get_time_date_column_sample ();
+		sample_strings[2] = NULL;
+		rb_entry_view_set_fixed_column_width (source->priv->posts, column, renderer, sample_strings);
+	}
+
+	gtk_tree_view_column_set_cell_data_func (column, renderer,
+						 (GtkTreeCellDataFunc) podcast_post_date_cell_data_func,
+						 source, NULL);
+
+	rb_entry_view_append_column_custom (source->priv->posts, column,
+					    _("Date"), "Date",
+					    (GCompareDataFunc) podcast_post_date_sort_func,
+					    0, NULL);
+
+	rb_entry_view_append_column (source->priv->posts, RB_ENTRY_VIEW_COL_TITLE, TRUE);
+
+	/* COLUMN FEED */
+	column = gtk_tree_view_column_new ();
+	renderer = gtk_cell_renderer_text_new();
+
+	gtk_tree_view_column_pack_start (column, renderer, TRUE);
+
+	gtk_tree_view_column_set_clickable (column, TRUE);
+	gtk_tree_view_column_set_resizable (column, TRUE);
+	gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
+	gtk_tree_view_column_set_expand (column, TRUE);
+
+	gtk_tree_view_column_set_cell_data_func (column, renderer,
+						 (GtkTreeCellDataFunc) podcast_post_feed_cell_data_func,
+						 source, NULL);
+
+	rb_entry_view_append_column_custom (source->priv->posts, column,
+					    _("Feed"), "Feed",
+					    (GCompareDataFunc) podcast_post_feed_sort_func,
+					    0, NULL);
+
+	rb_entry_view_append_column (source->priv->posts, RB_ENTRY_VIEW_COL_DURATION, FALSE);
+	rb_entry_view_append_column (source->priv->posts, RB_ENTRY_VIEW_COL_RATING, FALSE);
+	rb_entry_view_append_column (source->priv->posts, RB_ENTRY_VIEW_COL_PLAY_COUNT, FALSE);
+	rb_entry_view_append_column (source->priv->posts, RB_ENTRY_VIEW_COL_LAST_PLAYED, FALSE);
+
+	/* Status column */
+	column = gtk_tree_view_column_new ();
+	renderer = gtk_cell_renderer_progress_new();
+
+	gtk_tree_view_column_pack_start (column, renderer, TRUE);
+
+	gtk_tree_view_column_set_clickable (column, TRUE);
+	gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
+
+	{
+		static const char *status_strings[6];
+		status_strings[0] = _("Status");
+		status_strings[1] = _("Downloaded");
+		status_strings[2] = _("Waiting");
+		status_strings[3] = _("Failed");
+		status_strings[4] = "100 %";
+		status_strings[5] = NULL;
+
+		rb_entry_view_set_fixed_column_width (source->priv->posts,
+						      column,
+						      renderer,
+						      status_strings);
 	}
+
+	gtk_tree_view_column_set_cell_data_func (column, renderer,
+						 (GtkTreeCellDataFunc) podcast_post_status_cell_data_func,
+						 source, NULL);
+
+	rb_entry_view_append_column_custom (source->priv->posts, column,
+					    _("Status"), "Status",
+					    (GCompareDataFunc) podcast_post_status_sort_func,
+					    0, NULL);
+
+	g_signal_connect_object (source->priv->posts,
+				 "sort-order-changed",
+				 G_CALLBACK (podcast_posts_view_sort_order_changed_cb),
+				 source, 0);
+
+	g_signal_connect_object (source->priv->posts,
+				 "size_allocate",
+				 G_CALLBACK (paned_size_allocate_cb),
+				 source, 0);
+
+	g_signal_connect_object (source->priv->posts,
+				 "show_popup",
+				 G_CALLBACK (podcast_posts_show_popup_cb),
+				 source, 0);
+
+	/* configure feed view */
+	source->priv->feeds = rb_property_view_new (source->priv->db,
+						    RHYTHMDB_PROP_SUBTITLE,
+						    _("Feed"));
+	rb_property_view_set_selection_mode (RB_PROPERTY_VIEW (source->priv->feeds),
+					     GTK_SELECTION_MULTIPLE);
+
+	query_model = rhythmdb_query_model_new_empty (source->priv->db);
+	source->priv->feed_model = rb_property_view_get_model (RB_PROPERTY_VIEW (source->priv->feeds));
+	g_object_set (source->priv->feed_model, "query-model", query_model, NULL);
+
+	/* maybe do this async? */
+	rhythmdb_do_full_query_parsed (source->priv->db,
+				       RHYTHMDB_QUERY_RESULTS (query_model),
+				       source->priv->base_query);
+	g_object_unref (query_model);
+
+	/* error indicator column */
+	column = gtk_tree_view_column_new ();
+	renderer = rb_cell_renderer_pixbuf_new ();
+	gtk_tree_view_column_pack_start (column, renderer, TRUE);
+	gtk_tree_view_column_set_cell_data_func (column, renderer,
+						 (GtkTreeCellDataFunc) podcast_feed_error_cell_data_func,
+						 source, NULL);
+	gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
+	gtk_tree_view_column_set_fixed_width (column, gdk_pixbuf_get_width (source->priv->error_pixbuf) + 5);
+	gtk_tree_view_column_set_reorderable (column, FALSE);
+	gtk_tree_view_column_set_visible (column, TRUE);
+	rb_property_view_append_column_custom (source->priv->feeds, column);
+	g_signal_connect_object (renderer,
+				 "pixbuf-clicked",
+				 G_CALLBACK (podcast_error_pixbuf_clicked_cb),
+				 source, 0);
+
+	/* redraw error indicator when errors are set or cleared */
+	g_signal_connect_object (source->priv->db,
+				 "entry_changed",
+				 G_CALLBACK (podcast_entry_changed_cb),
+				 source, 0);
+
+	/* title column */
+	column = gtk_tree_view_column_new ();
+	renderer = gtk_cell_renderer_text_new ();
+
+	gtk_tree_view_column_pack_start (column, renderer, TRUE);
+
+	gtk_tree_view_column_set_cell_data_func (column,
+						 renderer,
+						 (GtkTreeCellDataFunc) podcast_feed_title_cell_data_func,
+						 source, NULL);
+
+	gtk_tree_view_column_set_title (column, _("Feed"));
+	gtk_tree_view_column_set_reorderable (column, FALSE);
+	gtk_tree_view_column_set_visible (column, TRUE);
+	rb_property_view_append_column_custom (source->priv->feeds, column);
+
+	g_signal_connect_object (source->priv->feeds, "show_popup",
+				 G_CALLBACK (podcast_feeds_show_popup_cb),
+				 source, 0);
+
+	g_signal_connect_object (source->priv->feeds,
+				 "properties-selected",
+				 G_CALLBACK (feed_select_change_cb),
+				 source, 0);
+
+	rb_property_view_set_search_func (source->priv->feeds,
+					  (GtkTreeViewSearchEqualFunc) podcast_feed_title_search_func,
+					  source,
+					  NULL);
+
+	/* set up drag and drop */
+	g_signal_connect_object (source->priv->feeds,
+				 "drag_data_received",
+				 G_CALLBACK (posts_view_drag_data_received_cb),
+				 source, 0);
+
+	gtk_drag_dest_set (GTK_WIDGET (source->priv->feeds),
+			   GTK_DEST_DEFAULT_ALL,
+			   posts_view_drag_types, 2,
+			   GDK_ACTION_COPY | GDK_ACTION_MOVE);
+
+	g_signal_connect_object (G_OBJECT (source->priv->posts),
+				 "drag_data_received",
+				 G_CALLBACK (posts_view_drag_data_received_cb),
+				 source, 0);
+
+	gtk_drag_dest_set (GTK_WIDGET (source->priv->posts),
+			   GTK_DEST_DEFAULT_ALL,
+			   posts_view_drag_types, 2,
+			   GDK_ACTION_COPY | GDK_ACTION_MOVE);
+
+	/* pack the feed and post views into the source */
+	gtk_paned_pack1 (GTK_PANED (source->priv->paned),
+			 GTK_WIDGET (source->priv->feeds), FALSE, FALSE);
+	gtk_paned_pack2 (GTK_PANED (source->priv->paned),
+			 GTK_WIDGET (source->priv->posts), TRUE, FALSE);
+
+	gtk_box_pack_start (GTK_BOX (source->priv->vbox), source->priv->paned, TRUE, TRUE, 0);
+
+	/* different prefix for each source? */
+	source->priv->prefs_notify_id = eel_gconf_notification_add (CONF_STATE_PODCAST_PREFIX,
+								    (GConfClientNotifyFunc) podcast_pref_change_cb,
+								    source);
+
+	gtk_widget_show_all (GTK_WIDGET (source));
+	rb_podcast_source_state_prefs_sync (source);
+
+	rb_podcast_source_do_query (source);
 }
 
+
 static void
-rb_podcast_source_pixbuf_clicked_cb (RBCellRendererPixbuf *renderer,
-				     const char *path_string,
-				     RBPodcastSource *source)
+impl_dispose (GObject *object)
 {
-	GtkTreePath *path;
-	GtkTreeIter iter;
+	RBPodcastSource *source;
 
-	g_return_if_fail (path_string != NULL);
+	source = RB_PODCAST_SOURCE (object);
 
-	path = gtk_tree_path_new_from_string (path_string);
-	if (gtk_tree_model_get_iter (GTK_TREE_MODEL (source->priv->feed_model), &iter, path)) {
-		RhythmDBEntry *entry;
-		char *feed_url;
+	if (source->priv->db != NULL) {
+		g_object_unref (source->priv->db);
+		source->priv->db = NULL;
+	}
 
-		gtk_tree_model_get (GTK_TREE_MODEL (source->priv->feed_model),
-				    &iter,
-				    RHYTHMDB_PROPERTY_MODEL_COLUMN_TITLE, &feed_url,
-				    -1);
+	if (source->priv->search_query != NULL) {
+		rhythmdb_query_free (source->priv->search_query);
+		source->priv->search_query = NULL;
+	}
 
-		entry = rhythmdb_entry_lookup_by_location (source->priv->db, feed_url);
-		if (entry != NULL) {
-			const gchar *error;
+	if (source->priv->action_group != NULL) {
+		g_object_unref (source->priv->action_group);
+		source->priv->action_group = NULL;
+	}
 
-			error = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_PLAYBACK_ERROR);
-			if (error) {
-				rb_error_dialog (NULL, _("Podcast Error"), "%s", error);
-			}
-		}
+	if (source->priv->podcast_mgr != NULL) {
+		g_object_unref (source->priv->podcast_mgr);
+		source->priv->podcast_mgr = NULL;
+	}
 
-		g_free (feed_url);
+	if (source->priv->error_pixbuf != NULL) {
+		g_object_unref (source->priv->error_pixbuf);
+		source->priv->error_pixbuf = NULL;
 	}
 
-	gtk_tree_path_free (path);
+	if (source->priv->prefs_notify_id != 0) {
+		eel_gconf_notification_remove (source->priv->prefs_notify_id);
+		source->priv->prefs_notify_id = 0;
+	}
+
+	G_OBJECT_CLASS (rb_podcast_source_parent_class)->dispose (object);
 }
 
-static char *
-impl_get_delete_action (RBSource *source)
+static void
+impl_finalize (GObject *object)
 {
-	return g_strdup ("EditDelete");
+	RBPodcastSource *source;
+
+	g_return_if_fail (object != NULL);
+	g_return_if_fail (RB_IS_PODCAST_SOURCE (object));
+
+	source = RB_PODCAST_SOURCE (object);
+
+	g_return_if_fail (source->priv != NULL);
+
+	if (source->priv->selected_feeds) {
+		g_list_foreach (source->priv->selected_feeds, (GFunc) g_free, NULL);
+	        g_list_free (source->priv->selected_feeds);
+	}
+
+	G_OBJECT_CLASS (rb_podcast_source_parent_class)->finalize (object);
+}
+
+static void
+rb_podcast_source_init (RBPodcastSource *source)
+{
+	GtkIconTheme *icon_theme;
+	source->priv = G_TYPE_INSTANCE_GET_PRIVATE (source,
+						    RB_TYPE_PODCAST_SOURCE,
+						    RBPodcastSourcePrivate);
+
+	source->priv->selected_feeds = NULL;
+	source->priv->vbox = gtk_vbox_new (FALSE, 5);
+
+	gtk_container_add (GTK_CONTAINER (source), source->priv->vbox);
+
+	icon_theme = gtk_icon_theme_get_default ();
+	source->priv->error_pixbuf = gtk_icon_theme_load_icon (icon_theme,
+							       "dialog-error",
+							       16,
+							       0,
+							       NULL);
+}
+
+static void
+rb_podcast_source_class_init (RBPodcastSourceClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
+
+	object_class->dispose = impl_dispose;
+	object_class->finalize = impl_finalize;
+	object_class->constructed = impl_constructed;
+	object_class->set_property = impl_set_property;
+	object_class->get_property = impl_get_property;
+
+	source_class->impl_add_to_queue = impl_add_to_queue;
+	source_class->impl_can_add_to_queue = impl_can_add_to_queue;
+	source_class->impl_can_browse = (RBSourceFeatureFunc) rb_true_function;
+	source_class->impl_can_copy = (RBSourceFeatureFunc) rb_false_function;
+	source_class->impl_can_cut = (RBSourceFeatureFunc) rb_false_function;
+	source_class->impl_can_delete = (RBSourceFeatureFunc) rb_true_function;
+	source_class->impl_delete = impl_delete;
+	source_class->impl_get_browser_key  = impl_get_browser_key;
+	source_class->impl_get_entry_view = impl_get_entry_view;
+	source_class->impl_get_search_actions = impl_get_search_actions;
+	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;
+	source_class->impl_search = impl_search;
+	source_class->impl_show_popup = impl_show_popup;
+	source_class->impl_song_properties = impl_song_properties;
+	source_class->impl_get_status = impl_get_status;
+	source_class->impl_get_delete_action = impl_get_delete_action;
+
+	g_object_class_install_property (object_class,
+					 PROP_PODCAST_MANAGER,
+					 g_param_spec_object ("podcast-manager",
+					                      "RBPodcastManager",
+					                      "RBPodcastManager object",
+					                      RB_TYPE_PODCAST_MANAGER,
+					                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+	g_object_class_install_property (object_class,
+					 PROP_BASE_QUERY,
+					 g_param_spec_pointer ("base-query",
+							       "Base query",
+							       "Base query for the source",
+							       G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+	g_type_class_add_private (klass, sizeof (RBPodcastSourcePrivate));
 }
diff --git a/sources/rb-podcast-source.h b/podcast/rb-podcast-source.h
similarity index 89%
rename from sources/rb-podcast-source.h
rename to podcast/rb-podcast-source.h
index 5b9dfe9..bea1f1f 100644
--- a/sources/rb-podcast-source.h
+++ b/podcast/rb-podcast-source.h
@@ -29,6 +29,7 @@
 #define __RB_PODCAST_SOURCE_H
 
 #include <shell/rb-shell.h>
+#include <podcast/rb-podcast-manager.h>
 #include <sources/rb-source.h>
 
 G_BEGIN_DECLS
@@ -43,7 +44,7 @@ G_BEGIN_DECLS
 typedef struct _RBPodcastSource RBPodcastSource;
 typedef struct _RBPodcastSourceClass RBPodcastSourceClass;
 
-typedef struct RBPodcastSourcePrivate RBPodcastSourcePrivate;
+typedef struct _RBPodcastSourcePrivate RBPodcastSourcePrivate;
 
 struct _RBPodcastSource
 {
@@ -58,9 +59,12 @@ struct _RBPodcastSourceClass
 };
 
 GType		rb_podcast_source_get_type	(void);
-RBSource *	rb_podcast_source_new		(RBShell *shell);
-void 		rb_podcast_source_add_feed	(RBPodcastSource *source, const gchar *uri);
-void 		rb_podcast_source_shutdown	(RBPodcastSource *source);
+
+RBSource 	*rb_podcast_source_new		(RBShell *shell,
+						 RBPodcastManager *podcast_manager,
+						 RhythmDBQuery *base_query,
+						 const char *name,
+						 const char *icon_name);
 
 G_END_DECLS
 
diff --git a/rhythmdb/rhythmdb-property-model.c b/rhythmdb/rhythmdb-property-model.c
index d7f5de4..644fbc3 100644
--- a/rhythmdb/rhythmdb-property-model.c
+++ b/rhythmdb/rhythmdb-property-model.c
@@ -1380,6 +1380,7 @@ rhythmdb_property_model_enable_drag (RhythmDBPropertyModel *model,
 		n_elements = G_N_ELEMENTS (targets_artist);
 		break;
 	case RHYTHMDB_PROP_LOCATION:
+	case RHYTHMDB_PROP_SUBTITLE:		/* more or less */
 		targets = targets_location;
 		n_elements = G_N_ELEMENTS (targets_location);
 		break;
diff --git a/shell/rb-shell.c b/shell/rb-shell.c
index f2c211b..4976349 100644
--- a/shell/rb-shell.c
+++ b/shell/rb-shell.c
@@ -91,6 +91,9 @@
 #include "rb-song-info.h"
 #include "rb-marshal.h"
 #include "rb-missing-plugins.h"
+#include "rb-podcast-manager.h"
+#include "rb-podcast-main-source.h"
+#include "rb-podcast-entry-types.h"
 
 #include "eggsmclient.h"
 
@@ -339,6 +342,7 @@ struct _RBShellPrivate
 	RBPlaylistManager *playlist_manager;
 	RBRemovableMediaManager *removable_media_manager;
 	RBTrackTransferQueue *track_transfer_queue;
+	RBPodcastManager *podcast_manager;
 
 	RBLibrarySource *library_source;
 	RBPodcastSource *podcast_source;
@@ -1124,6 +1128,9 @@ rb_shell_finalize (GObject *object)
 	g_object_unref (shell->priv->removable_media_manager);
 	g_object_unref (shell->priv->track_transfer_queue);
 
+	rb_debug ("unreffing podcast manager");
+	g_object_unref (shell->priv->podcast_manager);
+
 	rb_debug ("unreffing clipboard shell");
 	g_object_unref (shell->priv->clipboard_shell);
 
@@ -1271,6 +1278,7 @@ construct_widgets (RBShell *shell)
 
 	rb_debug ("shell: initializing shell services");
 
+	shell->priv->podcast_manager = rb_podcast_manager_new (shell->priv->db);
 	shell->priv->track_transfer_queue = rb_track_transfer_queue_new (shell);
 	shell->priv->ui_manager = gtk_ui_manager_new ();
 	shell->priv->source_ui_merge_id = gtk_ui_manager_new_merge_id (shell->priv->ui_manager);
@@ -1422,7 +1430,7 @@ construct_sources (RBShell *shell)
 
 	shell->priv->library_source = RB_LIBRARY_SOURCE (rb_library_source_new (shell));
 	rb_shell_append_source (shell, RB_SOURCE (shell->priv->library_source), NULL);
-	shell->priv->podcast_source = RB_PODCAST_SOURCE (rb_podcast_source_new (shell));
+	shell->priv->podcast_source = RB_PODCAST_SOURCE (rb_podcast_main_source_new (shell, shell->priv->podcast_manager));
 	rb_shell_append_source (shell, RB_SOURCE (shell->priv->podcast_source), NULL);
 	shell->priv->missing_files_source = rb_missing_files_source_new (shell, shell->priv->library_source);
 	rb_shell_append_source (shell, shell->priv->missing_files_source, NULL);
@@ -1432,6 +1440,8 @@ construct_sources (RBShell *shell)
 									 RHYTHMDB_ENTRY_TYPE_IGNORE);
 	rb_shell_append_source (shell, shell->priv->import_errors_source, NULL);
 
+	rb_podcast_main_source_add_subsources (RB_PODCAST_MAIN_SOURCE (shell->priv->podcast_source));
+
 	/* Find the playlist name if none supplied */
 	if (shell->priv->playlists_file) {
 		pathname = g_strdup (shell->priv->playlists_file);
@@ -2610,7 +2620,7 @@ rb_shell_quit (RBShell *shell,
 
 	rb_plugins_engine_shutdown ();
 
-	rb_podcast_source_shutdown (shell->priv->podcast_source);
+	rb_podcast_manager_shutdown (shell->priv->podcast_manager);
 
 	rb_shell_shutdown (shell);
 	rb_shell_sync_state (shell);
@@ -3371,7 +3381,7 @@ rb_shell_load_uri (RBShell *shell,
 	/* 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_podcast_manager_subscribe_feed (shell->priv->podcast_manager, uri, FALSE);
 		rb_shell_select_source (shell, RB_SOURCE (shell->priv->podcast_source));
 		return TRUE;
 	}
diff --git a/sources/Makefile.am b/sources/Makefile.am
index 9e284c1..4cc2b4f 100644
--- a/sources/Makefile.am
+++ b/sources/Makefile.am
@@ -32,8 +32,6 @@ libsources_la_SOURCES = 		\
 	rb-browser-source.c		\
 	rb-library-source.c		\
 	rb-library-source.h		\
-	rb-podcast-source.c		\
-	rb-podcast-source.h		\
 	rb-removable-media-source.c	\
 	rb-media-player-source.c	\
 	rb-playlist-source.c            \
@@ -58,8 +56,8 @@ INCLUDES =						\
 	-I$(top_srcdir)/rhythmdb			\
 	-I$(top_srcdir)/metadata 			\
 	-I$(top_srcdir)/widgets 			\
-	-I$(top_srcdir)/podcast				\
 	-I$(top_srcdir)/shell				\
+	-I$(top_srcdir)/podcast				\
 	-I$(top_srcdir)/sources/sync			\
 	-I$(top_srcdir)/backends			\
 	-DPIXMAP_DIR=\""$(datadir)/pixmaps"\"		\



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