[rhythmbox] add new track transfer batch and queue system



commit 46cbb164acee00905d427a047310f2b6d5be35d2
Author: Jonathan Matthew <jonathan d14n org>
Date:   Sat May 22 11:19:20 2010 +1000

    add new track transfer batch and queue system
    
    This system groups track transfers into batches.  A batch consists of a
    set of tracks to be transferred and the origin and destination sources.
    Batches report overall progress, can be cancelled as a unit, and provide
    a set of signals allowing greater integration into the UI, such as
    indicating which track is currently being ripped from a CD.
    
    The track transfer queue organises batches into an order of execution,
    only processing one at a time.  It provides overall progress information
    via signals and through a method.  It can also be used to find any
    outstanding transfer batches with a given source as the origin or
    destination.

 bindings/python/Makefile.am          |    2 +
 bindings/python/rb.defs              |  138 +++++-
 bindings/python/rb.override          |    2 +
 doc/reference/rhythmbox-docs.sgml    |    3 +
 doc/reference/rhythmbox-sections.txt |   59 +++-
 doc/reference/rhythmbox.types        |    6 +
 lib/rb-marshal.list                  |    8 +-
 shell/Makefile.am                    |   40 +-
 shell/rb-track-transfer-batch.c      |  882 ++++++++++++++++++++++++++++++++++
 shell/rb-track-transfer-batch.h      |  101 ++++
 shell/rb-track-transfer-queue.c      |  718 +++++++++++++++++++++++++++
 shell/rb-track-transfer-queue.h      |   83 ++++
 12 files changed, 2007 insertions(+), 35 deletions(-)
---
diff --git a/bindings/python/Makefile.am b/bindings/python/Makefile.am
index a0c4bfb..5b64c64 100644
--- a/bindings/python/Makefile.am
+++ b/bindings/python/Makefile.am
@@ -64,6 +64,8 @@ BINDING_HEADERS_SRCDIR_IN = \
 	shell/rb-removable-media-manager.h	\
 	shell/rb-shell.h			\
 	shell/rb-shell-player.h			\
+	shell/rb-track-transfer-batch.h		\
+	shell/rb-track-transfer-queue.h		\
 	sources/rb-source.h			\
 	sources/rb-auto-playlist-source.h	\
 	sources/rb-browser-source.h		\
diff --git a/bindings/python/rb.defs b/bindings/python/rb.defs
index 1cd6f8d..3cf0b97 100644
--- a/bindings/python/rb.defs
+++ b/bindings/python/rb.defs
@@ -179,6 +179,19 @@
   (gtype-id "RB_TYPE_SEARCH_ENTRY")
 )
 
+(define-object TrackTransferBatch
+  (in-module "RB")
+  (parent "GObject")
+  (c-name "RBTrackTransferBatch")
+  (gtype-id "RB_TYPE_TRACK_TRANSFER_BATCH")
+)
+
+(define-object TrackTransferQueue
+  (in-module "RB")
+  (parent "GObject")
+  (c-name "RBTrackTransferQueue")
+  (gtype-id "RB_TYPE_TRACK_TRANSFER_QUEUE")
+)
 
 ;; Enumerations and flags ...
 
@@ -1996,19 +2009,6 @@
   (return-type "none")
 )
 
-(define-method queue_transfer
-  (of-object "RBRemovableMediaManager")
-  (c-name "rb_removable_media_manager_queue_transfer")
-  (return-type "none")
-  (parameters
-    '("RhythmDBEntry*" "entry")
-    '("const-char*" "dest")
-    '("GList*" "mime_types")
-    '("RBTranferCompleteCallback" "callback")
-    '("gpointer" "userdata")
-  )
-)
-
 ;; From rb-streaming-source.h
 
 (define-function rb_streaming_source_get_type
@@ -2895,3 +2895,115 @@
   (c-name "rb_search_entry_grab_focus")
   (return-type "none")
 )
+
+;; From rb-track-transfer-batch.h
+
+(define-function rb_track_transfer_batch_get_type
+  (c-name "rb_track_transfer_batch_get_type")
+  (return-type "GType")
+)
+
+(define-function rb_track_transfer_batch_new
+  (c-name "rb_track_transfer_batch_new")
+  (is-constructor-of "RBTrackTransferBatch")
+  (return-type "RBTrackTransferBatch*")
+  (parameters
+    '("GList*" "media_type_list")
+    '("const-char*-const*" "media_types")
+    '("RBSource*" "source")
+    '("RBSource*" "destination")
+  )
+)
+
+(define-method add
+  (of-object "RBTrackTransferBatch")
+  (c-name "rb_track_transfer_batch_add")
+  (return-type "none")
+  (parameters
+    '("RhythmDBEntry*" "entry")
+  )
+)
+
+(define-method check_media_types
+  (of-object "RBTrackTransferBatch")
+  (c-name "rb_track_transfer_batch_check_media_types")
+  (return-type "guint")
+)
+
+(define-method start
+  (of-object "RBTrackTransferBatch")
+  (c-name "rb_track_transfer_batch_start")
+  (return-type "none")
+  (parameters
+    '("GObject*" "queue")
+  )
+)
+
+(define-method cancel
+  (of-object "RBTrackTransferBatch")
+  (c-name "rb_track_transfer_batch_cancel")
+  (return-type "none")
+)
+
+(define-method next
+  (of-object "RBTrackTransferBatch")
+  (c-name "rb_track_transfer_batch_next")
+  (return-type "gboolean")
+)
+
+
+
+;; From rb-track-transfer-queue.h
+
+(define-function rb_track_transfer_queue_new
+  (c-name "rb_track_transfer_queue_new")
+  (is-constructor-of "RBTrackTransferQueue")
+  (return-type "RBTrackTransferQueue*")
+  (parameters
+    '("RBShell*" "shell")
+  )
+)
+
+(define-function rb_track_transfer_queue_get_type
+  (c-name "rb_track_transfer_queue_get_type")
+  (return-type "GType")
+)
+
+(define-method start_batch
+  (of-object "RBTrackTransferQueue")
+  (c-name "rb_track_transfer_queue_start_batch")
+  (return-type "none")
+  (parameters
+    '("RBTrackTransferBatch*" "batch")
+  )
+)
+
+(define-method cancel_batch
+  (of-object "RBTrackTransferQueue")
+  (c-name "rb_track_transfer_queue_cancel_batch")
+  (return-type "none")
+  (parameters
+    '("RBTrackTransferBatch*" "batch")
+  )
+)
+
+(define-method get_status
+  (of-object "RBTrackTransferQueue")
+  (c-name "rb_track_transfer_queue_get_status")
+  (return-type "gboolean")
+  (parameters
+    '("char**" "text")
+    '("char**" "progress_text")
+    '("float*" "progress")
+    '("int*" "time_left")
+  )
+)
+
+(define-method find_batch_by_source
+  (of-object "RBTrackTransferQueue")
+  (c-name "rb_track_transfer_queue_find_batch_by_source")
+  (return-type "GList*")
+  (parameters
+    '("RBSource*" "source")
+  )
+)
diff --git a/bindings/python/rb.override b/bindings/python/rb.override
index 64d4d1c..cd6c34c 100644
--- a/bindings/python/rb.override
+++ b/bindings/python/rb.override
@@ -39,6 +39,8 @@ headers
 #include "rb-sourcelist-model.h"
 #include "rb-static-playlist-source.h"
 #include "rb-streaming-source.h"
+#include "rb-track-transfer-batch.h"
+#include "rb-track-transfer-queue.h"
 #include "rb-uri-dialog.h"
 
 void pyrb_register_classes (PyObject *d);
diff --git a/doc/reference/rhythmbox-docs.sgml b/doc/reference/rhythmbox-docs.sgml
index af64885..66412e0 100644
--- a/doc/reference/rhythmbox-docs.sgml
+++ b/doc/reference/rhythmbox-docs.sgml
@@ -55,6 +55,8 @@
 		<xi:include href="xml/rb-shell.xml"/>
 		<xi:include href="xml/rb-source-header.xml"/>
 		<xi:include href="xml/rb-statusbar.xml"/>
+		<xi:include href="xml/rb-track-transfer-batch.xml"/>
+		<xi:include href="xml/rb-track-transfer-queue.xml"/>
 	</chapter>
 
 	<chapter>  	
@@ -64,6 +66,7 @@
 		<xi:include href="xml/rb-import-errors-source.xml"/>
 		<xi:include href="xml/rb-library-source.xml"/>
 		<xi:include href="xml/rb-missing-files-source.xml"/>
+		<xi:include href="xml/rb-media-player-source.xml"/>
 		<xi:include href="xml/rb-play-queue-source.xml"/>
 		<xi:include href="xml/rb-playlist-source.xml"/>
 		<xi:include href="xml/rb-removable-media-source.xml"/>
diff --git a/doc/reference/rhythmbox-sections.txt b/doc/reference/rhythmbox-sections.txt
index 24ec4b9..93d3cd4 100644
--- a/doc/reference/rhythmbox-sections.txt
+++ b/doc/reference/rhythmbox-sections.txt
@@ -64,7 +64,6 @@ RBRemovableMediaManager
 RBRemovableMediaManagerClass
 rb_removable_media_manager_new
 rb_removable_media_manager_scan
-rb_removable_media_manager_queue_transfer
 <SUBSECTION Standard>
 RB_REMOVABLE_MEDIA_MANAGER
 RB_IS_REMOVABLE_MEDIA_MANAGER
@@ -938,6 +937,7 @@ RBEntryViewPrivate
 
 <SECTION>
 <FILE>rb-header</FILE>
+<TITLE>RBHeader</TITLE>
 RBHeader
 RBHeaderClass
 rb_header_new
@@ -1246,7 +1246,8 @@ RBEncoderIface
 rb_encoder_new
 rb_encoder_encode
 rb_encoder_cancel
-rb_encoder_get_preferred_mimetype
+rb_encoder_get_media_type
+rb_encoder_get_missing_plugins
 <SUBSECTION Standard>
 RB_ENCODER
 RB_IS_ENCODER
@@ -1519,3 +1520,57 @@ RB_IS_SOURCE_SEARCH_BASIC
 RB_IS_SOURCE_SEARCH_BASIC_CLASS
 RB_SOURCE_SEARCH_BASIC_GET_CLASS
 </SECTION>
+
+<SECTION>
+<FILE>rb-track-transfer-batch</FILE>
+<TITLE>RBTrackTransferBatch</TITLE>
+RBTrackTransferBatch
+RBTrackTransferBatchClass
+rb_track_transfer_batch_new
+rb_track_transfer_batch_add
+rb_track_transfer_batch_start
+rb_track_transfer_batch_next
+<SUBSECTION Standard>
+rb_track_transfer_batch_get_type
+RB_TYPE_TRACK_TRANSFER_BATCH
+RB_TRACK_TRANSFER_BATCH
+RB_TRACK_TRANSFER_BATCH_CLASS
+RB_IS_TRACK_TRANSFER_BATCH
+RB_IS_TRACK_TRANSFER_BATCH_CLASS
+RB_TRACK_TRANSFER_BATCH_GET_CLASS
+</SECTION>
+
+<SECTION>
+<FILE>rb-track-transfer-queue</FILE>
+<TITLE>RBTrackTransferQueue</TITLE>
+RBTrackTransferQueue
+RBTrackTransferQueueClass
+rb_track_transfer_queue_new
+rb_track_transfer_queue_start_batch
+rb_track_transfer_queue_cancel_batch
+rb_track_transfer_queue_get_status
+<SUBSECTION Standard>
+rb_track_transfer_queue_get_type
+RB_TYPE_TRACK_TRANSFER_QUEUE
+RB_TRACK_TRANSFER_QUEUE
+RB_TRACK_TRANSFER_QUEUE_CLASS
+RB_IS_TRACK_TRANSFER_QUEUE
+RB_IS_TRACK_TRANSFER_QUEUE_CLASS
+RB_TRACK_TRANSFER_QUEUE_GET_CLASS
+</SECTION>
+
+<SECTION>
+<FILE>rb-media-player-source</FILE>
+<TITLE>RBMediaPlayerSource</TITLE>
+RBMediaPlayerSource
+RBMediaPlayerSourceClass
+rb_media_player_source_show_properties
+<SUBSECTION Standard>
+rb_media_player_source_get_type
+RB_TYPE_MEDIA_PLAYER_SOURCE
+RB_MEDIA_PLAYER_SOURCE
+RB_MEDIA_PLAYER_SOURCE_CLASS
+RB_IS_MEDIA_PLAYER_SOURCE
+RB_IS_MEDIA_PLAYER_SOURCE_CLASS
+RB_MEDIA_PLAYER_SOURCE_GET_CLASS
+</SECTION>
diff --git a/doc/reference/rhythmbox.types b/doc/reference/rhythmbox.types
index b78c882..08bf899 100644
--- a/doc/reference/rhythmbox.types
+++ b/doc/reference/rhythmbox.types
@@ -19,6 +19,7 @@
 #include "rb-library-browser.h"
 #include "rb-library-source.h"
 #include "rb-marshal.h"
+#include "rb-media-player-source.h"
 #include "rb-metadata.h"
 #include "rb-missing-files-source.h"
 #include "rb-player.h"
@@ -56,6 +57,8 @@
 #include "rb-statusbar.h"
 #include "rb-stock-icons.h"
 #include "rb-streaming-source.h"
+#include "rb-track-transfer-queue.h"
+#include "rb-track-transfer-batch.h"
 #include "rb-tree-dnd.h"
 #include "rb-uri-dialog.h"
 #include "rb-util.h"
@@ -79,6 +82,7 @@ rb_history_get_type
 rb_import_errors_source_get_type
 rb_library_browser_get_type
 rb_library_source_get_type
+rb_media_player_source_get_type
 rb_metadata_get_type
 rb_missing_files_source_get_type
 rb_player_error_get_type
@@ -120,6 +124,8 @@ rb_static_playlist_source_get_type
 rb_statusbar_get_type
 rb_streaming_source_get_type
 rb_string_value_map_get_type
+rb_track_transfer_batch_get_type
+rb_track_transfer_queue_get_type
 rb_tree_drag_dest_get_type
 rb_tree_drag_source_get_type
 rb_uri_dialog_get_type
diff --git a/lib/rb-marshal.list b/lib/rb-marshal.list
index 0c103f0..316ca5b 100644
--- a/lib/rb-marshal.list
+++ b/lib/rb-marshal.list
@@ -11,6 +11,7 @@ OBJECT:OBJECT
 OBJECT:OBJECT,OBJECT
 OBJECT:VOID
 STRING:STRING
+STRING:BOXED,STRING,STRING
 VOID:BOOLEAN,BOOLEAN
 BOXED:BOXED
 BOXED:OBJECT
@@ -19,12 +20,14 @@ VOID:BOXED,BOXED
 VOID:BOXED,INT,POINTER,POINTER
 VOID:BOXED,OBJECT
 VOID:BOXED,POINTER
+VOID:BOXED,STRING
 VOID:BOXED,STRING,BOXED
+VOID:BOXED,STRING,INT,INT,DOUBLE
+VOID:BOXED,STRING,UINT64,STRING,POINTER
 VOID:BOXED,ULONG
 VOID:DOUBLE,LONG
-VOID:UINT64
 VOID:INT,INT
-VOID:INT,INT,DOUBLE
+VOID:INT,INT,DOUBLE,INT
 VOID:INT64
 VOID:OBJECT,INT,INT
 VOID:OBJECT,INT,INT,BOXED,UINT,UINT
@@ -48,6 +51,7 @@ VOID:STRING,STRING,OBJECT
 VOID:STRING,STRING,STRING
 VOID:STRING,STRING,STRING,UINT,BOOLEAN
 VOID:UINT,STRING,STRING,OBJECT,BOOLEAN
+VOID:UINT64
 VOID:UINT64,STRING,POINTER
 VOID:ULONG,FLOAT
 VOID:OBJECT,BOOLEAN
diff --git a/shell/Makefile.am b/shell/Makefile.am
index 81a14b3..fef1c78 100644
--- a/shell/Makefile.am
+++ b/shell/Makefile.am
@@ -58,22 +58,14 @@ shellinclude_HEADERS =					\
 	rb-removable-media-manager.h			\
 	rb-history.h					\
 	rb-play-order.h					\
-	rb-plugin.h
+	rb-plugin.h					\
+	rb-track-transfer-batch.h			\
+	rb-track-transfer-queue.h
 
 librhythmbox_core_la_SOURCES =				\
 	$(shellinclude_HEADERS)				\
-	rb-shell.c					\
-	rb-shell-player.c				\
-	rb-source-header.c				\
-	rb-source-header.h				\
-	rb-statusbar.c					\
-	rb-statusbar.h					\
-	rb-shell-preferences.c				\
-	rb-shell-clipboard.c				\
-	rb-shell-clipboard.h				\
-	rb-playlist-manager.c				\
-	rb-removable-media-manager.c			\
 	rb-history.c					\
+	rb-missing-plugins.c				\
 	rb-play-order.c					\
 	rb-play-order-linear.c				\
 	rb-play-order-linear.h				\
@@ -81,20 +73,32 @@ librhythmbox_core_la_SOURCES =				\
 	rb-play-order-linear-loop.h			\
 	rb-play-order-queue.c				\
 	rb-play-order-queue.h				\
-	rb-play-order-shuffle.c				\
-	rb-play-order-shuffle.h				\
 	rb-play-order-random.c				\
 	rb-play-order-random.h				\
-	rb-play-order-random-equal-weights.c		\
-	rb-play-order-random-equal-weights.h		\
 	rb-play-order-random-by-age.c			\
 	rb-play-order-random-by-age.h			\
 	rb-play-order-random-by-age-and-rating.c	\
 	rb-play-order-random-by-age-and-rating.h	\
 	rb-play-order-random-by-rating.c		\
 	rb-play-order-random-by-rating.h		\
-	rb-missing-plugins.c				\
-	rb-missing-plugins.h				\
+	rb-play-order-random-equal-weights.c		\
+	rb-play-order-random-equal-weights.h		\
+	rb-play-order-shuffle.c				\
+	rb-play-order-shuffle.h				\
+	rb-playlist-manager.c				\
+	rb-removable-media-manager.c			\
+	rb-shell.c					\
+	rb-shell-clipboard.c				\
+	rb-shell-clipboard.h				\
+	rb-shell-player.c				\
+	rb-shell-preferences.c				\
+	rb-shell-preferences.h				\
+	rb-source-header.c				\
+	rb-source-header.h				\
+	rb-statusbar.c					\
+	rb-statusbar.h					\
+	rb-track-transfer-batch.c			\
+	rb-track-transfer-queue.c			\
 	\
 	rb-plugin.c					\
 	rb-module.h					\
diff --git a/shell/rb-track-transfer-batch.c b/shell/rb-track-transfer-batch.c
new file mode 100644
index 0000000..11a02f6
--- /dev/null
+++ b/shell/rb-track-transfer-batch.c
@@ -0,0 +1,882 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ *  Copyright (C) 2010  Jonathan Matthew  <jonathan d14n org>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The Rhythmbox authors hereby grants permission for non-GPL compatible
+ *  GStreamer plugins to be used and distributed together with GStreamer
+ *  and Rhythmbox. This permission is above and beyond the permissions granted
+ *  by the GPL license by which Rhythmbox is covered. If you modify this code
+ *  you may extend this exception to your version of the code, but you are not
+ *  obligated to do so. If you do not wish to do so, delete this exception
+ *  statement from your version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
+ *
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+
+#include "rb-track-transfer-batch.h"
+#include "rb-track-transfer-queue.h"
+#include "rb-encoder.h"
+#include "rb-marshal.h"
+#include "rb-debug.h"
+#include "rb-util.h"
+
+enum
+{
+	STARTED,
+	COMPLETE,
+	CANCELLED,
+	GET_DEST_URI,
+	OVERWRITE_PROMPT,
+	TRACK_STARTED,
+	TRACK_PROGRESS,
+	TRACK_DONE,
+	LAST_SIGNAL
+};
+
+enum
+{
+	PROP_0,
+	PROP_MEDIA_TYPES,
+	PROP_MEDIA_TYPES_STRV,
+	PROP_SOURCE,
+	PROP_DESTINATION,
+	PROP_TOTAL_ENTRIES,
+	PROP_DONE_ENTRIES,
+	PROP_PROGRESS
+};
+
+static void	rb_track_transfer_batch_class_init (RBTrackTransferBatchClass *klass);
+static void	rb_track_transfer_batch_init (RBTrackTransferBatch *batch);
+
+static guint	signals[LAST_SIGNAL] = { 0 };
+
+struct _RBTrackTransferBatchPrivate
+{
+	RBTrackTransferQueue *queue;
+
+	GList *media_types;
+
+	RBSource *source;
+	RBSource *destination;
+
+	GList *entries;
+	GList *done_entries;
+
+	guint64 total_duration;
+	guint64 total_size;
+	double total_fraction;
+
+	RhythmDBEntry *current;
+	double current_entry_fraction;
+	char *current_dest_uri;
+	double current_fraction;
+	RBEncoder *current_encoder;
+	gboolean cancelled;
+};
+
+G_DEFINE_TYPE (RBTrackTransferBatch, rb_track_transfer_batch, G_TYPE_OBJECT)
+
+/**
+ * SECTION:rb-track-transfer-batch
+ * @short_description: batch track transfer job
+ *
+ * Manages the transfer of a set of tracks (using #RBEncoder), providing overall
+ * status information and allowing the transfer to be cancelled as a single unit.
+ */
+
+/**
+ * rb_track_transfer_batch_new:
+ * @media_types: array containing media type strings describing allowable output formats
+ * @media_type_list: 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,
+			     RBSource *source,
+			     RBSource *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);
+}
+
+/**
+ * rb_track_transfer_batch_add:
+ * @batch: a #RBTrackTransferBatch
+ * @entry: the source #RhythmDBEntry to transfer
+ *
+ * Adds an entry to be transferred.
+ */
+void
+rb_track_transfer_batch_add (RBTrackTransferBatch *batch, RhythmDBEntry *entry)
+{
+	batch->priv->entries = g_list_append (batch->priv->entries, rhythmdb_entry_ref (entry));
+}
+
+/**
+ * rb_track_transfer_batch_check_media_types:
+ * @batch: a #RBTrackTransferBatch
+ *
+ * Checks that all entries in the batch can be transferred in a format
+ * supported by the destination.
+ *
+ * Return value: number of entries that cannot be transferred
+ */
+guint
+rb_track_transfer_batch_check_media_types (RBTrackTransferBatch *batch)
+{
+	RBEncoder *encoder = rb_encoder_new ();
+	guint count = 0;
+	GList *l;
+
+	for (l = batch->priv->entries; l != NULL; l = l->next) {
+		RhythmDBEntry *entry = (RhythmDBEntry *)l->data;
+		/* check that we can transfer this entry to the device */
+		if (rb_encoder_get_media_type (encoder,
+					       entry,
+					       batch->priv->media_types,
+					       NULL,
+					       NULL) == FALSE) {
+			rb_debug ("unable to transfer %s (media type %s)",
+				  rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION),
+				  rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MIMETYPE));
+			count++;
+		}
+	}
+	g_object_unref (encoder);
+	return count;
+}
+
+/**
+ * rb_track_transfer_batch_start:
+ * @batch: a #RBTrackTransferBatch
+ * @queue: the #RBTrackTransferQueue
+ *
+ * Starts the batch transfer.  Only to be called by the #RBTrackTransferQueue.
+ */
+void
+rb_track_transfer_batch_start (RBTrackTransferBatch *batch, GObject *queue)
+{
+	gboolean total_duration_valid;
+	gboolean total_size_valid;
+	gboolean origin_valid;
+	guint64 filesize;
+	gulong duration;
+	RBSource *origin = NULL;
+	RBShell *shell;
+	GList *l;
+
+	g_object_get (queue, "shell", &shell, NULL);
+
+	/* calculate total duration and file size and figure out the
+	 * origin source if we weren't given one to start with.
+	 */
+	total_duration_valid = TRUE;
+	total_size_valid = TRUE;
+	origin_valid = TRUE;
+	for (l = batch->priv->entries; l != NULL; l = l->next) {
+		RhythmDBEntry *entry = (RhythmDBEntry *)l->data;
+
+		filesize = rhythmdb_entry_get_uint64 (entry, RHYTHMDB_PROP_FILE_SIZE);
+		if (total_size_valid && filesize > 0) {
+			batch->priv->total_size += filesize;
+		} else {
+			total_size_valid = FALSE;
+			batch->priv->total_size = 0;
+		}
+
+		duration = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DURATION);
+		if (total_duration_valid && duration > 0) {
+			batch->priv->total_duration += duration;
+		} else {
+			total_duration_valid = FALSE;
+			batch->priv->total_duration = 0;
+		}
+
+		if (batch->priv->source == NULL) {
+			RhythmDBEntryType entry_type;
+			RBSource *entry_origin;
+
+			entry_type = rhythmdb_entry_get_entry_type (entry);
+			entry_origin = rb_shell_get_source_by_entry_type (shell, entry_type);
+			if (origin == NULL && origin_valid  == TRUE) {
+				origin = entry_origin;
+			} else if (origin != entry_origin) {
+				origin = NULL;
+				origin_valid = FALSE;
+			}
+		}
+	}
+
+	g_object_unref (shell);
+
+	if (origin != NULL) {
+		batch->priv->source = origin;
+	}
+
+	batch->priv->queue = RB_TRACK_TRANSFER_QUEUE (queue);
+	batch->priv->cancelled = FALSE;
+	batch->priv->total_fraction = 0.0;
+
+	g_signal_emit (batch, signals[STARTED], 0);
+
+	rb_track_transfer_batch_next (batch);
+}
+
+/**
+ * rb_track_transfer_batch_cancel:
+ * @batch: a #RBTrackTransferBatch
+ *
+ * Cancels a batch transfer.  Only to be called by the #RBTrackTransferQueue.
+ */
+void
+rb_track_transfer_batch_cancel (RBTrackTransferBatch *batch)
+{
+	batch->priv->cancelled = TRUE;
+
+	if (batch->priv->current_encoder != NULL) {
+		rb_encoder_cancel (batch->priv->current_encoder);
+
+		/* other things take care of cleaning up the encoder */
+	}
+
+	g_signal_emit (batch, signals[CANCELLED], 0);
+
+	/* anything else? */
+}
+
+static void
+emit_progress (RBTrackTransferBatch *batch)
+{
+	int done;
+	int total;
+	double fraction;
+
+	g_object_get (batch,
+		      "total-entries", &total,
+		      "done-entries", &done,
+		      "progress", &fraction,
+		      NULL);
+	g_signal_emit (batch, signals[TRACK_PROGRESS], 0,
+		       batch->priv->current,
+		       batch->priv->current_dest_uri,
+		       done,
+		       total,
+		       fraction);
+}
+
+static void
+encoder_progress_cb (RBEncoder *encoder, double fraction, RBTrackTransferBatch *batch)
+{
+	batch->priv->current_fraction = fraction;
+	emit_progress (batch);
+}
+
+static void
+encoder_completed_cb (RBEncoder *encoder,
+		      guint64 dest_size,
+		      const char *mediatype,
+		      GError *error,
+		      RBTrackTransferBatch *batch)
+{
+	RhythmDBEntry *entry;
+
+	if (error != NULL) {
+		rb_debug ("encoder finished (error: %s)", error->message);
+	} else {
+		rb_debug ("encoder finished (size %" G_GUINT64_FORMAT ")", dest_size);
+	}
+
+	g_object_unref (batch->priv->current_encoder);
+	batch->priv->current_encoder = NULL;
+
+	entry = batch->priv->current;
+	batch->priv->current = NULL;
+
+	/* update batch state to reflect that the track is done */
+	batch->priv->total_fraction += batch->priv->current_entry_fraction;
+	batch->priv->done_entries = g_list_append (batch->priv->done_entries, entry);
+
+	if (batch->priv->cancelled == FALSE) {
+		/* keep ourselves alive until the end of the function, since it's
+		 * possible that a signal handler will cancel us.
+		 */
+		g_object_ref (batch);
+		g_signal_emit (batch, signals[TRACK_DONE], 0,
+			       entry,
+			       batch->priv->current_dest_uri,
+			       dest_size,
+			       mediatype,
+			       error);
+
+		rb_track_transfer_batch_next (batch);
+
+		g_object_unref (batch);
+	}
+}
+
+static gboolean
+encoder_overwrite_cb (RBEncoder *encoder, GFile *file, RBTrackTransferBatch *batch)
+{
+	gboolean overwrite = FALSE;
+	g_signal_emit (batch, signals[OVERWRITE_PROMPT], 0, file, &overwrite);
+
+	return overwrite;
+}
+
+/**
+ * rb_track_transfer_batch_next:
+ * @batch: a #RBTrackTransferBatch
+ *
+ * Starts the transfer of the next entry in the batch.
+ * Only to be called by the #RBTrackTransferQueue.
+ *
+ * Return value: %FALSE if there were no more entries to transfer
+ */
+gboolean
+rb_track_transfer_batch_next (RBTrackTransferBatch *batch)
+{
+	GList *n;
+	char *media_type;
+	char *extension;
+
+	if (batch->priv->cancelled == TRUE) {
+		return FALSE;
+	}
+
+	if (batch->priv->entries == NULL) {
+		/* guess we must be done.. */
+		g_signal_emit (batch, signals[COMPLETE], 0);
+		return FALSE;
+	}
+
+	batch->priv->current_fraction = 0.0;
+	batch->priv->current_encoder = rb_encoder_new ();
+	g_signal_connect_object (batch->priv->current_encoder, "progress",
+				 G_CALLBACK (encoder_progress_cb),
+				 batch, 0);
+	g_signal_connect_object (batch->priv->current_encoder, "overwrite",
+				 G_CALLBACK (encoder_overwrite_cb),
+				 batch, 0);
+	g_signal_connect_object (batch->priv->current_encoder, "completed",
+				 G_CALLBACK (encoder_completed_cb),
+				 batch, 0);
+
+	rb_debug ("%d entries remain in the batch", g_list_length (batch->priv->entries));
+
+	while ((batch->priv->entries != NULL) && (batch->priv->cancelled == FALSE)) {
+		RhythmDBEntry *entry;
+		guint64 filesize;
+		gulong duration;
+		double fraction;
+
+		n = batch->priv->entries;
+		batch->priv->entries = g_list_remove_link (batch->priv->entries, n);
+		entry = (RhythmDBEntry *)n->data;
+		g_list_free_1 (n);
+
+		rb_debug ("attempting to transfer %s", rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION));
+
+		/* calculate the fraction of the transfer that this entry represents */
+		filesize = rhythmdb_entry_get_uint64 (entry, RHYTHMDB_PROP_FILE_SIZE);
+		duration = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DURATION);
+		if (batch->priv->total_duration > 0) {
+			g_assert (duration > 0);	/* otherwise total_duration would be 0 */
+			fraction = ((double)duration) / (double) batch->priv->total_duration;
+		} else if (batch->priv->total_size > 0) {
+			g_assert (filesize > 0);	/* otherwise total_size would be 0 */
+			fraction = ((double)filesize) / (double) batch->priv->total_size;
+		} else {
+			int count = g_list_length (batch->priv->entries) +
+				    g_list_length (batch->priv->done_entries) + 1;
+			fraction = 1.0 / ((double)count);
+		}
+
+		media_type = NULL;
+		extension = NULL;
+		if (rb_encoder_get_media_type (batch->priv->current_encoder,
+					       entry,
+					       batch->priv->media_types,
+					       &media_type,
+					       &extension) == FALSE) {
+			rb_debug ("skipping entry %s, can't find a destination format",
+				  rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION));
+			rhythmdb_entry_unref (entry);
+			batch->priv->total_fraction += fraction;
+			continue;
+		}
+
+		g_free (batch->priv->current_dest_uri);
+		batch->priv->current_dest_uri = NULL;
+		g_signal_emit (batch, signals[GET_DEST_URI], 0,
+			       entry,
+			       media_type,
+			       extension,
+			       &batch->priv->current_dest_uri);
+		if (batch->priv->current_dest_uri == NULL) {
+			rb_debug ("unable to build destination URI for %s, skipping",
+				  rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION));
+			rhythmdb_entry_unref (entry);
+			batch->priv->total_fraction += fraction;
+			continue;
+		}
+		g_free (extension);
+
+		g_signal_emit (batch, signals[TRACK_STARTED], 0,
+			       entry,
+			       batch->priv->current_dest_uri);
+
+		if (rb_encoder_encode (batch->priv->current_encoder,
+				       entry,
+				       batch->priv->current_dest_uri,
+				       media_type) == FALSE) {
+			/* need to report this? */
+			rb_debug ("unable to transfer %s to %s",
+				  rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION),
+				  batch->priv->current_dest_uri);
+
+			g_free (media_type);
+			rhythmdb_entry_unref (entry);
+			batch->priv->total_fraction += fraction;
+			continue;
+		}
+
+		g_free (media_type);
+		batch->priv->current = entry;
+		batch->priv->current_entry_fraction = fraction;
+		break;
+	}
+
+	/* need to clean up encoder here? */
+	if (batch->priv->current == NULL) {
+		g_object_unref (batch->priv->current_encoder);
+		batch->priv->current_encoder = NULL;
+	}
+
+	return TRUE;
+}
+
+
+
+static void
+rb_track_transfer_batch_init (RBTrackTransferBatch *batch)
+{
+	batch->priv = G_TYPE_INSTANCE_GET_PRIVATE (batch,
+						   RB_TYPE_TRACK_TRANSFER_BATCH,
+						   RBTrackTransferBatchPrivate);
+}
+
+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;
+	}
+}
+
+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_MEDIA_TYPES:
+		g_value_set_pointer (value, batch->priv->media_types);
+		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;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+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_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);
+}
+
+static void
+rb_track_transfer_batch_class_init (RBTrackTransferBatchClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+	object_class->set_property = impl_set_property;
+	object_class->get_property = impl_get_property;
+	object_class->finalize = impl_finalize;
+	object_class->dispose = impl_dispose;
+
+	/**
+	 * RBTrackTransferBatch:media-types
+	 *
+	 * Array of media type strings describing the acceptable
+	 * destination formats.  If NULL, no format conversion will
+	 * be done.
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_MEDIA_TYPES_STRV,
+					 g_param_spec_boxed ("media-types-strv",
+							     "media types",
+							     "Set of allowable destination media types",
+							     G_TYPE_STRV,
+							     G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+	/**
+	 * RBTrackTransferBatch:media-types
+	 *
+	 * GList of media type strings describing the acceptable
+	 * destination formats.  If NULL, no format conversion will
+	 * be done.
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_MEDIA_TYPES,
+					 g_param_spec_pointer ("media-types",
+							       "media types",
+							       "Set of allowable destination media types",
+							       G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
+	/**
+	 * RBTrackTransferBatch:source
+	 *
+	 * The RBSource from which the tracks are being transferred.
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_SOURCE,
+					 g_param_spec_object ("source",
+							      "source source",
+							      "RBSource from which the tracks are being transferred",
+							      RB_TYPE_SOURCE,
+							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+	/**
+	 * RBTrackTransferBatch:destination
+	 *
+	 * The RBSource to which the tracks are being transferred.
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_DESTINATION,
+					 g_param_spec_object ("destination",
+							      "destination source",
+							      "RBSource to which the tracks are being transferred",
+							      RB_TYPE_SOURCE,
+							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+	/**
+	 * RBTrackTransferBatch:total-entries
+	 *
+	 * Total number of entries in the transfer batch.
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_TOTAL_ENTRIES,
+					 g_param_spec_int ("total-entries",
+							   "total entries",
+							   "Number of entries in the batch",
+							   0, G_MAXINT, 0,
+							   G_PARAM_READABLE));
+	/**
+	 * RBTrackTransferBatch:done-entries
+	 *
+	 * Number of entries in the batch that have been transferred.
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_DONE_ENTRIES,
+					 g_param_spec_int ("done-entries",
+							   "done entries",
+							   "Number of entries already transferred",
+							   0, G_MAXINT, 0,
+							   G_PARAM_READABLE));
+	/**
+	 * RBTrackTransferBatch:progress
+	 *
+	 * Fraction of the transfer batch that has been processed.
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_PROGRESS,
+					 g_param_spec_double ("progress",
+							      "progress fraction",
+							      "Fraction of the batch that has been transferred",
+							      0.0, 1.0, 0.0,
+							      G_PARAM_READABLE));
+
+	/**
+	 * RBTrackTransferBatch::started:
+	 * @batch: the #RBTrackTransferBatch
+	 *
+	 * Emitted when the batch is started.  This will be after
+	 * all previous batches have finished, which is not necessarily
+	 * when #rb_track_transfer_manager_start_batch is called.
+	 */
+	signals [STARTED] =
+		g_signal_new ("started",
+			      G_OBJECT_CLASS_TYPE (object_class),
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (RBTrackTransferBatchClass, started),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__VOID,
+			      G_TYPE_NONE,
+			      0);
+
+	/**
+	 * RBTrackTransferBatch::complete:
+	 * @batch: the #RBTrackTransferBatch
+	 *
+	 * Emitted when the batch is complete.  This will be immediately
+	 * after the final entry transfer is complete.
+	 */
+	signals [COMPLETE] =
+		g_signal_new ("complete",
+			      G_OBJECT_CLASS_TYPE (object_class),
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (RBTrackTransferBatchClass, complete),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__VOID,
+			      G_TYPE_NONE,
+			      0);
+
+	/**
+	 * RBTrackTransferBatch::cancelled:
+	 * @batch: the #RBTrackTransferBatch
+	 *
+	 * Emitted when the batch is cancelled.
+	 *
+	 * hmm.  will 'complete' still be emitted in this case?
+	 */
+	signals [CANCELLED] =
+		g_signal_new ("cancelled",
+			      G_OBJECT_CLASS_TYPE (object_class),
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (RBTrackTransferBatchClass, cancelled),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__VOID,
+			      G_TYPE_NONE,
+			      0);
+
+	/**
+	 * RBTrackTransferBatch::get-dest-uri:
+	 * @batch: the #RBTrackTransferBatch
+	 * @entry: the #RhythmDBEntry to be transferred
+	 * @mediatype: the destination media type for the transfer
+	 * @extension: usual extension for the destionation media type
+	 *
+	 * The batch emits this to allow the creator to provide a destination
+	 * URI for an entry being transferred.  This is emitted after the
+	 * output media type is decided, so the usual extension for the media
+	 * type can be taken into consideration.
+	 */
+	signals [GET_DEST_URI] =
+		g_signal_new ("get-dest-uri",
+			      G_OBJECT_CLASS_TYPE (object_class),
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (RBTrackTransferBatchClass, get_dest_uri),
+			      NULL, NULL,
+			      rb_marshal_STRING__BOXED_STRING_STRING,
+			      G_TYPE_STRING,
+			      3, RHYTHMDB_TYPE_ENTRY, G_TYPE_STRING, G_TYPE_STRING);
+
+	/**
+	 * RBTrackTransferBatch::overwrite-prompt:
+	 * @batch: the #RBTrackTransferBatch
+	 * @file: the #GFile that may be overwritten
+	 *
+	 * Emitted when the destination URI for a transfer already exists.
+	 * If a handler returns TRUE, the file will be overwritten, otherwise
+	 * the transfer will be skipped.
+	 */
+	signals [OVERWRITE_PROMPT] =
+		g_signal_new ("overwrite-prompt",
+			      G_OBJECT_CLASS_TYPE (object_class),
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (RBTrackTransferBatchClass, overwrite_prompt),
+			      NULL, NULL,
+			      rb_marshal_BOOLEAN__OBJECT,
+			      G_TYPE_BOOLEAN,
+			      1, G_TYPE_FILE);
+
+	/**
+	 * RBTrackTransferBatch::track-started:
+	 * @batch: the #RBTrackTransferBatch
+	 * @entry: the #RhythmDBEntry being transferred
+	 * @dest: the destination URI for the transfer
+	 *
+	 * Emitted when a new entry is about to be transferred.
+	 * This will be emitted for each entry in the batch, unless
+	 * the batch is cancelled.
+	 */
+	signals [TRACK_STARTED] =
+		g_signal_new ("track-started",
+			      G_OBJECT_CLASS_TYPE (object_class),
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (RBTrackTransferBatchClass, track_started),
+			      NULL, NULL,
+			      rb_marshal_VOID__BOXED_STRING,
+			      G_TYPE_NONE,
+			      2, RHYTHMDB_TYPE_ENTRY, G_TYPE_STRING);
+
+	/**
+	 * RBTrackTransferBatch::track-progress:
+	 * @batch: the #RBTrackTransferBatch
+	 * @entry: the #RhythmDBEntry being transferred
+	 * @dest: the destination URI for the transfer
+	 * @done: some measure of how much of the transfer is done
+	 * @total: the total amount of that same measure
+	 * @fraction: the fraction of the transfer that is done
+	 *
+	 * Emitted regularly throughout the transfer to allow progress bars
+	 * and other UI elements to be updated.
+	 */
+	signals [TRACK_PROGRESS] =
+		g_signal_new ("track-progress",
+			      G_OBJECT_CLASS_TYPE (object_class),
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (RBTrackTransferBatchClass, track_progress),
+			      NULL, NULL,
+			      rb_marshal_VOID__BOXED_STRING_INT_INT_DOUBLE,
+			      G_TYPE_NONE,
+			      5, RHYTHMDB_TYPE_ENTRY, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT, G_TYPE_DOUBLE);
+
+	/**
+	 * RBTrackTransferBatch::track-done:
+	 * @batch: the #RBTrackTransferBatch
+	 * @entry: the #RhythmDBEntry that was transferred
+	 * @dest: the destination URI for the transfer
+	 * @dest_size: size of the destination file
+	 * @dest_mediatype: the media type of the destination file
+	 * @error: any error that occurred during transfer
+	 *
+	 * Emitted when a track transfer is complete, whether because
+	 * the track was fully transferred, because an error occurred,
+	 * or because the batch was cancelled (maybe..).
+	 */
+	signals [TRACK_DONE] =
+		g_signal_new ("track-done",
+			      G_OBJECT_CLASS_TYPE (object_class),
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (RBTrackTransferBatchClass, track_done),
+			      NULL, NULL,
+			      rb_marshal_VOID__BOXED_STRING_UINT64_STRING_POINTER,
+			      G_TYPE_NONE,
+			      5, RHYTHMDB_TYPE_ENTRY, G_TYPE_STRING, G_TYPE_UINT64, G_TYPE_STRING, G_TYPE_POINTER);
+
+	g_type_class_add_private (klass, sizeof (RBTrackTransferBatchPrivate));
+}
+
diff --git a/shell/rb-track-transfer-batch.h b/shell/rb-track-transfer-batch.h
new file mode 100644
index 0000000..ac5ffab
--- /dev/null
+++ b/shell/rb-track-transfer-batch.h
@@ -0,0 +1,101 @@
+/*
+ *  Copyright (C) 2010 Jonathan Matthew  <jonathan d14n org>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The Rhythmbox authors hereby grants permission for non-GPL compatible
+ *  GStreamer plugins to be used and distributed together with GStreamer
+ *  and Rhythmbox. This permission is above and beyond the permissions granted
+ *  by the GPL license by which Rhythmbox is covered. If you modify this code
+ *  you may extend this exception to your version of the code, but you are not
+ *  obligated to do so. If you do not wish to do so, delete this exception
+ *  statement from your version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
+ *
+ */
+
+#ifndef __RB_TRACK_TRANSFER_BATCH_H
+#define __RB_TRACK_TRANSFER_BATCH_H
+
+#include <rhythmdb/rhythmdb.h>
+#include <sources/rb-source.h>
+
+G_BEGIN_DECLS
+
+#define RB_TYPE_TRACK_TRANSFER_BATCH           (rb_track_transfer_batch_get_type ())
+#define RB_TRACK_TRANSFER_BATCH(o)             (G_TYPE_CHECK_INSTANCE_CAST ((o), RB_TYPE_TRACK_TRANSFER_BATCH, RBTrackTransferBatch))
+#define RB_TRACK_TRANSFER_BATCH_CLASS(k)       (G_TYPE_CHECK_CLASS_CAST((k), RB_TYPE_TRACK_TRANSFER_BATCH, RBTrackTransferBatchClass))
+#define RB_IS_TRACK_TRANSFER_BATCH(o)          (G_TYPE_CHECK_INSTANCE_TYPE ((o), RB_TYPE_TRACK_TRANSFER_BATCH))
+#define RB_IS_TRACK_TRANSFER_BATCH_CLASS(k)    (G_TYPE_CHECK_CLASS_TYPE ((k), RB_TYPE_TRACK_TRANSFER_BATCH))
+#define RB_TRACK_TRANSFER_BATCH_GET_CLASS(o)   (G_TYPE_INSTANCE_GET_CLASS ((o), RB_TYPE_TRACK_TRANSFER_BATCH, RBTrackTransferBatchClass))
+
+typedef struct _RBTrackTransferBatch RBTrackTransferBatch;
+typedef struct _RBTrackTransferBatchClass RBTrackTransferBatchClass;
+typedef struct _RBTrackTransferBatchPrivate RBTrackTransferBatchPrivate;
+
+struct _RBTrackTransferBatch
+{
+	GObject parent;
+	RBTrackTransferBatchPrivate *priv;
+};
+
+struct _RBTrackTransferBatchClass
+{
+	GObjectClass parent_class;
+
+	/* signals */
+	void	(*started)		(RBTrackTransferBatch *batch);
+	void	(*cancelled)		(RBTrackTransferBatch *batch);
+	void	(*complete)		(RBTrackTransferBatch *batch);
+
+	char *  (*get_dest_uri)		(RBTrackTransferBatch *batch,
+					 RhythmDBEntry *entry,
+					 const char *mediatype,
+					 const char *extension);
+	gboolean (*overwrite_prompt)	(RBTrackTransferBatch *batch,
+					 GFile *dest_file);
+	void	(*track_started)	(RBTrackTransferBatch *batch,
+					 RhythmDBEntry *entry,
+					 const char *dest);
+	void	(*track_progress)	(RBTrackTransferBatch *batch,
+					 RhythmDBEntry *entry,
+					 const char *dest,
+					 int done,
+					 int total,
+					 double fraction);
+	void	(*track_done)		(RBTrackTransferBatch *batch,
+					 RhythmDBEntry *entry,
+					 const char *dest,
+					 guint64 dest_size,
+					 GError *error);
+};
+
+GType			rb_track_transfer_batch_get_type	(void);
+
+RBTrackTransferBatch *	rb_track_transfer_batch_new		(GList *media_type_list,
+								 const char * const *media_types,
+								 RBSource *source,
+								 RBSource *destination);
+void			rb_track_transfer_batch_add		(RBTrackTransferBatch *batch,
+								 RhythmDBEntry *entry);
+
+guint			rb_track_transfer_batch_check_media_types (RBTrackTransferBatch *batch);
+void			rb_track_transfer_batch_start		(RBTrackTransferBatch *batch,
+								 GObject *queue);	/* eh */
+void			rb_track_transfer_batch_cancel		(RBTrackTransferBatch *batch);
+gboolean		rb_track_transfer_batch_next		(RBTrackTransferBatch *batch);
+
+G_END_DECLS
+
+#endif /* __RB_TRACK_TRANSFER_BATCH_H */
diff --git a/shell/rb-track-transfer-queue.c b/shell/rb-track-transfer-queue.c
new file mode 100644
index 0000000..92079fb
--- /dev/null
+++ b/shell/rb-track-transfer-queue.c
@@ -0,0 +1,718 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ *  Copyright (C) 2010 Jonathan Matthew  <jonathan d14n org>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The Rhythmbox authors hereby grants permission for non-GPL compatible
+ *  GStreamer plugins to be used and distributed together with GStreamer
+ *  and Rhythmbox. This permission is above and beyond the permissions granted
+ *  by the GPL license by which Rhythmbox is covered. If you modify this code
+ *  you may extend this exception to your version of the code, but you are not
+ *  obligated to do so. If you do not wish to do so, delete this exception
+ *  statement from your version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
+ *
+ */
+
+#include "config.h"
+
+#include "rb-track-transfer-queue.h"
+#include "rb-encoder.h"
+#include "rb-marshal.h"
+#include "rb-library-source.h"
+#include "rb-debug.h"
+#include "rb-dialog.h"
+#include "rb-alert-dialog.h"
+
+#include <glib/gi18n.h>
+
+enum
+{
+	PROP_0,
+	PROP_SHELL,
+	PROP_BATCH
+};
+
+enum
+{
+	TRANSFER_PROGRESS,
+	MISSING_PLUGINS,
+	LAST_SIGNAL
+};
+
+static void rb_track_transfer_queue_class_init	(RBTrackTransferQueueClass *klass);
+static void rb_track_transfer_queue_init	(RBTrackTransferQueue *queue);
+
+static void start_next_batch (RBTrackTransferQueue *queue);
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+struct _RBTrackTransferQueuePrivate
+{
+	RBShell *shell;
+
+	GQueue *batch_queue;
+	enum {
+		OVERWRITE_PROMPT,
+		OVERWRITE_ALL,
+		OVERWRITE_NONE
+	} overwrite_decision;
+	RBTrackTransferBatch *current;
+	time_t current_start_time;
+};
+
+G_DEFINE_TYPE (RBTrackTransferQueue, rb_track_transfer_queue, G_TYPE_OBJECT)
+
+/**
+ * SECTION:rb-track-transfer-queue
+ * @short_description: track transfer queue and surrounding junk
+ *
+ */
+
+/**
+ * rb_track_transfer_queue_new:
+ * @shell: the #RBShell
+ *
+ * Creates the #RBTrackTransferQueue instance
+ *
+ * Return value: the #RBTrackTransferQueue
+ */
+RBTrackTransferQueue *
+rb_track_transfer_queue_new (RBShell *shell)
+{
+	return g_object_new (RB_TYPE_TRACK_TRANSFER_QUEUE, "shell", shell, NULL);
+}
+
+static gboolean
+overwrite_prompt (RBTrackTransferBatch *batch, GFile *file, RBTrackTransferQueue *queue)
+{
+	switch (queue->priv->overwrite_decision) {
+	case OVERWRITE_PROMPT:
+	{
+		GtkWindow *window;
+		GtkWidget *dialog;
+		GFileInfo *info;
+		gint response;
+		char *text;
+		char *free_name;
+		const char *display_name;
+
+		free_name = NULL;
+		display_name = NULL;
+		info = g_file_query_info (file,
+					  G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
+					  G_FILE_QUERY_INFO_NONE,
+					  NULL,
+					  NULL);
+		if (info != NULL) {
+			display_name = g_file_info_get_display_name (info);
+		}
+
+		if (display_name == NULL) {
+			free_name = g_file_get_uri (file);
+			display_name = free_name;
+		}
+
+		g_object_get (queue->priv->shell, "window", &window, NULL);
+		text = g_strdup_printf (_("The file \"%s\" already exists. Do you want to replace it?"),
+					display_name);
+		dialog = rb_alert_dialog_new (window,
+					      0,
+					      GTK_MESSAGE_WARNING,
+					      GTK_BUTTONS_NONE,
+					      text,
+					      NULL);
+		g_object_unref (window);
+		g_free (text);
+
+		rb_alert_dialog_set_details_label (RB_ALERT_DIALOG (dialog), NULL);
+		gtk_dialog_add_buttons (GTK_DIALOG (dialog),
+					_("_Cancel"), GTK_RESPONSE_CANCEL,
+					_("_Skip"), GTK_RESPONSE_NO,
+					_("_Replace"), GTK_RESPONSE_YES,
+					_("S_kip All"), GTK_RESPONSE_REJECT,
+					_("Replace _All"), GTK_RESPONSE_ACCEPT,
+					NULL);
+
+		gtk_widget_show (GTK_WIDGET (dialog));
+		response = gtk_dialog_run (GTK_DIALOG (dialog));
+		gtk_widget_destroy (dialog);
+		g_object_unref (dialog);
+		g_free (free_name);
+		if (info != NULL) {
+			g_object_unref (info);
+		}
+
+		switch (response) {
+		case GTK_RESPONSE_YES:
+			rb_debug ("replacing existing file");
+			return TRUE;
+
+		case GTK_RESPONSE_NO:
+			rb_debug ("skipping existing file");
+			return FALSE;
+
+		case GTK_RESPONSE_REJECT:
+			rb_debug ("skipping all existing files");
+			queue->priv->overwrite_decision = OVERWRITE_NONE;
+			return FALSE;
+
+		case GTK_RESPONSE_ACCEPT:
+			rb_debug ("replacing all existing files");
+			queue->priv->overwrite_decision = OVERWRITE_ALL;
+			return TRUE;
+
+		case GTK_RESPONSE_CANCEL:
+			rb_debug ("cancelling batch");
+			rb_track_transfer_queue_cancel_batch (queue, batch);
+			return FALSE;
+		}
+	}
+
+	case OVERWRITE_ALL:
+		rb_debug ("already decided to replace all existing files");
+		return TRUE;
+
+	case OVERWRITE_NONE:
+		rb_debug ("already decided to skip all existing files");
+		return FALSE;
+
+	default:
+		g_assert_not_reached ();
+	}
+}
+
+static void
+batch_complete (RBTrackTransferBatch *batch, RBTrackTransferQueue *queue)
+{
+	if (batch != queue->priv->current) {
+		rb_debug ("what?");
+		return;
+	}
+
+	/* batch itself will ensure we get a progress signal showing the
+	 * whole batch complete, so we don't need one here.
+	 */
+
+	queue->priv->current = NULL;
+	g_object_unref (batch);
+
+	start_next_batch (queue);
+}
+
+static int
+estimate_time_left (RBTrackTransferQueue *queue, double progress)
+{
+	time_t now;
+	time_t elapsed;
+	double total_time;
+
+	time (&now);
+	elapsed = now - queue->priv->current_start_time;
+	total_time = ((double)elapsed) / progress;
+	return ((time_t) total_time) - elapsed;
+}
+
+static void
+batch_progress (RBTrackTransferBatch *batch,
+		RhythmDBEntry *entry,
+		const char *dest,
+		int done,
+		int total,
+		double fraction,
+		RBTrackTransferQueue *queue)
+{
+	g_signal_emit (queue, signals[TRANSFER_PROGRESS], 0, done, total, fraction, estimate_time_left (queue, fraction));
+}
+
+#if 0
+static void
+missing_plugins_retry_cb (gpointer inst, gboolean retry, RBTrackTransferQueue *queue)
+{
+	rb_track_transfer_batch_start (queue->priv->current, G_OBJECT (queue));
+}
+#endif
+
+static void
+actually_start_batch (RBTrackTransferQueue *queue)
+{
+	g_signal_connect_object (queue->priv->current,
+				 "overwrite-prompt",
+				 G_CALLBACK (overwrite_prompt),
+				 queue, 0);
+	g_signal_connect_object (queue->priv->current,
+				 "complete",
+				 G_CALLBACK (batch_complete),
+				 queue, 0);
+	g_signal_connect_object (queue->priv->current,
+				 "track-progress",
+				 G_CALLBACK (batch_progress),
+				 queue, 0);
+	rb_track_transfer_batch_start (queue->priv->current, G_OBJECT (queue));
+}
+
+static void
+error_response_cb (GtkDialog *dialog, gint response, RBTrackTransferQueue *queue)
+{
+	rb_track_transfer_batch_cancel (queue->priv->current);
+	g_object_unref (queue->priv->current);
+	queue->priv->current = NULL;
+
+	start_next_batch (queue);
+	gtk_widget_destroy (GTK_WIDGET (dialog));
+}
+
+static void
+missing_encoder_response_cb (GtkDialog *dialog, gint response, RBTrackTransferQueue *queue)
+{
+	switch (response) {
+	case GTK_RESPONSE_YES:
+		/* 'continue' -> start the batch */
+		actually_start_batch (queue);
+		break;
+
+	case GTK_RESPONSE_CANCEL:
+	case GTK_RESPONSE_DELETE_EVENT:
+		/* 'cancel' -> cancel the batch and start the next one */
+		rb_track_transfer_batch_cancel (queue->priv->current);
+		g_object_unref (queue->priv->current);
+		queue->priv->current = NULL;
+
+		start_next_batch (queue);
+		break;
+
+#if 0
+	case GTK_RESPONSE_ACCEPT:
+		/* 'install an encoder' -> try to install an encoder */
+		/*
+		 * probably need RBEncoder API to get missing plugin installer details
+		 * for a specific pipeline or profile or something.
+		 * since gnome-media profiles use specific element names, installing by
+		 * caps won't necessarily install something that works.  guh.
+		 */
+
+		retry = g_cclosure_new ((GCallback) missing_plugins_retry_cb,
+					g_object_ref (queue),
+					(GClosureNotify) g_object_unref);
+		g_closure_set_marshal (retry, g_cclosure_marshal_VOID__BOOLEAN);
+		g_signal_emit (queue,
+			       signals[MISSING_PLUGINS], 0,
+			       details, descriptions, retry,
+			       &processing);
+		if (processing) {
+			rb_debug ("attempting to install missing plugins for transcoding");
+		} else {
+			rb_debug ("proceeding without the missing plugins for transcoding");
+		}
+
+		g_closure_sink (retry);
+		break;
+#endif
+
+	default:
+		g_assert_not_reached ();
+	}
+
+	gtk_widget_destroy (GTK_WIDGET (dialog));
+	g_object_unref (dialog);
+}
+
+static void
+start_next_batch (RBTrackTransferQueue *queue)
+{
+	int count;
+	int total;
+
+	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 = rb_track_transfer_batch_check_media_types (queue->priv->current);
+	rb_debug ("%d tracks in the batch, %d of which cannot be transferred", total, count);
+	if (total == 0) {
+		rb_debug ("what is this batch doing here anyway");
+	} else if (count == total) {
+		GtkWindow *window;
+		GtkWidget *dialog;
+		g_object_get (queue->priv->shell, "window", &window, NULL);
+		/* once we do encoder installation this should turn into a
+		 * normal confirmation dialog with no 'continue' option.
+		 */
+		dialog = rb_alert_dialog_new (window,
+					      0,
+					      GTK_MESSAGE_ERROR,
+					      GTK_BUTTONS_CANCEL,
+					      _("Unable to transfer tracks"),
+					      _("None of the tracks to be transferred "
+					        "are in a format supported by the target "
+						"device, and no encoders are available "
+						" for the supported formats."));
+		rb_alert_dialog_set_details_label (RB_ALERT_DIALOG (dialog), NULL);
+		g_object_unref (window);
+		g_signal_connect_object (dialog, "response", G_CALLBACK (error_response_cb), queue, 0);
+		gtk_widget_show (dialog);
+		return;
+
+	} else if (count > 0) {
+		GtkWindow *window;
+		GtkWidget *dialog;
+		char *text;
+
+
+		rb_debug ("can't find a supported media type for %d/%d files, prompting", count, total);
+		text = g_strdup_printf (_("%d of the %d files to be transferred are not in a format supported"
+					  " by the target device, and no encoders are available for the"
+					  " supported formats."),
+					count, total);
+		g_object_get (queue->priv->shell, "window", &window, NULL);
+		dialog = rb_alert_dialog_new (window,
+					      0,
+					      GTK_MESSAGE_WARNING,
+					      GTK_BUTTONS_NONE,
+					      _("Unable to transfer all tracks. Do you want to continue?"),
+					      text);
+		g_object_unref (window);
+		g_free (text);
+
+		rb_alert_dialog_set_details_label (RB_ALERT_DIALOG (dialog), NULL);
+		gtk_dialog_add_buttons (GTK_DIALOG (dialog),
+					_("_Cancel"), GTK_RESPONSE_CANCEL,
+					_("C_ontinue"), GTK_RESPONSE_YES,
+					/*_("_Install an encoder"), GTK_RESPONSE_ACCEPT,*/
+					NULL);
+
+		g_signal_connect_object (dialog, "response", G_CALLBACK (missing_encoder_response_cb), queue, 0);
+		gtk_widget_show (dialog);
+		return;
+	}
+
+	actually_start_batch (queue);
+}
+
+/**
+ * rb_track_transfer_queue_start_batch:
+ * @queue: the #RBTrackTransferQueue
+ * @batch: the #RBTrackTransferBatch to add to the queue
+ *
+ * Adds a new transfer batch to the transfer queue; if the queue is currently
+ * empty, the transfer will start immediately, but not before the call returns.
+ */
+void
+rb_track_transfer_queue_start_batch (RBTrackTransferQueue *queue,
+				     RBTrackTransferBatch *batch)
+{
+	g_queue_push_tail (queue->priv->batch_queue, g_object_ref (batch));
+	start_next_batch (queue);
+}
+
+/**
+ * rb_track_transfer_queue_cancel_batch:
+ * @queue: the #RBTrackTransferQueue
+ * @batch: the #RBTrackTransferBatch to cancel, or NULL for the current batch
+ *
+ * Removes a transfer batch from the queue.  If an entry from the
+ * batch is currently being transferred, the transfer will be
+ * aborted.
+ */
+void
+rb_track_transfer_queue_cancel_batch (RBTrackTransferQueue *queue,
+				      RBTrackTransferBatch *batch)
+{
+	gboolean found = FALSE;
+	if (batch == NULL || batch == queue->priv->current) {
+		batch = queue->priv->current;
+		queue->priv->current = NULL;
+		found = TRUE;
+	} else {
+		if (g_queue_find (queue->priv->batch_queue, batch)) {
+			g_queue_remove (queue->priv->batch_queue, batch);
+			found = TRUE;
+		}
+	}
+
+	if (found) {
+		rb_track_transfer_batch_cancel (batch);
+		g_object_unref (batch);
+
+		start_next_batch (queue);
+	}
+}
+
+/**
+ * rb_track_transfer_queue_get_status:
+ * @queue: the #RBTrackTransferQueue
+ * @text: returns the status bar text
+ * @progress_text: returns the progress bar text
+ * @progress: returns the progress fraction
+ * @time_left: returns the estimated number of seconds remaining
+ *
+ * Retrieves transfer status information.  Works similarly to
+ * #rb_source_get_status.
+ *
+ * Return value: TRUE if transfer status information is returned
+ */
+gboolean
+rb_track_transfer_queue_get_status (RBTrackTransferQueue *queue,
+				    char **text,
+				    char **progress_text,
+				    float *progress,
+				    int *time_left)
+{
+	int total;
+	int done;
+	double transfer_progress;
+
+	if (queue->priv->current == NULL) {
+		return FALSE;
+	}
+
+	g_object_get (queue->priv->current,
+		      "total-entries", &total,
+		      "done-entries", &done,
+		      "progress", &transfer_progress,
+		      NULL);
+	if (total > 0) {
+		char *s;
+
+		if (transfer_progress >= 0) {
+			s = g_strdup_printf (_("Transferring track %d out of %d (%.0f%%)"),
+					     done + 1, total, transfer_progress * 100);
+		} else {
+			s = g_strdup_printf (_("Transferring track %d out of %d"),
+					     done + 1, total);
+		}
+
+		g_free (*progress_text);
+		*progress_text = s;
+		*progress = transfer_progress;
+
+		*time_left = estimate_time_left (queue, transfer_progress);
+
+		return TRUE;
+	}
+	return FALSE;
+}
+
+struct FindBatchData
+{
+	GList *results;
+	RBSource *source;
+};
+
+static void
+find_batches (RBTrackTransferBatch *batch, struct FindBatchData *data)
+{
+	RBSource *src = NULL;
+	RBSource *dest = NULL;
+
+	g_object_get (batch, "source", &src, "destination", &dest, NULL);
+	if (src == data->source || dest == data->source) {
+		data->results = g_list_prepend (data->results, batch);
+	}
+	g_object_unref (src);
+	g_object_unref (dest);
+}
+
+/**
+ * rb_track_transfer_queue_find_batch_by_source:
+ * @queue: the #RBTrackTransferQueue
+ * @source: the #RBSource to search for
+ *
+ * Finds all transfer batches where @source is the source or destination.
+ * This should be used to wait for transfers to finish (or cancel them) before
+ * ejecting a device.  The transfer batches are returned in the order they're
+ * found in the queue, so waiting for the @RBTrackTransferBatch::complete signal
+ * on the last one is sufficient to wait for them all to finish.
+ *
+ * Return value: #GList of #RBTrackTransferBatch objects, not referenced
+ */
+GList *
+rb_track_transfer_queue_find_batch_by_source (RBTrackTransferQueue *queue, RBSource *source)
+{
+	struct FindBatchData data;
+	data.results = NULL;
+	data.source = source;
+
+	/* check the current batch */
+	find_batches (queue->priv->current, &data);
+	g_queue_foreach (queue->priv->batch_queue, (GFunc) find_batches, &data);
+	return data.results;
+}
+
+static void
+rb_track_transfer_queue_init (RBTrackTransferQueue *queue)
+{
+	queue->priv = G_TYPE_INSTANCE_GET_PRIVATE (queue,
+						   RB_TYPE_TRACK_TRANSFER_QUEUE,
+						   RBTrackTransferQueuePrivate);
+
+	queue->priv->batch_queue = g_queue_new ();
+}
+
+
+static void
+impl_set_property (GObject *object,
+		   guint prop_id,
+		   const GValue *value,
+		   GParamSpec *pspec)
+{
+	RBTrackTransferQueue *queue = RB_TRACK_TRANSFER_QUEUE (object);
+
+	switch (prop_id) {
+	case PROP_SHELL:
+		queue->priv->shell = g_value_get_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)
+{
+	RBTrackTransferQueue *queue = RB_TRACK_TRANSFER_QUEUE (object);
+
+	switch (prop_id) {
+	case PROP_SHELL:
+		g_value_set_object (value, queue->priv->shell);
+		break;
+	case PROP_BATCH:
+		g_value_set_object (value, queue->priv->current);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+impl_dispose (GObject *object)
+{
+	RBTrackTransferQueue *queue = RB_TRACK_TRANSFER_QUEUE (object);
+
+	if (queue->priv->current != NULL) {
+		rb_track_transfer_batch_cancel (queue->priv->current);
+		g_object_unref (queue->priv->current);
+		queue->priv->current = NULL;
+	}
+
+	if (queue->priv->batch_queue != NULL) {
+		g_queue_foreach (queue->priv->batch_queue, (GFunc) rb_track_transfer_batch_cancel, NULL);
+		g_queue_foreach (queue->priv->batch_queue, (GFunc) g_object_unref, NULL);
+		g_queue_free (queue->priv->batch_queue);
+	}
+
+	if (queue->priv->shell != NULL) {
+		/* we don't own a reference on the shell. */
+		queue->priv->shell = NULL;
+	}
+
+	G_OBJECT_CLASS (rb_track_transfer_queue_parent_class)->dispose (object);
+}
+
+static void
+rb_track_transfer_queue_class_init (RBTrackTransferQueueClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+	object_class->set_property = impl_set_property;
+	object_class->get_property = impl_get_property;
+	object_class->dispose = impl_dispose;
+
+	/**
+	 * RBTrackTransferQueue:shell
+	 *
+	 * The #RBShell
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_SHELL,
+					 g_param_spec_object ("shell",
+							      "shell",
+							      "the RBShell",
+							      RB_TYPE_SHELL,
+							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+	/**
+	 * RBTrackTransferQueue:batch
+	 *
+	 * The current #RBTrackTransferBatch being processed
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_BATCH,
+					 g_param_spec_object ("batch",
+							      "batch",
+							      "current RBTrackTransferBatch",
+							      RB_TYPE_TRACK_TRANSFER_BATCH,
+							      G_PARAM_READABLE));
+	/**
+	 * RBTrackTransferQueue::transfer-progress:
+	 * @queue: the #RBTrackTransferQueue
+	 * @done: the number of entries transferred
+	 * @total: the total number of entries in the batch
+	 * @fraction: the fraction of the batch that has been transferred
+	 * @time_left: the estimated remaining time (in seconds)
+	 *
+	 * Emitted regularly to convey progress information.  At the end of any given
+	 * transfer batch, there will be one signal emission with @done == @total and
+	 * @fraction == 1.0.
+	 */
+	signals[TRANSFER_PROGRESS] =
+		g_signal_new ("transfer-progress",
+			      RB_TYPE_TRACK_TRANSFER_QUEUE,
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (RBTrackTransferQueueClass, transfer_progress),
+			      NULL, NULL,
+			      rb_marshal_VOID__INT_INT_DOUBLE_INT,
+			      G_TYPE_NONE,
+			      4, G_TYPE_INT, G_TYPE_INT, G_TYPE_DOUBLE, G_TYPE_INT);
+	/**
+	 * RBTrackTransferQueue::missing-plugins:
+	 * @queue: the #RBTrackTransferQueue
+	 * @details: the list of plugin detail strings describing the missing plugins
+	 * @descriptions: the list of descriptions for the missing plugins
+	 * @closure: a #GClosure to be called when the plugin installation is complete
+	 *
+	 * Emitted to request installation of one or more encoder plugins for a
+	 * destination media format.  When the closure included in the signal args
+	 * is called, the transfer batch will be started.
+	 */
+	signals[MISSING_PLUGINS] =
+		g_signal_new ("missing-plugins",
+			      G_OBJECT_CLASS_TYPE (object_class),
+			      G_SIGNAL_RUN_LAST,
+			      0,
+			      NULL, NULL,
+			      rb_marshal_BOOLEAN__POINTER_POINTER_POINTER,
+			      G_TYPE_BOOLEAN,
+			      3,
+			      G_TYPE_STRV, G_TYPE_STRV, G_TYPE_CLOSURE);
+
+	g_type_class_add_private (klass, sizeof (RBTrackTransferQueuePrivate));
+}
diff --git a/shell/rb-track-transfer-queue.h b/shell/rb-track-transfer-queue.h
new file mode 100644
index 0000000..dc7cf9a
--- /dev/null
+++ b/shell/rb-track-transfer-queue.h
@@ -0,0 +1,83 @@
+/*
+ *  Copyright (C) 2010 Jonathan Matthew  <jonathan d14n org>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The Rhythmbox authors hereby grants permission for non-GPL compatible
+ *  GStreamer plugins to be used and distributed together with GStreamer
+ *  and Rhythmbox. This permission is above and beyond the permissions granted
+ *  by the GPL license by which Rhythmbox is covered. If you modify this code
+ *  you may extend this exception to your version of the code, but you are not
+ *  obligated to do so. If you do not wish to do so, delete this exception
+ *  statement from your version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
+ *
+ */
+
+#ifndef __RB_TRACK_TRANSFER_QUEUE_H
+#define __RB_TRACK_TRANSFER_QUEUE_H
+
+#include <rhythmdb/rhythmdb.h>
+#include <shell/rb-shell.h>
+#include <shell/rb-track-transfer-batch.h>
+
+G_BEGIN_DECLS
+
+#define RB_TYPE_TRACK_TRANSFER_QUEUE         (rb_track_transfer_queue_get_type ())
+#define RB_TRACK_TRANSFER_QUEUE(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), RB_TYPE_TRACK_TRANSFER_QUEUE, RBTrackTransferQueue))
+#define RB_TRACK_TRANSFER_QUEUE_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), RB_TYPE_TRACK_TRANSFER_QUEUE, RBTrackTransferQueueClass))
+#define RB_IS_TRACK_TRANSFER_QUEUE(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), RB_TYPE_TRACK_TRANSFER_QUEUE))
+#define RB_IS_TRACK_TRANSFER_QUEUE_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), RB_TYPE_TRACK_TRANSFER_QUEUE))
+#define RB_TRACK_TRANSFER_QUEUE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), RB_TYPE_TRACK_TRANSFER_QUEUE, RBTrackTransferQueueClass))
+
+typedef struct _RBTrackTransferQueue RBTrackTransferQueue;
+typedef struct _RBTrackTransferQueueClass RBTrackTransferQueueClass;
+typedef struct _RBTrackTransferQueuePrivate RBTrackTransferQueuePrivate;
+
+struct _RBTrackTransferQueue
+{
+	GObject parent;
+	RBTrackTransferQueuePrivate *priv;
+};
+
+struct _RBTrackTransferQueueClass
+{
+	GObjectClass parent_class;
+
+	/* signals */
+	void	(*transfer_progress)	(RBTrackTransferQueue *queue,
+					 int done,
+					 int total,
+					 double fraction,
+					 int time_left);
+};
+
+RBTrackTransferQueue*   rb_track_transfer_queue_new		(RBShell *shell);
+GType			rb_track_transfer_queue_get_type	(void);
+
+void			rb_track_transfer_queue_start_batch	(RBTrackTransferQueue *queue,
+								 RBTrackTransferBatch *batch);
+void			rb_track_transfer_queue_cancel_batch	(RBTrackTransferQueue *queue,
+								 RBTrackTransferBatch *batch);
+gboolean		rb_track_transfer_queue_get_status	(RBTrackTransferQueue *queue,
+								 char **text,
+								 char **progress_text,
+								 float *progress,
+								 int *time_left);
+GList *			rb_track_transfer_queue_find_batch_by_source (RBTrackTransferQueue *queue,
+								 RBSource *source);
+
+G_END_DECLS
+
+#endif /* __RB_TRACK_TRANSFER_QUEUE_H */



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