rhythmbox r5689 - in trunk: . lib podcast sources



Author: jmatthew
Date: Sat Apr 26 11:36:47 2008
New Revision: 5689
URL: http://svn.gnome.org/viewvc/rhythmbox?rev=5689&view=rev

Log:
2008-04-26  Jonathan Matthew  <jonathan d14n org>

	* podcast/rb-podcast-parse.c: (rb_podcast_parse_error_quark),
	(rb_podcast_parse_load_feed):
	* podcast/rb-podcast-parse.h:
	* podcast/test-podcast-parse.c: (main):
	In rb_podcast_parse_load_feed, add a flag indicating that the feed
	already exists, and return a GError indicating what actually went
	wrong when we can't parse it.  If the feed already exists, skip the
	MIME type check, as the user has already confirmed that they want to
	use the feed.

	* lib/rb-marshal.list:
	* podcast/rb-podcast-manager.c: (rb_podcast_manager_class_init),
	(rb_podcast_manager_head_query_cb),
	(rb_podcast_manager_subscribe_feed),
	(rb_podcast_manager_free_parse_result),
	(rb_podcast_manager_parse_complete_cb), (confirm_bad_mime_type),
	(rb_podcast_manager_thread_parse_feed),
	(rb_podcast_manager_update_synctime),
	(rb_podcast_manager_insert_feed),
	(rb_podcast_manager_handle_feed_error):
	* podcast/rb-podcast-manager.h:
	Add two new bits of information when parsing feeds: whether the user
	directly requested the feed be parsed (by adding a new feed, or
	updating a single existing feed), used to decide whether to show error
	dialogs; and whether the feed already exists in the database, which
	controls whether the MIME type check is performed.

	When the MIME type check fails for a new feed, ask the user to confirm
	they want to use the feed anyway, and if they do, set the existing
	feed flag and try again.

	* sources/rb-podcast-source.c: (rb_podcast_source_init),
	(rb_podcast_source_dispose), (rb_podcast_source_constructor),
	(rb_podcast_source_add_feed), (rb_podcast_source_cmd_update_feed),
	(rb_podcast_source_feed_error_cell_data_func),
	(rb_podcast_source_download_process_error_cb),
	(rb_podcast_source_entry_activated_cb),
	(rb_podcast_source_location_added_cb), (impl_add_uri),
	(rb_podcast_source_entry_changed_cb),
	(rb_podcast_source_pixbuf_clicked_cb):
	Add a column that displays the error icon for podcasts that had errors
	last time they were updated.

	The effect of all this is that you don't get error dialogs resulting
	from periodic podcast updates any more.  Instead, the error icon
	indicates which feeds had problems, and provides access to the error
	information.

	Fixes #352493.


Modified:
   trunk/ChangeLog
   trunk/lib/rb-marshal.list
   trunk/podcast/rb-podcast-manager.c
   trunk/podcast/rb-podcast-manager.h
   trunk/podcast/rb-podcast-parse.c
   trunk/podcast/rb-podcast-parse.h
   trunk/podcast/test-podcast-parse.c
   trunk/sources/rb-podcast-source.c

Modified: trunk/lib/rb-marshal.list
==============================================================================
--- trunk/lib/rb-marshal.list	(original)
+++ trunk/lib/rb-marshal.list	Sat Apr 26 11:36:47 2008
@@ -1,6 +1,7 @@
 BOOLEAN:BOOLEAN,BOOLEAN,BOOLEAN
 BOOLEAN:POINTER
 BOOLEAN:POINTER,POINTER,POINTER
+BOOLEAN:STRING,BOOLEAN
 BOOLEAN:STRING,STRING,OBJECT
 INT:VOID
 OBJECT:OBJECT

Modified: trunk/podcast/rb-podcast-manager.c
==============================================================================
--- trunk/podcast/rb-podcast-manager.c	(original)
+++ trunk/podcast/rb-podcast-manager.c	Sat Apr 26 11:36:47 2008
@@ -84,18 +84,13 @@
 	LAST_SIGNAL
 };
 
-typedef enum
-{
-	RESULT_PARSE_OK,
-	RESULT_PARSE_ERROR
-} RBPodcastParseResultType;
-
 /* passed from feed parsing threads back to main thread */
 typedef struct
 {
-	RBPodcastParseResultType result;
+	GError			*error;
 	RBPodcastChannel 	*channel;
 	RBPodcastManager	*pd;
+	gboolean		 automatic;
 } RBPodcastManagerParseResult;
 
 typedef struct
@@ -116,6 +111,8 @@
 {
 	RBPodcastManager *pd;
 	char *url;
+	gboolean automatic;
+	gboolean existing_feed;
 } RBPodcastThreadInfo;
 
 struct RBPodcastManagerPrivate
@@ -170,6 +167,10 @@
 							 RhythmDBEntry *entry);
 static gboolean rb_podcast_manager_next_file 		(RBPodcastManager * pd);
 static void rb_podcast_manager_insert_feed 		(RBPodcastManager *pd, RBPodcastChannel *data);
+static gboolean rb_podcast_manager_handle_feed_error	(RBPodcastManager *mgr,
+							 const char *url,
+							 GError *error,
+							 gboolean emit);
 
 static gpointer rb_podcast_manager_thread_parse_feed	(RBPodcastThreadInfo *info);
 
@@ -264,10 +265,11 @@
 				G_SIGNAL_RUN_LAST,
 				G_STRUCT_OFFSET (RBPodcastManagerClass, process_error),
 				NULL, NULL,
-				g_cclosure_marshal_VOID__STRING,
-				G_TYPE_NONE,
-				1,
-				G_TYPE_STRING);
+				rb_marshal_BOOLEAN__STRING_BOOLEAN,
+				G_TYPE_BOOLEAN,
+				2,
+				G_TYPE_STRING,
+				G_TYPE_BOOLEAN);
 
 #ifdef HAVE_GSTREAMER_0_10_MISSING_PLUGINS
 	rb_podcast_manager_signals[MISSING_PLUGINS] =
@@ -573,7 +575,7 @@
 	status = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_STATUS);
 
 	if (status == 1)
-		rb_podcast_manager_subscribe_feed (manager, uri);
+		rb_podcast_manager_subscribe_feed (manager, uri, TRUE);
 
 	rhythmdb_entry_unref (entry);
 
@@ -839,10 +841,11 @@
 }
 
 gboolean
-rb_podcast_manager_subscribe_feed (RBPodcastManager *pd, const char *url)
+rb_podcast_manager_subscribe_feed (RBPodcastManager *pd, const char *url, gboolean automatic)
 {
 	RBPodcastThreadInfo *info;
 	gchar *valid_url;
+	gboolean existing_feed;
 
 	if (g_str_has_prefix (url, "feed://") || g_str_has_prefix (url, "itpc://")) {
 		char *tmp;
@@ -869,11 +872,16 @@
 					 "If this is a podcast feed, please remove the radio station."), url);
 			return FALSE;
 		}
+		existing_feed = TRUE;
+	} else {
+		existing_feed = FALSE;
 	}
 
 	info = g_new0 (RBPodcastThreadInfo, 1);
 	info->pd = g_object_ref (pd);
 	info->url = valid_url;
+	info->automatic = automatic;
+	info->existing_feed = existing_feed;
 
 	g_thread_create ((GThreadFunc) rb_podcast_manager_thread_parse_feed,
 			 info, FALSE, NULL);
@@ -886,71 +894,116 @@
 {
 	rb_podcast_parse_channel_free (result->channel);
 	g_object_unref (result->pd);
+	g_clear_error (&result->error);
 	g_free (result);
 }
 
 static gboolean
 rb_podcast_manager_parse_complete_cb (RBPodcastManagerParseResult *result)
 {
+	gboolean add_feed = TRUE;
+
 	GDK_THREADS_ENTER ();
 	if (result->pd->priv->shutdown) {
 		GDK_THREADS_LEAVE ();
 		return FALSE;
 	}
 
-	switch (result->result)
-	{
-		case RESULT_PARSE_OK:
-			rb_podcast_manager_insert_feed (result->pd, result->channel);
-			break;
-		case RESULT_PARSE_ERROR:
-		{
-			gchar *error_msg;
-			error_msg = g_strdup_printf (_("There was a problem adding this podcast. Please verify the URL: %s"),
-						     (gchar *) result->channel->url);
-			g_signal_emit (result->pd,
-				       rb_podcast_manager_signals[PROCESS_ERROR],
-				       0, error_msg);
-			g_free (error_msg);
-			break;
+	if (result->error) {
+		if (rb_podcast_manager_handle_feed_error (result->pd,
+							  (char *)result->channel->url,
+							  result->error,
+							  result->automatic == FALSE) == FALSE) {
+			add_feed = FALSE;
 		}
 	}
 
+	if (add_feed) {
+		rb_podcast_manager_insert_feed (result->pd, result->channel);
+	}
+
 	GDK_THREADS_LEAVE ();
 	return FALSE;
 }
 
+static gboolean
+confirm_bad_mime_type (const char *url)
+{
+	GtkWidget *dialog;
+	gboolean result = FALSE;
+
+	GDK_THREADS_ENTER ();
+	dialog = gtk_message_dialog_new (NULL, 0,
+					 GTK_MESSAGE_QUESTION,
+					 GTK_BUTTONS_YES_NO,
+					 _("The URL '%s' does not appear to be a podcast feed. "
+					 "It may be the wrong URL, or the feed may be broken. "
+					 "Would you like Rhythmbox to attempt to use it anyway?"),
+					 url);
+
+	if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_YES) {
+		result = TRUE;
+	}
+
+	gtk_widget_destroy (dialog);
+	GDK_THREADS_LEAVE ();
+	return result;
+}
 
 static gpointer
 rb_podcast_manager_thread_parse_feed (RBPodcastThreadInfo *info)
 {
 	RBPodcastChannel *feed = g_new0 (RBPodcastChannel, 1);
+	gboolean retry = FALSE;
+	gboolean existing_feed;
+	RBPodcastManagerParseResult *result;
+
+	result = g_new0 (RBPodcastManagerParseResult, 1);
+	result->channel = feed;
+	result->pd = info->pd;		/* adopts our reference */
+	result->automatic = info->automatic;
+
+	existing_feed = info->existing_feed;
+	do {
+		retry = FALSE;
+		g_clear_error (&result->error);
+
+		rb_debug ("attempting to parse feed %s", info->url);
+		if (rb_podcast_parse_load_feed (feed, info->url, existing_feed, &result->error) == FALSE) {
+			if (g_error_matches (result->error,
+					     RB_PODCAST_PARSE_ERROR,
+					     RB_PODCAST_PARSE_ERROR_MIME_TYPE)) {
+				/* ask if the user really wants to use this feed.
+				 * if so, set the 'existing feed' flag, which causes
+				 * the mime type check to be skipped next time.
+				 */
+				if (confirm_bad_mime_type (info->url)) {
+					existing_feed = TRUE;
+					retry = TRUE;
+				}
+			}
+		}
+	} while (retry);
 
-	if (rb_podcast_parse_load_feed (feed, info->url) && (feed->is_opml == FALSE)) {
-		RBPodcastManagerParseResult *result;
-
-		result = g_new0 (RBPodcastManagerParseResult, 1);
-		result->channel = feed;
-		result->result = (feed->title == NULL) ? RESULT_PARSE_ERROR : RESULT_PARSE_OK;
-		result->pd = g_object_ref (info->pd);
-
-		g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
-				 (GSourceFunc) rb_podcast_manager_parse_complete_cb,
-				 result,
-				 (GDestroyNotify) rb_podcast_manager_free_parse_result);
-	} else if (feed->is_opml) {
+	if (feed->is_opml) {
 		GList *l;
 
 		rb_debug ("Loading OPML feeds from %s", info->url);
 
 		for (l = feed->posts; l != NULL; l = l->next) {
 			RBPodcastItem *item = l->data;
-			rb_podcast_manager_subscribe_feed (info->pd, item->url);
+			/* assume the feeds don't already exist */
+			rb_podcast_manager_subscribe_feed (info->pd, item->url, FALSE);
 		}
-		rb_podcast_parse_channel_free (feed);
+
+		rb_podcast_manager_free_parse_result (result);
+	} else {
+		g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
+				 (GSourceFunc) rb_podcast_manager_parse_complete_cb,
+				 result,
+				 (GDestroyNotify) rb_podcast_manager_free_parse_result);
 	}
 
-	g_object_unref (info->pd);
 	g_free (info->url);
 	g_free (info);
 	return NULL;
@@ -1615,6 +1668,7 @@
 	GValue status_val = { 0, };
 	GValue last_post_val = { 0, };
 	GValue last_update_val = { 0, };
+	GValue error_val = { 0, };
 	gulong last_post = 0;
 	gulong new_last_post;
 	GList *download_entries = NULL;
@@ -1626,12 +1680,6 @@
 
 	GList *lst_songs;
 
-	if (data->title == NULL) {
-		g_list_free (data->posts);
-		g_free (data);
-		return;
-	}
-
 	new_feed = TRUE;
 
 	/* processing podcast head */
@@ -1720,6 +1768,12 @@
 		g_value_unset (&image_val);
 	}
 
+	/* clear any error that might have been set earlier */
+	g_value_init (&error_val, G_TYPE_STRING);
+	g_value_set_string (&error_val, NULL);
+	rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_PLAYBACK_ERROR, &error_val);
+	g_value_unset (&error_val);
+
 	/* insert episodes */
 	new_last_post = last_post;
 
@@ -1822,6 +1876,43 @@
 	rhythmdb_commit (db);
 }
 
+static gboolean
+rb_podcast_manager_handle_feed_error (RBPodcastManager *mgr,
+				      const char *url,
+				      GError *error,
+				      gboolean emit)
+{
+	RhythmDBEntry *entry;
+	GValue v = {0,};
+	gboolean existing = FALSE;
+	gboolean ret = FALSE;
+
+	/* set the error in the feed entry, if one exists */
+	entry = rhythmdb_entry_lookup_by_location (mgr->priv->db, url);
+	if (entry != NULL && rhythmdb_entry_get_entry_type (entry) == RHYTHMDB_ENTRY_TYPE_PODCAST_FEED) {
+		g_value_init (&v, G_TYPE_STRING);
+		g_value_set_string (&v, error->message);
+		rhythmdb_entry_set (mgr->priv->db, entry, RHYTHMDB_PROP_PLAYBACK_ERROR, &v);
+		g_value_unset (&v);
+
+		rhythmdb_commit (mgr->priv->db);
+		existing = TRUE;
+	}
+
+	/* if this was a result of a direct user action, emit the error signal too */
+	if (emit) {
+		gchar *error_msg;
+		error_msg = g_strdup_printf (_("There was a problem adding this podcast: %s.  Please verify the URL: %s"),
+					     error->message, url);
+		g_signal_emit (mgr,
+			       rb_podcast_manager_signals[PROCESS_ERROR],
+			       0, error_msg, existing, &ret);
+		g_free (error_msg);
+	}
+
+	return ret;
+}
+
 void
 rb_podcast_manager_shutdown (RBPodcastManager *pd)
 {

Modified: trunk/podcast/rb-podcast-manager.h
==============================================================================
--- trunk/podcast/rb-podcast-manager.h	(original)
+++ trunk/podcast/rb-podcast-manager.h	Sat Apr 26 11:36:47 2008
@@ -78,7 +78,7 @@
 								 gboolean remove_files);
 gchar *                 rb_podcast_manager_get_podcast_dir	(RBPodcastManager *pd);
 
-gboolean                rb_podcast_manager_subscribe_feed    	(RBPodcastManager *pd, const gchar* url);
+gboolean                rb_podcast_manager_subscribe_feed    	(RBPodcastManager *pd, const gchar* url, gboolean automatic);
 void            	rb_podcast_manager_unsubscribe_feed    	(RhythmDB *db, const gchar* url);
 void			rb_podcast_manager_shutdown 		(RBPodcastManager *pd);
 RhythmDBEntry *         rb_podcast_manager_add_post  	  	(RhythmDB *db,

Modified: trunk/podcast/rb-podcast-parse.c
==============================================================================
--- trunk/podcast/rb-podcast-parse.c	(original)
+++ trunk/podcast/rb-podcast-parse.c	Sat Apr 26 11:36:47 2008
@@ -35,12 +35,23 @@
 #include <totem-pl-parser.h>
 #include <libgnomevfs/gnome-vfs.h>
 #include <glib/gi18n.h>
-#include <gtk/gtk.h>
+#include <glib.h>
+#include <glib/gprintf.h>
 
 #include "rb-debug.h"
 #include "rb-podcast-parse.h"
 #include "rb-file-helpers.h"
 
+GQuark
+rb_podcast_parse_error_quark (void)
+{
+	static GQuark quark = 0;
+	if (!quark)
+		quark = g_quark_from_static_string ("rb_podcast_parse_error");
+
+	return quark;
+}
+
 static void
 playlist_metadata_foreach (const char *key,
 			   const char *value,
@@ -126,7 +137,9 @@
 
 gboolean
 rb_podcast_parse_load_feed (RBPodcastChannel *data,
-			    const char *file_name)
+			    const char *file_name,
+			    gboolean existing_feed,
+			    GError **error)
 {
 	GnomeVFSResult result;
 	GnomeVFSFileInfo *info;
@@ -137,67 +150,41 @@
 	/* if the URL has a .rss, .xml or .atom extension (before the query string),
 	 * don't bother checking the MIME type.
 	 */
-	if (rb_uri_could_be_podcast (file_name, &data->is_opml)) {
+	if (rb_uri_could_be_podcast (file_name, &data->is_opml) || existing_feed) {
 		rb_debug ("not checking mime type for %s (should be %s file)", file_name,
 			  data->is_opml ? "OPML" : "Podcast");
 	} else {
-		gboolean invalid_mime_type;
-
 		rb_debug ("checking mime type for %s", file_name);
 		info = gnome_vfs_file_info_new ();
 
 		result = gnome_vfs_get_file_info (file_name, info, GNOME_VFS_FILE_INFO_DEFAULT);
-
-		if ((result != GNOME_VFS_OK)) {
-			if (info->mime_type != NULL) {
-				rb_debug ("Invalid mime-type in podcast feed %s", info->mime_type);
-			} else {
-				rb_debug ("Couldn't get mime type for %s: %s", file_name,
-					  gnome_vfs_result_to_string (result));
-			}
+		if (result != GNOME_VFS_OK) {
+			g_set_error (error,
+				     RB_PODCAST_PARSE_ERROR,
+				     RB_PODCAST_PARSE_ERROR_FILE_INFO,
+				     _("Unable to check file type: %s"),
+				     gnome_vfs_result_to_string (result));
 			gnome_vfs_file_info_unref (info);
-			return TRUE;
+			return FALSE;
 		}
 
-		if (info != NULL
-		    && info->mime_type != NULL
+		if (info->mime_type != NULL
 		    && strstr (info->mime_type, "html") == NULL
 		    && strstr (info->mime_type, "xml") == NULL
 		    && strstr (info->mime_type, "rss") == NULL
 		    && strstr (info->mime_type, "opml") == NULL) {
-			invalid_mime_type = TRUE;
-		} else if (info != NULL
-			   && info->mime_type != NULL
+			g_set_error (error,
+				     RB_PODCAST_PARSE_ERROR,
+				     RB_PODCAST_PARSE_ERROR_MIME_TYPE,
+				     _("Unexpected file type: %s"),
+				     info->mime_type);
+			gnome_vfs_file_info_unref (info);
+			return FALSE;
+		} else if (info->mime_type != NULL
 			   && strstr (info->mime_type, "opml") != NULL) {
 			data->is_opml = TRUE;
-			invalid_mime_type = FALSE;
-		} else {
-			invalid_mime_type = FALSE;
 		}
-
-		if (invalid_mime_type) {
-			GtkWidget *dialog;
-
-			GDK_THREADS_ENTER ();
-			dialog = gtk_message_dialog_new (NULL, 0,
-							 GTK_MESSAGE_QUESTION,
-							 GTK_BUTTONS_YES_NO,
-							 _("The URL '%s' does not appear to be a podcast feed. "
-							 "It may be the wrong URL, or the feed may be broken. "
-							 "Would you like Rhythmbox to attempt to use it anyway?"),
-							 file_name);
-
-			if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_YES)
-				invalid_mime_type = FALSE;
-
-			gtk_widget_destroy (dialog);
-			GDK_THREADS_LEAVE ();
-		}
-
 		gnome_vfs_file_info_unref (info);
-
-		if (invalid_mime_type)
-			return FALSE;
 	}
 
 	plparser = totem_pl_parser_new ();
@@ -208,11 +195,15 @@
 
 	if (totem_pl_parser_parse (plparser, file_name, FALSE) != TOTEM_PL_PARSER_RESULT_SUCCESS) {
 		rb_debug ("Parsing %s as a Podcast failed", file_name);
+		g_set_error (error,
+			     RB_PODCAST_PARSE_ERROR,
+			     RB_PODCAST_PARSE_ERROR_XML_PARSE,
+			     _("Unable to parse the feed contents"));
 		g_object_unref (plparser);
 		return FALSE;
 	}
-	rb_debug ("Parsing %s as a Podcast succeeded", file_name);
 
+	rb_debug ("Parsing %s as a Podcast succeeded", file_name);
 	return TRUE;
 }
 

Modified: trunk/podcast/rb-podcast-parse.h
==============================================================================
--- trunk/podcast/rb-podcast-parse.h	(original)
+++ trunk/podcast/rb-podcast-parse.h	Sat Apr 26 11:36:47 2008
@@ -32,6 +32,16 @@
 
 #include <glib.h>
 
+typedef enum
+{
+	RB_PODCAST_PARSE_ERROR_FILE_INFO,		/* error getting podcast file info */
+	RB_PODCAST_PARSE_ERROR_MIME_TYPE,		/* podcast has unexpected mime type */
+	RB_PODCAST_PARSE_ERROR_XML_PARSE		/* error parsing podcast xml */
+} RBPodcastParseError;
+
+#define RB_PODCAST_PARSE_ERROR rb_podcast_parse_error_quark ()
+GQuark rb_podcast_parse_error_quark (void);
+
 typedef struct
 {
 	char* title;
@@ -60,7 +70,10 @@
 	GList *posts;
 } RBPodcastChannel;
 
-gboolean rb_podcast_parse_load_feed	(RBPodcastChannel *data, const char *file_name);
+gboolean rb_podcast_parse_load_feed	(RBPodcastChannel *data,
+					 const char *url,
+					 gboolean existing_feed,
+					 GError **error);
 void rb_podcast_parse_channel_free 	(RBPodcastChannel *data);
 void rb_podcast_parse_item_free 	(RBPodcastItem *data);
 

Modified: trunk/podcast/test-podcast-parse.c
==============================================================================
--- trunk/podcast/test-podcast-parse.c	(original)
+++ trunk/podcast/test-podcast-parse.c	Sat Apr 26 11:36:47 2008
@@ -73,6 +73,7 @@
 	GList *l;
 	GDate date = {0,};
 	char datebuf[1024];
+	GError *error = NULL;
 
 	setlocale (LC_ALL, "");
 	bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
@@ -85,8 +86,9 @@
 	}
 
 	data = g_new0 (RBPodcastChannel, 1);
-	if (rb_podcast_parse_load_feed (data, argv[1]) == FALSE) {
-		g_warning ("Couldn't parse %s", argv[1]);
+	if (rb_podcast_parse_load_feed (data, argv[1], FALSE, &error) == FALSE) {
+		g_warning ("Couldn't parse %s: %s", argv[1], error->message);
+		g_clear_error (&error);
 		return 1;
 	}
 

Modified: trunk/sources/rb-podcast-source.c
==============================================================================
--- trunk/sources/rb-podcast-source.c	(original)
+++ trunk/sources/rb-podcast-source.c	Sat Apr 26 11:36:47 2008
@@ -42,6 +42,7 @@
 
 #include "rb-podcast-source.h"
 
+#include "rhythmdb.h"
 #include "rhythmdb-query-model.h"
 #include "rb-statusbar.h"
 #include "rb-glade-helpers.h"
@@ -61,6 +62,7 @@
 #include "rb-podcast-manager.h"
 #include "rb-static-playlist-source.h"
 #include "rb-cut-and-paste-code.h"
+#include "rb-cell-renderer-pixbuf.h"
 
 typedef enum
 {
@@ -189,6 +191,11 @@
 						     	 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,
@@ -202,8 +209,9 @@
 							 RhythmDBEntry *entry,
 							 RBPodcastSource *source);
 
-static void rb_podcast_source_download_process_error_cb (RBPodcastManager *pd,
+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);
@@ -215,6 +223,13 @@
 						  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,
+						 GSList *changes,
+						 RBPodcastSource *source);
+static void rb_podcast_source_pixbuf_clicked_cb	(RBCellRendererPixbuf *renderer,
+						 const char *path,
+						 RBPodcastSource *source);
 
 /* source methods */
 static char *impl_get_browser_key	 		(RBSource *source);
@@ -268,6 +283,7 @@
 	GtkWidget *config_widget;
 	GtkWidget *paned;
 
+	RhythmDBPropertyModel *feed_model;
 	RBPropertyView *feeds;
 	RBEntryView *posts;
 	GtkActionGroup *action_group;
@@ -279,9 +295,10 @@
 
 	gboolean initialized;
 
-	RhythmDBEntryType entry_type;
 	RBPodcastManager *podcast_mgr;
 
+	GdkPixbuf *error_pixbuf;
+
 	gboolean dispose_has_run;
 };
 
@@ -329,7 +346,6 @@
 enum
 {
 	PROP_0,
-	PROP_ENTRY_TYPE,
 	PROP_PODCAST_MANAGER
 };
 
@@ -386,6 +402,7 @@
 static void
 rb_podcast_source_init (RBPodcastSource *source)
 {
+	GtkIconTheme *icon_theme;
 	GdkPixbuf *pixbuf;
 	gint       size;
 
@@ -407,6 +424,13 @@
 		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,
+							       "stock_dialog-error",
+							       16,
+							       0,
+							       NULL);
 }
 
 static void
@@ -444,6 +468,11 @@
 		source->priv->podcast_mgr = NULL;
 	}
 
+	if (source->priv->error_pixbuf != NULL) {
+		g_object_unref (source->priv->error_pixbuf);
+		source->priv->error_pixbuf = NULL;
+	}
+
 	eel_gconf_notification_remove (source->priv->prefs_notify_id);
 
 	G_OBJECT_CLASS (rb_podcast_source_parent_class)->dispose (object);
@@ -527,7 +556,6 @@
 	RBPodcastSourceClass *klass;
 	GtkTreeViewColumn *column;
 	GtkCellRenderer *renderer;
-	RhythmDBPropertyModel *feed_model;
 	RBShell *shell;
 	RhythmDBQueryModel *query_model;
 	GPtrArray *query;
@@ -566,7 +594,7 @@
 						 CONF_STATE_PODCAST_SORTING_POSTS,
 						 TRUE, FALSE);
 
-	g_signal_connect_object (G_OBJECT (source->priv->posts),
+	g_signal_connect_object (source->priv->posts,
 				 "entry-activated",
 				 G_CALLBACK (rb_podcast_source_entry_activated_cb),
 				 source, 0);
@@ -654,36 +682,41 @@
 					    _("Status"), "Status",
 					    (GCompareDataFunc) rb_podcast_source_post_status_cell_sort_func, 0, NULL);
 
-	g_signal_connect_object (G_OBJECT (source->priv->posts),
+	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 (G_OBJECT (source->priv->podcast_mgr),
+	g_signal_connect (source->priv->podcast_mgr,
 			  "status_changed",
 			  G_CALLBACK (rb_podcast_source_download_status_changed_cb),
 			  source);
 
-	g_signal_connect_object (G_OBJECT (source->priv->podcast_mgr),
+	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 (G_OBJECT (source->priv->posts),
+	g_signal_connect_object (source->priv->posts,
 				 "size_allocate",
 				 G_CALLBACK (paned_size_allocate_cb),
 				 source, 0);
 
-	g_signal_connect_object (G_OBJECT (source->priv->posts), "show_popup",
-				 G_CALLBACK (rb_podcast_source_songs_show_popup_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);
+	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);
-	feed_model = rb_property_view_get_model (RB_PROPERTY_VIEW (source->priv->feeds));
-	g_object_set (feed_model, "query-model", query_model, NULL);
+	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,
@@ -697,7 +730,30 @@
 	rhythmdb_query_free (query);
 	g_object_unref (query_model);
 
-	/* column title */
+	/* 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 ();
 
@@ -1289,7 +1345,7 @@
 void
 rb_podcast_source_add_feed (RBPodcastSource *source, const char *uri)
 {
-	rb_podcast_manager_subscribe_feed (source->priv->podcast_mgr, uri);
+	rb_podcast_manager_subscribe_feed (source->priv->podcast_mgr, uri, FALSE);
 }
 
 static void
@@ -1460,7 +1516,8 @@
 		const char *location = l->data;
 
 		rb_podcast_manager_subscribe_feed (source->priv->podcast_mgr,
-						   location);
+						   location,
+						   FALSE);
 	}
 
 	rb_list_deep_free (feeds);
@@ -1622,6 +1679,32 @@
 }
 
 static void
+rb_podcast_source_feed_error_cell_data_func (GtkTreeViewColumn *column,
+					     GtkCellRenderer *renderer,
+					     GtkTreeModel *tree_model,
+					     GtkTreeIter *iter,
+					     RBPodcastSource *source)
+{
+	char *title;
+	RhythmDBEntry *entry = NULL;
+	GdkPixbuf *pixbuf = NULL;
+
+	gtk_tree_model_get (tree_model, iter,
+			    RHYTHMDB_PROPERTY_MODEL_COLUMN_TITLE, &title,
+			    -1);
+
+	entry = rhythmdb_entry_lookup_by_location (source->priv->db, title);
+	g_free (title);
+
+	if (entry != NULL) {
+		if (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_PLAYBACK_ERROR)) {
+			pixbuf = source->priv->error_pixbuf;
+		}
+	}
+	g_object_set (renderer, "pixbuf", pixbuf, NULL);
+}
+
+static void
 rb_podcast_source_post_date_cell_data_func (GtkTreeViewColumn *column,
 					    GtkCellRenderer *renderer,
 				     	    GtkTreeModel *tree_model,
@@ -1869,17 +1952,49 @@
 	return ret;
 }
 
-static void
+static gboolean
 rb_podcast_source_download_process_error_cb (RBPodcastManager *pd,
 					     const char *error,
+					     gboolean existing,
 					     RBPodcastSource *source)
 {
-	rb_error_dialog (NULL, _("Error in podcast"), "%s", error);
+	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_source_entry_activated_cb (RBEntryView *view,
-						  RhythmDBEntry *entry,
-						  RBPodcastSource *source)
+static void
+rb_podcast_source_entry_activated_cb (RBEntryView *view,
+				      RhythmDBEntry *entry,
+				      RBPodcastSource *source)
 {
 	GValue val = {0,};
 
@@ -1977,7 +2092,7 @@
 				     const char *location,
 				     RBPodcastSource *source)
 {
-	rb_podcast_manager_subscribe_feed (source->priv->podcast_mgr, location);
+	rb_podcast_manager_subscribe_feed (source->priv->podcast_mgr, location, FALSE);
 }
 
 static void
@@ -2038,7 +2153,87 @@
 impl_add_uri (RBSource *asource, const char *uri, const char *title, const char *genre)
 {
 	RBPodcastSource *source = RB_PODCAST_SOURCE (asource);
-	rb_podcast_manager_subscribe_feed (source->priv->podcast_mgr, uri);
+	rb_podcast_manager_subscribe_feed (source->priv->podcast_mgr, uri, FALSE);
 	return TRUE;
 }
 
+static void
+rb_podcast_source_entry_changed_cb (RhythmDB *db,
+				    RhythmDBEntry *entry,
+				    GSList *changes,
+				    RBPodcastSource *source)
+{
+	RhythmDBEntryType entry_type;
+	gboolean feed_changed;
+	GSList *t;
+
+	entry_type = rhythmdb_entry_get_entry_type (entry);
+	if (entry_type != RHYTHMDB_ENTRY_TYPE_PODCAST_FEED)
+		return;
+
+	feed_changed = FALSE;
+	for (t = changes; t; t = t->next) {
+		RhythmDBEntryChange *change = t->data;
+
+		if (change->prop == RHYTHMDB_PROP_PLAYBACK_ERROR) {
+			feed_changed = TRUE;
+			break;
+		}
+	}
+
+	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 void
+rb_podcast_source_pixbuf_clicked_cb (RBCellRendererPixbuf *renderer,
+				     const char *path_string,
+				     RBPodcastSource *source)
+{
+	GtkTreePath *path;
+	GtkTreeIter iter;
+
+	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_tree_path_free (path);
+}
+



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