diff --git a/rhythmbox-0.11.3-add-missing-plugins-support.patch b/rhythmbox-0.11.3-add-missing-plugins-support.patch new file mode 100644 index 0000000..4e23a54 --- /dev/null +++ b/rhythmbox-0.11.3-add-missing-plugins-support.patch @@ -0,0 +1,2194 @@ +diff --git a/backends/gstreamer/Makefile.am b/backends/gstreamer/Makefile.am +index 01cb788..093a02d 100644 +--- a/backends/gstreamer/Makefile.am ++++ b/backends/gstreamer/Makefile.am +@@ -8,6 +8,7 @@ librbbackendsgstreamer_la_SOURCES = \ + $(NULL) + + librbbackendsgstreamer_la_LIBADD = \ ++ -lgstpbutils-0.10 \ + $(RHYTHMBOX_LIBS) + + librbbackendsgstreamer_la_LDFLAGS = -export-dynamic +diff --git a/backends/gstreamer/rb-player-gst-xfade.c b/backends/gstreamer/rb-player-gst-xfade.c +index 0dd55e9..e3d625a 100644 +--- a/backends/gstreamer/rb-player-gst-xfade.c ++++ b/backends/gstreamer/rb-player-gst-xfade.c +@@ -141,6 +141,10 @@ + #include + #include + ++#ifdef HAVE_GSTREAMER_0_10_MISSING_PLUGINS ++#include ++#endif /* HAVE_GSTREAMER_0_10_MISSING_PLUGINS */ ++ + #include "rb-player.h" + #include "rb-player-gst-xfade.h" + #include "rb-debug.h" +@@ -218,6 +222,7 @@ enum + { + CAN_REUSE_STREAM, + REUSE_STREAM, ++ MISSING_PLUGINS, + LAST_SIGNAL + }; + +@@ -298,6 +303,10 @@ typedef struct + gboolean emitted_error; + gulong error_idle_id; + GError *error; ++#ifdef HAVE_GSTREAMER_0_10_MISSING_PLUGINS ++ GSList *missing_plugins; ++ gulong emit_missing_plugins_id; ++#endif + } RBXFadeStream; + + #define RB_TYPE_XFADE_STREAM (rb_xfade_stream_get_type ()) +@@ -636,6 +645,18 @@ rb_player_gst_xfade_class_init (RBPlayerGstXFadeClass *klass) + G_TYPE_NONE, + 3, + G_TYPE_STRING, G_TYPE_STRING, GST_TYPE_ELEMENT); ++#ifdef HAVE_GSTREAMER_0_10_MISSING_PLUGINS ++ signals[MISSING_PLUGINS] = ++ g_signal_new ("missing-plugins", ++ G_OBJECT_CLASS_TYPE (object_class), ++ G_SIGNAL_RUN_LAST, ++ 0, /* no point handling this internally */ ++ NULL, NULL, ++ rb_marshal_VOID__POINTER_POINTER_POINTER, ++ G_TYPE_NONE, ++ 3, ++ G_TYPE_POINTER, G_TYPE_STRV, G_TYPE_STRV); ++#endif + + g_type_class_add_private (klass, sizeof (RBPlayerGstXFadePrivate)); + } +@@ -1362,6 +1383,84 @@ process_tag (const GstTagList *list, const gchar *tag, RBXFadeStream *stream) + g_value_unset (&newval); + } + ++#ifdef HAVE_GSTREAMER_0_10_MISSING_PLUGINS ++ ++static gboolean ++emit_missing_plugins (RBXFadeStream *stream) ++{ ++ char **details; ++ char **descriptions; ++ int count; ++ GSList *t; ++ int i; ++ ++ stream->emit_missing_plugins_id = 0; ++ count = g_slist_length (stream->missing_plugins); ++ ++ details = g_new0 (char *, count + 1); ++ descriptions = g_new0 (char *, count + 1); ++ i = 0; ++ for (t = stream->missing_plugins; t != NULL; t = t->next) { ++ GstMessage *msg = GST_MESSAGE (t->data); ++ char *detail; ++ char *description; ++ ++ detail = gst_missing_plugin_message_get_installer_detail (msg); ++ description = gst_missing_plugin_message_get_description (msg); ++ details[i] = g_strdup (detail); ++ descriptions[i] = g_strdup (description); ++ i++; ++ ++ gst_message_unref (msg); ++ } ++ ++ g_signal_emit (stream->player, signals[MISSING_PLUGINS], 0, stream->stream_data, details, descriptions); ++ g_strfreev (details); ++ g_strfreev (descriptions); ++ ++ g_slist_free (stream->missing_plugins); ++ stream->missing_plugins = NULL; ++ ++ return FALSE; ++} ++ ++ ++static void ++rb_player_gst_xfade_handle_missing_plugin_message (RBPlayerGstXFade *player, RBXFadeStream *stream, GstMessage *message) ++{ ++ if (stream == NULL) { ++ rb_debug ("got missing-plugin message from unknown stream"); ++ return; ++ } ++ ++ rb_debug ("got missing-plugin message from %s: %s", ++ stream->uri, ++ gst_missing_plugin_message_get_installer_detail (message)); ++ ++ /* can only handle missing-plugins while prerolling */ ++ switch (stream->state) { ++ case PREROLLING: ++ case PREROLL_PLAY: ++ stream->missing_plugins = g_slist_prepend (stream->missing_plugins, ++ gst_message_ref (message)); ++ if (stream->emit_missing_plugins_id == 0) { ++ stream->emit_missing_plugins_id = ++ g_idle_add ((GSourceFunc) emit_missing_plugins, ++ g_object_ref (stream)); ++ } ++ ++ /* what do we do now? if we're missing the decoder ++ * or something, it'll never preroll.. ++ */ ++ break; ++ ++ default: ++ rb_debug ("can't process missing-plugin messages for this stream now"); ++ break; ++ } ++} ++#endif ++ + /* gstreamer message bus callback */ + static gboolean + rb_player_gst_xfade_bus_cb (GstBus *bus, GstMessage *message, RBPlayerGstXFade *player) +@@ -1504,25 +1603,29 @@ rb_player_gst_xfade_bus_cb (GstBus *bus, GstMessage *message, RBPlayerGstXFade * + } + case GST_MESSAGE_ELEMENT: + { +- /* currently only used to report imperfect stream messages */ + const GstStructure *s; + const char *name; + ++#ifdef HAVE_GSTREAMER_0_10_MISSING_PLUGINS ++ if (gst_is_missing_plugin_message (message)) { ++ rb_player_gst_xfade_handle_missing_plugin_message (player, stream, message); ++ break; ++ } ++#endif ++ + s = gst_message_get_structure (message); + name = gst_structure_get_name (s); + if ((strcmp (name, "imperfect-timestamp") == 0) || + (strcmp (name, "imperfect-offset") == 0)) { + char *details; +- RBXFadeStream *stream; +- const char *uri = "unknown stream";; ++ const char *uri = "unknown-stream"; + +- stream = find_stream_by_element (player, GST_ELEMENT (GST_MESSAGE_SRC (message))); + if (stream != NULL) { + uri = stream->uri; + } + + details = gst_structure_to_string (s); +- rb_debug_real ("check-imperfect", __FILE__, __LINE__, TRUE, "%s: %s", uri, details); ++ rb_debug_real ("check-imperfect", __FILE__, __LINE__, TRUE, "%s: %s", stream->uri, details); + g_free (details); + } + break; +diff --git a/backends/gstreamer/rb-player-gst.c b/backends/gstreamer/rb-player-gst.c +index e737b86..90f3a2a 100644 +--- a/backends/gstreamer/rb-player-gst.c ++++ b/backends/gstreamer/rb-player-gst.c +@@ -32,6 +32,10 @@ + #include + #include + ++#ifdef HAVE_GSTREAMER_0_10_MISSING_PLUGINS ++#include ++#endif /* HAVE_GSTREAMER_0_10_MISSING_PLUGINS */ ++ + #include "rb-debug.h" + #include "rb-marshal.h" + #include "rb-util.h" +@@ -90,6 +94,16 @@ enum + PROP_PLAYBIN + }; + ++#ifdef HAVE_GSTREAMER_0_10_MISSING_PLUGINS ++enum ++{ ++ MISSING_PLUGINS, ++ LAST_SIGNAL ++}; ++ ++static guint signals[LAST_SIGNAL] = { 0 }; ++#endif ++ + struct _RBPlayerGstPrivate + { + char *uri; +@@ -160,6 +174,19 @@ rb_player_gst_class_init (RBPlayerGstClass *klass) + "playbin element", + GST_TYPE_ELEMENT, + G_PARAM_READABLE)); ++ ++#ifdef HAVE_GSTREAMER_0_10_MISSING_PLUGINS ++ signals[MISSING_PLUGINS] = ++ g_signal_new ("missing-plugins", ++ G_OBJECT_CLASS_TYPE (object_class), ++ G_SIGNAL_RUN_LAST, ++ 0, /* no point handling this internally */ ++ NULL, NULL, ++ rb_marshal_VOID__POINTER_POINTER_POINTER, ++ G_TYPE_NONE, ++ 3, ++ G_TYPE_POINTER, G_TYPE_STRV, G_TYPE_STRV); ++#endif + + g_type_class_add_private (klass, sizeof (RBPlayerGstPrivate)); + } +@@ -389,6 +416,42 @@ process_tag (const GstTagList *list, const gchar *tag, RBPlayerGst *player) + g_hash_table_insert (player->priv->idle_info_ids, GUINT_TO_POINTER (signal->id), NULL); + } + ++#ifdef HAVE_GSTREAMER_0_10_MISSING_PLUGINS ++static void ++rb_player_gst_handle_missing_plugin_message (RBPlayerGst *player, GstMessage *message) ++{ ++ char **details; ++ char **descriptions; ++ char *detail; ++ char *description; ++ int count; ++ ++ rb_debug ("got missing-plugin message from %s: %s", ++ GST_OBJECT_NAME (GST_MESSAGE_SRC (message)), ++ gst_missing_plugin_message_get_installer_detail (message)); ++ ++ /* probably need to wait to collect any subsequent missing-plugin ++ * messages, but I think we'd need to wait for state changes to do ++ * that. for now, we can only handle a single message. ++ */ ++ count = 1; ++ ++ details = g_new0 (char *, count + 1); ++ descriptions = g_new0 (char *, count + 1); ++ ++ detail = gst_missing_plugin_message_get_installer_detail (message); ++ description = gst_missing_plugin_message_get_description (message); ++ details[0] = g_strdup (detail); ++ descriptions[0] = g_strdup (description); ++ ++ g_signal_emit (player, signals[MISSING_PLUGINS], 0, player->priv->stream_data, details, descriptions); ++ g_strfreev (details); ++ g_strfreev (descriptions); ++ ++ gst_message_unref (message); ++} ++#endif ++ + static gboolean + rb_player_gst_bus_cb (GstBus * bus, GstMessage * message, RBPlayerGst *mp) + { +@@ -504,6 +567,14 @@ rb_player_gst_bus_cb (GstBus * bus, GstMessage * message, RBPlayerGst *mp) + g_value_set_string (signal->info, gst_structure_get_name (structure)); + g_idle_add ((GSourceFunc) emit_signal_idle, signal); + } ++#ifdef HAVE_GSTREAMER_0_10_MISSING_PLUGINS ++ case GST_MESSAGE_ELEMENT: { ++ if (gst_is_missing_plugin_message (message)) { ++ rb_player_gst_handle_missing_plugin_message (mp, message); ++ } ++ break; ++ } ++#endif + default: + break; + } +@@ -662,6 +733,7 @@ rb_player_gst_sync_pipeline (RBPlayerGst *mp) + return FALSE; + } + } ++ + /* FIXME: Set up a timeout to watch if the pipeline doesn't + * go to PAUSED/PLAYING within some time (5 secs maybe?) + */ +@@ -694,6 +766,7 @@ end_gstreamer_operation (RBPlayerGst *mp, gboolean op_failed, GError **error) + RB_PLAYER_ERROR_GENERAL, + _("Unknown playback error")); + } ++ + } + + static void +diff --git a/configure.ac b/configure.ac +index 0cdb99f..35bd609 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -27,6 +27,7 @@ AC_CHECK_SIZEOF(long) + DBUS_MIN_REQS=0.35 + GST_0_10_REQS=0.10.4 + GST_0_10_XFADE_REQS=0.10.11 ++GST_0_10_MISSING_PLUGIN_REQS=0.10.12 + GTK_REQS=2.8.0 + GNOME_MEDIA_PROFILES_REQS=2.8 + GNOME_VFS_REQS=2.8.0 +@@ -273,6 +274,14 @@ if test x"$have_gstreamer_0_10_xfade" = xyes; then + fi + AM_CONDITIONAL(USE_GSTREAMER_0_10_XFADE, test x"$have_gstreamer_0_10_xfade" = xyes) + ++PKG_CHECK_MODULES(GSTREAMER_0_10_MISSING_PLUGINS, \ ++ gstreamer-plugins-base-0.10 >= $GST_0_10_MISSING_PLUGIN_REQS, ++ have_gstreamer_0_10_missing_plugins=yes,have_gstreamer_0_10_missing_plugins=no) ++if test x"$have_gstreamer_0_10_missing_plugins" = xyes; then ++ AC_DEFINE(HAVE_GSTREAMER_0_10_MISSING_PLUGINS,1,[Define if you want to enable GStreamer plugin installation]) ++fi ++AM_CONDITIONAL(USE_GSTREAMER_0_10_MISSING_PLUGINS, test x"$have_gstreamer_0_10_missing_plugins" = xyes) ++ + dnl Tag writing + AC_ARG_ENABLE(tag-writing, + AC_HELP_STRING([--disable-tag-writing], +diff --git a/lib/rb-marshal.list b/lib/rb-marshal.list +index 61f90d0..2845e25 100644 +--- a/lib/rb-marshal.list ++++ b/lib/rb-marshal.list +@@ -1,5 +1,6 @@ + BOOLEAN:BOOLEAN,BOOLEAN,BOOLEAN + BOOLEAN:POINTER ++BOOLEAN:POINTER,POINTER,POINTER + BOOLEAN:STRING,STRING,OBJECT + INT:VOID + OBJECT:OBJECT +@@ -22,6 +23,7 @@ VOID:POINTER,INT + VOID:POINTER,INT,POINTER + VOID:POINTER,LONG,LONG + VOID:POINTER,POINTER ++VOID:POINTER,POINTER,POINTER + VOID:POINTER,UINT + VOID:POINTER,ULONG + VOID:STRING,DOUBLE +diff --git a/metadata/Makefile.am b/metadata/Makefile.am +index 459d63b..1b5e1c2 100644 +--- a/metadata/Makefile.am ++++ b/metadata/Makefile.am +@@ -52,6 +52,7 @@ rhythmbox_metadata_LDADD = \ + librbmetadatasvc.la \ + $(top_builddir)/lib/librb.la \ + $(RHYTHMBOX_LIBS) \ ++ -lgstpbutils-0.10 \ + $(DBUS_LIBS) + + # test program? +@@ -63,6 +64,7 @@ test_metadata_LDADD = \ + librbmetadata.la \ + $(top_builddir)/lib/librb.la \ + $(RHYTHMBOX_LIBS) \ ++ -lgstpbutils-0.10 \ + $(DBUS_LIBS) + + else +diff --git a/metadata/rb-metadata-dbus-client.c b/metadata/rb-metadata-dbus-client.c +index 018fe2c..9c02259 100644 +--- a/metadata/rb-metadata-dbus-client.c ++++ b/metadata/rb-metadata-dbus-client.c +@@ -71,6 +71,8 @@ struct RBMetaDataPrivate + { + char *uri; + char *mimetype; ++ char **missing_plugins; ++ char **plugin_descriptions; + GHashTable *metadata; + }; + +@@ -389,6 +391,35 @@ rb_metadata_load (RBMetaData *md, + rb_debug ("couldn't read response message"); + } + } ++ ++ if (*error == NULL) { ++ if (!rb_metadata_dbus_get_strv (&iter, &md->priv->missing_plugins)) { ++ g_set_error (error, ++ RB_METADATA_ERROR, ++ RB_METADATA_ERROR_INTERNAL, ++ _("D-BUS communication error")); ++ rb_debug ("couldn't get missing plugin data from response message"); ++ } ++ } ++ ++ if (*error == NULL) { ++ if (!rb_metadata_dbus_get_strv (&iter, &md->priv->plugin_descriptions)) { ++ g_set_error (error, ++ RB_METADATA_ERROR, ++ RB_METADATA_ERROR_INTERNAL, ++ _("D-BUS communication error")); ++ rb_debug ("couldn't get missing plugin descriptions from response message"); ++ } ++ } ++ ++ /* if we're missing some plugins, we'll need to make sure the ++ * metadata helper rereads the registry before the next load. ++ * the easiest way to do this is to kill it. ++ */ ++ if (*error == NULL && md->priv->missing_plugins != NULL) { ++ rb_debug ("missing plugins; killing metadata service to force registry reload"); ++ kill_metadata_service (); ++ } + + if (*error == NULL) { + if (!rb_metadata_dbus_get_boolean (&iter, &ok)) { +@@ -397,23 +428,26 @@ rb_metadata_load (RBMetaData *md, + RB_METADATA_ERROR_INTERNAL, + _("D-BUS communication error")); + rb_debug ("couldn't get success flag from response message"); +- } else if (ok) { +- /* get mime type */ +- if (!rb_metadata_dbus_get_string (&iter, &md->priv->mimetype)) { +- g_set_error (error, +- RB_METADATA_ERROR, +- RB_METADATA_ERROR_INTERNAL, +- _("D-BUS communication error")); +- } else { +- /* get metadata */ +- rb_debug ("got mimetype: %s", md->priv->mimetype); +- rb_metadata_dbus_read_from_message (md, md->priv->metadata, &iter); +- } +- } else { ++ } else if (ok == FALSE) { + read_error_from_message (md, &iter, error); + } + } + ++ if (ok && *error == NULL) { ++ if (!rb_metadata_dbus_get_string (&iter, &md->priv->mimetype)) { ++ g_set_error (error, ++ RB_METADATA_ERROR, ++ RB_METADATA_ERROR_INTERNAL, ++ _("D-BUS communication error")); ++ } else { ++ rb_debug ("got mimetype: %s", md->priv->mimetype); ++ } ++ } ++ ++ if (ok && *error == NULL) { ++ rb_metadata_dbus_read_from_message (md, md->priv->metadata, &iter); ++ } ++ + if (message) + dbus_message_unref (message); + if (response) +@@ -431,6 +465,27 @@ rb_metadata_get_mime (RBMetaData *md) + } + + gboolean ++rb_metadata_has_missing_plugins (RBMetaData *md) ++{ ++ return (md->priv->missing_plugins != NULL); ++} ++ ++gboolean ++rb_metadata_get_missing_plugins (RBMetaData *md, ++ char ***missing_plugins, ++ char ***plugin_descriptions) ++{ ++ if (md->priv->missing_plugins == NULL) { ++ return FALSE; ++ } ++ ++ *missing_plugins = g_strdupv (md->priv->missing_plugins); ++ *plugin_descriptions = g_strdupv (md->priv->plugin_descriptions); ++ return TRUE; ++} ++ ++ ++gboolean + rb_metadata_get (RBMetaData *md, RBMetaDataField field, + GValue *ret) + { +diff --git a/metadata/rb-metadata-dbus-service.c b/metadata/rb-metadata-dbus-service.c +index 5d0115c..53a8cdc 100644 +--- a/metadata/rb-metadata-dbus-service.c ++++ b/metadata/rb-metadata-dbus-service.c +@@ -52,14 +52,22 @@ typedef struct { + gboolean external; + } ServiceData; + ++enum { ++ ERROR_FLAG = 1, ++ MISSING_PLUGINS = 2 ++}; ++ + static DBusHandlerResult + _send_error (DBusConnection *connection, + DBusMessage *request, +- gboolean include_flag, ++ int details, ++ char **missing_plugins, ++ char **plugin_descriptions, + gint error_type, + const char *message) + { + DBusMessage *reply = dbus_message_new_method_return (request); ++ DBusMessageIter iter; + + if (!message) { + message = ""; +@@ -68,18 +76,29 @@ _send_error (DBusConnection *connection, + rb_debug ("attempting to return error: %s", message); + } + +- if (include_flag) { ++ dbus_message_iter_init_append (reply, &iter); ++ ++ if (details & MISSING_PLUGINS) { ++ if (!rb_metadata_dbus_add_strv (&iter, missing_plugins)) { ++ rb_debug ("couldn't append missing plugins data"); ++ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; ++ } ++ if (!rb_metadata_dbus_add_strv (&iter, plugin_descriptions)) { ++ rb_debug ("couldn't append missing plugin descriptions"); ++ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; ++ } ++ } ++ ++ if (details & ERROR_FLAG) { + gboolean ok = FALSE; +- if (!dbus_message_append_args (reply, DBUS_TYPE_BOOLEAN, &ok, DBUS_TYPE_INVALID)) { ++ if (!dbus_message_iter_append_basic (&iter, DBUS_TYPE_BOOLEAN, &ok)) { + rb_debug ("couldn't append error flag"); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + } + +- if (!dbus_message_append_args (reply, +- DBUS_TYPE_UINT32, &error_type, +- DBUS_TYPE_STRING, &message, +- DBUS_TYPE_INVALID)) { ++ if (!dbus_message_iter_append_basic (&iter, DBUS_TYPE_UINT32, &error_type) || ++ !dbus_message_iter_append_basic (&iter, DBUS_TYPE_STRING, &message)) { + rb_debug ("couldn't append error data"); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } +@@ -100,6 +119,8 @@ rb_metadata_dbus_load (DBusConnection *connection, + GError *error = NULL; + gboolean ok = TRUE; + const char *mimetype = NULL; ++ char **missing_plugins = NULL; ++ char **plugin_descriptions = NULL; + + if (!dbus_message_iter_init (message, &iter)) { + return DBUS_HANDLER_RESULT_NEED_MEMORY; +@@ -107,21 +128,29 @@ rb_metadata_dbus_load (DBusConnection *connection, + + if (!rb_metadata_dbus_get_string (&iter, &uri)) { + /* make translatable? */ +- return _send_error (connection, message, TRUE, ++ return _send_error (connection, message, ++ ERROR_FLAG | MISSING_PLUGINS, ++ NULL, NULL, + RB_METADATA_ERROR_INTERNAL, + "Unable to read URI from request"); + } + + rb_debug ("loading metadata from %s", uri); +- + rb_metadata_load (svc->metadata, uri, &error); + g_free (uri); ++ ++ rb_metadata_get_missing_plugins (svc->metadata, &missing_plugins, &plugin_descriptions); ++ + if (error != NULL) { + DBusHandlerResult r; + rb_debug ("metadata error: %s", error->message); + +- r = _send_error (connection, message, TRUE, error->code, error->message); ++ r = _send_error (connection, message, ++ ERROR_FLAG | MISSING_PLUGINS, ++ missing_plugins, plugin_descriptions, ++ error->code, error->message); + g_clear_error (&error); ++ g_strfreev (missing_plugins); + return r; + } + rb_debug ("metadata load finished; mimetype = %s", rb_metadata_get_mime (svc->metadata)); +@@ -135,6 +164,15 @@ rb_metadata_dbus_load (DBusConnection *connection, + + mimetype = rb_metadata_get_mime (svc->metadata); + dbus_message_iter_init_append (reply, &iter); ++ ++ if (!rb_metadata_dbus_add_strv (&iter, missing_plugins)) { ++ rb_debug ("out of memory adding data to return message"); ++ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; ++ } ++ if (!rb_metadata_dbus_add_strv (&iter, plugin_descriptions)) { ++ rb_debug ("out of memory adding data to return message"); ++ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; ++ } + + if (!dbus_message_iter_append_basic (&iter, DBUS_TYPE_BOOLEAN, &ok) || + !dbus_message_iter_append_basic (&iter, DBUS_TYPE_STRING, &mimetype)) { +@@ -144,7 +182,9 @@ rb_metadata_dbus_load (DBusConnection *connection, + + if (!rb_metadata_dbus_add_to_message (svc->metadata, &iter)) { + /* make translatable? */ +- return _send_error (connection, message, TRUE, ++ return _send_error (connection, message, ++ ERROR_FLAG | MISSING_PLUGINS, ++ missing_plugins, plugin_descriptions, + RB_METADATA_ERROR_INTERNAL, + "Unable to add metadata to return message"); + } +@@ -175,7 +215,8 @@ rb_metadata_dbus_can_save (DBusConnection *connection, + + if (!rb_metadata_dbus_get_string (&iter, &mimetype)) { + /* make translatable? */ +- return _send_error (connection, message, TRUE, ++ return _send_error (connection, message, ERROR_FLAG, ++ NULL, NULL, + RB_METADATA_ERROR_INTERNAL, + "Unable to read MIME type from request"); + } +@@ -233,7 +274,7 @@ rb_metadata_dbus_save (DBusConnection *connection, + data, + &iter)) { + /* make translatable? */ +- return _send_error (connection, message, FALSE, ++ return _send_error (connection, message, 0, NULL, NULL, + RB_METADATA_ERROR_INTERNAL, + "Unable to read metadata from message"); + } +@@ -248,7 +289,7 @@ rb_metadata_dbus_save (DBusConnection *connection, + DBusHandlerResult r; + rb_debug ("metadata error: %s", error->message); + +- r = _send_error (connection, message, FALSE, error->code, error->message); ++ r = _send_error (connection, message, 0, NULL, NULL, error->code, error->message); + g_clear_error (&error); + return r; + } +@@ -385,6 +426,8 @@ test_load (const char *uri) + RBMetaData *md; + GError *error = NULL; + int rv = 0; ++ char **missing_plugins; ++ char **plugin_descriptions; + + md = rb_metadata_new (); + rb_metadata_load (md, uri, &error); +@@ -415,6 +458,17 @@ test_load (const char *uri) + } + } + } ++ ++ if (rb_metadata_get_missing_plugins (md, &missing_plugins, &plugin_descriptions)) { ++ int i = 0; ++ g_print ("missing plugins:\n"); ++ while (missing_plugins[i] != NULL) { ++ g_print ("\t%s (%s)\n", missing_plugins[i], plugin_descriptions[i]); ++ i++; ++ } ++ g_strfreev (missing_plugins); ++ } ++ + g_object_unref (G_OBJECT (md)); + return rv; + } +diff --git a/metadata/rb-metadata-dbus.c b/metadata/rb-metadata-dbus.c +index af62f84..afb57bb 100644 +--- a/metadata/rb-metadata-dbus.c ++++ b/metadata/rb-metadata-dbus.c +@@ -64,6 +64,55 @@ rb_metadata_dbus_get_string (DBusMessageIter *iter, gchar **value) + } + + gboolean ++rb_metadata_dbus_get_strv (DBusMessageIter *iter, char ***strv) ++{ ++ guint32 count; ++ guint32 i; ++ ++ /* strv is stored as a count followed by that many strings */ ++ if (rb_metadata_dbus_get_uint32 (iter, &count) == FALSE) { ++ return FALSE; ++ } ++ ++ if (count == 0) { ++ *strv = NULL; ++ return TRUE; ++ } ++ ++ *strv = g_new0 (char *, count+1); ++ for (i = 0; i < count; i++) { ++ if (rb_metadata_dbus_get_string (iter, (*strv)+i) == FALSE) { ++ return FALSE; ++ } ++ } ++ return TRUE; ++} ++ ++gboolean ++rb_metadata_dbus_add_strv (DBusMessageIter *iter, char **strv) ++{ ++ guint32 count; ++ guint32 i; ++ ++ if (strv == NULL) { ++ count = 0; ++ } else { ++ count = g_strv_length ((char **)strv); ++ } ++ ++ if (!dbus_message_iter_append_basic (iter, DBUS_TYPE_UINT32, &count)) { ++ return FALSE; ++ } ++ ++ for (i=0; i < count; i++) { ++ if (!dbus_message_iter_append_basic (iter, DBUS_TYPE_STRING, &strv[i])) { ++ return FALSE; ++ } ++ } ++ return TRUE; ++} ++ ++gboolean + rb_metadata_dbus_add_to_message (RBMetaData *md, DBusMessageIter *iter) + { + DBusMessageIter a_iter; +diff --git a/metadata/rb-metadata-dbus.h b/metadata/rb-metadata-dbus.h +index 43e4731..f67f2d3 100644 +--- a/metadata/rb-metadata-dbus.h ++++ b/metadata/rb-metadata-dbus.h +@@ -43,6 +43,11 @@ gboolean rb_metadata_dbus_get_uint32 (DBusMessageIter *iter, + guint32 *value); + gboolean rb_metadata_dbus_get_string (DBusMessageIter *iter, + gchar **value); ++gboolean rb_metadata_dbus_get_strv (DBusMessageIter *iter, ++ char ***strv); ++ ++gboolean rb_metadata_dbus_add_strv (DBusMessageIter *iter, ++ char **strv); + + gboolean rb_metadata_dbus_add_to_message (RBMetaData *md, + DBusMessageIter *iter); +diff --git a/metadata/rb-metadata-gst.c b/metadata/rb-metadata-gst.c +index 47c1048..c407e9b 100644 +--- a/metadata/rb-metadata-gst.c ++++ b/metadata/rb-metadata-gst.c +@@ -29,6 +29,9 @@ + #include + #include + ++#ifdef HAVE_GSTREAMER_0_10_MISSING_PLUGINS ++#include ++#endif /* HAVE_GSTREAMER_0_10_MISSING_PLUGINS */ + + #include "rb-metadata.h" + #include "rb-debug.h" +@@ -49,7 +52,8 @@ const char * ignore_mime_types[] = { + "image/", + "text/", + "application/xml", +- "application/zip" ++ "application/zip", ++ "application/x-executable" + }; + + /* +@@ -83,8 +87,10 @@ struct RBMetaDataPrivate + char *type; + gboolean handoff; + gboolean eos; +- gboolean non_audio; ++ gboolean has_audio; ++ gboolean has_non_audio; + gboolean has_video; ++ GSList *missing_plugins; + GError *error; + }; + +@@ -713,7 +719,7 @@ rb_metadata_gst_new_decoded_pad_cb (GstElement *decodebin, GstPad *pad, gboolean + /* we get "ANY" caps for text/plain files etc. */ + if (gst_caps_is_empty (caps) || gst_caps_is_any (caps)) { + rb_debug ("decoded pad with no caps or any caps. this file is boring."); +- md->priv->non_audio = TRUE; ++ md->priv->has_non_audio = TRUE; + cancel = TRUE; + } else { + GstPad *sink_pad; +@@ -727,16 +733,17 @@ rb_metadata_gst_new_decoded_pad_cb (GstElement *decodebin, GstPad *pad, gboolean + + if (g_str_has_prefix (mimetype, "audio/x-raw")) { + rb_debug ("got decoded audio pad of type %s", mimetype); ++ md->priv->has_audio = TRUE; + } else if (g_str_has_prefix (mimetype, "video/")) { + rb_debug ("got decoded video pad of type %s", mimetype); +- md->priv->non_audio = TRUE; ++ md->priv->has_non_audio = TRUE; + md->priv->has_video = TRUE; + } else { + /* assume anything we can get a video or text stream out of is + * something that should be fed to totem rather than rhythmbox. + */ + rb_debug ("got decoded pad of non-audio type %s", mimetype); +- md->priv->non_audio = TRUE; ++ md->priv->has_non_audio = TRUE; + } + } + +@@ -753,9 +760,6 @@ rb_metadata_gst_new_decoded_pad_cb (GstElement *decodebin, GstPad *pad, gboolean + static void + rb_metadata_gst_unknown_type_cb (GstElement *decodebin, GstPad *pad, GstCaps *caps, RBMetaData *md) + { +- /* try to shortcut it a bit */ +- md->priv->non_audio = TRUE; +- + if (!gst_caps_is_empty (caps) && !gst_caps_is_any (caps)) { + GstStructure *structure; + const gchar *mimetype; +@@ -771,6 +775,9 @@ rb_metadata_gst_unknown_type_cb (GstElement *decodebin, GstPad *pad, GstCaps *ca + rb_debug ("decodebin emitted unknown type signal"); + } + ++ md->priv->has_non_audio = TRUE; ++#ifndef HAVE_GSTREAMER_0_10_MISSING_PLUGINS ++ /* try to shortcut it a bit */ + { + char *msg; + +@@ -778,6 +785,7 @@ rb_metadata_gst_unknown_type_cb (GstElement *decodebin, GstPad *pad, GstCaps *ca + GST_ELEMENT_ERROR (md->priv->pipeline, STREAM, CODEC_NOT_FOUND, ("%s", msg), (NULL)); + g_free (msg); + } ++#endif + } + + static GstElement *make_pipeline_element (GstElement *pipeline, const char *element, GError **error) +@@ -796,6 +804,17 @@ static GstElement *make_pipeline_element (GstElement *pipeline, const char *elem + return elem; + } + ++#ifdef HAVE_GSTREAMER_0_10_MISSING_PLUGINS ++static void ++rb_metadata_handle_missing_plugin_message (RBMetaData *md, GstMessage *message) ++{ ++ rb_debug ("got missing-plugin message from %s: %s", ++ GST_OBJECT_NAME (GST_MESSAGE_SRC (message)), ++ gst_missing_plugin_message_get_installer_detail (message)); ++ md->priv->missing_plugins = g_slist_prepend (md->priv->missing_plugins, gst_message_ref (message)); ++} ++#endif ++ + static gboolean + rb_metadata_bus_handler (GstBus *bus, GstMessage *message, RBMetaData *md) + { +@@ -809,6 +828,11 @@ rb_metadata_bus_handler (GstBus *bus, GstMessage *message, RBMetaData *md) + { + GError *gerror; + gchar *debug; ++ char *src; ++ ++ src = gst_element_get_name (GST_MESSAGE_SRC (message)); ++ rb_debug ("got error message from %s", src); ++ g_free (src); + + gst_message_parse_error (message, &gerror, &debug); + if (gerror->domain == GST_STREAM_ERROR && +@@ -819,7 +843,7 @@ rb_metadata_bus_handler (GstBus *bus, GstMessage *message, RBMetaData *md) + md->priv->type != NULL && + strcmp (md->priv->type, "text/plain") == 0) { + rb_debug ("got WRONG_TYPE error for text/plain: setting non-audio flag"); +- md->priv->non_audio = TRUE; ++ md->priv->has_non_audio = TRUE; + } else if (md->priv->error) { + rb_debug ("caught error: %s, but we've already got one", gerror->message); + } else { +@@ -856,10 +880,26 @@ rb_metadata_bus_handler (GstBus *bus, GstMessage *message, RBMetaData *md) + } + break; + } ++ case GST_MESSAGE_ELEMENT: ++ { ++#ifdef HAVE_GSTREAMER_0_10_MISSING_PLUGINS ++ if (gst_is_missing_plugin_message (message)) { ++ rb_metadata_handle_missing_plugin_message (md, message); ++ } ++#endif ++ break; ++ } + default: +- rb_debug ("message of type %d", GST_MESSAGE_TYPE (message)); ++ { ++ char *src; ++ ++ src = gst_element_get_name (GST_MESSAGE_SRC (message)); ++ rb_debug ("message of type %s from %s", ++ GST_MESSAGE_TYPE_NAME (message), src); ++ g_free (src); + break; + } ++ } + + return FALSE; + } +@@ -914,8 +954,10 @@ rb_metadata_load (RBMetaData *md, + md->priv->error = NULL; + md->priv->eos = FALSE; + md->priv->handoff = FALSE; +- md->priv->non_audio = FALSE; ++ md->priv->has_audio = FALSE; ++ md->priv->has_non_audio = FALSE; + md->priv->has_video = FALSE; ++ md->priv->missing_plugins = NULL; + + if (md->priv->pipeline) { + gst_object_unref (GST_OBJECT (md->priv->pipeline)); +@@ -988,7 +1030,6 @@ rb_metadata_load (RBMetaData *md, + change_timeout = 0; + while (state_ret == GST_STATE_CHANGE_ASYNC && + !md->priv->eos && +- !md->priv->non_audio && + change_timeout < 5) { + GstMessage *msg; + +@@ -1009,7 +1050,7 @@ rb_metadata_load (RBMetaData *md, + + if (state_ret != GST_STATE_CHANGE_SUCCESS) { + rb_debug ("failed to go to PAUSED for %s", uri); +- if (!md->priv->non_audio && md->priv->error == NULL) ++ if (!md->priv->has_non_audio && md->priv->error == NULL) + g_set_error (error, + RB_METADATA_ERROR, + RB_METADATA_ERROR_INTERNAL, +@@ -1061,7 +1102,8 @@ rb_metadata_load (RBMetaData *md, + * these don't include the URI as the import errors source + * already displays it. + */ +- if (md->priv->non_audio || !md->priv->handoff) { ++ if ((md->priv->has_video || !md->priv->has_audio) && ++ (md->priv->has_non_audio || !md->priv->handoff)) { + gboolean ignore = FALSE; + int i; + +@@ -1375,3 +1417,59 @@ rb_metadata_set (RBMetaData *md, RBMetaDataField field, + newval); + return TRUE; + } ++ ++gboolean ++rb_metadata_has_missing_plugins (RBMetaData *md) ++{ ++#ifdef HAVE_GSTREAMER_0_10_MISSING_PLUGINS ++ return (g_slist_length (md->priv->missing_plugins) > 0); ++#else ++ return FALSE; ++#endif ++} ++ ++gboolean ++rb_metadata_get_missing_plugins (RBMetaData *md, ++ char ***missing_plugins, ++ char ***plugin_descriptions) ++{ ++#ifdef HAVE_GSTREAMER_0_10_MISSING_PLUGINS ++ char **mp; ++ char **pd; ++ int count; ++ int i; ++ GSList *t; ++ ++ count = g_slist_length (md->priv->missing_plugins); ++ if (count == 0) { ++ return FALSE; ++ } ++ ++ mp = g_new0 (char *, count + 1); ++ pd = g_new0 (char *, count + 1); ++ i = 0; ++ for (t = md->priv->missing_plugins; t != NULL; t = t->next) { ++ GstMessage *msg = GST_MESSAGE (t->data); ++ char *detail; ++ char *description; ++ ++ detail = gst_missing_plugin_message_get_installer_detail (msg); ++ description = gst_missing_plugin_message_get_description (msg); ++ rb_debug ("adding [%s,%s] to return data", detail, description); ++ mp[i] = g_strdup (detail); ++ pd[i] = g_strdup (description); ++ i++; ++ ++ gst_message_unref (msg); ++ } ++ g_slist_free (md->priv->missing_plugins); ++ md->priv->missing_plugins = NULL; ++ ++ *missing_plugins = mp; ++ *plugin_descriptions = pd; ++ return TRUE; ++#else ++ return FALSE; ++#endif ++} ++ +diff --git a/metadata/rb-metadata.h b/metadata/rb-metadata.h +index 2810b8f..4c325e1 100644 +--- a/metadata/rb-metadata.h ++++ b/metadata/rb-metadata.h +@@ -122,6 +122,12 @@ void rb_metadata_save (RBMetaData *md, + + const char * rb_metadata_get_mime (RBMetaData *md); + ++gboolean rb_metadata_has_missing_plugins (RBMetaData *md); ++ ++gboolean rb_metadata_get_missing_plugins (RBMetaData *md, ++ char ***missing_plugins, ++ char ***plugin_descriptions); ++ + gboolean rb_metadata_get (RBMetaData *md, RBMetaDataField field, + GValue *val); + +diff --git a/metadata/test-metadata.c b/metadata/test-metadata.c +index 878a46a..d6adb29 100644 +--- a/metadata/test-metadata.c ++++ b/metadata/test-metadata.c +@@ -75,6 +75,8 @@ static gboolean + load_metadata_cb (gpointer file) + { + char *uri = (char *)file; ++ char **missing_plugins; ++ char **plugin_descriptions; + GError *error = NULL; + + if (strncmp (uri, "file://", 7)) { +@@ -112,6 +114,15 @@ load_metadata_cb (gpointer file) + for (f =(RBMetaDataField)0; f < RB_METADATA_FIELD_LAST; f++) + print_metadata_string (md, f, rb_metadata_get_field_name (f)); + } ++ if (rb_metadata_get_missing_plugins (md, &missing_plugins, &plugin_descriptions)) { ++ int i = 0; ++ g_print ("missing plugins:\n"); ++ while (missing_plugins[i] != NULL) { ++ g_print ("\t%s (%s)\n", missing_plugins[i], plugin_descriptions[i]); ++ i++; ++ } ++ g_strfreev (missing_plugins); ++ } + printf ("---\n"); + return FALSE; + } +diff --git a/podcast/rb-podcast-manager.c b/podcast/rb-podcast-manager.c +index ba9a1e8..135e0c2 100644 +--- a/podcast/rb-podcast-manager.c ++++ b/podcast/rb-podcast-manager.c +@@ -70,6 +70,9 @@ enum + FINISH_DOWNLOAD, + PROCESS_ERROR, + FEED_UPDATES_AVAILABLE, ++#ifdef HAVE_GSTREAMER_0_10_MISSING_PLUGINS ++ MISSING_PLUGINS, ++#endif + LAST_SIGNAL + }; + +@@ -150,7 +153,7 @@ static gboolean rb_podcast_manager_head_query_cb (GtkTreeModel *query_model, + GtkTreePath *path, + GtkTreeIter *iter, + RBPodcastManager *data); +-static gboolean rb_podcast_manager_save_metadata (RhythmDB *db, ++static void rb_podcast_manager_save_metadata (RBPodcastManager *pd, + RhythmDBEntry *entry, + const char *uri); + static void rb_podcast_manager_db_entry_added_cb (RBPodcastManager *pd, +@@ -205,7 +208,7 @@ rb_podcast_manager_class_init (RBPodcastManagerClass *klass) + rb_podcast_manager_signals[STATUS_CHANGED] = + g_signal_new ("status_changed", + G_OBJECT_CLASS_TYPE (object_class), +- GTK_RUN_LAST, ++ G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (RBPodcastManagerClass, status_changed), + NULL, NULL, + rb_marshal_VOID__BOXED_ULONG, +@@ -217,7 +220,7 @@ rb_podcast_manager_class_init (RBPodcastManagerClass *klass) + rb_podcast_manager_signals[START_DOWNLOAD] = + g_signal_new ("start_download", + G_OBJECT_CLASS_TYPE (object_class), +- GTK_RUN_LAST, ++ G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (RBPodcastManagerClass, start_download), + NULL, NULL, + g_cclosure_marshal_VOID__BOXED, +@@ -228,7 +231,7 @@ rb_podcast_manager_class_init (RBPodcastManagerClass *klass) + rb_podcast_manager_signals[FINISH_DOWNLOAD] = + g_signal_new ("finish_download", + G_OBJECT_CLASS_TYPE (object_class), +- GTK_RUN_LAST, ++ G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (RBPodcastManagerClass, finish_download), + NULL, NULL, + g_cclosure_marshal_VOID__BOXED, +@@ -239,7 +242,7 @@ rb_podcast_manager_class_init (RBPodcastManagerClass *klass) + rb_podcast_manager_signals[FEED_UPDATES_AVAILABLE] = + g_signal_new ("feed_updates_available", + G_OBJECT_CLASS_TYPE (object_class), +- GTK_RUN_LAST, ++ G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (RBPodcastManagerClass, feed_updates_available), + NULL, NULL, + g_cclosure_marshal_VOID__BOXED, +@@ -250,7 +253,7 @@ rb_podcast_manager_class_init (RBPodcastManagerClass *klass) + rb_podcast_manager_signals[PROCESS_ERROR] = + g_signal_new ("process_error", + G_OBJECT_CLASS_TYPE (object_class), +- GTK_RUN_LAST, ++ G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (RBPodcastManagerClass, process_error), + NULL, NULL, + g_cclosure_marshal_VOID__STRING, +@@ -258,6 +261,20 @@ rb_podcast_manager_class_init (RBPodcastManagerClass *klass) + 1, + G_TYPE_STRING); + ++#ifdef HAVE_GSTREAMER_0_10_MISSING_PLUGINS ++ rb_podcast_manager_signals[MISSING_PLUGINS] = ++ g_signal_new ("missing-plugins", ++ G_OBJECT_CLASS_TYPE (object_class), ++ G_SIGNAL_RUN_LAST, ++ 0, /* no internal handler */ ++ NULL, NULL, ++ rb_marshal_BOOLEAN__POINTER_POINTER_POINTER, ++ G_TYPE_BOOLEAN, ++ 3, ++ G_TYPE_STRV, G_TYPE_STRV, G_TYPE_CLOSURE); ++#endif ++ ++ + g_type_class_add_private (klass, sizeof (RBPodcastManagerPrivate)); + } + +@@ -772,7 +789,7 @@ rb_podcast_manager_download_file_info_cb (GnomeVFSAsyncHandle *handle, + rhythmdb_entry_set (data->pd->priv->db, data->entry, RHYTHMDB_PROP_MOUNTPOINT, &val); + g_value_unset (&val); + +- rb_podcast_manager_save_metadata (data->pd->priv->db, data->entry, canon_uri); ++ rb_podcast_manager_save_metadata (data->pd, data->entry, canon_uri); + + g_free (canon_uri); + +@@ -1021,63 +1038,118 @@ rb_podcast_manager_add_post (RhythmDB *db, + return entry; + } + +-static gboolean +-rb_podcast_manager_save_metadata (RhythmDB *db, RhythmDBEntry *entry, const char *uri) ++#ifdef HAVE_GSTREAMER_0_10_MISSING_PLUGINS ++typedef struct { ++ RhythmDBEntry *entry; ++ RBPodcastManager *mgr; ++} MissingPluginRetryData; ++ ++static void ++missing_plugins_retry_cb (gpointer inst, gboolean retry, MissingPluginRetryData *retry_data) ++{ ++ const char *uri; ++ if (retry == FALSE) ++ return; ++ ++ uri = rhythmdb_entry_get_string (retry_data->entry, RHYTHMDB_PROP_MOUNTPOINT); ++ rb_podcast_manager_save_metadata (retry_data->mgr, retry_data->entry, uri); ++} ++ ++static void ++missing_plugins_retry_cleanup (MissingPluginRetryData *retry) ++{ ++ g_object_unref (retry->mgr); ++ rhythmdb_entry_unref (retry->entry); ++ g_free (retry); ++} ++ ++#endif ++ ++static void ++rb_podcast_manager_save_metadata (RBPodcastManager *pd, RhythmDBEntry *entry, const char *uri) + { + RBMetaData *md = rb_metadata_new (); + GError *error = NULL; + GValue val = { 0, }; + const char *mime; ++#ifdef HAVE_GSTREAMER_0_10_MISSING_PLUGINS ++ char **missing_plugins; ++ char **plugin_descriptions; ++#endif + +- rb_debug ("Loading podcast metadata from %s", uri); ++ rb_debug ("loading podcast metadata from %s", uri); + rb_metadata_load (md, uri, &error); + ++#ifdef HAVE_GSTREAMER_0_10_MISSING_PLUGINS ++ if (rb_metadata_get_missing_plugins (md, &missing_plugins, &plugin_descriptions)) { ++ GClosure *closure; ++ gboolean processing; ++ MissingPluginRetryData *data; ++ ++ rb_debug ("missing plugins during podcast metadata load for %s", uri); ++ data = g_new0 (MissingPluginRetryData, 1); ++ data->mgr = g_object_ref (pd); ++ data->entry = rhythmdb_entry_ref (entry); ++ ++ closure = g_cclosure_new ((GCallback) missing_plugins_retry_cb, ++ data, ++ (GClosureNotify) missing_plugins_retry_cleanup); ++ g_closure_set_marshal (closure, g_cclosure_marshal_VOID__BOOLEAN); ++ g_signal_emit (pd, rb_podcast_manager_signals[MISSING_PLUGINS], 0, missing_plugins, plugin_descriptions, closure, &processing); ++ g_closure_sink (closure); ++ ++ if (processing) { ++ /* when processing is complete, we'll retry */ ++ return; ++ } ++ } ++#endif ++ + if (error != NULL) { + /* this probably isn't an audio enclosure. or some other error */ + g_value_init (&val, G_TYPE_ULONG); + g_value_set_ulong (&val, RHYTHMDB_PODCAST_STATUS_ERROR); +- rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_STATUS, &val); ++ rhythmdb_entry_set (pd->priv->db, entry, RHYTHMDB_PROP_STATUS, &val); + g_value_unset (&val); + + g_value_init (&val, G_TYPE_STRING); + g_value_set_string (&val, error->message); +- rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_PLAYBACK_ERROR, &val); ++ rhythmdb_entry_set (pd->priv->db, entry, RHYTHMDB_PROP_PLAYBACK_ERROR, &val); + g_value_unset (&val); + +- rhythmdb_commit (db); ++ rhythmdb_commit (pd->priv->db); + + g_object_unref (md); + g_error_free (error); + +- return FALSE; ++ return; + } + + mime = rb_metadata_get_mime (md); + if (mime) { + g_value_init (&val, G_TYPE_STRING); + g_value_set_string (&val, mime); +- rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_MIMETYPE, &val); ++ rhythmdb_entry_set (pd->priv->db, entry, RHYTHMDB_PROP_MIMETYPE, &val); + g_value_unset (&val); + } + + if (rb_metadata_get (md, + RB_METADATA_FIELD_DURATION, + &val)) { +- rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_DURATION, &val); ++ rhythmdb_entry_set (pd->priv->db, entry, RHYTHMDB_PROP_DURATION, &val); + g_value_unset (&val); + } + + if (rb_metadata_get (md, + RB_METADATA_FIELD_BITRATE, + &val)) { +- rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_BITRATE, &val); ++ rhythmdb_entry_set (pd->priv->db, entry, RHYTHMDB_PROP_BITRATE, &val); + g_value_unset (&val); + } + +- rhythmdb_commit (db); ++ rhythmdb_commit (pd->priv->db); + + g_object_unref (md); +- return TRUE; + } + + static void +@@ -1265,7 +1337,7 @@ download_progress_cb (GnomeVFSXferProgressInfo *info, gpointer cb_data) + rhythmdb_entry_set (data->pd->priv->db, data->entry, RHYTHMDB_PROP_STATUS, &val); + g_value_unset (&val); + +- rb_podcast_manager_save_metadata (data->pd->priv->db, ++ rb_podcast_manager_save_metadata (data->pd, + data->entry, + canon_uri); + g_free (canon_uri); +diff --git a/rhythmdb/rhythmdb-private.h b/rhythmdb/rhythmdb-private.h +index 4a998ee..9971656 100644 +--- a/rhythmdb/rhythmdb-private.h ++++ b/rhythmdb/rhythmdb-private.h +@@ -127,6 +127,9 @@ struct RhythmDBPrivate + gint read_counter; + + RBMetaData *metadata; ++ gboolean metadata_blocked; ++ GMutex *metadata_lock; ++ GCond *metadata_cond; + + xmlChar **column_xml_names; + +@@ -138,6 +141,7 @@ struct RhythmDBPrivate + GAsyncQueue *action_queue; + GAsyncQueue *event_queue; + GAsyncQueue *restored_queue; ++ GAsyncQueue *delayed_write_queue; + GThreadPool *query_thread_pool; + + GList *stat_list; +diff --git a/rhythmdb/rhythmdb.c b/rhythmdb/rhythmdb.c +index 4f363b8..6b700dd 100644 +--- a/rhythmdb/rhythmdb.c ++++ b/rhythmdb/rhythmdb.c +@@ -156,6 +156,7 @@ enum + SAVE_COMPLETE, + SAVE_ERROR, + READ_ONLY, ++ MISSING_PLUGINS, + LAST_SIGNAL + }; + +@@ -317,6 +318,19 @@ rhythmdb_class_init (RhythmDBClass *klass) + 1, + G_TYPE_BOOLEAN); + ++#ifdef HAVE_GSTREAMER_0_10_MISSING_PLUGINS ++ rhythmdb_signals[MISSING_PLUGINS] = ++ g_signal_new ("missing-plugins", ++ G_OBJECT_CLASS_TYPE (object_class), ++ G_SIGNAL_RUN_LAST, ++ 0, /* no need for an internal handler */ ++ NULL, NULL, ++ rb_marshal_BOOLEAN__POINTER_POINTER_POINTER, ++ G_TYPE_BOOLEAN, ++ 3, ++ G_TYPE_STRV, G_TYPE_STRV, G_TYPE_CLOSURE); ++#endif ++ + g_type_class_add_private (klass, sizeof (RhythmDBPrivate)); + } + +@@ -447,6 +461,7 @@ rhythmdb_init (RhythmDB *db) + + db->priv->action_queue = g_async_queue_new (); + db->priv->event_queue = g_async_queue_new (); ++ db->priv->delayed_write_queue = g_async_queue_new (); + db->priv->event_queue_watch_id = rb_async_queue_watch_new (db->priv->event_queue, + G_PRIORITY_LOW, /* really? */ + (RBAsyncQueueWatchFunc) rhythmdb_process_one_event, +@@ -461,6 +476,9 @@ rhythmdb_init (RhythmDB *db) + -1, FALSE, NULL); + + db->priv->metadata = rb_metadata_new (); ++ db->priv->metadata_blocked = FALSE; ++ db->priv->metadata_cond = g_cond_new (); ++ db->priv->metadata_lock = g_mutex_new (); + + prop_class = g_type_class_ref (RHYTHMDB_TYPE_PROP_TYPE); + +@@ -721,6 +739,8 @@ rhythmdb_shutdown (RhythmDB *db) + /* FIXME */ + while ((result = g_async_queue_try_pop (db->priv->event_queue)) != NULL) + rhythmdb_event_free (db, result); ++ while ((result = g_async_queue_try_pop (db->priv->delayed_write_queue)) != NULL) ++ rhythmdb_event_free (db, result); + + while ((action = g_async_queue_try_pop (db->priv->action_queue)) != NULL) { + rhythmdb_action_free (db, action); +@@ -794,6 +814,7 @@ rhythmdb_finalize (GObject *object) + g_async_queue_unref (db->priv->action_queue); + g_async_queue_unref (db->priv->event_queue); + g_async_queue_unref (db->priv->restored_queue); ++ g_async_queue_unref (db->priv->delayed_write_queue); + + g_mutex_free (db->priv->saving_mutex); + g_cond_free (db->priv->saving_condition); +@@ -923,9 +944,21 @@ rhythmdb_read_leave (RhythmDB *db) + + count = g_atomic_int_exchange_and_add (&db->priv->read_counter, -1); + rb_debug ("counter: %d", count-1); +- if (count == 1) ++ if (count == 1) { ++ + g_signal_emit (G_OBJECT (db), rhythmdb_signals[READ_ONLY], + 0, FALSE); ++ ++ /* move any delayed writes back to the main event queue */ ++ if (g_async_queue_length (db->priv->delayed_write_queue) > 0) { ++ RhythmDBEvent *event; ++ while ((event = g_async_queue_try_pop (db->priv->delayed_write_queue)) != NULL) ++ g_async_queue_push (db->priv->event_queue, event); ++ ++ g_main_context_wakeup (g_main_context_default ()); ++ } ++ ++ } + } + + static gboolean +@@ -1847,8 +1880,7 @@ rhythmdb_add_import_error_entry (RhythmDB *db, + } + + static gboolean +-rhythmdb_process_metadata_load (RhythmDB *db, +- RhythmDBEvent *event) ++rhythmdb_process_metadata_load_real (RhythmDBEvent *event) + { + RhythmDBEntry *entry; + GValue value = {0,}; +@@ -1856,7 +1888,7 @@ rhythmdb_process_metadata_load (RhythmDB *db, + GTimeVal time; + + if (event->error) { +- rhythmdb_add_import_error_entry (db, event); ++ rhythmdb_add_import_error_entry (event->db, event); + return TRUE; + } + +@@ -1869,14 +1901,14 @@ rhythmdb_process_metadata_load (RhythmDB *db, + + g_get_current_time (&time); + +- entry = rhythmdb_entry_lookup_by_location_refstring (db, event->real_uri); ++ entry = rhythmdb_entry_lookup_by_location_refstring (event->db, event->real_uri); + + if (entry != NULL) { + if ((event->entry_type != RHYTHMDB_ENTRY_TYPE_INVALID) && + (rhythmdb_entry_get_entry_type (entry) != event->entry_type)) { + /* switching from IGNORE to SONG or vice versa, recreate the entry */ +- rhythmdb_entry_delete (db, entry); +- rhythmdb_add_timeout_commit (db, FALSE); ++ rhythmdb_entry_delete (event->db, entry); ++ rhythmdb_add_timeout_commit (event->db, FALSE); + entry = NULL; + } + } +@@ -1885,7 +1917,7 @@ rhythmdb_process_metadata_load (RhythmDB *db, + if (event->entry_type == RHYTHMDB_ENTRY_TYPE_INVALID) + event->entry_type = RHYTHMDB_ENTRY_TYPE_SONG; + +- entry = rhythmdb_entry_new (db, event->entry_type, rb_refstring_get (event->real_uri)); ++ entry = rhythmdb_entry_new (event->db, event->entry_type, rb_refstring_get (event->real_uri)); + if (entry == NULL) { + rb_debug ("entry already exists"); + return TRUE; +@@ -1894,20 +1926,20 @@ rhythmdb_process_metadata_load (RhythmDB *db, + /* initialize the last played date to 0=never */ + g_value_init (&value, G_TYPE_ULONG); + g_value_set_ulong (&value, 0); +- rhythmdb_entry_set (db, entry, ++ rhythmdb_entry_set (event->db, entry, + RHYTHMDB_PROP_LAST_PLAYED, &value); + g_value_unset (&value); + + /* initialize the rating */ + g_value_init (&value, G_TYPE_DOUBLE); + g_value_set_double (&value, 0); +- rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_RATING, &value); ++ rhythmdb_entry_set (event->db, entry, RHYTHMDB_PROP_RATING, &value); + g_value_unset (&value); + + /* first seen */ + g_value_init (&value, G_TYPE_ULONG); + g_value_set_ulong (&value, time.tv_sec); +- rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_FIRST_SEEN, &value); ++ rhythmdb_entry_set (event->db, entry, RHYTHMDB_PROP_FIRST_SEEN, &value); + g_value_unset (&value); + } + +@@ -1918,36 +1950,119 @@ rhythmdb_process_metadata_load (RhythmDB *db, + if (event->vfsinfo) { + g_value_init (&value, G_TYPE_ULONG); + g_value_set_ulong (&value, event->vfsinfo->mtime); +- rhythmdb_entry_set_internal (db, entry, TRUE, RHYTHMDB_PROP_MTIME, &value); ++ rhythmdb_entry_set_internal (event->db, entry, TRUE, RHYTHMDB_PROP_MTIME, &value); + g_value_unset (&value); + } + + if (event->entry_type != event->ignore_type && + event->entry_type != event->error_type) { +- set_props_from_metadata (db, entry, event->vfsinfo, event->metadata); ++ set_props_from_metadata (event->db, entry, event->vfsinfo, event->metadata); + } + + /* we've seen this entry */ +- rhythmdb_entry_set_visibility (db, entry, TRUE); ++ rhythmdb_entry_set_visibility (event->db, entry, TRUE); + + g_value_init (&value, G_TYPE_ULONG); + g_value_set_ulong (&value, time.tv_sec); +- rhythmdb_entry_set_internal (db, entry, TRUE, RHYTHMDB_PROP_LAST_SEEN, &value); ++ rhythmdb_entry_set_internal (event->db, entry, TRUE, RHYTHMDB_PROP_LAST_SEEN, &value); + g_value_unset (&value); + + /* Remember the mount point of the volume the song is on */ +- rhythmdb_entry_set_mount_point (db, entry, rb_refstring_get (event->real_uri)); ++ rhythmdb_entry_set_mount_point (event->db, entry, rb_refstring_get (event->real_uri)); + + /* monitor the file for changes */ + /* FIXME: watch for errors */ + if (eel_gconf_get_boolean (CONF_MONITOR_LIBRARY) && event->entry_type == RHYTHMDB_ENTRY_TYPE_SONG) +- rhythmdb_monitor_uri_path (db, rb_refstring_get (entry->location), NULL); ++ rhythmdb_monitor_uri_path (event->db, rb_refstring_get (entry->location), NULL); + +- rhythmdb_add_timeout_commit (db, FALSE); ++ rhythmdb_add_timeout_commit (event->db, FALSE); + + return TRUE; + } + ++#ifdef HAVE_GSTREAMER_0_10_MISSING_PLUGINS ++ ++static void ++rhythmdb_missing_plugins_cb (gpointer duh, gboolean should_retry, RhythmDBEvent *event) ++{ ++ rb_debug ("missing-plugin retry closure called: event %p, retry %d", event, should_retry); ++ ++ if (should_retry) { ++ RhythmDBAction *load_action; ++ ++ rb_debug ("retrying RHYTHMDB_ACTION_LOAD for %s", rb_refstring_get (event->real_uri)); ++ load_action = g_new0 (RhythmDBAction, 1); ++ load_action->type = RHYTHMDB_ACTION_LOAD; ++ load_action->uri = rb_refstring_ref (event->real_uri); ++ load_action->entry_type = RHYTHMDB_ENTRY_TYPE_INVALID; ++ g_async_queue_push (event->db->priv->action_queue, load_action); ++ } else { ++ /* TODO replace event->error with something like ++ * "Additional GStreamer plugins are required to play this file: %s" ++ */ ++ ++ rb_debug ("not retrying RHYTHMDB_ACTION_LOAD for %s", rb_refstring_get (event->real_uri)); ++ rhythmdb_process_metadata_load_real (event); ++ } ++} ++ ++static void ++rhythmdb_missing_plugin_event_cleanup (RhythmDBEvent *event) ++{ ++ rb_debug ("cleaning up missing plugin event %p", event); ++ ++ event->db->priv->metadata_blocked = FALSE; ++ g_cond_signal (event->db->priv->metadata_cond); ++ ++ g_mutex_unlock (event->db->priv->metadata_lock); ++ rhythmdb_event_free (event->db, event); ++} ++ ++#endif /* HAVE_GSTREAMER_0_10_MISSING_PLUGINS */ ++ ++static gboolean ++rhythmdb_process_metadata_load (RhythmDB *db, ++ RhythmDBEvent *event) ++{ ++#ifdef HAVE_GSTREAMER_0_10_MISSING_PLUGINS ++ char **missing_plugins; ++ char **plugin_descriptions; ++ ++ /* don't process missing plugin messages for files we're ignoring */ ++ if (g_error_matches (event->error, ++ RB_METADATA_ERROR, ++ RB_METADATA_ERROR_NOT_AUDIO_IGNORE)) { ++ return rhythmdb_process_metadata_load_real (event); ++ } else if (rb_metadata_get_missing_plugins (event->metadata, ++ &missing_plugins, ++ &plugin_descriptions)) { ++ GClosure *closure; ++ gboolean processing; ++ ++ rb_debug ("missing plugins during metadata load for %s (event = %p)", rb_refstring_get (event->real_uri), event); ++ ++ g_mutex_lock (event->db->priv->metadata_lock); ++ ++ closure = g_cclosure_new ((GCallback) rhythmdb_missing_plugins_cb, ++ event, ++ (GClosureNotify) rhythmdb_missing_plugin_event_cleanup); ++ g_closure_set_marshal (closure, g_cclosure_marshal_VOID__BOOLEAN); ++ g_signal_emit (db, rhythmdb_signals[MISSING_PLUGINS], 0, missing_plugins, plugin_descriptions, closure, &processing); ++ if (processing) { ++ rb_debug ("processing missing plugins"); ++ } else { ++ rhythmdb_process_metadata_load_real (event); ++ } ++ ++ g_closure_sink (closure); ++ return FALSE; ++ } ++#endif /* HAVE_GSTREAMER_0_10_MISSING_PLUGINS */ ++ ++ return rhythmdb_process_metadata_load_real (event); ++} ++ ++ + static void + rhythmdb_process_queued_entry_set_event (RhythmDB *db, + RhythmDBEvent *event) +@@ -2008,8 +2123,8 @@ rhythmdb_process_one_event (RhythmDBEvent *event, RhythmDB *db) + ((event->type == RHYTHMDB_EVENT_STAT) + || (event->type == RHYTHMDB_EVENT_METADATA_LOAD) + || (event->type == RHYTHMDB_EVENT_ENTRY_SET))) { +- rb_debug ("Database is read-only, delaying event processing\n"); +- g_async_queue_push (db->priv->event_queue, event); ++ rb_debug ("Database is read-only, delaying event processing"); ++ g_async_queue_push (db->priv->delayed_write_queue, event); + return; + } + +@@ -2217,9 +2332,26 @@ rhythmdb_execute_load (RhythmDB *db, + event->vfsinfo = NULL; + } else { + if (event->type == RHYTHMDB_EVENT_METADATA_LOAD) { ++ g_mutex_lock (event->db->priv->metadata_lock); ++ while (event->db->priv->metadata_blocked) { ++ g_cond_wait (event->db->priv->metadata_cond, event->db->priv->metadata_lock); ++ } ++ + event->metadata = rb_metadata_new (); + rb_metadata_load (event->metadata, rb_refstring_get (event->real_uri), + &event->error); ++ ++ /* if we're missing some plugins, block further attempts to ++ * read metadata until we've processed them. ++ */ ++ if (!g_error_matches (event->error, ++ RB_METADATA_ERROR, ++ RB_METADATA_ERROR_NOT_AUDIO_IGNORE) && ++ rb_metadata_has_missing_plugins (event->metadata)) { ++ event->db->priv->metadata_blocked = TRUE; ++ } ++ ++ g_mutex_unlock (event->db->priv->metadata_lock); + } + } + +diff --git a/shell/Makefile.am b/shell/Makefile.am +index 1130dce..475e89d 100644 +--- a/shell/Makefile.am ++++ b/shell/Makefile.am +@@ -89,7 +89,9 @@ librbshell_la_SOURCES = \ + rb-play-order-random-by-rating.c \ + rb-play-order-random-by-rating.h \ + rb-tray-icon.c \ +- rb-tray-icon.h ++ rb-tray-icon.h \ ++ rb-missing-plugins.c \ ++ rb-missing-plugins.h + + rhythmbox_LDADD = \ + librbshell.la \ +@@ -118,6 +120,10 @@ rhythmbox_LDADD += $(GNOME_MEDIA_PROFILES_LIBS) + INCLUDES += $(GNOME_MEDIA_PROFILES_CFLAGS) + endif + ++if USE_GSTREAMER_0_10_MISSING_PLUGINS ++rhythmbox_LDADD += \ ++ -lgstpbutils-0.10 ++endif + + rb-shell-glue.h: rb-shell.xml Makefile + $(LIBTOOL) --mode=execute $(DBUS_GLIB_BIN)/dbus-binding-tool --prefix=rb_shell --mode=glib-server --output=$@ $< +diff --git a/shell/rb-missing-plugins.c b/shell/rb-missing-plugins.c +new file mode 100644 +index 0000000..118d66b +--- /dev/null ++++ b/shell/rb-missing-plugins.c +@@ -0,0 +1,308 @@ ++/* rb-missing-plugins.c ++ ++ Copyright (C) 2007 Tim-Philipp Müller ++ ++ The Gnome Library is free software; you can redistribute it and/or ++ modify it under the terms of the GNU Library General Public License as ++ published by the Free Software Foundation; either version 2 of the ++ License, or (at your option) any later version. ++ ++ The Gnome Library 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 ++ Library General Public License for more details. ++ ++ You should have received a copy of the GNU Library General Public ++ License along with the Gnome Library; see the file COPYING.LIB. If not, ++ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, ++ Boston, MA 02111-1307, USA. ++ ++ Based on totem-missing-plugins.c, authored by Tim-Philipp Müller ++ */ ++ ++#include "config.h" ++ ++#include "rb-missing-plugins.h" ++ ++#ifdef HAVE_GSTREAMER_0_10_MISSING_PLUGINS ++ ++#include "rhythmdb.h" ++#include "rb-shell-player.h" ++#include "rb-debug.h" ++#include "rb-podcast-manager.h" ++ ++#include ++#include ++ ++#include /* for gst_registry_update */ ++ ++#include ++ ++#ifdef GDK_WINDOWING_X11 ++#include ++#endif ++ ++#include ++ ++/* list of blacklisted detail strings */ ++static GList *blacklisted_plugins = NULL; ++ ++typedef struct ++{ ++ GClosure *closure; ++ gchar **details; ++ RBShell *shell; ++} RBPluginInstallContext; ++ ++static gboolean ++rb_plugin_install_plugin_is_blacklisted (const gchar * detail) ++{ ++ GList *res; ++ ++ res = g_list_find_custom (blacklisted_plugins, ++ detail, ++ (GCompareFunc) strcmp); ++ ++ return (res != NULL); ++} ++ ++static void ++rb_plugin_install_blacklist_plugin (const gchar * detail) ++{ ++ if (!rb_plugin_install_plugin_is_blacklisted (detail)) { ++ blacklisted_plugins = g_list_prepend (blacklisted_plugins, ++ g_strdup (detail)); ++ } ++} ++ ++static void ++rb_plugin_install_context_free (RBPluginInstallContext *ctx) ++{ ++ rb_debug ("cleaning up plugin install context %p", ctx); ++ g_strfreev (ctx->details); ++ g_object_unref (ctx->shell); ++ g_closure_unref (ctx->closure); ++ g_free (ctx); ++} ++ ++static void ++rb_plugin_install_done (RBPluginInstallContext *ctx, gboolean retry) ++{ ++ GValue param[2] = { {0,}, {0,} }; ++ ++ rb_debug ("invoking plugin install context %p callback: retry %d", ctx, retry); ++ ++ g_value_init (¶m[0], G_TYPE_POINTER); ++ g_value_set_pointer (¶m[0], NULL); ++ g_value_init (¶m[1], G_TYPE_BOOLEAN); ++ g_value_set_boolean (¶m[1], retry); ++ ++ g_closure_invoke (ctx->closure, NULL, 2, param, NULL); ++ g_value_unset (¶m[0]); ++ g_value_unset (¶m[1]); ++} ++ ++static void ++on_plugin_installation_done (GstInstallPluginsReturn res, gpointer user_data) ++{ ++ RBPluginInstallContext *ctx = (RBPluginInstallContext *) user_data; ++ gchar **p; ++ gboolean retry; ++ ++ rb_debug ("res = %d (%s)", res, gst_install_plugins_return_get_name (res)); ++ ++ switch (res) ++ { ++ /* treat partial success the same as success; in the worst case we'll ++ * just do another round and get NOT_FOUND as result that time */ ++ case GST_INSTALL_PLUGINS_PARTIAL_SUCCESS: ++ case GST_INSTALL_PLUGINS_SUCCESS: ++ { ++ /* blacklist installed plugins too, so that we don't get ++ * into endless installer loops in case of inconsistencies */ ++ for (p = ctx->details; p != NULL && *p != NULL; ++p) { ++ rb_plugin_install_blacklist_plugin (*p); ++ } ++ ++ g_message ("Missing plugins installed. Updating plugin registry ..."); ++ ++ /* force GStreamer to re-read its plugin registry */ ++ retry = gst_update_registry (); ++ ++ rb_plugin_install_done (ctx, retry); ++ break; ++ } ++ ++ case GST_INSTALL_PLUGINS_NOT_FOUND: ++ { ++ g_message ("No installation candidate for missing plugins found."); ++ ++ /* NOT_FOUND should only be returned if not a single one of the ++ * requested plugins was found; if we managed to play something ++ * anyway, we should just continue playing what we have and ++ * blacklist the requested plugins for this session; if we ++ * could not play anything we should blacklist them as well, ++ * so the install wizard isn't called again for nothing */ ++ for (p = ctx->details; p != NULL && *p != NULL; ++p) { ++ rb_plugin_install_blacklist_plugin (*p); ++ } ++ ++ rb_plugin_install_done (ctx, FALSE); ++ break; ++ } ++ ++ case GST_INSTALL_PLUGINS_USER_ABORT: ++ { ++ /* blacklist on user abort, so we show an error next time (or ++ * just play what we can) instead of calling the installer */ ++ for (p = ctx->details; p != NULL && *p != NULL; ++p) { ++ rb_plugin_install_blacklist_plugin (*p); ++ } ++ ++ rb_plugin_install_done (ctx, FALSE); ++ break; ++ } ++ ++ case GST_INSTALL_PLUGINS_ERROR: ++ case GST_INSTALL_PLUGINS_CRASHED: ++ default: ++ { ++ g_message ("Missing plugin installation failed: %s", ++ gst_install_plugins_return_get_name (res)); ++ ++ rb_plugin_install_done (ctx, FALSE); ++ break; ++ } ++ } ++ ++ rb_plugin_install_context_free (ctx); ++} ++ ++static gboolean ++missing_plugins_event (RBShell *shell, RBPluginInstallContext *ctx) ++{ ++ GstInstallPluginsContext *install_ctx; ++ GstInstallPluginsReturn status; ++ int i, num; ++ GtkWindow *window; ++ ++ num = g_strv_length (ctx->details); ++ for (i = 0; i < num; ++i) { ++ if (rb_plugin_install_plugin_is_blacklisted (ctx->details[i])) { ++ g_message ("Missing plugin: %s (ignoring)", ctx->details[i]); ++ g_free (ctx->details[i]); ++ ctx->details[i] = ctx->details[num-1]; ++ ctx->details[num-1] = NULL; ++ --num; ++ --i; ++ } else { ++ g_message ("Missing plugin: %s", ctx->details[i]); ++ } ++ } ++ ++ if (num == 0) { ++ g_message ("All missing plugins are blacklisted, doing nothing"); ++ rb_plugin_install_context_free (ctx); ++ return FALSE; ++ } ++ ++ install_ctx = gst_install_plugins_context_new (); ++ ++ g_object_get (shell, "window", &window, NULL); ++ if (window != NULL && GTK_WIDGET_REALIZED (window)) { ++#ifdef GDK_WINDOWING_X11 ++ gulong xid = 0; ++ xid = GDK_WINDOW_XWINDOW (GTK_WIDGET (window)->window); ++ gst_install_plugins_context_set_xid (install_ctx, xid); ++#endif ++ g_object_unref (window); ++ } ++ ++ status = gst_install_plugins_async (ctx->details, install_ctx, ++ on_plugin_installation_done, ++ ctx); ++ ++ gst_install_plugins_context_free (install_ctx); ++ ++ rb_debug ("gst_install_plugins_async() result = %d", status); ++ ++ if (status != GST_INSTALL_PLUGINS_STARTED_OK) { ++ if (status == GST_INSTALL_PLUGINS_HELPER_MISSING) { ++ g_message ("Automatic missing codec installation not supported " ++ "(helper script missing)"); ++ } else { ++ g_warning ("Failed to start codec installation: %s", ++ gst_install_plugins_return_get_name (status)); ++ } ++ rb_plugin_install_context_free (ctx); ++ return FALSE; ++ } ++ ++ return TRUE; ++} ++ ++static gboolean ++missing_plugins_cb (gpointer instance, ++ const char **details, ++ const char **descriptions, ++ GClosure *closure, ++ RBShell *shell) ++{ ++ RBPluginInstallContext *ctx; ++ guint num; ++ ++ num = g_strv_length ((char **)details); ++ g_return_val_if_fail (num > 0, FALSE); ++ ++ ctx = g_new0 (RBPluginInstallContext, 1); ++ ctx->closure = g_closure_ref (closure); ++ ctx->details = g_strdupv ((char **)details); ++ ctx->shell = g_object_ref (shell); ++ ++ return missing_plugins_event (shell, ctx); ++} ++ ++#endif /* HAVE_GSTREAMER_0_10_MISSING_PLUGINS */ ++ ++void ++rb_missing_plugins_init (RBShell *shell) ++{ ++#ifdef HAVE_GSTREAMER_0_10_MISSING_PLUGINS ++ RhythmDB *db; ++ RBShellPlayer *player; ++ RBSource *podcast_source; ++ RBPodcastManager *podcast_mgr; ++ ++ g_object_get (shell, ++ "db", &db, ++ "shell-player", &player, ++ NULL); ++ g_signal_connect (player, ++ "missing-plugins", ++ G_CALLBACK (missing_plugins_cb), ++ shell); ++ ++ g_signal_connect (db, ++ "missing-plugins", ++ G_CALLBACK (missing_plugins_cb), ++ shell); ++ ++ g_object_unref (db); ++ g_object_unref (player); ++ ++ podcast_source = rb_shell_get_source_by_entry_type (shell, RHYTHMDB_ENTRY_TYPE_PODCAST_FEED); ++ g_object_get (podcast_source, "podcast-manager", &podcast_mgr, NULL); ++ ++ g_signal_connect (podcast_mgr, ++ "missing-plugins", ++ G_CALLBACK (missing_plugins_cb), ++ shell); ++ ++ g_object_unref (podcast_mgr); ++ ++ gst_pb_utils_init (); ++ ++ GST_INFO ("Set up support for automatic missing plugin installation"); ++#endif ++} ++ +diff --git a/shell/rb-missing-plugins.h b/shell/rb-missing-plugins.h +new file mode 100644 +index 0000000..215185f +--- /dev/null ++++ b/shell/rb-missing-plugins.h +@@ -0,0 +1,34 @@ ++/* rb-missing-plugins.h ++ ++ Copyright (C) 2007 Tim-Philipp Müller ++ ++ The Gnome Library is free software; you can redistribute it and/or ++ modify it under the terms of the GNU Library General Public License as ++ published by the Free Software Foundation; either version 2 of the ++ License, or (at your option) any later version. ++ ++ The Gnome Library 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 ++ Library General Public License for more details. ++ ++ You should have received a copy of the GNU Library General Public ++ License along with the Gnome Library; see the file COPYING.LIB. If not, ++ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, ++ Boston, MA 02111-1307, USA. ++ ++ Author: Tim-Philipp Müller ++ */ ++ ++#ifndef RB_MISSING_PLUGINS_H ++#define RB_MISSING_PLUGINS_H ++ ++#include "rb-shell.h" ++ ++G_BEGIN_DECLS ++ ++void rb_missing_plugins_init (RBShell *shell); ++ ++G_END_DECLS ++ ++#endif /* RB_MISSING_PLUGINS_H */ +diff --git a/shell/rb-shell-player.c b/shell/rb-shell-player.c +index 4b554c4..2d6b12a 100644 +--- a/shell/rb-shell-player.c ++++ b/shell/rb-shell-player.c +@@ -129,6 +129,9 @@ static void rb_shell_player_sync_replaygain (RBShellPlayer *player, + RhythmDBEntry *entry); + static void tick_cb (RBPlayer *player, RhythmDBEntry *entry, long elapsed, long duration, gpointer data); + static void error_cb (RBPlayer *player, RhythmDBEntry *entry, const GError *err, gpointer data); ++#ifdef HAVE_GSTREAMER_0_10_MISSING_PLUGINS ++static void missing_plugins_cb (RBPlayer *player, RhythmDBEntry *entry, const char **details, const char **descriptions, RBShellPlayer *sp); ++#endif + static void playing_stream_cb (RBPlayer *player, RhythmDBEntry *entry, RBShellPlayer *shell_player); + static void rb_shell_player_error (RBShellPlayer *player, gboolean async, const GError *err); + +@@ -277,6 +280,7 @@ enum + PLAYING_SONG_CHANGED, + PLAYING_URI_CHANGED, + PLAYING_SONG_PROPERTY_CHANGED, ++ MISSING_PLUGINS, + LAST_SIGNAL + }; + +@@ -503,6 +507,20 @@ rb_shell_player_class_init (RBShellPlayerClass *klass) + G_TYPE_STRING, G_TYPE_STRING, + G_TYPE_VALUE, G_TYPE_VALUE); + ++#ifdef HAVE_GSTREAMER_0_10_MISSING_PLUGINS ++ rb_shell_player_signals[MISSING_PLUGINS] = ++ g_signal_new ("missing-plugins", ++ G_OBJECT_CLASS_TYPE (object_class), ++ G_SIGNAL_RUN_LAST, ++ 0, /* no need for an internal handler */ ++ NULL, NULL, ++ rb_marshal_BOOLEAN__POINTER_POINTER_POINTER, ++ G_TYPE_BOOLEAN, ++ 3, ++ G_TYPE_STRV, G_TYPE_STRV, G_TYPE_CLOSURE); ++#endif ++ ++ + g_type_class_add_private (klass, sizeof (RBShellPlayerPrivate)); + } + +@@ -835,6 +853,13 @@ rb_shell_player_init (RBShellPlayer *player) + G_CALLBACK (playing_stream_cb), + player, 0); + ++#ifdef HAVE_GSTREAMER_0_10_MISSING_PLUGINS ++ g_signal_connect_object (player->priv->mmplayer, ++ "missing-plugins", ++ G_CALLBACK (missing_plugins_cb), ++ player, 0); ++#endif ++ + g_signal_connect (G_OBJECT (gnome_vfs_get_volume_monitor ()), + "volume-pre-unmount", + G_CALLBACK (volume_pre_unmount_cb), +@@ -3180,6 +3205,87 @@ tick_cb (RBPlayer *mmplayer, + GDK_THREADS_LEAVE (); + } + ++#ifdef HAVE_GSTREAMER_0_10_MISSING_PLUGINS ++ ++typedef struct { ++ RhythmDBEntry *entry; ++ RBShellPlayer *player; ++} MissingPluginRetryData; ++ ++static void ++missing_plugins_retry_cb (gpointer inst, ++ gboolean retry, ++ MissingPluginRetryData *retry_data) ++{ ++ GError *error = NULL; ++ if (retry == FALSE) { ++ /* next? or stop playback? */ ++ rb_debug ("not retrying playback; stopping player"); ++ rb_player_close (retry_data->player->priv->mmplayer, NULL, NULL); ++ return; ++ } ++ ++ rb_debug ("retrying playback"); ++ rb_shell_player_set_playing_entry (retry_data->player, ++ retry_data->entry, ++ FALSE, FALSE, ++ &error); ++ if (error != NULL) { ++ rb_shell_player_error (retry_data->player, FALSE, error); ++ g_clear_error (&error); ++ } ++} ++ ++static void ++missing_plugins_retry_cleanup (MissingPluginRetryData *retry) ++{ ++ retry->player->priv->handling_error = FALSE; ++ ++ g_object_unref (retry->player); ++ rhythmdb_entry_unref (retry->entry); ++ g_free (retry); ++} ++ ++ ++static void ++missing_plugins_cb (RBPlayer *player, ++ RhythmDBEntry *entry, ++ const char **details, ++ const char **descriptions, ++ RBShellPlayer *sp) ++{ ++ gboolean processing; ++ GClosure *retry; ++ MissingPluginRetryData *retry_data; ++ ++ retry_data = g_new0 (MissingPluginRetryData, 1); ++ retry_data->player = g_object_ref (sp); ++ retry_data->entry = rhythmdb_entry_ref (entry); ++ ++ retry = g_cclosure_new ((GCallback) missing_plugins_retry_cb, ++ retry_data, ++ (GClosureNotify) missing_plugins_retry_cleanup); ++ g_closure_set_marshal (retry, g_cclosure_marshal_VOID__BOOLEAN); ++ g_signal_emit (sp, ++ rb_shell_player_signals[MISSING_PLUGINS], 0, ++ details, descriptions, retry, ++ &processing); ++ if (processing) { ++ /* don't handle any further errors */ ++ sp->priv->handling_error = TRUE; ++ ++ /* probably specify the URI here.. */ ++ rb_debug ("stopping player while processing missing plugins"); ++ rb_player_close (retry_data->player->priv->mmplayer, NULL, NULL); ++ } else { ++ rb_debug ("not processing missing plugins; simulating EOS"); ++ rb_shell_player_handle_eos (NULL, NULL, retry_data->player); ++ } ++ ++ g_closure_sink (retry); ++} ++#endif ++ + gboolean + rb_shell_player_get_playing_path (RBShellPlayer *shell_player, + const gchar **path, +diff --git a/shell/rb-shell.c b/shell/rb-shell.c +index 247a34a..37797fb 100644 +--- a/shell/rb-shell.c ++++ b/shell/rb-shell.c +@@ -84,6 +84,7 @@ + #include "rb-sourcelist-model.h" + #include "rb-song-info.h" + #include "rb-marshal.h" ++#include "rb-missing-plugins.h" + + #define PLAYING_ENTRY_NOTIFY_TIME 4 + +@@ -1372,6 +1373,8 @@ rb_shell_constructor (GType type, + + rb_plugins_engine_init (shell); + ++ rb_missing_plugins_init (shell); ++ + g_idle_add ((GSourceFunc)_scan_idle, shell); + + /* GO GO GO! */ diff --git a/rhythmbox.spec b/rhythmbox.spec index d0ef0e5..b634deb 100644 --- a/rhythmbox.spec +++ b/rhythmbox.spec @@ -3,7 +3,7 @@ Name: rhythmbox Summary: Music Management Application Version: 0.11.3 -Release: 1%{?dist} +Release: 2%{?dist} License: GPLv2+ and GFDL+ Group: Applications/Multimedia URL: http://www.gnome.org/projects/rhythmbox/ @@ -53,6 +53,8 @@ ExcludeArch: s390 s390x Patch1: rb-delete-ipod-tracks.patch # http://bugzilla.gnome.org/show_bug.cgi?id=484768 Patch2: rb-use-newer-plparser-7.patch +# http://bugzilla.gnome.org/show_bug.cgi?id=338308 +Patch3: rhythmbox-0.11.3-add-missing-plugins-support.patch %description Rhythmbox is an integrated music management application based on the powerful @@ -85,6 +87,8 @@ pushd plugins/ipod/ %patch1 -p0 -b .ipod-trash popd %patch2 -p0 -b .podcast +%patch3 -p1 -b .missing-plugins +autoconf automake %build @@ -197,6 +201,9 @@ fi %{_libdir}/rhythmbox/plugins/upnp_coherence %changelog +* Tue Nov 13 2007 - Bastien Nocera - 0.11.3-2 +- Add upstream patch to implement missing plugins support + * Mon Nov 12 2007 - Bastien Nocera - 0.11.3-1 - Update to 0.11.3 - Remove a whole load of upstreamed patches