static void
impl_set_property (GObject *object,
		   guint prop_id,
		   const GValue *value,
		   GParamSpec *pspec)
{
	RBTrackTransferBatch *batch = RB_TRACK_TRANSFER_BATCH (object);
	const char * const *strv;
	int i;
	switch (prop_id) {
	case PROP_MEDIA_TYPES:
		batch->priv->media_types = rb_string_list_copy (g_value_get_pointer (value));
		break;
	case PROP_MEDIA_TYPES_STRV:
		strv = g_value_get_boxed (value);
		if (strv != NULL) {
			for (i = 0; strv[i] != NULL; i++) {
				batch->priv->media_types = g_list_append (batch->priv->media_types, g_strdup (strv[i]));
			}
		}
		break;
	case PROP_SOURCE:
		batch->priv->source = g_value_dup_object (value);
		break;
	case PROP_DESTINATION:
		batch->priv->destination = g_value_dup_object (value);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}
/**
 * rb_track_transfer_batch_new:
 * @media_types: array containing media type strings describing allowable output formats
 * @media_type_list: (element-type utf8): GList containing media type strings.
 * @source: the #RBSource from which the entries are to be transferred
 * @destination: the #RBSource to which the entries are to be transferred
 *
 * Creates a new transfer batch with the specified output types.  Only one of media_types
 * and media_types_list may be specified.
 *
 * One or more entries must be added to the batch (using #rb_track_transfer_batch_add)
 * before the batch can be started (#rb_track_transfer_manager_start_batch).
 *
 * Return value: new #RBTrackTransferBatch object
 */
RBTrackTransferBatch *
rb_track_transfer_batch_new (GList *media_types,
			     const char * const *media_types_strv,
			     GObject *source,
			     GObject *destination)
{
	GObject *obj;

	/* can't specify both, can specify neither */
	g_assert (media_types == NULL || media_types_strv == NULL);

	if (media_types != NULL) {
		obj = g_object_new (RB_TYPE_TRACK_TRANSFER_BATCH,
				    "media-types", media_types,
				    "source", source,
				    "destination", destination,
				    NULL);
	} else {
		obj = g_object_new (RB_TYPE_TRACK_TRANSFER_BATCH,
				    "media-types-strv", &media_types_strv,
				    "source", source,
				    "destination", destination,
				    NULL);
	}
	return RB_TRACK_TRANSFER_BATCH (obj);
}
static void
impl_get_property (GObject *object,
		   guint prop_id,
		   GValue *value,
		   GParamSpec *pspec)
{
	RBTrackTransferBatch *batch = RB_TRACK_TRANSFER_BATCH (object);
	switch (prop_id) {
	case PROP_ENCODING_TARGET:
		g_value_set_object (value, batch->priv->target);
		break;
	case PROP_SOURCE:
		g_value_set_object (value, batch->priv->source);
		break;
	case PROP_DESTINATION:
		g_value_set_object (value, batch->priv->destination);
		break;
	case PROP_TOTAL_ENTRIES:
		{
			int count;
			count = g_list_length (batch->priv->done_entries) +
				g_list_length (batch->priv->entries);
			if (batch->priv->current != NULL) {
				count++;
			}
			g_value_set_int (value, count);
		}
		break;
	case PROP_DONE_ENTRIES:
		g_value_set_int (value, g_list_length (batch->priv->done_entries));
		break;
	case PROP_PROGRESS:
		{
			double p = batch->priv->total_fraction;
			if (batch->priv->current != NULL) {
				p += batch->priv->current_fraction * batch->priv->current_entry_fraction;
			}
			g_value_set_double (value, p);
		}
		break;
	case PROP_ENTRY_LIST:
		{
			GList *l;
			l = g_list_copy (batch->priv->entries);
			if (batch->priv->current != NULL) {
				l = g_list_append (l, batch->priv->current);
			}
			l = g_list_concat (l, g_list_copy (batch->priv->done_entries));
			g_list_foreach (l, (GFunc) rhythmdb_entry_ref, NULL);
			g_value_set_pointer (value, l);
		}
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}
static void
impl_finalize (GObject *object)
{
	RBTrackTransferBatch *batch = RB_TRACK_TRANSFER_BATCH (object);

	rb_list_deep_free (batch->priv->media_types);
	rb_list_destroy_free (batch->priv->entries, (GDestroyNotify) rhythmdb_entry_unref);
	rb_list_destroy_free (batch->priv->done_entries, (GDestroyNotify) rhythmdb_entry_unref);
	rhythmdb_entry_unref (batch->priv->current);

	G_OBJECT_CLASS (rb_track_transfer_batch_parent_class)->finalize (object);
}
/**
 * rb_track_transfer_batch_new:
 * @target: a #GstEncodingTarget describing allowable encodings (or NULL for defaults)
 * @source: the #RBSource from which the entries are to be transferred
 * @destination: the #RBSource to which the entries are to be transferred
 *
 * Creates a new transfer batch with the specified encoding target.  If no target
 * is specified, the default target will be used with the user's preferred
 * encoding type.
 *
 * One or more entries must be added to the batch (using #rb_track_transfer_batch_add)
 * before the batch can be started (#rb_track_transfer_manager_start_batch).
 *
 * Return value: new #RBTrackTransferBatch object
 */
RBTrackTransferBatch *
rb_track_transfer_batch_new (GstEncodingTarget *target,
			     GObject *source,
			     GObject *destination)
{
	GObject *obj;

	obj = g_object_new (RB_TYPE_TRACK_TRANSFER_BATCH,
			    "encoding-target", target,
			    "source", source,
			    "destination", destination,
			    NULL);
	return RB_TRACK_TRANSFER_BATCH (obj);
}
static void
impl_dispose (GObject *object)
{
	RBTrackTransferBatch *batch = RB_TRACK_TRANSFER_BATCH (object);

	g_clear_object (&batch->priv->source);
	g_clear_object (&batch->priv->destination);
	g_clear_object (&batch->priv->settings);

	if (batch->priv->target != NULL) {
		gst_encoding_target_unref (batch->priv->target);
		batch->priv->target = NULL;
	}

	G_OBJECT_CLASS (rb_track_transfer_batch_parent_class)->dispose (object);
}
static void
impl_dispose (GObject *object)
{
	RBTrackTransferBatch *batch = RB_TRACK_TRANSFER_BATCH (object);

	if (batch->priv->source != NULL) {
		g_object_unref (batch->priv->source);
		batch->priv->source = NULL;
	}

	if (batch->priv->destination != NULL) {
		g_object_unref (batch->priv->destination);
		batch->priv->destination = NULL;
	}

	G_OBJECT_CLASS (rb_track_transfer_batch_parent_class)->dispose (object);
}
static void
impl_set_property (GObject *object,
		   guint prop_id,
		   const GValue *value,
		   GParamSpec *pspec)
{
	RBTrackTransferBatch *batch = RB_TRACK_TRANSFER_BATCH (object);
	switch (prop_id) {
	case PROP_ENCODING_TARGET:
		batch->priv->target = GST_ENCODING_TARGET (g_value_dup_object (value));
		break;
	case PROP_SETTINGS:
		batch->priv->settings = g_value_dup_object (value);
		break;
	case PROP_SOURCE:
		batch->priv->source = g_value_dup_object (value);
		break;
	case PROP_DESTINATION:
		batch->priv->destination = g_value_dup_object (value);
		break;
	case PROP_TASK_LABEL:
		batch->priv->task_label = g_value_dup_string (value);
		break;
	case PROP_TASK_DETAIL:
		/* ignore */
		break;
	case PROP_TASK_PROGRESS:
		/* ignore */
		break;
	case PROP_TASK_OUTCOME:
		/* ignore */
		break;
	case PROP_TASK_NOTIFY:
		batch->priv->task_notify = g_value_get_boolean (value);
		break;
	case PROP_TASK_CANCELLABLE:
		/* ignore */
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}
static void
impl_set_property (GObject *object,
		   guint prop_id,
		   const GValue *value,
		   GParamSpec *pspec)
{
	RBTrackTransferBatch *batch = RB_TRACK_TRANSFER_BATCH (object);
	switch (prop_id) {
	case PROP_ENCODING_TARGET:
		batch->priv->target = GST_ENCODING_TARGET (g_value_dup_object (value));
		break;
	case PROP_SOURCE:
		batch->priv->source = g_value_dup_object (value);
		break;
	case PROP_DESTINATION:
		batch->priv->destination = g_value_dup_object (value);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}
static void
impl_get_property (GObject *object,
		   guint prop_id,
		   GValue *value,
		   GParamSpec *pspec)
{
	RBTrackTransferBatch *batch = RB_TRACK_TRANSFER_BATCH (object);
	switch (prop_id) {
	case PROP_ENCODING_TARGET:
		g_value_set_object (value, batch->priv->target);
		break;
	case PROP_SETTINGS:
		g_value_set_object (value, batch->priv->settings);
		break;
	case PROP_SOURCE:
		g_value_set_object (value, batch->priv->source);
		break;
	case PROP_DESTINATION:
		g_value_set_object (value, batch->priv->destination);
		break;
	case PROP_TOTAL_ENTRIES:
		{
			int count;
			count = g_list_length (batch->priv->done_entries) +
				g_list_length (batch->priv->entries);
			if (batch->priv->current != NULL) {
				count++;
			}
			g_value_set_int (value, count);
		}
		break;
	case PROP_DONE_ENTRIES:
		g_value_set_int (value, g_list_length (batch->priv->done_entries));
		break;
	case PROP_TASK_PROGRESS:
	case PROP_PROGRESS:		/* needed? */
		{
			double p = batch->priv->total_fraction;
			if (batch->priv->current != NULL) {
				p += batch->priv->current_fraction * batch->priv->current_entry_fraction;
			}
			g_value_set_double (value, p);
		}
		break;
	case PROP_ENTRY_LIST:
		{
			GList *l;
			l = g_list_copy (batch->priv->entries);
			if (batch->priv->current != NULL) {
				l = g_list_append (l, batch->priv->current);
			}
			l = g_list_concat (l, g_list_copy (batch->priv->done_entries));
			g_list_foreach (l, (GFunc) rhythmdb_entry_ref, NULL);
			g_value_set_pointer (value, l);
		}
		break;
	case PROP_TASK_LABEL:
		g_value_set_string (value, batch->priv->task_label);
		break;
	case PROP_TASK_DETAIL:
		{
			int done;
			int total;

			done = g_list_length (batch->priv->done_entries);
			total = done + g_list_length (batch->priv->entries);
			if (batch->priv->current) {
				total++;
				done++;
			}
			g_value_take_string (value, g_strdup_printf (_("%d of %d"), done, total));
		}
		break;
	case PROP_TASK_OUTCOME:
		if (batch->priv->cancelled) {
			g_value_set_enum (value, RB_TASK_OUTCOME_CANCELLED);
		} else if ((batch->priv->entries == NULL) && (batch->priv->done_entries != NULL)) {
			g_value_set_enum (value, RB_TASK_OUTCOME_COMPLETE);
		} else {
			g_value_set_enum (value, RB_TASK_OUTCOME_NONE);
		}
		break;
	case PROP_TASK_NOTIFY:
		/* we might want to notify sometimes, but we never did before */
		g_value_set_boolean (value, FALSE);
		break;
	case PROP_TASK_CANCELLABLE:
		g_value_set_boolean (value, TRUE);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}
static void
task_progress_cancel (RBTaskProgress *progress)
{
	rb_track_transfer_batch_cancel (RB_TRACK_TRANSFER_BATCH (progress));
}
static void
start_next_batch (RBTrackTransferQueue *queue)
{
	int count;
	int total;
	gboolean can_continue;
	GtkWidget *dialog;
	GtkWindow *window;
	GList *profiles = NULL;
	char *message;

	if (queue->priv->current != NULL) {
		return;
	}

	queue->priv->current = RB_TRACK_TRANSFER_BATCH (g_queue_pop_head (queue->priv->batch_queue));
	g_object_notify (G_OBJECT (queue), "batch");

	if (queue->priv->current == NULL) {
		/* indicate to anyone watching that we're not doing anything */
		g_signal_emit (queue, signals[TRANSFER_PROGRESS], 0, 0, 0, 0.0, 0);
		return;
	}

	queue->priv->overwrite_decision = OVERWRITE_PROMPT;
	g_object_get (queue->priv->current, "total-entries", &total, NULL);

	count = 0;
	can_continue = rb_track_transfer_batch_check_profiles (queue->priv->current,
							       &profiles,
							       &count);

	if (can_continue && count == 0 && profiles == NULL) {
		/* no problems, go ahead */
		actually_start_batch (queue);
		return;
	}

	if (profiles == NULL) {
		const char *str;
		str = ngettext ("%d file cannot be transferred as it must be converted into "
				"a format supported by the target device but no suitable "
				"encoding profiles are available",
				"%d files cannot be transferred as they must be converted into "
				"a format supported by the target device but no suitable "
				"encoding profiles are available",
				count);
		message = g_strdup_printf (str, count);
	} else {
		GPtrArray *descriptions;
		GstEncodingTarget *target;
		char *plugins;
		gboolean is_library;

		descriptions = get_missing_plugin_strings (profiles, TRUE);
		plugins = g_strjoinv ("\n", (char **)descriptions->pdata);

		/* this is a tiny bit hackish */
		g_object_get (queue->priv->current, "encoding-target", &target, NULL);
		is_library = (g_strcmp0 (gst_encoding_target_get_name (target), "rhythmbox-library") == 0);
		gst_encoding_target_unref (target);

		if (is_library) {
			/* XXX should provide the option of picking a different format? */
			message = g_strdup_printf (_("Additional software is required to encode media "
						     "in your preferred format:\n%s"), plugins);
		} else {
			const char *str;
			str = ngettext ("Additional software is required to convert %d file "
					"into a format supported by the target device:\n%s",
					"Additional software is required to convert %d files "
					"into a format supported by the target device:\n%s",
					count);
			message = g_strdup_printf (str, count, plugins);
		}

		g_free (plugins);
		g_ptr_array_free (descriptions, TRUE);
	}

	g_object_get (queue->priv->shell, "window", &window, NULL);
	dialog = rb_alert_dialog_new (window,
				      0,
				      GTK_MESSAGE_ERROR,
				      GTK_BUTTONS_NONE,
				      _("Unable to transfer tracks"),
				      message);
	g_object_unref (window);
	g_free (message);

	gtk_dialog_add_button (GTK_DIALOG (dialog), _("_Cancel the transfer"), GTK_RESPONSE_CANCEL);
	if (can_continue) {
		gtk_dialog_add_button (GTK_DIALOG (dialog), _("_Skip these files"), GTK_RESPONSE_YES);
	}
	if (profiles != NULL && gst_install_plugins_supported ()) {
		gtk_dialog_add_button (GTK_DIALOG (dialog), _("_Install"), GTK_RESPONSE_ACCEPT);
	}

	rb_alert_dialog_set_details_label (RB_ALERT_DIALOG (dialog), NULL);
	g_signal_connect_object (dialog, "response", G_CALLBACK (missing_encoder_response_cb), queue, 0);
	gtk_widget_show (dialog);

	if (profiles != NULL) {
		g_list_free (profiles);
	}
}