[rhythmbox] split RBRemovableMediaSource up into device source and transfer target



commit d8782223dff07c27b7aa16b435fc738dc42c42af
Author: Jonathan Matthew <jonathan d14n org>
Date:   Sun Oct 30 11:08:58 2011 +1000

    split RBRemovableMediaSource up into device source and transfer target
    
    RBRemovableMediaSource tied together too many unrelated aspects -
    the source UI structure, the ability to eject the device, and the
    ability to transfer things to the device.  In some cases (particularly
    audio CDs) we only want some of those things.
    
    RBDeviceSource is an interface for things that can be ejected.  The
    default implementation operates on a GMount or a GVolume, but the
    MTP plugin does its own thing.
    
    RBTransferTarget is an interface for things that can accept transfers.
    Currently only implemented by the generic player, iPod and MTP plugins,
    but the library might also use it.  The implementation needs to provide
    an encoding target and a method for building destination URIs.
    
    The remaining parts of RBRemovableMediaSource now live in
    RBMediaPlayerSource.

 bindings/gi/Makefile.am                            |    6 +-
 doc/reference/rhythmbox.types                      |    6 +-
 plugins/audiocd/album-info.ui                      |  178 ++--
 plugins/audiocd/rb-audiocd-source.c                |  377 ++++----
 plugins/audiocd/rb-audiocd-source.h                |   10 +-
 .../rb-dbus-media-server-plugin.c                  |    4 +-
 plugins/generic-player/rb-generic-player-plugin.c  |    6 +-
 plugins/generic-player/rb-generic-player-source.c  |   95 ++-
 plugins/generic-player/rb-generic-player-source.h  |    2 +-
 plugins/generic-player/rb-nokia770-source.c        |    4 +-
 plugins/generic-player/rb-nokia770-source.h        |    2 +-
 plugins/generic-player/rb-psp-source.c             |    4 +-
 plugins/generic-player/rb-psp-source.h             |    2 +-
 plugins/ipod/rb-ipod-source.c                      |   82 ++-
 plugins/mtpdevice/rb-mtp-source.c                  |   81 +-
 plugins/mtpdevice/rb-mtp-source.h                  |    1 -
 shell/rb-removable-media-manager.c                 |   29 +-
 sources/Makefile.am                                |    8 +-
 sources/rb-device-source.c                         |  418 ++++++++
 sources/rb-device-source.h                         |   63 ++
 sources/rb-media-player-source.c                   |  172 ++++-
 sources/rb-media-player-source.h                   |    6 +-
 sources/rb-removable-media-source.c                | 1029 --------------------
 sources/rb-removable-media-source.h                |  104 --
 sources/rb-transfer-target.c                       |  457 +++++++++
 sources/rb-transfer-target.h                       |   94 ++
 widgets/rb-entry-view.c                            |    2 +
 widgets/rb-property-view.c                         |    2 +
 28 files changed, 1744 insertions(+), 1500 deletions(-)
---
diff --git a/bindings/gi/Makefile.am b/bindings/gi/Makefile.am
index 47fd1a2..8b74063 100644
--- a/bindings/gi/Makefile.am
+++ b/bindings/gi/Makefile.am
@@ -86,8 +86,6 @@ rb_introspection_sources = \
 		sources/rb-source-search.c \
 		sources/rb-browser-source.h \
 		sources/rb-browser-source.c \
-		sources/rb-removable-media-source.h \
-		sources/rb-removable-media-source.c \
 		sources/rb-media-player-source.h \
 		sources/rb-media-player-source.c \
 		sources/rb-playlist-source.h \
@@ -99,6 +97,10 @@ rb_introspection_sources = \
 		sources/rb-static-playlist-source.c \
 		sources/rb-source-search-basic.h \
 		sources/rb-source-search-basic.c \
+		sources/rb-device-source.h \
+		sources/rb-device-source.c \
+		sources/rb-transfer-target.h \
+		sources/rb-transfer-target.c \
 		widgets/rb-entry-view.h \
 		widgets/rb-entry-view.c \
 		widgets/rb-property-view.h \
diff --git a/doc/reference/rhythmbox.types b/doc/reference/rhythmbox.types
index a2771a8..cb77c7c 100644
--- a/doc/reference/rhythmbox.types
+++ b/doc/reference/rhythmbox.types
@@ -7,6 +7,7 @@
 #include "rb-cell-renderer-rating.h"
 #include "rb-cut-and-paste-code.h"
 #include "rb-debug.h"
+#include "rb-device-source.h"
 #include "rb-dialog.h"
 #include "rb-display-page-group.h"
 #include "rb-display-page-model.h"
@@ -40,7 +41,6 @@
 #include "rb-rating-helper.h"
 #include "rb-refstring.h"
 #include "rb-removable-media-manager.h"
-#include "rb-removable-media-source.h"
 #include "rb-search-entry.h"
 #include "rb-shell-clipboard.h"
 #include "rb-shell.h"
@@ -57,6 +57,7 @@
 #include "rb-streaming-source.h"
 #include "rb-track-transfer-queue.h"
 #include "rb-track-transfer-batch.h"
+#include "rb-transfer-target.h"
 #include "rb-tree-dnd.h"
 #include "rb-uri-dialog.h"
 #include "rb-util.h"
@@ -73,6 +74,7 @@ rb_auto_playlist_source_get_type
 rb_browser_source_get_type
 rb_cell_renderer_pixbuf_get_type
 rb_cell_renderer_rating_get_type
+rb_device_source_get_type
 rb_display_page_get_type
 rb_display_page_group_get_type
 rb_display_page_model_get_type
@@ -104,7 +106,6 @@ rb_random_play_order_get_type
 rb_rating_get_type
 rb_refstring_get_type
 rb_removable_media_manager_get_type
-rb_removable_media_source_get_type
 rb_search_entry_get_type
 rb_shell_clipboard_get_type
 rb_shell_get_type
@@ -124,6 +125,7 @@ rb_streaming_source_get_type
 rb_string_value_map_get_type
 rb_track_transfer_batch_get_type
 rb_track_transfer_queue_get_type
+rb_transfer_target_get_type
 rb_tree_drag_dest_get_type
 rb_tree_drag_source_get_type
 rb_uri_dialog_get_type
diff --git a/plugins/audiocd/album-info.ui b/plugins/audiocd/album-info.ui
index 0f2bcd6..eeaf804 100644
--- a/plugins/audiocd/album-info.ui
+++ b/plugins/audiocd/album-info.ui
@@ -1,17 +1,16 @@
-<?xml version="1.0"?>
+<?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <!-- interface-requires gtk+ 2.12 -->
-  <!-- interface-naming-policy toplevel-contextual -->
-  <object class="GtkTable" id="album_info">
+  <object class="GtkGrid" id="album_info">
     <property name="visible">True</property>
-    <property name="border_width">6</property>
-    <property name="n_rows">5</property>
-    <property name="n_columns">2</property>
-    <property name="column_spacing">12</property>
+    <property name="can_focus">False</property>
+    <property name="margin_right">6</property>
     <property name="row_spacing">6</property>
+    <property name="column_spacing">6</property>
     <child>
       <object class="GtkLabel" id="album_label">
         <property name="visible">True</property>
+        <property name="can_focus">False</property>
         <property name="xalign">0</property>
         <property name="label" translatable="yes">A_lbum:</property>
         <property name="use_markup">True</property>
@@ -19,15 +18,16 @@
         <property name="mnemonic_widget">album_entry</property>
       </object>
       <packing>
+        <property name="left_attach">0</property>
         <property name="top_attach">1</property>
-        <property name="bottom_attach">2</property>
-        <property name="x_options">GTK_SHRINK | GTK_FILL</property>
-        <property name="y_options"></property>
+        <property name="width">1</property>
+        <property name="height">1</property>
       </packing>
     </child>
     <child>
       <object class="GtkLabel" id="artist_label">
         <property name="visible">True</property>
+        <property name="can_focus">False</property>
         <property name="xalign">0</property>
         <property name="label" translatable="yes">_Artist:</property>
         <property name="use_markup">True</property>
@@ -35,43 +35,115 @@
         <property name="mnemonic_widget">artist_entry</property>
       </object>
       <packing>
+        <property name="left_attach">0</property>
         <property name="top_attach">2</property>
-        <property name="bottom_attach">3</property>
-        <property name="x_options">GTK_SHRINK | GTK_FILL</property>
-        <property name="y_options"></property>
+        <property name="width">1</property>
+        <property name="height">1</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkLabel" id="artist_sort_label">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="xalign">0</property>
+        <property name="label" translatable="yes">Artist s_ort order:</property>
+        <property name="use_markup">True</property>
+        <property name="use_underline">True</property>
+        <property name="mnemonic_widget">artist_sort_entry</property>
+      </object>
+      <packing>
+        <property name="left_attach">0</property>
+        <property name="top_attach">3</property>
+        <property name="width">1</property>
+        <property name="height">1</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkLabel" id="genre_label">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="xalign">0</property>
+        <property name="label" translatable="yes">_Genre:</property>
+        <property name="use_markup">True</property>
+        <property name="use_underline">True</property>
+        <property name="mnemonic_widget">genre_entry</property>
+      </object>
+      <packing>
+        <property name="left_attach">0</property>
+        <property name="top_attach">4</property>
+        <property name="width">1</property>
+        <property name="height">1</property>
       </packing>
     </child>
     <child>
       <object class="GtkEntry" id="album_entry">
         <property name="visible">True</property>
         <property name="can_focus">True</property>
-        <property name="invisible_char">&#x25CF;</property>
+        <property name="hexpand">True</property>
+        <property name="invisible_char">â</property>
+        <property name="invisible_char_set">True</property>
       </object>
       <packing>
         <property name="left_attach">1</property>
-        <property name="right_attach">2</property>
         <property name="top_attach">1</property>
-        <property name="bottom_attach">2</property>
+        <property name="width">1</property>
+        <property name="height">1</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkEntry" id="artist_entry">
+        <property name="visible">True</property>
+        <property name="can_focus">True</property>
+        <property name="hexpand">True</property>
+        <property name="invisible_char">â</property>
+        <property name="invisible_char_set">True</property>
+      </object>
+      <packing>
+        <property name="left_attach">1</property>
+        <property name="top_attach">2</property>
+        <property name="width">1</property>
+        <property name="height">1</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkEntry" id="artist_sort_entry">
+        <property name="visible">True</property>
+        <property name="can_focus">True</property>
+        <property name="hexpand">True</property>
+        <property name="invisible_char">â</property>
+        <property name="invisible_char_set">True</property>
+      </object>
+      <packing>
+        <property name="left_attach">1</property>
+        <property name="top_attach">3</property>
+        <property name="width">1</property>
+        <property name="height">1</property>
       </packing>
     </child>
     <child>
       <object class="GtkHBox" id="hbox1">
         <property name="visible">True</property>
+        <property name="can_focus">False</property>
         <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+        <property name="hexpand">True</property>
         <property name="spacing">12</property>
         <child>
           <object class="GtkEntry" id="genre_entry">
             <property name="visible">True</property>
             <property name="can_focus">True</property>
-            <property name="invisible_char">&#x25CF;</property>
+            <property name="invisible_char">â</property>
+            <property name="invisible_char_set">True</property>
           </object>
           <packing>
+            <property name="expand">True</property>
+            <property name="fill">True</property>
             <property name="position">0</property>
           </packing>
         </child>
         <child>
           <object class="GtkLabel" id="year_label">
             <property name="visible">True</property>
+            <property name="can_focus">False</property>
             <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
             <property name="xalign">0</property>
             <property name="label" translatable="yes">_Year:</property>
@@ -84,6 +156,7 @@
           </object>
           <packing>
             <property name="expand">False</property>
+            <property name="fill">True</property>
             <property name="position">1</property>
           </packing>
         </child>
@@ -92,17 +165,20 @@
             <property name="visible">True</property>
             <property name="can_focus">True</property>
             <property name="max_length">4</property>
-            <property name="invisible_char">&#x25CF;</property>
+            <property name="invisible_char">â</property>
             <property name="width_chars">4</property>
+            <property name="invisible_char_set">True</property>
           </object>
           <packing>
             <property name="expand">False</property>
+            <property name="fill">True</property>
             <property name="position">2</property>
           </packing>
         </child>
         <child>
           <object class="GtkLabel" id="disc_number_label">
             <property name="visible">True</property>
+            <property name="can_focus">False</property>
             <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
             <property name="xalign">0</property>
             <property name="label" translatable="yes">_Disc:</property>
@@ -115,6 +191,7 @@
           </object>
           <packing>
             <property name="expand">False</property>
+            <property name="fill">True</property>
             <property name="position">3</property>
           </packing>
         </child>
@@ -122,79 +199,38 @@
           <object class="GtkEntry" id="disc_number_entry">
             <property name="visible">True</property>
             <property name="can_focus">True</property>
-            <property name="invisible_char">&#x25CF;</property>
+            <property name="invisible_char">â</property>
             <property name="width_chars">2</property>
+            <property name="invisible_char_set">True</property>
           </object>
           <packing>
             <property name="expand">False</property>
+            <property name="fill">True</property>
             <property name="position">4</property>
           </packing>
         </child>
       </object>
       <packing>
         <property name="left_attach">1</property>
-        <property name="right_attach">2</property>
         <property name="top_attach">4</property>
-        <property name="bottom_attach">5</property>
-        <property name="x_options">GTK_FILL</property>
-        <property name="y_options">GTK_SHRINK | GTK_FILL</property>
+        <property name="width">1</property>
+        <property name="height">1</property>
       </packing>
     </child>
     <child>
-      <object class="GtkLabel" id="genre_label">
-        <property name="visible">True</property>
-        <property name="xalign">0</property>
-        <property name="label" translatable="yes">_Genre:</property>
-        <property name="use_markup">True</property>
-        <property name="use_underline">True</property>
-        <property name="mnemonic_widget">genre_entry</property>
-      </object>
-      <packing>
-        <property name="top_attach">4</property>
-        <property name="bottom_attach">5</property>
-        <property name="x_options">GTK_SHRINK | GTK_FILL</property>
-      </packing>
+      <placeholder/>
     </child>
     <child>
-      <object class="GtkLabel" id="artist_sort_label">
-        <property name="visible">True</property>
-        <property name="xalign">0</property>
-        <property name="label" translatable="yes">Artist s_ort order:</property>
-        <property name="use_markup">True</property>
-        <property name="use_underline">True</property>
-        <property name="mnemonic_widget">artist_sort_entry</property>
-      </object>
-      <packing>
-        <property name="top_attach">3</property>
-        <property name="bottom_attach">4</property>
-        <property name="x_options">GTK_SHRINK | GTK_FILL</property>
-      </packing>
+      <placeholder/>
     </child>
     <child>
-      <object class="GtkEntry" id="artist_sort_entry">
-        <property name="visible">True</property>
-        <property name="can_focus">True</property>
-        <property name="invisible_char">&#x25CF;</property>
-      </object>
-      <packing>
-        <property name="left_attach">1</property>
-        <property name="right_attach">2</property>
-        <property name="top_attach">3</property>
-        <property name="bottom_attach">4</property>
-      </packing>
+      <placeholder/>
     </child>
     <child>
-      <object class="GtkEntry" id="artist_entry">
-        <property name="visible">True</property>
-        <property name="can_focus">True</property>
-        <property name="invisible_char">&#x25CF;</property>
-      </object>
-      <packing>
-        <property name="left_attach">1</property>
-        <property name="right_attach">2</property>
-        <property name="top_attach">2</property>
-        <property name="bottom_attach">3</property>
-      </packing>
+      <placeholder/>
+    </child>
+    <child>
+      <placeholder/>
     </child>
   </object>
 </interface>
diff --git a/plugins/audiocd/rb-audiocd-source.c b/plugins/audiocd/rb-audiocd-source.c
index 54bb763..7a0619c 100644
--- a/plugins/audiocd/rb-audiocd-source.c
+++ b/plugins/audiocd/rb-audiocd-source.c
@@ -42,11 +42,13 @@
 #include "rhythmdb.h"
 #include "rb-shell.h"
 #include "rb-audiocd-source.h"
+#include "rb-device-source.h"
 #include "rb-util.h"
 #include "rb-debug.h"
 #include "rb-dialog.h"
 #include "rb-builder-helpers.h"
 #include "rb-file-helpers.h"
+#include "rb-shell-player.h"
 
 #ifdef HAVE_SJ_METADATA_GETTER
 #include "sj-metadata-getter.h"
@@ -56,12 +58,14 @@
 enum
 {
 	PROP_0,
-	PROP_SEARCH_TYPE
+	PROP_SEARCH_TYPE,
+	PROP_VOLUME,
 };
 
 static void rb_audiocd_source_dispose (GObject *object);
 static void rb_audiocd_source_finalize (GObject *object);
 static void rb_audiocd_source_constructed (GObject *object);
+static void rb_audiocd_device_source_init (RBDeviceSourceInterface *interface);
 static void impl_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
 static void impl_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
 
@@ -73,8 +77,6 @@ static GList* impl_get_ui_actions (RBDisplayPage *page);
 static guint impl_want_uri (RBSource *source, const char *uri);
 static gboolean impl_uri_is_source (RBSource *source, const char *uri);
 
-static void impl_pack_paned (RBBrowserSource *source, GtkWidget *paned);
-
 static gpointer rb_audiocd_load_songs (RBAudioCdSource *source);
 static void rb_audiocd_load_metadata (RBAudioCdSource *source, RhythmDB *db);
 static void rb_audiocd_load_metadata_cancel (RBAudioCdSource *source);
@@ -110,8 +112,10 @@ typedef struct
 	gboolean extract;
 } RBAudioCDEntryData;
 
-typedef struct
+struct _RBAudioCdSourcePrivate
 {
+	GVolume *volume;
+
 	gchar *device_path;
 	GList *tracks;
 
@@ -119,7 +123,6 @@ typedef struct
 	GstElement *cdda;
 	GstElement *fakesink;
 
-	GtkWidget *box;
 	GtkWidget *artist_entry;
 	GtkWidget *artist_sort_entry;
 	GtkWidget *album_entry;
@@ -137,10 +140,16 @@ typedef struct
 #endif
 
 	GtkActionGroup *action_group;
-} RBAudioCdSourcePrivate;
+};
+
+G_DEFINE_DYNAMIC_TYPE_EXTENDED (
+	RBAudioCdSource,
+	rb_audiocd_source,
+	RB_TYPE_SOURCE,
+	0,
+	G_IMPLEMENT_INTERFACE_DYNAMIC (RB_TYPE_DEVICE_SOURCE,
+				       rb_audiocd_device_source_init))
 
-G_DEFINE_DYNAMIC_TYPE (RBAudioCdSource, rb_audiocd_source, RB_TYPE_REMOVABLE_MEDIA_SOURCE)
-#define AUDIOCD_SOURCE_GET_PRIVATE(o)   (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_AUDIOCD_SOURCE, RBAudioCdSourcePrivate))
 
 /* entry type */
 typedef struct _RhythmDBEntryType RBAudioCdEntryType;
@@ -197,12 +206,17 @@ get_db_for_source (RBAudioCdSource *source)
 }
 
 static void
+rb_audiocd_device_source_init (RBDeviceSourceInterface *interface)
+{
+	/* nothing, the default implementations are fine */
+}
+
+static void
 rb_audiocd_source_class_init (RBAudioCdSourceClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 	RBDisplayPageClass *page_class = RB_DISPLAY_PAGE_CLASS (klass);
 	RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
-	RBBrowserSourceClass *browser_source_class = RB_BROWSER_SOURCE_CLASS (klass);
 
 	object_class->constructed = rb_audiocd_source_constructed;
 	object_class->dispose = rb_audiocd_source_dispose;
@@ -224,13 +238,16 @@ rb_audiocd_source_class_init (RBAudioCdSourceClass *klass)
 	source_class->impl_try_playlist = (RBSourceFeatureFunc) rb_true_function;	/* shouldn't need this. */
 	source_class->impl_want_uri = impl_want_uri;
 
-	/* add the browser below the album info section */
-	browser_source_class->impl_pack_paned = impl_pack_paned;
-
 	g_object_class_override_property (object_class,
 					  PROP_SEARCH_TYPE,
 					  "search-type");
-
+	g_object_class_install_property (object_class,
+					 PROP_VOLUME,
+					 g_param_spec_object ("volume",
+							      "volume",
+							      "volume",
+							      G_TYPE_VOLUME,
+							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 	g_type_class_add_private (klass, sizeof (RBAudioCdSourcePrivate));
 }
 
@@ -240,25 +257,26 @@ rb_audiocd_source_class_finalize (RBAudioCdSourceClass *klass)
 }
 
 static void
-rb_audiocd_source_init (RBAudioCdSource *self)
+rb_audiocd_source_init (RBAudioCdSource *source)
 {
+	source->priv = G_TYPE_INSTANCE_GET_PRIVATE (source,
+						    RB_TYPE_AUDIOCD_SOURCE,
+						    RBAudioCdSourcePrivate);
 }
 
 static void
 rb_audiocd_source_finalize (GObject *object)
 {
-	RBAudioCdSourcePrivate *priv = AUDIOCD_SOURCE_GET_PRIVATE (object);
-
-	g_free (priv->device_path);
+	RBAudioCdSource *source = RB_AUDIOCD_SOURCE (object);
 
+	g_free (source->priv->device_path);
 #ifdef HAVE_SJ_METADATA_GETTER
-	g_free (priv->submit_url);
-	priv->submit_url = NULL;
+	g_free (source->priv->submit_url);
 #endif
 
-	if (priv->tracks) {
-		g_list_free (priv->tracks);
-		priv->tracks = NULL;
+	if (source->priv->tracks) {
+		g_list_free (source->priv->tracks);
+		source->priv->tracks = NULL;
 	}
 
 	G_OBJECT_CLASS (rb_audiocd_source_parent_class)->finalize (object);
@@ -267,16 +285,16 @@ rb_audiocd_source_finalize (GObject *object)
 static void
 rb_audiocd_source_dispose (GObject *object)
 {
-	RBAudioCdSourcePrivate *priv = AUDIOCD_SOURCE_GET_PRIVATE (object);
+	RBAudioCdSource *source = RB_AUDIOCD_SOURCE (object);
 
-	if (priv->action_group != NULL) {
-		g_object_unref (priv->action_group);
-		priv->action_group = NULL;
+	if (source->priv->action_group != NULL) {
+		g_object_unref (source->priv->action_group);
+		source->priv->action_group = NULL;
 	}
 
-	if (priv->pipeline) {
-		gst_object_unref (GST_OBJECT (priv->pipeline));
-		priv->pipeline = NULL;
+	if (source->priv->pipeline) {
+		gst_object_unref (GST_OBJECT (source->priv->pipeline));
+		source->priv->pipeline = NULL;
 	}
 
 	G_OBJECT_CLASS (rb_audiocd_source_parent_class)->dispose (object);
@@ -303,51 +321,99 @@ force_no_spacing (GtkWidget *widget)
 }
 
 static void
+sort_order_changed_cb (GObject *object, GParamSpec *pspec, RBAudioCdSource *source)
+{
+	rb_debug ("sort order changed");
+	rb_entry_view_resort_model (RB_ENTRY_VIEW (object));
+}
+
+static void
 rb_audiocd_source_constructed (GObject *object)
 {
-	RBAudioCdSourcePrivate *priv;
 	RBAudioCdSource *source;
 	RBEntryView *entry_view;
 	GtkCellRenderer *renderer;
 	GtkTreeViewColumn *extract;
+	GtkBuilder *builder;
+	GtkWidget *grid;
 	GtkWidget *widget;
+	GtkWidget *infogrid;
 	GtkAction *action;
 	GObject *plugin;
 	RBShell *shell;
+	RBShellPlayer *shell_player;
+	RhythmDB *db;
+	RhythmDBQueryModel *query_model;
+	RhythmDBQuery *query;
+	RhythmDBEntryType *entry_type;
 	char *ui_file;
 	int toggle_width;
+#if defined(HAVE_SJ_METADATA_GETTER)
+	GtkWidget *box;
+	char *message;
+#endif
 
 	RB_CHAIN_GOBJECT_METHOD (rb_audiocd_source_parent_class, constructed, object);
 	source = RB_AUDIOCD_SOURCE (object);
-	priv = AUDIOCD_SOURCE_GET_PRIVATE (source);
 
-	g_object_set (G_OBJECT (source), "name", "Unknown Audio", NULL);
+	rb_device_source_set_display_details (RB_DEVICE_SOURCE (source));
 
 	g_object_get (source, "shell", &shell, NULL);
-	priv->action_group = _rb_display_page_register_action_group (RB_DISPLAY_PAGE (source),
-								     "AudioCdActions",
-								     NULL, 0, NULL);
-	_rb_action_group_add_display_page_actions (priv->action_group,
+	g_object_get (shell,
+		      "db", &db,
+		      "shell-player", &shell_player,
+		      NULL);
+
+	source->priv->action_group =
+		_rb_display_page_register_action_group (RB_DISPLAY_PAGE (source),
+							"AudioCdActions",
+							NULL, 0, NULL);
+	_rb_action_group_add_display_page_actions (source->priv->action_group,
 						   G_OBJECT (shell),
 						   rb_audiocd_source_actions,
 						   G_N_ELEMENTS (rb_audiocd_source_actions));
 	g_object_unref (shell);
 
-	action = gtk_action_group_get_action (priv->action_group,
+	action = gtk_action_group_get_action (source->priv->action_group,
 					      "AudioCdCopyTracks");
 	/* Translators: this is the toolbar button label
 	   for Copy to Library action. */
 	g_object_set (action, "short-label", _("Extract"), NULL);
 
 #if !defined(HAVE_SJ_METADATA_GETTER)
-	action = gtk_action_group_get_action (priv->action_group, "AudioCdSourceReloadMetadata");
+	action = gtk_action_group_get_action (source->priv->action_group, "AudioCdSourceReloadMetadata");
 	g_object_set (action, "visible", FALSE, NULL);
 #endif
 
+	g_object_get (source, "entry-type", &entry_type, NULL);
+	query = rhythmdb_query_parse (db,
+				      RHYTHMDB_QUERY_PROP_EQUALS,
+				      RHYTHMDB_PROP_TYPE,
+				      entry_type,
+				      RHYTHMDB_QUERY_END);
+	g_object_unref (entry_type);
+
+	query_model = rhythmdb_query_model_new (db,
+						query,
+						(GCompareDataFunc) rhythmdb_query_model_track_sort_func,
+						NULL, NULL, FALSE);
+	rhythmdb_do_full_query_parsed (db, RHYTHMDB_QUERY_RESULTS (query_model), query);
+	g_object_set (source, "query-model", query_model, NULL);
+	rhythmdb_query_free (query);
 
 	/* we want audio cds to sort by track# by default */
-	entry_view = rb_source_get_entry_view (RB_SOURCE (source));
+	entry_view = rb_entry_view_new (db, G_OBJECT (shell_player), TRUE, FALSE);
+	g_signal_connect_object (entry_view,
+				 "notify::sort-order",
+				 G_CALLBACK (sort_order_changed_cb),
+				 source, 0);
 	rb_entry_view_set_sorting_order (entry_view, "Track", GTK_SORT_ASCENDING);
+	rb_entry_view_set_model (entry_view, query_model);
+
+	rb_entry_view_append_column (entry_view, RB_ENTRY_VIEW_COL_TRACK_NUMBER, TRUE);
+	rb_entry_view_append_column (entry_view, RB_ENTRY_VIEW_COL_TITLE, TRUE);
+	rb_entry_view_append_column (entry_view, RB_ENTRY_VIEW_COL_ARTIST, TRUE);
+	rb_entry_view_append_column (entry_view, RB_ENTRY_VIEW_COL_GENRE, FALSE);
 
 	/* enable in-place editing for titles, artists, and genres */
 	rb_entry_view_set_column_editable (entry_view, RB_ENTRY_VIEW_COL_TITLE, TRUE);
@@ -382,75 +448,65 @@ rb_audiocd_source_constructed (GObject *object)
 	gtk_widget_set_tooltip_text (gtk_tree_view_column_get_widget (extract),
 	                             _("Select tracks to be extracted"));
 
-	/* hide the 'album' column */
-	gtk_tree_view_column_set_visible (rb_entry_view_get_column (entry_view, RB_ENTRY_VIEW_COL_ALBUM), FALSE);
-
 	/* set up the album info widgets */
 	g_object_get (source, "plugin", &plugin, NULL);
 	ui_file = rb_find_plugin_data_file (G_OBJECT (plugin), "album-info.ui");
+	g_assert (ui_file != NULL);
 	g_object_unref (plugin);
 
-	if (ui_file == NULL) {
-		g_warning ("couldn't find album-info.ui");
-	} else {
-		RBAudioCdSourcePrivate *priv;
-		GtkWidget *table;
-		GtkBuilder *builder;
-#if defined(HAVE_SJ_METADATA_GETTER)
-		GtkWidget *box;
-		char *message;
-#endif
-
-		priv = AUDIOCD_SOURCE_GET_PRIVATE (source);
-
-		builder = rb_builder_load (ui_file, NULL);
-		g_free (ui_file);
+	builder = rb_builder_load (ui_file, NULL);
+	g_free (ui_file);
 
-		table = GTK_WIDGET (gtk_builder_get_object (builder, "album_info"));
-		g_assert (table != NULL);
+	infogrid = GTK_WIDGET (gtk_builder_get_object (builder, "album_info"));
+	g_assert (infogrid != NULL);
 
 #if defined(HAVE_SJ_METADATA_GETTER)
-		/* Info bar for non-Musicbrainz data */
-		priv->info_bar = gtk_info_bar_new_with_buttons (_("S_ubmit Album"), GTK_RESPONSE_OK,
+	/* Info bar for non-Musicbrainz data */
+	source->priv->info_bar = gtk_info_bar_new_with_buttons (_("S_ubmit Album"), GTK_RESPONSE_OK,
 								_("Hide"), GTK_RESPONSE_CANCEL,
 								NULL);
-		message = g_strdup_printf ("<b>%s</b>\n%s", _("Could not find this album on MusicBrainz."),
-					   _("You can improve the MusicBrainz database by adding this album."));
-		priv->info_bar_label = gtk_label_new (NULL);
-		gtk_label_set_markup (GTK_LABEL (priv->info_bar_label), message);
-		gtk_label_set_justify (GTK_LABEL (priv->info_bar_label), GTK_JUSTIFY_LEFT);
-		g_free (message);
-		box = gtk_info_bar_get_content_area (GTK_INFO_BAR (priv->info_bar));
-		gtk_container_add (GTK_CONTAINER (box), priv->info_bar_label);
-		gtk_widget_show_all (box);
-		gtk_widget_set_no_show_all (priv->info_bar, TRUE);
-		g_signal_connect (G_OBJECT (priv->info_bar), "response",
-				  G_CALLBACK (info_bar_response_cb), source);
-		gtk_table_attach_defaults (GTK_TABLE (table), priv->info_bar, 0, 2, 0, 1);
+	message = g_strdup_printf ("<b>%s</b>\n%s", _("Could not find this album on MusicBrainz."),
+				   _("You can improve the MusicBrainz database by adding this album."));
+	source->priv->info_bar_label = gtk_label_new (NULL);
+	gtk_label_set_markup (GTK_LABEL (source->priv->info_bar_label), message);
+	gtk_label_set_justify (GTK_LABEL (source->priv->info_bar_label), GTK_JUSTIFY_LEFT);
+	g_free (message);
+	box = gtk_info_bar_get_content_area (GTK_INFO_BAR (source->priv->info_bar));
+	gtk_container_add (GTK_CONTAINER (box), source->priv->info_bar_label);
+	gtk_widget_show_all (box);
+	gtk_widget_set_no_show_all (source->priv->info_bar, TRUE);
+	g_signal_connect (G_OBJECT (source->priv->info_bar), "response",
+			  G_CALLBACK (info_bar_response_cb), source);
+	gtk_grid_attach (GTK_GRID (infogrid), source->priv->info_bar, 0, 0, 2, 1);
 #endif
 
-		priv->artist_entry = GTK_WIDGET (gtk_builder_get_object (builder, "artist_entry"));
-		priv->artist_sort_entry = GTK_WIDGET (gtk_builder_get_object (builder, "artist_sort_entry"));
-		priv->album_entry = GTK_WIDGET (gtk_builder_get_object (builder, "album_entry"));
-		priv->year_entry = GTK_WIDGET (gtk_builder_get_object (builder, "year_entry"));
-		priv->genre_entry = GTK_WIDGET (gtk_builder_get_object (builder, "genre_entry"));
-		priv->disc_number_entry = GTK_WIDGET (gtk_builder_get_object (builder, "disc_number_entry"));
-
-		g_signal_connect_object (priv->artist_entry, "focus-out-event", G_CALLBACK (update_artist_cb), source, 0);
-		g_signal_connect_object (priv->artist_sort_entry, "focus-out-event", G_CALLBACK (update_artist_sort_cb), source, 0);
-		g_signal_connect_object (priv->album_entry, "focus-out-event", G_CALLBACK (update_album_cb), source, 0);
-		g_signal_connect_object (priv->genre_entry, "focus-out-event", G_CALLBACK (update_genre_cb), source, 0);
-		g_signal_connect_object (priv->year_entry, "focus-out-event", G_CALLBACK (update_year_cb), source, 0);
-		g_signal_connect_object (priv->disc_number_entry, "focus-out-event", G_CALLBACK (update_disc_number_cb), source, 0);
-
-		gtk_widget_set_vexpand (table, FALSE);
-		gtk_box_pack_start (GTK_BOX (priv->box), table, FALSE, FALSE, 0);
-		gtk_box_reorder_child (GTK_BOX (priv->box), table, 0);
-		g_object_unref (builder);
-	}
+	source->priv->artist_entry = GTK_WIDGET (gtk_builder_get_object (builder, "artist_entry"));
+	source->priv->artist_sort_entry = GTK_WIDGET (gtk_builder_get_object (builder, "artist_sort_entry"));
+	source->priv->album_entry = GTK_WIDGET (gtk_builder_get_object (builder, "album_entry"));
+	source->priv->year_entry = GTK_WIDGET (gtk_builder_get_object (builder, "year_entry"));
+	source->priv->genre_entry = GTK_WIDGET (gtk_builder_get_object (builder, "genre_entry"));
+	source->priv->disc_number_entry = GTK_WIDGET (gtk_builder_get_object (builder, "disc_number_entry"));
+
+	g_signal_connect_object (source->priv->artist_entry, "focus-out-event", G_CALLBACK (update_artist_cb), source, 0);
+	g_signal_connect_object (source->priv->artist_sort_entry, "focus-out-event", G_CALLBACK (update_artist_sort_cb), source, 0);
+	g_signal_connect_object (source->priv->album_entry, "focus-out-event", G_CALLBACK (update_album_cb), source, 0);
+	g_signal_connect_object (source->priv->genre_entry, "focus-out-event", G_CALLBACK (update_genre_cb), source, 0);
+	g_signal_connect_object (source->priv->year_entry, "focus-out-event", G_CALLBACK (update_year_cb), source, 0);
+	g_signal_connect_object (source->priv->disc_number_entry, "focus-out-event", G_CALLBACK (update_disc_number_cb), source, 0);
+
+	grid = gtk_grid_new ();
+	gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
+	gtk_grid_attach (GTK_GRID (grid), infogrid, 0, 0, 1, 1);
+	gtk_grid_attach (GTK_GRID (grid), GTK_WIDGET (entry_view), 0, 1, 1, 1);
+	g_object_unref (builder);
+
+	gtk_widget_show_all (grid);
+	gtk_container_add (GTK_CONTAINER (source), grid);
 
-	g_object_ref (G_OBJECT (source));
-	g_thread_create ((GThreadFunc)rb_audiocd_load_songs, source, FALSE, NULL);
+	g_thread_create ((GThreadFunc)rb_audiocd_load_songs, g_object_ref (source), FALSE, NULL);
+
+	g_object_unref (db);
+	g_object_unref (shell_player);
 }
 
 RBSource *
@@ -500,10 +556,15 @@ rb_audiocd_source_new (GObject *plugin,
 static void
 impl_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
 {
+	RBAudioCdSource *source = RB_AUDIOCD_SOURCE (object);
+
 	switch (prop_id) {
 	case PROP_SEARCH_TYPE:
 		/* ignored */
 		break;
+	case PROP_VOLUME:
+		source->priv->volume = g_value_dup_object (value);
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -513,10 +574,15 @@ impl_set_property (GObject *object, guint prop_id, const GValue *value, GParamSp
 static void
 impl_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
 {
+	RBAudioCdSource *source = RB_AUDIOCD_SOURCE (object);
+
 	switch (prop_id) {
 	case PROP_SEARCH_TYPE:
 		g_value_set_enum (value, RB_SOURCE_SEARCH_NONE);
 		break;
+	case PROP_VOLUME:
+		g_value_set_object (value, source->priv->volume);
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -524,17 +590,6 @@ impl_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *ps
 }
 
 static void
-impl_pack_paned (RBBrowserSource *source, GtkWidget *paned)
-{
-	RBAudioCdSourcePrivate *priv = AUDIOCD_SOURCE_GET_PRIVATE (source);
-
-	priv->box = gtk_vbox_new (FALSE, 6);
-	gtk_widget_show_all (priv->box);
-	gtk_container_add (GTK_CONTAINER (source), priv->box);
-	gtk_box_pack_start (GTK_BOX (priv->box), paned, TRUE, TRUE, 0);
-}
-
-static void
 entry_set_string_prop (RhythmDB *db,
 		       RhythmDBEntry *entry,
 		       RhythmDBPropType propid,
@@ -568,7 +623,6 @@ rb_audiocd_create_track_entry (RBAudioCdSource *source,
 			       guint track_number)
 {
 	RhythmDBEntry *entry;
-	RBAudioCdSourcePrivate *priv = AUDIOCD_SOURCE_GET_PRIVATE (source);
 	char *audio_path;
 	guint64 duration;
 	GValue value = {0, };
@@ -576,7 +630,7 @@ rb_audiocd_create_track_entry (RBAudioCdSource *source,
 	RhythmDBEntryType *entry_type;
 	RBAudioCDEntryData *extra_data;
 
-	audio_path = g_strdup_printf ("cdda://%d#%s", track_number, priv->device_path);
+	audio_path = g_strdup_printf ("cdda://%d#%s", track_number, source->priv->device_path);
 
 	g_object_get (source, "entry-type", &entry_type, NULL);
 	rb_debug ("Audio CD - create entry for track %d from %s", track_number, audio_path);
@@ -606,7 +660,7 @@ rb_audiocd_create_track_entry (RBAudioCdSource *source,
 
 	/* determine the duration
 	 * FIXME: http://bugzilla.gnome.org/show_bug.cgi?id=551011 */
-	if (gst_tag_list_get_uint64 (GST_CDDA_BASE_SRC(priv->cdda)->tracks[track_number - 1].tags, GST_TAG_DURATION, &duration)) {
+	if (gst_tag_list_get_uint64 (GST_CDDA_BASE_SRC(source->priv->cdda)->tracks[track_number - 1].tags, GST_TAG_DURATION, &duration)) {
 		g_value_init (&value, G_TYPE_ULONG);
 		g_value_set_ulong (&value, (gulong)(duration / GST_SECOND));
 		rhythmdb_entry_set (db, entry,
@@ -635,10 +689,9 @@ static gboolean
 rb_audiocd_get_cd_info (RBAudioCdSource *source,
 			gint64 *num_tracks)
 {
-	RBAudioCdSourcePrivate *priv = AUDIOCD_SOURCE_GET_PRIVATE (source);
 	GstFormat fmt = gst_format_get_by_nick ("track");
 	GstFormat out_fmt = fmt;
-	if (!gst_element_query_duration (priv->cdda, &out_fmt, num_tracks) || out_fmt != fmt) {
+	if (!gst_element_query_duration (source->priv->cdda, &out_fmt, num_tracks) || out_fmt != fmt) {
 		return FALSE;
 	}
 
@@ -650,13 +703,12 @@ rb_audiocd_scan_songs (RBAudioCdSource *source,
 		       RhythmDB *db)
 {
 	gint64 i, num_tracks;
-	RBAudioCdSourcePrivate *priv = AUDIOCD_SOURCE_GET_PRIVATE (source);
         GstStateChangeReturn ret;
 	gboolean ok = TRUE;
 
-	ret = gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
+	ret = gst_element_set_state (source->priv->pipeline, GST_STATE_PAUSED);
 	if (ret == GST_STATE_CHANGE_ASYNC) {
-		ret = gst_element_get_state (priv->pipeline, NULL, NULL, 3 * GST_SECOND);
+		ret = gst_element_get_state (source->priv->pipeline, NULL, NULL, 3 * GST_SECOND);
 	}
         if (ret == GST_STATE_CHANGE_FAILURE) {
 		gdk_threads_enter ();
@@ -675,19 +727,19 @@ rb_audiocd_scan_songs (RBAudioCdSource *source,
 	}
 
 	if (ok) {
-		rb_debug ("importing Audio Cd %s - %d tracks", priv->device_path, (int)num_tracks);
+		rb_debug ("importing Audio Cd %s - %d tracks", source->priv->device_path, (int)num_tracks);
 		for (i = 1; i <= num_tracks; i++) {
 			RhythmDBEntry* entry = rb_audiocd_create_track_entry (source, db, i);
 
 			if (entry)
-				priv->tracks = g_list_prepend (priv->tracks, entry);
+				source->priv->tracks = g_list_prepend (source->priv->tracks, entry);
 			else
 				g_warning ("Could not create audio cd track entry");
 		}
-		priv->tracks = g_list_reverse (priv->tracks);
+		source->priv->tracks = g_list_reverse (source->priv->tracks);
 	}
 
-	if (gst_element_set_state (priv->pipeline, GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE) {
+	if (gst_element_set_state (source->priv->pipeline, GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE) {
 		rb_debug ("failed to set cd state");
 	}
 
@@ -830,32 +882,31 @@ metadata_cb (SjMetadataGetter *metadata,
 	     GError *error,
 	     RBAudioCdSource *source)
 {
-	RBAudioCdSourcePrivate *priv = AUDIOCD_SOURCE_GET_PRIVATE (source);
-	GList *cd_track = priv->tracks;
+	GList *cd_track = source->priv->tracks;
 	RhythmDB *db;
 	GValue true_value = {0,};
 	AlbumDetails *album;
 
-	g_assert (metadata == priv->metadata);
+	g_assert (metadata == source->priv->metadata);
 
 	if (error != NULL) {
 		rb_debug ("Failed to load cd metadata: %s", error->message);
 		/* TODO display error to user? */
 		g_object_unref (metadata);
-		priv->metadata = NULL;
+		source->priv->metadata = NULL;
 		return;
 	}
 	if (albums == NULL) {
 		rb_debug ("Musicbrainz didn't return any CD metadata, but didn't give an error");
 		g_object_unref (metadata);
-		priv->metadata = NULL;
+		source->priv->metadata = NULL;
 		return;
 	}
 	if (cd_track == NULL) {
 		/* empty cd? */
 		rb_debug ("no tracks on the CD?");
 		g_object_unref (metadata);
-		priv->metadata = NULL;
+		source->priv->metadata = NULL;
 		return;
 	}
 
@@ -864,8 +915,8 @@ metadata_cb (SjMetadataGetter *metadata,
 	g_value_init (&true_value, G_TYPE_BOOLEAN);
 	g_value_set_boolean (&true_value, TRUE);
 
-	g_free (priv->submit_url);
-	priv->submit_url = NULL;
+	g_free (source->priv->submit_url);
+	source->priv->submit_url = NULL;
 
 	/* if we have multiple results, ask the user to pick one */
 	if (g_list_length (albums) > 1) {
@@ -876,42 +927,42 @@ metadata_cb (SjMetadataGetter *metadata,
 		album = (AlbumDetails *)albums->data;
 
 	if (album->metadata_source != SOURCE_MUSICBRAINZ) {
-		priv->submit_url = sj_metadata_getter_get_submit_url (metadata);
-		if (priv->submit_url != NULL)
-			gtk_widget_show (priv->info_bar);
+		source->priv->submit_url = sj_metadata_getter_get_submit_url (metadata);
+		if (source->priv->submit_url != NULL)
+			gtk_widget_show (source->priv->info_bar);
 	}
 
 	if (album->metadata_source == SOURCE_FALLBACK) {
 		rb_debug ("ignoring CD metadata from fallback source");
 		g_object_unref (metadata);
-		priv->metadata = NULL;
+		source->priv->metadata = NULL;
 		g_object_unref (db);
 		return;
 	}
 
 	if (album->artist != NULL) {
-		gtk_entry_set_text (GTK_ENTRY (priv->artist_entry), album->artist);
+		gtk_entry_set_text (GTK_ENTRY (source->priv->artist_entry), album->artist);
 	}
 	if (album->artist_sortname != NULL) {
-		gtk_entry_set_text (GTK_ENTRY (priv->artist_sort_entry), album->artist_sortname);
+		gtk_entry_set_text (GTK_ENTRY (source->priv->artist_sort_entry), album->artist_sortname);
 	}
 	if (album->title != NULL) {
-		gtk_entry_set_text (GTK_ENTRY (priv->album_entry), album->title);
+		gtk_entry_set_text (GTK_ENTRY (source->priv->album_entry), album->title);
 	}
 	if (album->release_date != NULL) {
 		char *year;
 		year = g_strdup_printf ("%d", g_date_get_year (album->release_date));
-		gtk_entry_set_text (GTK_ENTRY (priv->year_entry), year);
+		gtk_entry_set_text (GTK_ENTRY (source->priv->year_entry), year);
 		g_free (year);
 	}
 	if (album->disc_number != 0) {
 		char *num;
 		num = g_strdup_printf ("%d", album->disc_number);
-		gtk_entry_set_text (GTK_ENTRY (priv->disc_number_entry), num);
+		gtk_entry_set_text (GTK_ENTRY (source->priv->disc_number_entry), num);
 		g_free (num);
 	}
 	if (album->genre != NULL) {
-		gtk_entry_set_text (GTK_ENTRY (priv->genre_entry), album->genre);
+		gtk_entry_set_text (GTK_ENTRY (source->priv->genre_entry), album->genre);
 	}
 
 	g_object_set (G_OBJECT (source), "name", album->title, NULL);
@@ -992,7 +1043,7 @@ metadata_cb (SjMetadataGetter *metadata,
 	g_list_free (albums);
 
 	g_object_unref (metadata);
-	priv->metadata = NULL;
+	source->priv->metadata = NULL;
 
 	g_object_unref (db);
 }
@@ -1015,14 +1066,12 @@ rb_audiocd_load_metadata (RBAudioCdSource *source,
 			  RhythmDB *db)
 {
 #ifdef HAVE_SJ_METADATA_GETTER
-	RBAudioCdSourcePrivate *priv = AUDIOCD_SOURCE_GET_PRIVATE (source);
-
-	priv->metadata = sj_metadata_getter_new ();
-	sj_metadata_getter_set_cdrom (priv->metadata, priv->device_path);
+	source->priv->metadata = sj_metadata_getter_new ();
+	sj_metadata_getter_set_cdrom (source->priv->metadata, source->priv->device_path);
 
-	g_signal_connect (G_OBJECT (priv->metadata), "metadata",
+	g_signal_connect (G_OBJECT (source->priv->metadata), "metadata",
 			  G_CALLBACK (metadata_cb), source);
-	sj_metadata_getter_list_albums (priv->metadata, NULL);
+	sj_metadata_getter_list_albums (source->priv->metadata, NULL);
 #endif
 }
 
@@ -1030,12 +1079,10 @@ static void
 rb_audiocd_load_metadata_cancel (RBAudioCdSource *source)
 {
 #ifdef HAVE_SJ_METADATA_GETTER
-	RBAudioCdSourcePrivate *priv = AUDIOCD_SOURCE_GET_PRIVATE (source);
-
-	if (priv->metadata) {
-		g_signal_handlers_disconnect_by_func (G_OBJECT (priv->metadata),
+	if (source->priv->metadata) {
+		g_signal_handlers_disconnect_by_func (G_OBJECT (source->priv->metadata),
 						      G_CALLBACK (metadata_cb), source);
-		g_signal_connect (G_OBJECT (priv->metadata), "metadata",
+		g_signal_connect (G_OBJECT (source->priv->metadata), "metadata",
 				  G_CALLBACK (metadata_cancelled_cb), source);
 	}
 #endif
@@ -1044,21 +1091,19 @@ rb_audiocd_load_metadata_cancel (RBAudioCdSource *source)
 static gpointer
 rb_audiocd_load_songs (RBAudioCdSource *source)
 {
-	RBAudioCdSourcePrivate *priv = AUDIOCD_SOURCE_GET_PRIVATE (source);
 	RhythmDB *db;
 	GVolume *volume;
 
 	g_object_get (source, "volume", &volume, NULL);
-	priv->device_path = g_volume_get_identifier (volume,
-						     G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE);
+	source->priv->device_path = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE);
 	g_object_unref (volume);
 
 	db = get_db_for_source (source);
 
-	rb_debug ("loading Audio CD from %s", priv->device_path);
+	rb_debug ("loading Audio CD from %s", source->priv->device_path);
 	/* create a cdda gstreamer element, to get cd info from */
-	priv->cdda = gst_element_make_from_uri (GST_URI_SRC, "cdda://", NULL);
-	if (!priv->cdda) {
+	source->priv->cdda = gst_element_make_from_uri (GST_URI_SRC, "cdda://", NULL);
+	if (!source->priv->cdda) {
 		gdk_threads_enter ();
 		rb_error_dialog (NULL, _("Couldn't load Audio CD"),
 					_("Rhythmbox could not get access to the CD device."));
@@ -1066,12 +1111,12 @@ rb_audiocd_load_songs (RBAudioCdSource *source)
 		goto error_out;
 	}
 
-	rb_debug ("cdda longname: %s", gst_element_factory_get_longname (gst_element_get_factory (priv->cdda)));
-	g_object_set (G_OBJECT (priv->cdda), "device", priv->device_path, NULL);
-	priv->pipeline = gst_pipeline_new ("pipeline");
-	priv->fakesink = gst_element_factory_make ("fakesink", "fakesink");
-	gst_bin_add_many (GST_BIN (priv->pipeline), priv->cdda, priv->fakesink, NULL);
-	gst_element_link (priv->cdda, priv->fakesink);
+	rb_debug ("cdda longname: %s", gst_element_factory_get_longname (gst_element_get_factory (source->priv->cdda)));
+	g_object_set (G_OBJECT (source->priv->cdda), "device", source->priv->device_path, NULL);
+	source->priv->pipeline = gst_pipeline_new ("pipeline");
+	source->priv->fakesink = gst_element_factory_make ("fakesink", "fakesink");
+	gst_bin_add_many (GST_BIN (source->priv->pipeline), source->priv->cdda, source->priv->fakesink, NULL);
+	gst_element_link (source->priv->cdda, source->priv->fakesink);
 
 	/* disable paranoia (if using cdparanoia) since we're only reading track information here.
 	 * this reduces cdparanoia's cache size, so the process is much faster.
@@ -1202,13 +1247,12 @@ impl_uri_is_source (RBSource *source, const char *uri)
 static void
 update_tracks (RBAudioCdSource *source, RhythmDBPropType property, GValue *value)
 {
-	RBAudioCdSourcePrivate *priv = AUDIOCD_SOURCE_GET_PRIVATE (source);
 	RhythmDB *db;
 	GList *i;
 
 	db = get_db_for_source (source);
 
-	for (i = priv->tracks; i != NULL; i = i->next) {
+	for (i = source->priv->tracks; i != NULL; i = i->next) {
 		rhythmdb_entry_set (db, i->data, property, value);
 	}
 
@@ -1296,20 +1340,19 @@ update_disc_number_cb (GtkWidget *widget, GdkEventFocus *event, RBAudioCdSource
 static void
 info_bar_response_cb (GtkInfoBar *info_bar, gint response_id, RBAudioCdSource *source)
 {
-	RBAudioCdSourcePrivate *priv = AUDIOCD_SOURCE_GET_PRIVATE (source);
 	GError *error = NULL;
 
-	g_return_if_fail (priv->submit_url != NULL);
+	g_return_if_fail (source->priv->submit_url != NULL);
 
 	if (response_id == GTK_RESPONSE_OK) {
-		if (!gtk_show_uri (NULL, priv->submit_url, GDK_CURRENT_TIME, &error)) {
-			rb_debug ("Could not launch submit URL %s: %s", priv->submit_url, error->message);
+		if (!gtk_show_uri (NULL, source->priv->submit_url, GDK_CURRENT_TIME, &error)) {
+			rb_debug ("Could not launch submit URL %s: %s", source->priv->submit_url, error->message);
 			g_error_free (error);
 			return;
 		}
 	}
 
-	gtk_widget_hide (priv->info_bar);
+	gtk_widget_hide (source->priv->info_bar);
 }
 #endif
 
diff --git a/plugins/audiocd/rb-audiocd-source.h b/plugins/audiocd/rb-audiocd-source.h
index e18f496..20061de 100644
--- a/plugins/audiocd/rb-audiocd-source.h
+++ b/plugins/audiocd/rb-audiocd-source.h
@@ -29,7 +29,7 @@
 #define __RB_AUDIOCD_SOURCE_H
 
 #include "rb-shell.h"
-#include "rb-removable-media-source.h"
+#include "rb-source.h"
 #include "rhythmdb.h"
 
 G_BEGIN_DECLS
@@ -41,14 +41,18 @@ G_BEGIN_DECLS
 #define RB_IS_AUDIOCD_SOURCE_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), RB_TYPE_AUDIOCD_SOURCE))
 #define RB_AUDIOCD_SOURCE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), RB_TYPE_AUDIOCD_SOURCE, RBAudioCdSourceClass))
 
+typedef struct _RBAudioCdSourcePrivate RBAudioCdSourcePrivate;
+
 typedef struct
 {
-	RBRemovableMediaSource parent;
+	RBSource parent;
+
+	RBAudioCdSourcePrivate *priv;
 } RBAudioCdSource;
 
 typedef struct
 {
-	RBRemovableMediaSourceClass parent;
+	RBSourceClass parent;
 } RBAudioCdSourceClass;
 
 RBSource *		rb_audiocd_source_new			(GObject *plugin,
diff --git a/plugins/dbus-media-server/rb-dbus-media-server-plugin.c b/plugins/dbus-media-server/rb-dbus-media-server-plugin.c
index 65b6f04..8523952 100644
--- a/plugins/dbus-media-server/rb-dbus-media-server-plugin.c
+++ b/plugins/dbus-media-server/rb-dbus-media-server-plugin.c
@@ -44,7 +44,7 @@
 #include <shell/rb-shell-player.h>
 #include <sources/rb-display-page-model.h>
 #include <sources/rb-playlist-source.h>
-#include <sources/rb-removable-media-source.h>
+#include <sources/rb-device-source.h>
 #include <rhythmdb/rhythmdb-property-model.h>
 
 #define RB_TYPE_DBUS_MEDIA_SERVER_PLUGIN	(rb_dbus_media_server_plugin_get_type ())
@@ -2273,7 +2273,7 @@ is_shareable_playlist (RBSource *source)
 static gboolean
 is_shareable_device (RBSource *source)
 {
-	return RB_IS_REMOVABLE_MEDIA_SOURCE (source);
+	return RB_IS_DEVICE_SOURCE (source);
 }
 */
 
diff --git a/plugins/generic-player/rb-generic-player-plugin.c b/plugins/generic-player/rb-generic-player-plugin.c
index 3d06831..d0abb46 100644
--- a/plugins/generic-player/rb-generic-player-plugin.c
+++ b/plugins/generic-player/rb-generic-player-plugin.c
@@ -156,11 +156,11 @@ create_source_cb (RBRemovableMediaManager *rmm, GMount *mount, MPIDDevice *devic
 	g_object_get (plugin, "object", &shell, NULL);
 
 	if (rb_psp_is_mount_player (mount, device_info))
-		source = RB_SOURCE (rb_psp_source_new (G_OBJECT (plugin), shell, mount, device_info));
+		source = rb_psp_source_new (G_OBJECT (plugin), shell, mount, device_info);
 	if (source == NULL && rb_nokia770_is_mount_player (mount, device_info))
-		source = RB_SOURCE (rb_nokia770_source_new (G_OBJECT (plugin), shell, mount, device_info));
+		source = rb_nokia770_source_new (G_OBJECT (plugin), shell, mount, device_info);
 	if (source == NULL && rb_generic_player_is_mount_player (mount, device_info))
-		source = RB_SOURCE (rb_generic_player_source_new (G_OBJECT (plugin), shell, mount, device_info));
+		source = rb_generic_player_source_new (G_OBJECT (plugin), shell, mount, device_info);
 
 	if (plugin->actions == NULL) {
 		plugin->actions = gtk_action_group_new ("GenericPlayerActions");
diff --git a/plugins/generic-player/rb-generic-player-source.c b/plugins/generic-player/rb-generic-player-source.c
index 5e18ec2..72cc214 100644
--- a/plugins/generic-player/rb-generic-player-source.c
+++ b/plugins/generic-player/rb-generic-player-source.c
@@ -42,6 +42,8 @@
 #include "rb-generic-player-source.h"
 #include "rb-generic-player-playlist-source.h"
 #include "rb-removable-media-manager.h"
+#include "rb-transfer-target.h"
+#include "rb-device-source.h"
 #include "rb-debug.h"
 #include "rb-util.h"
 #include "rb-file-helpers.h"
@@ -54,6 +56,9 @@
 #include "rb-sync-settings.h"
 #include "rb-missing-plugins.h"
 
+static void rb_generic_player_device_source_init (RBDeviceSourceInterface *interface);
+static void rb_generic_player_source_transfer_target_init (RBTransferTargetInterface *interface);
+
 static void impl_constructed (GObject *object);
 static void impl_dispose (GObject *object);
 static void impl_set_property (GObject *object,
@@ -72,10 +77,11 @@ static void impl_delete_thyself (RBDisplayPage *page);
 static void impl_get_status (RBDisplayPage *page, char **text, char **progress_text, float *progress);
 
 static gboolean impl_can_paste (RBSource *source);
+static RBTrackTransferBatch *impl_paste (RBSource *source, GList *entries);
 static gboolean impl_can_delete (RBSource *source);
 static void impl_delete (RBSource *source);
 
-static char* impl_build_dest_uri (RBRemovableMediaSource *source,
+static char* impl_build_dest_uri (RBTransferTarget *target,
 				  RhythmDBEntry *entry,
 				  const char *media_type,
 				  const char *extension);
@@ -102,6 +108,7 @@ static char * default_uri_to_playlist_uri (RBGenericPlayerSource *source,
 enum
 {
 	PROP_0,
+	PROP_MOUNT,
 	PROP_IGNORE_ENTRY_TYPE,
 	PROP_ERROR_ENTRY_TYPE,
 	PROP_DEVICE_INFO
@@ -126,10 +133,18 @@ typedef struct
 	gboolean read_only;
 
 	MPIDDevice *device_info;
+	GMount *mount;
 
 } RBGenericPlayerSourcePrivate;
 
-G_DEFINE_DYNAMIC_TYPE (RBGenericPlayerSource, rb_generic_player_source, RB_TYPE_MEDIA_PLAYER_SOURCE)
+G_DEFINE_DYNAMIC_TYPE_EXTENDED (
+	RBGenericPlayerSource,
+	rb_generic_player_source,
+	RB_TYPE_MEDIA_PLAYER_SOURCE,
+	0,
+	G_IMPLEMENT_INTERFACE_DYNAMIC (RB_TYPE_DEVICE_SOURCE, rb_generic_player_device_source_init)
+	G_IMPLEMENT_INTERFACE_DYNAMIC (RB_TYPE_TRANSFER_TARGET, rb_generic_player_source_transfer_target_init))
+
 #define GET_PRIVATE(o)   (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_GENERIC_PLAYER_SOURCE, RBGenericPlayerSourcePrivate))
 
 static void
@@ -139,7 +154,6 @@ rb_generic_player_source_class_init (RBGenericPlayerSourceClass *klass)
 	RBDisplayPageClass *page_class = RB_DISPLAY_PAGE_CLASS (klass);
 	RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
 	RBMediaPlayerSourceClass *mps_class = RB_MEDIA_PLAYER_SOURCE_CLASS (klass);
-	RBRemovableMediaSourceClass *rms_class = RB_REMOVABLE_MEDIA_SOURCE_CLASS (klass);
 
 	object_class->set_property = impl_set_property;
 	object_class->get_property = impl_get_property;
@@ -154,6 +168,7 @@ rb_generic_player_source_class_init (RBGenericPlayerSourceClass *klass)
 	source_class->impl_delete = impl_delete;
 	source_class->impl_can_move_to_trash = (RBSourceFeatureFunc) rb_false_function;
 	source_class->impl_can_paste = impl_can_paste;
+	source_class->impl_paste = impl_paste;
 
 	mps_class->impl_get_entries = impl_get_entries;
 	mps_class->impl_get_capacity = impl_get_capacity;
@@ -163,9 +178,6 @@ rb_generic_player_source_class_init (RBGenericPlayerSourceClass *klass)
 	mps_class->impl_add_playlist = impl_add_playlist;
 	mps_class->impl_remove_playlists = impl_remove_playlists;
 
-	rms_class->impl_build_dest_uri = impl_build_dest_uri;
-	rms_class->impl_should_paste = rb_removable_media_source_should_paste_no_duplicate;
-
 	klass->impl_get_mount_path = default_get_mount_path;
 	klass->impl_load_playlists = default_load_playlists;
 	klass->impl_uri_from_playlist_uri = default_uri_from_playlist_uri;
@@ -192,10 +204,27 @@ rb_generic_player_source_class_init (RBGenericPlayerSourceClass *klass)
 							      "device information object",
 							      MPID_TYPE_DEVICE,
 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+	g_object_class_install_property (object_class,
+					 PROP_MOUNT,
+					 g_param_spec_object ("mount",
+							      "mount",
+							      "GMount object",
+							      G_TYPE_MOUNT,
+							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 
+	g_type_class_add_private (klass, sizeof (RBGenericPlayerSourcePrivate));
+}
 
+static void
+rb_generic_player_device_source_init (RBDeviceSourceInterface *interface)
+{
+	/* nothing */
+}
 
-	g_type_class_add_private (klass, sizeof (RBGenericPlayerSourcePrivate));
+static void
+rb_generic_player_source_transfer_target_init (RBTransferTargetInterface *interface)
+{
+	interface->build_dest_uri = impl_build_dest_uri;
 }
 
 static void
@@ -215,7 +244,6 @@ impl_constructed (GObject *object)
 	RBGenericPlayerSource *source;
 	RBGenericPlayerSourcePrivate *priv;
 	RhythmDBEntryType *entry_type;
-	GMount *mount;
 	char **playlist_formats;
 	char **output_formats;
 	char *mount_name;
@@ -229,13 +257,15 @@ impl_constructed (GObject *object)
 
 	priv = GET_PRIVATE (source);
 
+	rb_device_source_set_display_details (RB_DEVICE_SOURCE (source));
+
 	g_object_get (source,
 		      "shell", &shell,
 		      "entry-type", &entry_type,
 		      NULL);
 
 	g_object_get (shell, "db", &priv->db, NULL);
-	
+
 	priv->import_errors = rb_import_errors_source_new (shell,
 							   priv->error_type,
 							   entry_type,
@@ -243,10 +273,8 @@ impl_constructed (GObject *object)
 
 	g_object_unref (shell);
 
-	g_object_get (source, "mount", &mount, NULL);
-
-	root = g_mount_get_root (mount);
-	mount_name = g_mount_get_name (mount);
+	root = g_mount_get_root (priv->mount);
+	mount_name = g_mount_get_name (priv->mount);
 
 	info = g_file_query_filesystem_info (root, G_FILE_ATTRIBUTE_FILESYSTEM_READONLY, NULL, &error);
 	if (error != NULL) {
@@ -260,7 +288,6 @@ impl_constructed (GObject *object)
 
 	g_free (mount_name);
 	g_object_unref (root);
-	g_object_unref (mount);
 
 	g_object_get (priv->device_info, "playlist-formats", &playlist_formats, NULL);
 	if (playlist_formats != NULL && g_strv_length (playlist_formats) > 0) {
@@ -308,6 +335,9 @@ impl_set_property (GObject *object, guint prop_id, const GValue *value, GParamSp
 	case PROP_DEVICE_INFO:
 		priv->device_info = g_value_dup_object (value);
 		break;
+	case PROP_MOUNT:
+		priv->mount = g_value_dup_object (value);
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -329,6 +359,9 @@ impl_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *ps
 	case PROP_DEVICE_INFO:
 		g_value_set_object (value, priv->device_info);
 		break;
+	case PROP_MOUNT:
+		g_value_set_object (value, priv->mount);
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -372,10 +405,15 @@ impl_dispose (GObject *object)
 		priv->device_info = NULL;
 	}
 
+	if (priv->mount != NULL) {
+		g_object_unref (priv->mount);
+		priv->mount = NULL;
+	}
+
 	G_OBJECT_CLASS (rb_generic_player_source_parent_class)->dispose (object);
 }
 
-RBRemovableMediaSource *
+RBSource *
 rb_generic_player_source_new (GObject *plugin, RBShell *shell, GMount *mount, MPIDDevice *device_info)
 {
 	RBGenericPlayerSource *source;
@@ -442,7 +480,7 @@ rb_generic_player_source_new (GObject *plugin, RBShell *shell, GMount *mount, MP
 
 	rb_shell_register_entry_type_for_source (shell, RB_SOURCE (source), entry_type);
 
-	return RB_REMOVABLE_MEDIA_SOURCE (source);
+	return RB_SOURCE (source);
 }
 
 static void
@@ -478,7 +516,7 @@ import_complete_cb (RhythmDBImportJob *job, int total, RBGenericPlayerSource *so
 	RBShell *shell;
 
 	GDK_THREADS_ENTER ();
-	
+
 	g_object_get (source, "shell", &shell, NULL);
 	rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (priv->import_errors), RB_DISPLAY_PAGE (source));
 	g_object_unref (shell);
@@ -488,7 +526,7 @@ import_complete_cb (RhythmDBImportJob *job, int total, RBGenericPlayerSource *so
 
 	g_object_unref (priv->import_job);
 	priv->import_job = NULL;
-	
+
 	rb_display_page_notify_status_changed (RB_DISPLAY_PAGE (source));
 
 	GDK_THREADS_LEAVE ();
@@ -555,18 +593,13 @@ default_get_mount_path (RBGenericPlayerSource *source)
 	RBGenericPlayerSourcePrivate *priv = GET_PRIVATE (source);
 
 	if (priv->mount_path == NULL) {
-		GMount *mount;
 		GFile *root;
 
-		g_object_get (source, "mount", &mount, NULL);
-
-		root = g_mount_get_root (mount);
+		root = g_mount_get_root (priv->mount);
 		if (root != NULL) {
 			priv->mount_path = g_file_get_uri (root);
 			g_object_unref (root);
 		}
-
-		g_object_unref (mount);
 	}
 
 	return g_strdup (priv->mount_path);
@@ -848,6 +881,12 @@ impl_can_paste (RBSource *source)
 	return (priv->read_only == FALSE);
 }
 
+static RBTrackTransferBatch *
+impl_paste (RBSource *source, GList *entries)
+{
+	return rb_transfer_target_transfer (RB_TRANSFER_TARGET (source), entries);
+}
+
 static gboolean
 impl_can_delete (RBSource *source)
 {
@@ -984,12 +1023,12 @@ sanitize_path (const char *str)
 }
 
 static char *
-impl_build_dest_uri (RBRemovableMediaSource *source,
+impl_build_dest_uri (RBTransferTarget *target,
 		     RhythmDBEntry *entry,
 		     const char *media_type,
 		     const char *extension)
 {
-	RBGenericPlayerSourcePrivate *priv = GET_PRIVATE (source);
+	RBGenericPlayerSourcePrivate *priv = GET_PRIVATE (target);
 	const char *in_artist;
 	char *artist, *album, *title;
 	gulong track_number, disc_number;
@@ -1082,7 +1121,7 @@ impl_build_dest_uri (RBRemovableMediaSource *source,
 	}
 	g_strfreev (audio_folders);
 
-	mount_path = rb_generic_player_source_get_mount_path (RB_GENERIC_PLAYER_SOURCE (source));
+	mount_path = rb_generic_player_source_get_mount_path (RB_GENERIC_PLAYER_SOURCE (target));
 	path = g_build_filename (mount_path, folders, file, NULL);
 	g_free (file);
 	g_free (mount_path);
@@ -1331,7 +1370,7 @@ impl_show_properties (RBMediaPlayerSource *source, GtkWidget *info_box, GtkWidge
 	g_free (serial_id);
 
 	str = g_string_new ("");
-	output_formats = rb_removable_media_source_get_format_descriptions (RB_REMOVABLE_MEDIA_SOURCE (source));
+	output_formats = rb_transfer_target_get_format_descriptions (RB_TRANSFER_TARGET (source));
 	for (t = output_formats; t != NULL; t = t->next) {
 		if (t != output_formats) {
 			g_string_append (str, "\n");
diff --git a/plugins/generic-player/rb-generic-player-source.h b/plugins/generic-player/rb-generic-player-source.h
index d1d04b2..7e8427f 100644
--- a/plugins/generic-player/rb-generic-player-source.h
+++ b/plugins/generic-player/rb-generic-player-source.h
@@ -65,7 +65,7 @@ typedef struct
 	char *		(*impl_build_filename) (RBGenericPlayerSource *source, RhythmDBEntry *entry);
 } RBGenericPlayerSourceClass;
 
-RBRemovableMediaSource *rb_generic_player_source_new			(GObject *plugin,
+RBSource *		rb_generic_player_source_new			(GObject *plugin,
 									 RBShell *shell,
 									 GMount *mount,
 									 MPIDDevice *device_info);
diff --git a/plugins/generic-player/rb-nokia770-source.c b/plugins/generic-player/rb-nokia770-source.c
index b23028a..447c8cf 100644
--- a/plugins/generic-player/rb-nokia770-source.c
+++ b/plugins/generic-player/rb-nokia770-source.c
@@ -69,7 +69,7 @@ rb_nokia770_source_init (RBNokia770Source *source)
 
 }
 
-RBRemovableMediaSource *
+RBSource *
 rb_nokia770_source_new (GObject *plugin, RBShell *shell, GMount *mount, MPIDDevice *device_info)
 {
 	RBNokia770Source *source;
@@ -109,7 +109,7 @@ rb_nokia770_source_new (GObject *plugin, RBShell *shell, GMount *mount, MPIDDevi
 
 	rb_shell_register_entry_type_for_source (shell, RB_SOURCE (source), entry_type);
 
-	return RB_REMOVABLE_MEDIA_SOURCE (source);
+	return RB_SOURCE (source);
 }
 
 static char *
diff --git a/plugins/generic-player/rb-nokia770-source.h b/plugins/generic-player/rb-nokia770-source.h
index a259294..3529104 100644
--- a/plugins/generic-player/rb-nokia770-source.h
+++ b/plugins/generic-player/rb-nokia770-source.h
@@ -53,7 +53,7 @@ typedef struct
 	RBGenericPlayerSourceClass parent;
 } RBNokia770SourceClass;
 
-RBRemovableMediaSource *	rb_nokia770_source_new		(GObject *plugin,
+RBSource *			rb_nokia770_source_new		(GObject *plugin,
 								 RBShell *shell,
 								 GMount *mount,
 								 MPIDDevice *device_info);
diff --git a/plugins/generic-player/rb-psp-source.c b/plugins/generic-player/rb-psp-source.c
index 39005d3..28c896b 100644
--- a/plugins/generic-player/rb-psp-source.c
+++ b/plugins/generic-player/rb-psp-source.c
@@ -67,7 +67,7 @@ rb_psp_source_init (RBPspSource *source)
 {
 }
 
-RBRemovableMediaSource *
+RBSource *
 rb_psp_source_new (GObject *plugin, RBShell *shell, GMount *mount, MPIDDevice *device_info)
 {
 	RBPspSource *source;
@@ -106,7 +106,7 @@ rb_psp_source_new (GObject *plugin, RBShell *shell, GMount *mount, MPIDDevice *d
 
 	rb_shell_register_entry_type_for_source (shell, RB_SOURCE (source), entry_type);
 
-	return RB_REMOVABLE_MEDIA_SOURCE (source);
+	return RB_SOURCE (source);
 }
 
 static GFile *
diff --git a/plugins/generic-player/rb-psp-source.h b/plugins/generic-player/rb-psp-source.h
index 7b9cab2..936c05e 100644
--- a/plugins/generic-player/rb-psp-source.h
+++ b/plugins/generic-player/rb-psp-source.h
@@ -53,7 +53,7 @@ typedef struct
 	RBGenericPlayerSourceClass parent;
 } RBPspSourceClass;
 
-RBRemovableMediaSource *rb_psp_source_new		(GObject *plugin,
+RBSource *		rb_psp_source_new		(GObject *plugin,
 							 RBShell *shell,
 							 GMount *mount,
 							 MPIDDevice *device_info);
diff --git a/plugins/ipod/rb-ipod-source.c b/plugins/ipod/rb-ipod-source.c
index 17a4fd5..262ca08 100644
--- a/plugins/ipod/rb-ipod-source.c
+++ b/plugins/ipod/rb-ipod-source.c
@@ -42,6 +42,7 @@
 #include "rb-file-helpers.h"
 #include "rb-builder-helpers.h"
 #include "rb-removable-media-manager.h"
+#include "rb-device-source.h"
 #include "rb-ipod-static-playlist-source.h"
 #include "rb-util.h"
 #include "rhythmdb.h"
@@ -54,6 +55,10 @@
 #include "rb-podcast-entry-types.h"
 #include "rb-stock-icons.h"
 #include "rb-gst-media-types.h"
+#include "rb-transfer-target.h"
+
+static void rb_ipod_device_source_init (RBDeviceSourceInterface *interface);
+static void rb_ipod_source_transfer_target_init (RBTransferTargetInterface *interface);
 
 static void rb_ipod_source_constructed (GObject *object);
 static void rb_ipod_source_dispose (GObject *object);
@@ -65,12 +70,12 @@ static gboolean impl_show_popup (RBDisplayPage *page);
 static void impl_delete_thyself (RBDisplayPage *page);
 static GList* impl_get_ui_actions (RBDisplayPage *page);
 
-static gboolean impl_track_added (RBRemovableMediaSource *source,
+static gboolean impl_track_added (RBTransferTarget *target,
 				  RhythmDBEntry *entry,
 				  const char *dest,
 				  guint64 filesize,
 				  const char *media_type);
-static char* impl_build_dest_uri (RBRemovableMediaSource *source,
+static char* impl_build_dest_uri (RBTransferTarget *target,
 				  RhythmDBEntry *entry,
 				  const char *media_type,
 				  const char *extension);
@@ -115,6 +120,7 @@ typedef struct _PlayedEntry PlayedEntry;
 
 typedef struct
 {
+	GMount *mount;
 	RbIpodDb *ipod_db;
 	GHashTable *entry_map;
 
@@ -140,10 +146,17 @@ enum
 {
 	PROP_0,
 	PROP_DEVICE_INFO,
-	PROP_DEVICE_SERIAL
+	PROP_DEVICE_SERIAL,
+	PROP_MOUNT
 };
 
-G_DEFINE_DYNAMIC_TYPE(RBiPodSource, rb_ipod_source, RB_TYPE_MEDIA_PLAYER_SOURCE)
+G_DEFINE_DYNAMIC_TYPE_EXTENDED(
+	RBiPodSource,
+	rb_ipod_source,
+	RB_TYPE_MEDIA_PLAYER_SOURCE,
+	0,
+	G_IMPLEMENT_INTERFACE_DYNAMIC (RB_TYPE_DEVICE_SOURCE, rb_ipod_device_source_init)
+	G_IMPLEMENT_INTERFACE_DYNAMIC (RB_TYPE_TRANSFER_TARGET, rb_ipod_source_transfer_target_init))
 
 #define IPOD_SOURCE_GET_PRIVATE(o)   (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_IPOD_SOURCE, RBiPodSourcePrivate))
 
@@ -154,7 +167,6 @@ rb_ipod_source_class_init (RBiPodSourceClass *klass)
 	RBDisplayPageClass *page_class = RB_DISPLAY_PAGE_CLASS (klass);
 	RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
 	RBMediaPlayerSourceClass *mps_class = RB_MEDIA_PLAYER_SOURCE_CLASS (klass);
-	RBRemovableMediaSourceClass *rms_class = RB_REMOVABLE_MEDIA_SOURCE_CLASS (klass);
 
 	object_class->constructed = rb_ipod_source_constructed;
 	object_class->dispose = rb_ipod_source_dispose;
@@ -182,10 +194,6 @@ rb_ipod_source_class_init (RBiPodSourceClass *klass)
 	mps_class->impl_remove_playlists = impl_remove_playlists;
 	mps_class->impl_show_properties = impl_show_properties;
 
-	rms_class->impl_should_paste = rb_removable_media_source_should_paste_no_duplicate;
-	rms_class->impl_track_added = impl_track_added;
-	rms_class->impl_build_dest_uri = impl_build_dest_uri;
-
 	g_object_class_install_property (object_class,
 					 PROP_DEVICE_INFO,
 					 g_param_spec_object ("device-info",
@@ -193,12 +201,32 @@ rb_ipod_source_class_init (RBiPodSourceClass *klass)
 							      "device information object",
 							      MPID_TYPE_DEVICE,
 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+	g_object_class_install_property (object_class,
+					 PROP_MOUNT,
+					 g_param_spec_object ("mount",
+							      "mount",
+							      "GMount object",
+							      G_TYPE_MOUNT,
+							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 	g_object_class_override_property (object_class, PROP_DEVICE_SERIAL, "serial");
 
 	g_type_class_add_private (klass, sizeof (RBiPodSourcePrivate));
 }
 
 static void
+rb_ipod_device_source_init (RBDeviceSourceInterface *interface)
+{
+	/* nothing */
+}
+
+static void
+rb_ipod_source_transfer_target_init (RBTransferTargetInterface *interface)
+{
+	interface->track_added = impl_track_added;
+	interface->build_dest_uri = impl_build_dest_uri;
+}
+
+static void
 rb_ipod_source_class_finalize (RBiPodSourceClass *klass)
 {
 }
@@ -215,6 +243,9 @@ rb_ipod_source_set_property (GObject *object,
 	case PROP_DEVICE_INFO:
 		priv->device_info = g_value_dup_object (value);
 		break;
+	case PROP_MOUNT:
+		priv->mount = g_value_dup_object (value);
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -240,6 +271,9 @@ rb_ipod_source_get_property (GObject *object,
 			g_value_take_string (value, serial);
 		}
 		break;
+	case PROP_MOUNT:
+		g_value_set_object (value, priv->mount);
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -286,6 +320,8 @@ rb_ipod_source_constructed (GObject *object)
 	RB_CHAIN_GOBJECT_METHOD (rb_ipod_source_parent_class, constructed, object);
 	source = RB_IPOD_SOURCE (object);
 
+	rb_device_source_set_display_details (RB_DEVICE_SOURCE (source));
+
 	songs = rb_source_get_entry_view (RB_SOURCE (source));
 	rb_entry_view_append_column (songs, RB_ENTRY_VIEW_COL_RATING, FALSE);
 	rb_entry_view_append_column (songs, RB_ENTRY_VIEW_COL_LAST_PLAYED, FALSE);
@@ -351,6 +387,11 @@ rb_ipod_source_dispose (GObject *object)
 		priv->offline_plays = NULL;
 	}
 
+	if (priv->mount) {
+		g_object_unref (priv->mount);
+		priv->mount = NULL;
+	}
+
 	G_OBJECT_CLASS (rb_ipod_source_parent_class)->dispose (object);
 }
 
@@ -1202,10 +1243,8 @@ static void
 rb_ipod_load_songs (RBiPodSource *source)
 {
 	RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
-	GMount *mount;
 
-	g_object_get (source, "mount", &mount, NULL);
- 	priv->ipod_db = rb_ipod_db_new (mount);
+	priv->ipod_db = rb_ipod_db_new (priv->mount);
 	priv->entry_map = g_hash_table_new (g_direct_hash, g_direct_equal);
 
 	if ((priv->ipod_db != NULL) && (priv->entry_map != NULL)) {
@@ -1221,7 +1260,6 @@ rb_ipod_load_songs (RBiPodSource *source)
                                   NULL);
 		priv->load_idle_id = g_idle_add ((GSourceFunc)load_ipod_db_idle_cb, source);
 	}
-	g_object_unref (mount);
 }
 
 static GList*
@@ -1381,12 +1419,12 @@ impl_remove_playlists (RBMediaPlayerSource *source)
 }
 
 static char *
-impl_build_dest_uri (RBRemovableMediaSource *source,
+impl_build_dest_uri (RBTransferTarget *target,
 		     RhythmDBEntry *entry,
 		     const char *media_type,
 		     const char *extension)
 {
-	RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
+	RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (target);
 	const char *uri;
 	char *dest;
 	const char *mount_path;
@@ -1593,17 +1631,17 @@ add_to_podcasts (RBiPodSource *source, Itdb_Track *song)
 }
 
 static gboolean
-impl_track_added (RBRemovableMediaSource *source,
+impl_track_added (RBTransferTarget *target,
 		  RhythmDBEntry *entry,
 		  const char *dest,
 		  guint64 filesize,
 		  const char *media_type)
 {
-	RBiPodSource *isource = RB_IPOD_SOURCE (source);
+	RBiPodSource *source = RB_IPOD_SOURCE (target);
 	RhythmDB *db;
 	Itdb_Track *song;
 
-	db = get_db_for_source (isource);
+	db = get_db_for_source (source);
 
 	song = create_ipod_song_from_entry (entry, filesize, media_type);
 	if (song != NULL) {
@@ -1619,13 +1657,13 @@ impl_track_added (RBRemovableMediaSource *source,
 		g_free (filename);
 
 		if (song->mediatype == ITDB_MEDIATYPE_PODCAST) {
-			add_to_podcasts (isource, song);
+			add_to_podcasts (source, song);
 		}
 		device = rb_ipod_db_get_device (priv->ipod_db);
 		if (device && itdb_device_supports_artwork (device)) {
-			request_artwork (isource, entry, db, song);
+			request_artwork (source, entry, db, song);
 		}
-		add_ipod_song_to_db (isource, db, song);
+		add_ipod_song_to_db (source, db, song);
 		rb_ipod_db_add_track (priv->ipod_db, song);
 	}
 
@@ -2086,7 +2124,7 @@ impl_show_properties (RBMediaPlayerSource *source, GtkWidget *info_box, GtkWidge
 	gtk_label_set_text (GTK_LABEL (widget), itdb_device_get_sysinfo (ipod_dev, "VisibleBuildID"));
 
 	str = g_string_new ("");
-	output_formats = rb_removable_media_source_get_format_descriptions (RB_REMOVABLE_MEDIA_SOURCE (source));
+	output_formats = rb_transfer_target_get_format_descriptions (RB_TRANSFER_TARGET (source));
 	for (t = output_formats; t != NULL; t = t->next) {
 		if (t != output_formats) {
 			g_string_append (str, "\n");
diff --git a/plugins/mtpdevice/rb-mtp-source.c b/plugins/mtpdevice/rb-mtp-source.c
index ff35ec4..82102aa 100644
--- a/plugins/mtpdevice/rb-mtp-source.c
+++ b/plugins/mtpdevice/rb-mtp-source.c
@@ -43,6 +43,8 @@
 #include "rb-builder-helpers.h"
 #include "rb-removable-media-manager.h"
 #include "rb-static-playlist-source.h"
+#include "rb-transfer-target.h"
+#include "rb-device-source.h"
 #include "rb-util.h"
 #include "rb-refstring.h"
 #include "rhythmdb.h"
@@ -64,6 +66,8 @@
 static void rb_mtp_source_constructed (GObject *object);
 static void rb_mtp_source_dispose (GObject *object);
 static void rb_mtp_source_finalize (GObject *object);
+static void rb_mtp_device_source_init (RBDeviceSourceInterface *interface);
+static void rb_mtp_source_transfer_target_init (RBTransferTargetInterface *interface);
 
 static void rb_mtp_source_set_property (GObject *object,
 			                guint prop_id,
@@ -75,24 +79,26 @@ static void rb_mtp_source_get_property (GObject *object,
 			                GParamSpec *pspec);
 
 static void impl_delete (RBSource *asource);
+static RBTrackTransferBatch *impl_paste (RBSource *asource, GList *entries);
 static gboolean impl_show_popup (RBDisplayPage *page);
 static GList* impl_get_ui_actions (RBDisplayPage *page);
 
-static gboolean impl_track_added (RBRemovableMediaSource *source,
+static gboolean impl_track_added (RBTransferTarget *target,
 				  RhythmDBEntry *entry,
 				  const char *dest,
 				  guint64 filesize,
 				  const char *media_type);
-static gboolean impl_track_add_error (RBRemovableMediaSource *source,
+static gboolean impl_track_add_error (RBTransferTarget *target,
 				      RhythmDBEntry *entry,
 				      const char *dest,
 				      GError *error);
-static char* impl_build_dest_uri (RBRemovableMediaSource *source,
+static char *impl_build_dest_uri (RBTransferTarget *target,
 				  RhythmDBEntry *entry,
 				  const char *media_type,
 				  const char *extension);
-static void impl_eject (RBRemovableMediaSource *source);
-static gboolean impl_can_eject (RBRemovableMediaSource *source);
+
+static void impl_eject (RBDeviceSource *source);
+static gboolean impl_can_eject (RBDeviceSource *source);
 
 static void mtp_device_open_cb (LIBMTP_mtpdevice_t *device, RBMtpSource *source);
 static void mtp_tracklist_cb (LIBMTP_track_t *tracks, RBMtpSource *source);
@@ -155,7 +161,13 @@ typedef struct
 
 } RBMtpSourcePrivate;
 
-G_DEFINE_DYNAMIC_TYPE(RBMtpSource, rb_mtp_source, RB_TYPE_MEDIA_PLAYER_SOURCE)
+G_DEFINE_DYNAMIC_TYPE_EXTENDED(
+	RBMtpSource,
+	rb_mtp_source,
+	RB_TYPE_MEDIA_PLAYER_SOURCE,
+	0,
+	G_IMPLEMENT_INTERFACE_DYNAMIC (RB_TYPE_DEVICE_SOURCE, rb_mtp_device_source_init)
+	G_IMPLEMENT_INTERFACE_DYNAMIC (RB_TYPE_TRANSFER_TARGET, rb_mtp_source_transfer_target_init))
 
 #define MTP_SOURCE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_MTP_SOURCE, RBMtpSourcePrivate))
 
@@ -174,7 +186,6 @@ rb_mtp_source_class_init (RBMtpSourceClass *klass)
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 	RBDisplayPageClass *page_class = RB_DISPLAY_PAGE_CLASS (klass);
 	RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
-	RBRemovableMediaSourceClass *rms_class = RB_REMOVABLE_MEDIA_SOURCE_CLASS (klass);
 	RBMediaPlayerSourceClass *mps_class = RB_MEDIA_PLAYER_SOURCE_CLASS (klass);
 
 	object_class->constructed = rb_mtp_source_constructed;
@@ -195,13 +206,7 @@ rb_mtp_source_class_init (RBMtpSourceClass *klass)
 	source_class->impl_can_copy = (RBSourceFeatureFunc) rb_true_function;
 	source_class->impl_can_cut = (RBSourceFeatureFunc) rb_false_function;
 	source_class->impl_delete = impl_delete;
-
-	rms_class->impl_track_added = impl_track_added;
-	rms_class->impl_track_add_error = impl_track_add_error;
-	rms_class->impl_build_dest_uri = impl_build_dest_uri;
-	rms_class->impl_should_paste = rb_removable_media_source_should_paste_no_duplicate;
-	rms_class->impl_can_eject = impl_can_eject;
-	rms_class->impl_eject = impl_eject;
+	source_class->impl_paste = impl_paste;
 
 	mps_class->impl_get_entries = impl_get_entries;
 	mps_class->impl_get_capacity = impl_get_capacity;
@@ -238,6 +243,21 @@ rb_mtp_source_class_init (RBMtpSourceClass *klass)
 }
 
 static void
+rb_mtp_device_source_init (RBDeviceSourceInterface *interface)
+{
+	interface->can_eject = impl_can_eject;
+	interface->eject = impl_eject;
+}
+
+static void
+rb_mtp_source_transfer_target_init (RBTransferTargetInterface *interface)
+{
+	interface->build_dest_uri = impl_build_dest_uri;
+	interface->track_added = impl_track_added;
+	interface->track_add_error = impl_track_add_error;
+}
+
+static void
 rb_mtp_source_class_finalize (RBMtpSourceClass *klass)
 {
 }
@@ -570,7 +590,6 @@ rb_mtp_source_new (RBShell *shell,
 					      "entry-type", entry_type,
 					      "shell", shell,
 					      "visibility", TRUE,
-					      "volume", NULL,
 					      "raw-device", device,
 #if defined(HAVE_GUDEV)
 					      "udev-device", udev_device,
@@ -1017,6 +1036,12 @@ impl_delete (RBSource *source)
 	rb_list_destroy_free (sel, (GDestroyNotify) rhythmdb_entry_unref);
 }
 
+static RBTrackTransferBatch *
+impl_paste (RBSource *source, GList *entries)
+{
+	return rb_transfer_target_transfer (RB_TRANSFER_TARGET (source), entries);
+}
+
 static gboolean
 impl_show_popup (RBDisplayPage *page)
 {
@@ -1086,14 +1111,14 @@ request_album_art_idle (RequestAlbumArtData *data)
 }
 
 static gboolean
-impl_track_added (RBRemovableMediaSource *source,
+impl_track_added (RBTransferTarget *target,
 		  RhythmDBEntry *entry,
 		  const char *dest,
 		  guint64 filesize,
 		  const char *media_type)
 {
 	LIBMTP_track_t *track = NULL;
-	RBMtpSourcePrivate *priv = MTP_SOURCE_GET_PRIVATE (source);
+	RBMtpSourcePrivate *priv = MTP_SOURCE_GET_PRIVATE (target);
 	RhythmDB *db;
 	RhythmDBEntry *mtp_entry;
 
@@ -1104,9 +1129,9 @@ impl_track_added (RBRemovableMediaSource *source,
 	}
 	g_hash_table_remove (priv->track_transfer_map, dest);
 
-	db = get_db_for_source (RB_MTP_SOURCE (source));
+	db = get_db_for_source (RB_MTP_SOURCE (target));
 	/* entry_map takes ownership of the track here */
-	mtp_entry = add_mtp_track_to_db (RB_MTP_SOURCE (source), db, track);
+	mtp_entry = add_mtp_track_to_db (RB_MTP_SOURCE (target), db, track);
 	g_object_unref (db);
 
 	if (strcmp (track->album, _("Unknown")) != 0) {
@@ -1116,21 +1141,21 @@ impl_track_added (RBRemovableMediaSource *source,
 	if (priv->album_art_supported) {
 		RequestAlbumArtData *artdata;
 		artdata = g_new0 (RequestAlbumArtData, 1);
-		artdata->source = g_object_ref (source);
+		artdata->source = g_object_ref (target);
 		artdata->entry = rhythmdb_entry_ref (mtp_entry);
 		g_idle_add ((GSourceFunc) request_album_art_idle, artdata);
 	}
-	queue_free_space_update (RB_MTP_SOURCE (source));
+	queue_free_space_update (RB_MTP_SOURCE (target));
 	return FALSE;
 }
 
 static gboolean
-impl_track_add_error (RBRemovableMediaSource *source,
+impl_track_add_error (RBTransferTarget *target,
 		      RhythmDBEntry *entry,
 		      const char *dest,
 		      GError *error)
 {
-	RBMtpSourcePrivate *priv = MTP_SOURCE_GET_PRIVATE (source);
+	RBMtpSourcePrivate *priv = MTP_SOURCE_GET_PRIVATE (target);
 	/* we don't actually do anything with the error here, we just need to clean up the transfer map */
 	LIBMTP_track_t *track = g_hash_table_lookup (priv->track_transfer_map, dest);
 	if (track != NULL) {
@@ -1236,7 +1261,7 @@ prepare_encoder_sink_cb (RBEncoderFactory *factory,
 }
 
 static char *
-impl_build_dest_uri (RBRemovableMediaSource *source,
+impl_build_dest_uri (RBTransferTarget *target,
 		     RhythmDBEntry *entry,
 		     const char *media_type,
 		     const char *extension)
@@ -1248,7 +1273,7 @@ impl_build_dest_uri (RBRemovableMediaSource *source,
 	if (media_type == NULL) {
 		media_type = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MEDIA_TYPE);
 	}
-	filetype = media_type_to_filetype (RB_MTP_SOURCE (source), media_type);
+	filetype = media_type_to_filetype (RB_MTP_SOURCE (target), media_type);
 	rb_debug ("using libmtp filetype %d (%s) for source media type %s",
 		  filetype,
 		  LIBMTP_Get_Filetype_Description (filetype),
@@ -1607,7 +1632,7 @@ impl_show_properties (RBMediaPlayerSource *source, GtkWidget *info_box, GtkWidge
 	gtk_label_set_text (GTK_LABEL (widget), priv->manufacturer);
 
 	str = g_string_new ("");
-	output_formats = rb_removable_media_source_get_format_descriptions (RB_REMOVABLE_MEDIA_SOURCE (source));
+	output_formats = rb_transfer_target_get_format_descriptions (RB_TRANSFER_TARGET (source));
 	for (t = output_formats; t != NULL; t = t->next) {
 		if (t != output_formats) {
 			g_string_append (str, "\n");
@@ -1669,13 +1694,13 @@ prepare_encoder_source_cb (RBEncoderFactory *factory,
 }
 
 static gboolean
-impl_can_eject (RBRemovableMediaSource *source)
+impl_can_eject (RBDeviceSource *source)
 {
 	return TRUE;
 }
 
 static void
-impl_eject (RBRemovableMediaSource *source)
+impl_eject (RBDeviceSource *source)
 {
 	rb_display_page_delete_thyself (RB_DISPLAY_PAGE (source));
 }
diff --git a/plugins/mtpdevice/rb-mtp-source.h b/plugins/mtpdevice/rb-mtp-source.h
index 19e7c0e..ca4eb5d 100644
--- a/plugins/mtpdevice/rb-mtp-source.h
+++ b/plugins/mtpdevice/rb-mtp-source.h
@@ -29,7 +29,6 @@
 #define __RB_MTP_SOURCE_H
 
 #include "rb-shell.h"
-#include "rb-removable-media-source.h"
 #include "rb-media-player-source.h"
 #include "rhythmdb.h"
 #include <libmtp.h>
diff --git a/shell/rb-removable-media-manager.c b/shell/rb-removable-media-manager.c
index 747787f..3f54dc9 100644
--- a/shell/rb-removable-media-manager.c
+++ b/shell/rb-removable-media-manager.c
@@ -48,7 +48,7 @@
 
 #include "rb-removable-media-manager.h"
 #include "rb-library-source.h"
-#include "rb-removable-media-source.h"
+#include "rb-device-source.h"
 
 #include "rb-shell.h"
 #include "rb-shell-player.h"
@@ -644,7 +644,7 @@ static void
 rb_removable_media_manager_add_volume (RBRemovableMediaManager *mgr, GVolume *volume)
 {
 	RBRemovableMediaManagerPrivate *priv = GET_PRIVATE (mgr);
-	RBRemovableMediaSource *source = NULL;
+	RBSource *source = NULL;
 	GMount *mount;
 
 	g_assert (volume != NULL);
@@ -677,18 +677,17 @@ rb_removable_media_manager_add_volume (RBRemovableMediaManager *mgr, GVolume *vo
 
 	if (source) {
 		g_hash_table_insert (priv->volume_mapping, volume, source);
-		rb_removable_media_manager_append_media_source (mgr, RB_SOURCE (source));
+		rb_removable_media_manager_append_media_source (mgr, source);
 	} else {
 		rb_debug ("Unhandled media");
 	}
-
 }
 
 static void
 rb_removable_media_manager_remove_volume (RBRemovableMediaManager *mgr, GVolume *volume)
 {
 	RBRemovableMediaManagerPrivate *priv = GET_PRIVATE (mgr);
-	RBRemovableMediaSource *source;
+	RBSource *source;
 
 	g_assert (volume != NULL);
 
@@ -703,7 +702,7 @@ static void
 rb_removable_media_manager_add_mount (RBRemovableMediaManager *mgr, GMount *mount)
 {
 	RBRemovableMediaManagerPrivate *priv = GET_PRIVATE (mgr);
-	RBRemovableMediaSource *source = NULL;
+	RBSource *source = NULL;
 	GVolume *volume;
 	GFile *mount_root;
 	char *mountpoint;
@@ -753,7 +752,7 @@ rb_removable_media_manager_add_mount (RBRemovableMediaManager *mgr, GMount *moun
 
 	if (source) {
 		g_hash_table_insert (priv->mount_mapping, mount, source);
-		rb_removable_media_manager_append_media_source (mgr, RB_SOURCE (source));
+		rb_removable_media_manager_append_media_source (mgr, source);
 	} else {
 		rb_debug ("Unhandled media");
 	}
@@ -765,7 +764,7 @@ static void
 rb_removable_media_manager_remove_mount (RBRemovableMediaManager *mgr, GMount *mount)
 {
 	RBRemovableMediaManagerPrivate *priv = GET_PRIVATE (mgr);
-	RBRemovableMediaSource *source;
+	RBSource *source;
 
 	g_assert (mount != NULL);
 
@@ -830,25 +829,19 @@ rb_removable_media_manager_source_can_eject (RBRemovableMediaManager *mgr)
 {
 	RBRemovableMediaManagerPrivate *priv = GET_PRIVATE (mgr);
 
-	if (RB_IS_REMOVABLE_MEDIA_SOURCE (priv->selected_source) == FALSE) {
+	if (RB_IS_DEVICE_SOURCE (priv->selected_source) == FALSE) {
 		return FALSE;
 	}
-
-	return rb_removable_media_source_can_eject (RB_REMOVABLE_MEDIA_SOURCE (priv->selected_source));
+	return rb_device_source_can_eject (RB_DEVICE_SOURCE (priv->selected_source));
 }
 
 static void
 rb_removable_media_manager_cmd_eject_medium (GtkAction *action, RBRemovableMediaManager *mgr)
 {
 	RBRemovableMediaManagerPrivate *priv = GET_PRIVATE (mgr);
-	RBRemovableMediaSource *source;
-
-	if (RB_IS_REMOVABLE_MEDIA_SOURCE (priv->selected_source) == FALSE) {
-		return;
+	if (RB_IS_DEVICE_SOURCE (priv->selected_source)) {
+		rb_device_source_eject (RB_DEVICE_SOURCE (priv->selected_source));
 	}
-
-	source = RB_REMOVABLE_MEDIA_SOURCE (priv->selected_source);
-	rb_removable_media_source_eject (source);
 }
 
 static void
diff --git a/sources/Makefile.am b/sources/Makefile.am
index eb9e217..7020d48 100644
--- a/sources/Makefile.am
+++ b/sources/Makefile.am
@@ -14,13 +14,14 @@ sourceinclude_HEADERS = 		\
 	rb-display-page-tree.h		\
 	rb-display-page-model.h		\
 	rb-browser-source.h		\
-	rb-removable-media-source.h	\
 	rb-media-player-source.h	\
 	rb-playlist-source.h		\
 	rb-playlist-xml.h		\
 	rb-auto-playlist-source.h	\
 	rb-static-playlist-source.h	\
-	rb-source-search-basic.h
+	rb-source-search-basic.h	\
+	rb-device-source.h		\
+	rb-transfer-target.h
 
 libsources_la_SOURCES = 		\
 	$(sourceinclude_HEADERS)	\
@@ -34,7 +35,6 @@ libsources_la_SOURCES = 		\
 	rb-browser-source.c		\
 	rb-library-source.c		\
 	rb-library-source.h		\
-	rb-removable-media-source.c	\
 	rb-media-player-source.c	\
 	rb-playlist-source.c            \
 	rb-auto-playlist-source.c	\
@@ -46,6 +46,8 @@ libsources_la_SOURCES = 		\
 	rb-import-errors-source.c	\
 	rb-import-errors-source.h	\
 	rb-source-search-basic.c	\
+	rb-device-source.c		\
+	rb-transfer-target.c		\
 	$(NULL)
 
 INCLUDES =						\
diff --git a/sources/rb-device-source.c b/sources/rb-device-source.c
new file mode 100644
index 0000000..75eb033
--- /dev/null
+++ b/sources/rb-device-source.c
@@ -0,0 +1,418 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ *  Copyright (C) 2011  Jonathan Matthew  <jonathan d14n org>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The Rhythmbox authors hereby grant permission for non-GPL compatible
+ *  GStreamer plugins to be used and distributed together with GStreamer
+ *  and Rhythmbox. This permission is above and beyond the permissions granted
+ *  by the GPL license by which Rhythmbox is covered. If you modify this code
+ *  you may extend this exception to your version of the code, but you are not
+ *  obligated to do so. If you do not wish to do so, delete this exception
+ *  statement from your version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#include <config.h>
+
+#include <gio/gio.h>
+#include <glib/gi18n.h>
+
+#include "rb-device-source.h"
+#include "rb-source.h"
+#include "rb-debug.h"
+#include "rb-dialog.h"
+
+G_DEFINE_INTERFACE (RBDeviceSource, rb_device_source, 0)
+
+/**
+ * SECTION:rb-device-source
+ * @short_description: interface for sources based on physical devices
+ * @include: rb-device-source.h
+ *
+ * Sources that represent physical devices should implement this interface.
+ * It exposes the ability to eject the device, and also can be used to
+ * implement some #RBSource methods by using details from a #GVolume or
+ * #GMount accessed via 'volume' and 'mount' properties on the source object.
+ * Devices that are not based on a #GVolume or #GMount can still use the
+ * interface, but they must provide implementations of the @can_eject and
+ * @eject methods.
+ */
+
+static gboolean
+default_can_eject (RBDeviceSource *source)
+{
+	gboolean result = FALSE;
+	GVolume *volume = NULL;
+	GMount *mount = NULL;
+
+	if (g_object_class_find_property (G_OBJECT_GET_CLASS (source), "volume")) {
+		g_object_get (source, "volume", &volume, NULL);
+	}
+	if (g_object_class_find_property (G_OBJECT_GET_CLASS (source), "mount")) {
+		g_object_get (source, "mount", &mount, NULL);
+	}
+
+	if (volume != NULL) {
+		result = g_volume_can_eject (volume);
+
+		g_object_unref (volume);
+		if (mount != NULL) {
+			g_object_unref (mount);
+		}
+	} else if (mount != NULL) {
+		result = g_mount_can_eject (mount) || g_mount_can_unmount (mount);
+
+		if (mount != NULL) {
+			g_object_unref (mount);
+		}
+	}
+
+	return result;
+}
+
+static void
+eject_cb (GObject *object, GAsyncResult *result, gpointer nothing)
+{
+	GError *error = NULL;
+
+	if (G_IS_VOLUME (object)) {
+		GVolume *volume = G_VOLUME (object);
+
+		rb_debug ("finishing ejection of volume");
+		g_volume_eject_with_operation_finish (volume, result, &error);
+	} else if (G_IS_MOUNT (object)) {
+		GMount *mount = G_MOUNT (object);
+
+		rb_debug ("finishing ejection of mount");
+		g_mount_eject_with_operation_finish (mount, result, &error);
+	}
+
+	if (error != NULL) {
+		if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_FAILED_HANDLED)) {
+			rb_error_dialog (NULL, _("Unable to eject"), "%s", error->message);
+		} else {
+			rb_debug ("eject failure has already been handled");
+		}
+		g_error_free (error);
+	}
+}
+
+static void
+unmount_cb (GObject *object, GAsyncResult *result, gpointer nothing)
+{
+	GMount *mount = G_MOUNT (object);
+	GError *error = NULL;
+
+	rb_debug ("finishing unmount of mount");
+	g_mount_unmount_with_operation_finish (mount, result, &error);
+	if (error != NULL) {
+		if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_FAILED_HANDLED)) {
+			rb_error_dialog (NULL, _("Unable to unmount"), "%s", error->message);
+		} else {
+			rb_debug ("unmount failure has already been handled");
+		}
+		g_error_free (error);
+	}
+}
+
+static void
+default_eject (RBDeviceSource *source)
+{
+	GVolume *volume = NULL;
+	GMount *mount = NULL;
+
+	if (g_object_class_find_property (G_OBJECT_GET_CLASS (source), "volume")) {
+		g_object_get (source, "volume", &volume, NULL);
+	}
+	if (g_object_class_find_property (G_OBJECT_GET_CLASS (source), "mount")) {
+		g_object_get (source, "mount", &mount, NULL);
+	}
+
+	/* try ejecting based on volume first, then based on the mount,
+	 * and finally try unmounting.
+	 */
+	if (volume != NULL) {
+		if (g_volume_can_eject (volume)) {
+			rb_debug ("ejecting volume");
+			g_volume_eject_with_operation (volume,
+						       G_MOUNT_UNMOUNT_NONE,
+						       NULL,
+						       NULL,
+						       (GAsyncReadyCallback) eject_cb,
+						       NULL);
+		} else {
+			/* this should never happen; the eject command will be
+			 * insensitive if the selected source cannot be ejected.
+			 */
+			rb_debug ("don't know what to do with this volume");
+		}
+	} else if (mount != NULL) {
+		if (g_mount_can_eject (mount)) {
+			rb_debug ("ejecting mount");
+			g_mount_eject_with_operation (mount,
+						      G_MOUNT_UNMOUNT_NONE,
+						      NULL,
+						      NULL,
+						      (GAsyncReadyCallback) eject_cb,
+						      NULL);
+		} else if (g_mount_can_unmount (mount)) {
+			rb_debug ("unmounting mount");
+			g_mount_unmount_with_operation (mount,
+							G_MOUNT_UNMOUNT_NONE,
+							NULL,
+							NULL,
+							(GAsyncReadyCallback) unmount_cb,
+							NULL);
+		} else {
+			/* this should never happen; the eject command will be
+			 * insensitive if the selected source cannot be ejected.
+			 */
+			rb_debug ("don't know what to do with this mount");
+		}
+	}
+
+	if (volume != NULL) {
+		g_object_unref (volume);
+	}
+	if (mount != NULL) {
+		g_object_unref (mount);
+	}
+}
+
+/**
+ * rb_device_can_eject:
+ * @source: a #RBDeviceSource
+ *
+ * Checks if the device that the source represents can be ejected.
+ *
+ * Return value: %TRUE if the device can be ejected
+ */
+gboolean
+rb_device_source_can_eject (RBDeviceSource *source)
+{
+	RBDeviceSourceInterface *iface = RB_DEVICE_SOURCE_GET_IFACE (source);
+	return iface->can_eject (source);
+}
+
+/**
+ * rb_device_source_eject:
+ * @source: a #RBDeviceSource
+ *
+ * Ejects the device that the source represents.
+ */
+void
+rb_device_source_eject (RBDeviceSource *source)
+{
+	RBDeviceSourceInterface *iface = RB_DEVICE_SOURCE_GET_IFACE (source);
+	iface->eject (source);
+}
+
+/**
+ * rb_device_source_want_uri:
+ * @source: a #RBDeviceSource
+ * @uri: a URI to consider
+ *
+ * Checks whether @uri identifies a path underneath the
+ * device's mount point.  Should be used to implement
+ * the #RBSource impl_want_uri method.
+ *
+ * Return value: URI match strength
+ */
+int
+rb_device_source_want_uri (RBDeviceSource *source, const char *uri)
+{
+	GMount *mount = NULL;
+	GVolume *volume = NULL;
+	GFile *file;
+	char *device_path, *uri_path;
+	int retval;
+	int len;
+
+	retval = 0;
+
+	/* ignore anything that isn't a local file */
+	file = g_file_new_for_uri (uri);
+	if (g_file_has_uri_scheme (file, "file") == FALSE) {
+		g_object_unref (file);
+		return 0;
+	}
+
+	/* Deal with the mount root being passed, eg. file:///media/IPODNAME */
+	if (g_object_class_find_property (G_OBJECT_GET_CLASS (source), "mount")) {
+		g_object_get (source, "mount", &volume, NULL);
+	}
+	if (mount != NULL) {
+		GFile *root;
+
+		root = g_mount_get_root (mount);
+		retval = g_file_equal (root, file) ? 100 : 0;
+		g_object_unref (root);
+		if (retval) {
+			g_object_unref (file);
+			g_object_unref (mount);
+			return retval;
+		}
+		volume = g_mount_get_volume (mount);
+		g_object_unref (mount);
+	} else {
+		if (g_object_class_find_property (G_OBJECT_GET_CLASS (source), "volume")) {
+			g_object_get (source, "volume", &volume, NULL);
+		}
+	}
+
+	if (volume == NULL) {
+		g_object_unref (file);
+		return 0;
+	}
+
+	/* Deal with the path to the device node being passed */
+	device_path = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE);
+	g_object_unref (volume);
+	if (device_path == NULL) {
+		g_object_unref (file);
+		return 0;
+	}
+
+	uri_path = g_file_get_path (file);
+	g_object_unref (file);
+	if (uri_path == NULL)
+		return 0;
+	len = strlen (uri_path);
+	if (uri_path[len - 1] == '/') {
+		if (strncmp (uri_path, device_path, len - 1) == 0) {
+			retval = 100;
+		}
+	} else if (strcmp (uri_path, device_path) == 0) {
+		retval = 100;
+	}
+
+	g_free (device_path);
+	g_free (uri_path);
+	return retval;
+}
+
+/**
+ * rb_device_source_uri_is_source:
+ * @source: a #RBDeviceSource
+ * @uri: a URI to check
+ *
+ * Returns %TRUE if @uri matches @source.  This should be
+ * used to implement the impl_uri_is_source #RBSource method.
+ *
+ * Return value: %TRUE if @uri matches @source
+ */
+gboolean
+rb_device_source_uri_is_source (RBDeviceSource *source, const char *uri)
+{
+	return (rb_device_source_want_uri (source, uri) == 100);
+}
+
+/**
+ * rb_device_source_set_display_details:
+ * @source: a #RBDeviceSource
+ *
+ * Sets the icon and display name for a device-based source.
+ * The details come from the mount and/or volume.  This should
+ * be called in the source's constructed method.
+ */
+void
+rb_device_source_set_display_details (RBDeviceSource *source)
+{
+	GMount *mount = NULL;
+	GVolume *volume = NULL;
+	GIcon *icon = NULL;
+	char *display_name;
+	GdkPixbuf *pixbuf = NULL;
+
+	if (g_object_class_find_property (G_OBJECT_GET_CLASS (source), "volume")) {
+		g_object_get (source, "volume", &volume, NULL);
+	}
+	if (g_object_class_find_property (G_OBJECT_GET_CLASS (source), "mount")) {
+		g_object_get (source, "mount", &mount, NULL);
+	}
+
+	/* prefer mount details to volume details, as the nautilus sidebar does */
+	if (mount != NULL) {
+		mount = g_object_ref (mount);
+	} else if (volume != NULL) {
+		mount = g_volume_get_mount (volume);
+	} else {
+		mount = NULL;
+	}
+
+	if (mount != NULL) {
+		display_name = g_mount_get_name (mount);
+		icon = g_mount_get_icon (mount);
+		rb_debug ("details from mount: display name = %s, icon = %p", display_name, icon);
+	} else if (volume != NULL) {
+		display_name = g_volume_get_name (volume);
+		icon = g_volume_get_icon (volume);
+		rb_debug ("details from volume: display name = %s, icon = %p", display_name, icon);
+	} else {
+		display_name = g_strdup ("Unknown Device");
+		icon = g_themed_icon_new ("multimedia-player");
+	}
+
+	g_object_set (source, "name", display_name, NULL);
+	g_free (display_name);
+
+	if (icon == NULL) {
+		rb_debug ("no icon set");
+		pixbuf = NULL;
+	} else if (G_IS_THEMED_ICON (icon)) {
+		GtkIconTheme *theme;
+		const char * const *names;
+		gint size;
+		int i;
+
+		theme = gtk_icon_theme_get_default ();
+		gtk_icon_size_lookup (RB_SOURCE_ICON_SIZE, &size, NULL);
+
+		i = 0;
+		names = g_themed_icon_get_names (G_THEMED_ICON (icon));
+		while (names[i] != NULL && pixbuf == NULL) {
+			rb_debug ("looking up themed icon: %s", names[i]);
+			pixbuf = gtk_icon_theme_load_icon (theme, names[i], size, 0, NULL);
+			i++;
+		}
+
+	} else if (G_IS_LOADABLE_ICON (icon)) {
+		rb_debug ("loading of GLoadableIcons is not implemented yet");
+		pixbuf = NULL;
+	}
+
+	if (pixbuf != NULL) {
+		g_object_set (source, "pixbuf", pixbuf, NULL);
+		g_object_unref (pixbuf);
+	}
+	if (mount != NULL) {
+		g_object_unref (mount);
+	}
+	if (volume != NULL) {
+		g_object_unref (volume);
+	}
+	if (icon != NULL) {
+		g_object_unref (icon);
+	}
+}
+
+static void
+rb_device_source_default_init (RBDeviceSourceInterface *interface)
+{
+	interface->can_eject = default_can_eject;
+	interface->eject = default_eject;
+}
diff --git a/sources/rb-device-source.h b/sources/rb-device-source.h
new file mode 100644
index 0000000..f788206
--- /dev/null
+++ b/sources/rb-device-source.h
@@ -0,0 +1,63 @@
+/*
+ *  Copyright (C) 2011 Jonathan Matthew  <jonathan d14n org>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The Rhythmbox authors hereby grant permission for non-GPL compatible
+ *  GStreamer plugins to be used and distributed together with GStreamer
+ *  and Rhythmbox. This permission is above and beyond the permissions granted
+ *  by the GPL license by which Rhythmbox is covered. If you modify this code
+ *  you may extend this exception to your version of the code, but you are not
+ *  obligated to do so. If you do not wish to do so, delete this exception
+ *  statement from your version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
+ *
+ */
+
+#ifndef RB_DEVICE_SOURCE_H
+#define RB_DEVICE_SOURCE_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define RB_TYPE_DEVICE_SOURCE         (rb_device_source_get_type ())
+#define RB_DEVICE_SOURCE(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), RB_TYPE_DEVICE_SOURCE, RBDeviceSource))
+#define RB_IS_DEVICE_SOURCE(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), RB_TYPE_DEVICE_SOURCE))
+#define RB_DEVICE_SOURCE_GET_IFACE(o) (G_TYPE_INSTANCE_GET_INTERFACE ((o), RB_TYPE_DEVICE_SOURCE, RBDeviceSourceInterface))
+
+typedef struct _RBDeviceSource RBDeviceSource;
+typedef struct _RBDeviceSourceInterface RBDeviceSourceInterface;
+
+struct _RBDeviceSourceInterface
+{
+	GTypeInterface g_iface;
+
+	gboolean	(*can_eject)		(RBDeviceSource *source);
+	void		(*eject)		(RBDeviceSource *source);
+};
+
+GType		rb_device_source_get_type	(void);
+
+gboolean	rb_device_source_can_eject	(RBDeviceSource *source);
+void		rb_device_source_eject		(RBDeviceSource *source);
+
+int		rb_device_source_want_uri	(RBDeviceSource *source, const char *uri);
+gboolean	rb_device_source_uri_is_source	(RBDeviceSource *source, const char *uri);
+
+void		rb_device_source_set_display_details (RBDeviceSource *source);
+
+G_END_DECLS
+
+#endif  /* RB_DEVICE_SOURCE_H */
diff --git a/sources/rb-media-player-source.c b/sources/rb-media-player-source.c
index 2be14e3..405bdac 100644
--- a/sources/rb-media-player-source.c
+++ b/sources/rb-media-player-source.c
@@ -35,6 +35,7 @@
 
 #include "rb-shell.h"
 #include "rb-media-player-source.h"
+#include "rb-transfer-target.h"
 #include "rb-sync-settings.h"
 #include "rb-sync-settings-ui.h"
 #include "rb-sync-state.h"
@@ -44,9 +45,7 @@
 #include "rb-file-helpers.h"
 #include "rb-builder-helpers.h"
 #include "rb-playlist-manager.h"
-#include "rb-podcast-manager.h"
 #include "rb-util.h"
-#include "rb-segmented-bar.h"
 
 typedef struct {
 	RBSyncSettings *sync_settings;
@@ -67,9 +66,10 @@ typedef struct {
 	/* sync state */
 	RBSyncState *sync_state;
 
+	GstEncodingTarget *encoding_target;
 } RBMediaPlayerSourcePrivate;
 
-G_DEFINE_TYPE (RBMediaPlayerSource, rb_media_player_source, RB_TYPE_REMOVABLE_MEDIA_SOURCE);
+G_DEFINE_TYPE (RBMediaPlayerSource, rb_media_player_source, RB_TYPE_BROWSER_SOURCE);
 
 #define MEDIA_PLAYER_SOURCE_GET_PRIVATE(o)   (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_MEDIA_PLAYER_SOURCE, RBMediaPlayerSourcePrivate))
 
@@ -90,6 +90,11 @@ static void rb_media_player_source_constructed (GObject *object);
 static void sync_cmd (GtkAction *action, RBSource *source);
 static gboolean sync_idle_delete_entries (RBMediaPlayerSource *source);
 
+static gboolean impl_receive_drag (RBDisplayPage *page, GtkSelectionData *data);
+static void impl_delete_thyself (RBDisplayPage *page);
+
+static char *impl_get_delete_action (RBSource *source);
+
 static GtkActionEntry rb_media_player_source_actions[] = {
 	{ "MediaPlayerSourceSync", GTK_STOCK_REFRESH, N_("Sync with Library"), NULL,
 	  N_("Synchronize media player with the library"),
@@ -99,7 +104,8 @@ static GtkActionEntry rb_media_player_source_actions[] = {
 enum
 {
 	PROP_0,
-	PROP_DEVICE_SERIAL
+	PROP_DEVICE_SERIAL,
+	PROP_ENCODING_TARGET
 };
 
 static GtkActionGroup *action_group = NULL;
@@ -130,13 +136,27 @@ static void
 rb_media_player_source_class_init (RBMediaPlayerSourceClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	RBDisplayPageClass *page_class = RB_DISPLAY_PAGE_CLASS (klass);
+	RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
+	RBBrowserSourceClass *browser_source_class = RB_BROWSER_SOURCE_CLASS (klass);
 
 	object_class->dispose = rb_media_player_source_dispose;
-
 	object_class->set_property = rb_media_player_source_set_property;
 	object_class->get_property = rb_media_player_source_get_property;
 	object_class->constructed = rb_media_player_source_constructed;
 
+	page_class->receive_drag = impl_receive_drag;
+	page_class->delete_thyself = impl_delete_thyself;
+
+	source_class->impl_can_cut = (RBSourceFeatureFunc) rb_false_function;
+	source_class->impl_can_copy = (RBSourceFeatureFunc) rb_true_function;
+	source_class->impl_can_paste = (RBSourceFeatureFunc) rb_false_function;
+	source_class->impl_can_delete = (RBSourceFeatureFunc) rb_false_function;
+	source_class->impl_get_delete_action = impl_get_delete_action;
+	source_class->impl_delete = NULL;
+
+	browser_source_class->impl_has_drop_support = (RBBrowserSourceFeatureFunc) rb_false_function;
+
 	klass->impl_get_entries = NULL;
 	klass->impl_get_capacity = NULL;
 	klass->impl_get_free_space = NULL;
@@ -151,6 +171,18 @@ rb_media_player_source_class_init (RBMediaPlayerSourceClass *klass)
 							      "device serial number",
 							      NULL,
 							      G_PARAM_READABLE));
+	/**
+	 * RBMediaPlayerSource:encoding-target
+	 *
+	 * The #GstEncodingTarget for this device
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_ENCODING_TARGET,
+					 gst_param_spec_mini_object ("encoding-target",
+								     "encoding target",
+								     "GstEncodingTarget",
+								     GST_TYPE_ENCODING_TARGET,
+								     G_PARAM_READWRITE));
 
 	g_type_class_add_private (klass, sizeof (RBMediaPlayerSourcePrivate));
 }
@@ -170,6 +202,11 @@ rb_media_player_source_dispose (GObject *object)
 		priv->sync_state = NULL;
 	}
 
+	if (priv->encoding_target) {
+		gst_encoding_target_unref (priv->encoding_target);
+		priv->encoding_target = NULL;
+	}
+
 	G_OBJECT_CLASS (rb_media_player_source_parent_class)->dispose (object);
 }
 
@@ -184,8 +221,14 @@ rb_media_player_source_set_property (GObject *object,
 			     const GValue *value,
 			     GParamSpec *pspec)
 {
-	/*RBMediaPlayerSourcePrivate *priv = MEDIA_PLAYER_SOURCE_GET_PRIVATE (object);*/
+	RBMediaPlayerSourcePrivate *priv = MEDIA_PLAYER_SOURCE_GET_PRIVATE (object);
 	switch (prop_id) {
+	case PROP_ENCODING_TARGET:
+		if (priv->encoding_target) {
+			gst_encoding_target_unref (priv->encoding_target);
+		}
+		priv->encoding_target = GST_ENCODING_TARGET (gst_value_dup_mini_object (value));
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -198,11 +241,14 @@ rb_media_player_source_get_property (GObject *object,
 			     GValue *value,
 			     GParamSpec *pspec)
 {
-	/*RBMediaPlayerSourcePrivate *priv = MEDIA_PLAYER_SOURCE_GET_PRIVATE (object);*/
+	RBMediaPlayerSourcePrivate *priv = MEDIA_PLAYER_SOURCE_GET_PRIVATE (object);
 	switch (prop_id) {
 	case PROP_DEVICE_SERIAL:
 		/* not actually supported in the base class */
 		break;
+	case PROP_ENCODING_TARGET:
+		gst_value_set_mini_object (value, GST_MINI_OBJECT (priv->encoding_target));
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -762,6 +808,118 @@ sync_cmd (GtkAction *action, RBSource *source)
 	rb_media_player_source_sync (RB_MEDIA_PLAYER_SOURCE (source));
 }
 
+static RhythmDB *
+get_db_for_source (RBSource *source)
+{
+	RBShell *shell;
+	RhythmDB *db;
+
+	g_object_get (source, "shell", &shell, NULL);
+	g_object_get (shell, "db", &db, NULL);
+	g_object_unref (shell);
+
+	return db;
+}
+
+gboolean
+impl_receive_drag (RBDisplayPage *page, GtkSelectionData *data)
+{
+	GList *entries;
+	RhythmDB *db;
+	char *type;
+
+	entries = NULL;
+	type = gdk_atom_name (gtk_selection_data_get_data_type (data));
+        db = get_db_for_source (RB_SOURCE (page));
+
+	if (strcmp (type, "text/uri-list") == 0) {
+		GList *list;
+		GList *i;
+
+		rb_debug ("parsing uri list");
+		list = rb_uri_list_parse ((const char *) gtk_selection_data_get_data (data));
+
+		for (i = list; i != NULL; i = g_list_next (i)) {
+			char *uri;
+			RhythmDBEntry *entry;
+
+			if (i->data == NULL)
+				continue;
+
+			uri = i->data;
+			entry = rhythmdb_entry_lookup_by_location (db, uri);
+
+			if (entry == NULL) {
+				/* add to the library */
+				rb_debug ("received drop of unknown uri: %s", uri);
+			} else {
+				/* add to list of entries to copy */
+				entries = g_list_prepend (entries, entry);
+			}
+			g_free (uri);
+		}
+		g_list_free (list);
+	} else if (strcmp (type, "application/x-rhythmbox-entry") == 0) {
+		char **list;
+		char **i;
+
+		rb_debug ("parsing entry ids");
+		list = g_strsplit ((const char*) gtk_selection_data_get_data (data), "\n", -1);
+		for (i = list; *i != NULL; i++) {
+			RhythmDBEntry *entry;
+			gulong id;
+
+			id = atoi (*i);
+			entry = rhythmdb_entry_lookup_by_id (db, id);
+			if (entry != NULL)
+				entries = g_list_prepend (entries, entry);
+		}
+
+		g_strfreev (list);
+	} else {
+		rb_debug ("received unknown drop type");
+	}
+
+	g_object_unref (db);
+	g_free (type);
+
+	if (entries) {
+		entries = g_list_reverse (entries);
+		if (rb_source_can_paste (RB_SOURCE (page))) {
+			rb_transfer_target_transfer (RB_TRANSFER_TARGET (page), entries);
+		}
+		g_list_free (entries);
+	}
+
+	return TRUE;
+}
+
+static char *
+impl_get_delete_action (RBSource *source)
+{
+	return g_strdup ("EditDelete");
+}
+
+static void
+impl_delete_thyself (RBDisplayPage *page)
+{
+	RhythmDB *db;
+	RBShell *shell;
+	RhythmDBEntryType *entry_type;
+
+	g_object_get (page, "shell", &shell, NULL);
+	g_object_get (shell, "db", &db, NULL);
+	g_object_unref (shell);
+
+	g_object_get (page, "entry-type", &entry_type, NULL);
+	rb_debug ("deleting all entries of type '%s'", rhythmdb_entry_type_get_name (entry_type));
+	rhythmdb_entry_delete_by_type (db, entry_type);
+	g_object_unref (entry_type);
+
+	rhythmdb_commit (db);
+	g_object_unref (db);
+}
+
 /* annotations for methods */
 
 /**
diff --git a/sources/rb-media-player-source.h b/sources/rb-media-player-source.h
index 4c802d0..eef8835 100644
--- a/sources/rb-media-player-source.h
+++ b/sources/rb-media-player-source.h
@@ -31,7 +31,7 @@
 #include <glib.h>
 
 #include <shell/rb-shell.h>
-#include <sources/rb-removable-media-source.h>
+#include <sources/rb-browser-source.h>
 #include <rhythmdb/rhythmdb.h>
 
 G_BEGIN_DECLS
@@ -50,12 +50,12 @@ typedef void (*RBMediaPlayerSourceDeleteCallback) (RBMediaPlayerSource *source,
 
 struct _RBMediaPlayerSource
 {
-	RBRemovableMediaSource parent_instance;
+	RBBrowserSource parent_instance;
 };
 
 struct _RBMediaPlayerSourceClass
 {
-	RBRemovableMediaSourceClass parent_class;
+	RBBrowserSourceClass parent_class;
 
 	/* class members */
 	void		(*impl_get_entries)	(RBMediaPlayerSource *source, const char *category, GHashTable *map);
diff --git a/sources/rb-transfer-target.c b/sources/rb-transfer-target.c
new file mode 100644
index 0000000..478127d
--- /dev/null
+++ b/sources/rb-transfer-target.c
@@ -0,0 +1,457 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ *  Copyright (C) 2011  Jonathan Matthew  <jonathan d14n org>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The Rhythmbox authors hereby grant permission for non-GPL compatible
+ *  GStreamer plugins to be used and distributed together with GStreamer
+ *  and Rhythmbox. This permission is above and beyond the permissions granted
+ *  by the GPL license by which Rhythmbox is covered. If you modify this code
+ *  you may extend this exception to your version of the code, but you are not
+ *  obligated to do so. If you do not wish to do so, delete this exception
+ *  statement from your version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <gst/gst.h>
+#include <gst/pbutils/encoding-target.h>
+#include <glib/gi18n.h>
+
+#include <sources/rb-transfer-target.h>
+#include <shell/rb-track-transfer-queue.h>
+#include <backends/rb-encoder.h>
+#include <lib/rb-debug.h>
+#include <lib/rb-file-helpers.h>
+#include <widgets/rb-dialog.h>
+
+/* arbitrary length limit for file extensions */
+#define EXTENSION_LENGTH_LIMIT	8
+
+G_DEFINE_INTERFACE (RBTransferTarget, rb_transfer_target, 0)
+
+/**
+ * SECTION:rb-transfer-target
+ * @short_description: interface for sources that can receive track transfers
+ * @include: rb-transfer-target.h
+ *
+ * Sources that can accept track transfers should implement this interface
+ * and call the associated functions to perform transfers.  The source
+ * needs to be able to construct target URIs for transfers, and can optionally
+ * perform its own processing after transfers have finished.  The source
+ * must also provide a #GstEncodingTarget that describes the formats it
+ * accepts.
+ */
+
+
+/**
+ * rb_transfer_target_build_dest_uri:
+ * @target: an #RBTransferTarget
+ * @entry: a #RhythmDBEntry being transferred
+ * @media_type: destination media type
+ * @extension: extension associated with destination media type
+ *
+ * Constructs a URI to use as the destination for a transfer or transcoding
+ * operation.  The URI may be on the device itself, if the device is mounted
+ * into the normal filesystem or through gvfs, or it may be a temporary
+ * location used to store the file before uploading it to the device.
+ *
+ * The destination URI should conform to the device's normal URI format,
+ * and should use the provided extension instead of the extension from
+ * the source entry.
+ *
+ * Return value: constructed URI
+ */
+char *
+rb_transfer_target_build_dest_uri (RBTransferTarget *target,
+				   RhythmDBEntry *entry,
+				   const char *media_type,
+				   const char *extension)
+{
+	RBTransferTargetInterface *iface = RB_TRANSFER_TARGET_GET_IFACE (target);
+	char *uri;
+
+	uri = iface->build_dest_uri (target, entry, media_type, extension);
+	if (uri != NULL) {
+		char *sane_uri;
+
+		sane_uri = rb_sanitize_uri_for_filesystem (uri);
+		g_return_val_if_fail (sane_uri != NULL, NULL);
+		g_free (uri);
+		uri = sane_uri;
+
+		rb_debug ("built dest uri for media type '%s', extension '%s': %s",
+			  media_type, extension, uri);
+	} else {
+		rb_debug ("couldn't build dest uri for media type %s, extension %s",
+			  media_type, extension);
+	}
+
+	return uri;
+}
+
+/**
+ * rb_transfer_target_track_added:
+ * @target: an #RBTransferTarget
+ * @entry: the source #RhythmDBEntry for the transfer
+ * @uri: the destination URI
+ * @filesize: size of the destination file
+ * @media_type: media type of the destination file
+ *
+ * This is called when a transfer to the target has completed.
+ * If the source's @track_added method returns %TRUE, the destination
+ * URI will be added to the database using the entry type for the device.
+ *
+ * If the target uses a temporary area as the destination for transfers,
+ * it can instead upload the destination file to the device and create an
+ * entry for it, then return %FALSE.
+ */
+void
+rb_transfer_target_track_added (RBTransferTarget *target,
+				RhythmDBEntry *entry,
+				const char *uri,
+				guint64 filesize,
+				const char *media_type)
+{
+	RBTransferTargetInterface *iface = RB_TRANSFER_TARGET_GET_IFACE (target);
+	gboolean add_to_db = TRUE;
+
+	if (iface->track_added)
+		add_to_db = iface->track_added (target, entry, uri, filesize, media_type);
+
+	if (add_to_db) {
+		RhythmDBEntryType *entry_type;
+		RhythmDB *db;
+		RBShell *shell;
+
+		g_object_get (target, "shell", &shell, NULL);
+		g_object_get (shell, "db", &db, NULL);
+		g_object_unref (shell);
+
+		g_object_get (target, "entry-type", &entry_type, NULL);
+		rhythmdb_add_uri_with_types (db, uri, entry_type, NULL, NULL);
+		g_object_unref (entry_type);
+
+		g_object_unref (db);
+	}
+}
+
+/**
+ * rb_transfer_target_track_add_error:
+ * @target: an #RBTransferTarget
+ * @entry: the source #RhythmDBEntry for the transfer
+ * @uri: the destination URI
+ * @error: the transfer error information
+ *
+ * This is called when a transfer fails.  If the source's
+ * impl_track_add_error implementation returns %TRUE, an error dialog
+ * will be displayed to the user containing the error message, unless
+ * the error indicates that the destination file already exists.
+ */
+void
+rb_transfer_target_track_add_error (RBTransferTarget *target,
+				    RhythmDBEntry *entry,
+				    const char *uri,
+				    GError *error)
+{
+	RBTransferTargetInterface *iface = RB_TRANSFER_TARGET_GET_IFACE (target);
+	gboolean show_dialog = TRUE;
+
+	/* hrm, want the subclass to decide whether to display the error and
+	 * whether to cancel the batch (may have some device-specific errors?)
+	 *
+	 * for now we'll just cancel on the most common things..
+	 */
+	if (iface->track_add_error)
+		show_dialog = iface->track_add_error (target, entry, uri, error);
+
+	if (show_dialog) {
+		if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS)) {
+			rb_debug ("not displaying 'file exists' error for %s", uri);
+		} else {
+			rb_error_dialog (NULL, _("Error transferring track"), "%s", error->message);
+		}
+	}
+}
+
+/**
+ * rb_transfer_target_get_format_descriptions:
+ * @target: an #RBTransferTarget
+ *
+ * Returns a #GList of allocated media format descriptions for
+ * the formats supported by the target.  The list and the strings
+ * it holds must be freed by the caller.
+ *
+ * Return value: (element-type utf8) (transfer full): list of descriptions.
+ */
+GList *
+rb_transfer_target_get_format_descriptions (RBTransferTarget *target)
+{
+	GstEncodingTarget *enctarget;
+	const GList *l;
+	GList *desc = NULL;
+	g_object_get (target, "encoding-target", &enctarget, NULL);
+	if (enctarget != NULL) {
+		for (l = gst_encoding_target_get_profiles (enctarget); l != NULL; l = l->next) {
+			GstEncodingProfile *profile = l->data;
+			desc = g_list_append (desc, g_strdup (gst_encoding_profile_get_description (profile)));
+		}
+		gst_encoding_target_unref (enctarget);
+	}
+	return desc;
+}
+
+/**
+ * rb_transfer_target_should_transfer:
+ * @target: an #RBTransferTarget
+ * @entry: a #RhythmDBEntry to consider transferring
+ *
+ * Checks whether @entry should be transferred to the target.
+ * The target can check whether a matching entry already exists on the device,
+ * for instance.  @rb_transfer_target_check_duplicate may form part of
+ * an implementation.  If this method returns %FALSE, the entry
+ * will be skipped.
+ *
+ * Return value: %TRUE if the entry should be transferred to the target
+ */
+gboolean
+rb_transfer_target_should_transfer (RBTransferTarget *target, RhythmDBEntry *entry)
+{
+	RBTransferTargetInterface *iface = RB_TRANSFER_TARGET_GET_IFACE (target);
+
+	return iface->should_transfer (target, entry);
+}
+
+/**
+ * rb_transfer_target_check_category:
+ * @target: an #RBTransferTarget
+ * @entry: a #RhythmDBEntry to check
+ *
+ * This checks that the entry type of @entry is in a suitable
+ * category for transfer.  This can be used to implement
+ * @should_transfer.
+ *
+ * Return value: %TRUE if the entry is in a suitable category
+ */
+gboolean
+rb_transfer_target_check_category (RBTransferTarget *target, RhythmDBEntry *entry)
+{
+	RhythmDBEntryCategory cat;
+	RhythmDBEntryType *entry_type;
+
+	entry_type = rhythmdb_entry_get_entry_type (entry);
+	g_object_get (entry_type, "category", &cat, NULL);
+	return (cat == RHYTHMDB_ENTRY_NORMAL);
+}
+
+/**
+ * rb_transfer_target_check_duplicate:
+ * @target: an #RBTransferTarget
+ * @entry: a #RhythmDBEntry to check
+ *
+ * This checks for an existing entry in the target that matches
+ * the title, album, artist, and track number of the entry being
+ * considered.  This can be used to implement @should_transfer.
+ *
+ * Return value: %TRUE if the entry already exists on the target.
+ */
+gboolean
+rb_transfer_target_check_duplicate (RBTransferTarget *target, RhythmDBEntry *entry)
+{
+	RhythmDBEntryType *entry_type;
+	RhythmDB *db;
+	RBShell *shell;
+	const char *title;
+	const char *album;
+	const char *artist;
+	gulong track_number;
+	GtkTreeModel *query_model;
+	GtkTreeIter iter;
+	gboolean is_dup;
+
+	g_object_get (target, "shell", &shell, "entry-type", &entry_type, NULL);
+	g_object_get (shell, "db", &db, NULL);
+	g_object_unref (shell);
+
+	query_model = GTK_TREE_MODEL (rhythmdb_query_model_new_empty (db));
+	title = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_TITLE);
+	album = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ALBUM);
+	artist = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ARTIST);
+	track_number = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_TRACK_NUMBER);
+	rhythmdb_do_full_query (db, RHYTHMDB_QUERY_RESULTS (query_model),
+				RHYTHMDB_QUERY_PROP_EQUALS,
+				RHYTHMDB_PROP_TYPE, entry_type,
+				RHYTHMDB_QUERY_PROP_EQUALS,
+				RHYTHMDB_PROP_ARTIST, artist,
+				RHYTHMDB_QUERY_PROP_EQUALS,
+				RHYTHMDB_PROP_ALBUM, album,
+				RHYTHMDB_QUERY_PROP_EQUALS,
+				RHYTHMDB_PROP_TITLE, title,
+				RHYTHMDB_QUERY_PROP_EQUALS,
+				RHYTHMDB_PROP_TRACK_NUMBER, track_number,
+				RHYTHMDB_QUERY_END);
+
+	is_dup = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (query_model), &iter);
+	g_object_unref (entry_type);
+	g_object_unref (query_model);
+	g_object_unref (db);
+	if (is_dup) {
+		rb_debug ("not transferring %lu - %s - %s - %s as already present",
+			  track_number, title, album, artist);
+	}
+	return is_dup;
+}
+
+static gboolean
+default_should_transfer (RBTransferTarget *target, RhythmDBEntry *entry)
+{
+	if (rb_transfer_target_check_category (target, entry) == FALSE)
+		return FALSE;
+
+	return (rb_transfer_target_check_duplicate (target, entry) == FALSE);
+}
+
+static char *
+get_dest_uri_cb (RBTrackTransferBatch *batch,
+		 RhythmDBEntry *entry,
+		 const char *mediatype,
+		 const char *extension,
+		 RBTransferTarget *target)
+{
+	char *free_ext = NULL;
+	char *uri;
+
+	/* make sure the extension isn't ludicrously long */
+	if (extension == NULL) {
+		extension = "";
+	} else if (strlen (extension) > EXTENSION_LENGTH_LIMIT) {
+		free_ext = g_strdup (extension);
+		free_ext[EXTENSION_LENGTH_LIMIT] = '\0';
+		extension = free_ext;
+	}
+	uri = rb_transfer_target_build_dest_uri (target, entry, mediatype, extension);
+	g_free (free_ext);
+	return uri;
+}
+
+static void
+track_done_cb (RBTrackTransferBatch *batch,
+	       RhythmDBEntry *entry,
+	       const char *dest,
+	       guint64 dest_size,
+	       const char *dest_mediatype,
+	       GError *error,
+	       RBTransferTarget *target)
+{
+	if (error == NULL) {
+		rb_transfer_target_track_added (target, entry, dest, dest_size, dest_mediatype);
+	} else {
+		if (g_error_matches (error, RB_ENCODER_ERROR, RB_ENCODER_ERROR_OUT_OF_SPACE) ||
+		    g_error_matches (error, RB_ENCODER_ERROR, RB_ENCODER_ERROR_DEST_READ_ONLY)) {
+			rb_debug ("fatal transfer error: %s", error->message);
+			rb_track_transfer_batch_cancel (batch);
+		}
+		rb_transfer_target_track_add_error (target, entry, dest, error);
+	}
+}
+
+/**
+ * rb_transfer_target_transfer:
+ * @target: an #RBTransferTarget
+ * @entries: a #GList of entries to transfer
+ *
+ * Starts tranferring @entries to the target.  This returns the
+ * #RBTrackTransferBatch that it starts, so the caller can track
+ * the progress of the transfer, or NULL if the target doesn't
+ * want any of the entries.
+ *
+ * Return value: (transfer full): an #RBTrackTransferBatch, or NULL
+ */
+RBTrackTransferBatch *
+rb_transfer_target_transfer (RBTransferTarget *target, GList *entries)
+{
+	RBTrackTransferQueue *xferq;
+	RBShell *shell;
+	GList *l;
+	RhythmDBEntryType *our_entry_type;
+	RBTrackTransferBatch *batch;
+	GstEncodingTarget *encoding_target;
+	gboolean start_batch = FALSE;
+
+	g_object_get (target,		/* hrm */
+		      "shell", &shell,
+		      "entry-type", &our_entry_type,
+		      "encoding-target", &encoding_target,
+		      NULL);
+	g_object_get (shell, "track-transfer-queue", &xferq, NULL);
+	g_object_unref (shell);
+
+	batch = rb_track_transfer_batch_new (encoding_target, NULL, G_OBJECT (target));
+	gst_encoding_target_unref (encoding_target);
+
+	g_signal_connect_object (batch, "get-dest-uri", G_CALLBACK (get_dest_uri_cb), target, 0);
+	g_signal_connect_object (batch, "track-done", G_CALLBACK (track_done_cb), target, 0);
+
+	for (l = entries; l != NULL; l = l->next) {
+		RhythmDBEntry *entry;
+		RhythmDBEntryType *entry_type;
+		const char *location;
+
+		entry = (RhythmDBEntry *)l->data;
+		location = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
+		entry_type = rhythmdb_entry_get_entry_type (entry);
+
+		if (entry_type != our_entry_type) {
+			if (rb_transfer_target_should_transfer (target, entry)) {
+				rb_debug ("pasting entry %s", location);
+				rb_track_transfer_batch_add (batch, entry);
+				start_batch = TRUE;
+			} else {
+				rb_debug ("target doesn't want entry %s", location);
+			}
+		} else {
+			rb_debug ("can't copy entry %s from the target to itself", location);
+		}
+	}
+	g_object_unref (our_entry_type);
+
+	if (start_batch) {
+		rb_track_transfer_queue_start_batch (xferq, batch);
+	} else {
+		g_object_unref (batch);
+		batch = NULL;
+	}
+	g_object_unref (xferq);
+	return batch;
+}
+
+
+static void
+rb_transfer_target_default_init (RBTransferTargetInterface *interface)
+{
+	interface->should_transfer = default_should_transfer;
+
+	g_object_interface_install_property (interface,
+					     gst_param_spec_mini_object ("encoding-target",
+									 "encoding target",
+									 "GstEncodingTarget",
+									 GST_TYPE_ENCODING_TARGET,
+									 G_PARAM_READWRITE));
+}
diff --git a/sources/rb-transfer-target.h b/sources/rb-transfer-target.h
new file mode 100644
index 0000000..cb3df15
--- /dev/null
+++ b/sources/rb-transfer-target.h
@@ -0,0 +1,94 @@
+/*
+ *  Copyright (C) 2011 Jonathan Matthew  <jonathan d14n org>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The Rhythmbox authors hereby grant permission for non-GPL compatible
+ *  GStreamer plugins to be used and distributed together with GStreamer
+ *  and Rhythmbox. This permission is above and beyond the permissions granted
+ *  by the GPL license by which Rhythmbox is covered. If you modify this code
+ *  you may extend this exception to your version of the code, but you are not
+ *  obligated to do so. If you do not wish to do so, delete this exception
+ *  statement from your version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
+ *
+ */
+
+#ifndef RB_TRANSFER_TARGET_H
+#define RB_TRANSFER_TARGET_H
+
+#include <glib-object.h>
+
+#include <rhythmdb/rhythmdb.h>
+#include <shell/rb-track-transfer-batch.h>
+
+#define RB_TYPE_TRANSFER_TARGET         (rb_transfer_target_get_type ())
+#define RB_TRANSFER_TARGET(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), RB_TYPE_TRANSFER_TARGET, RBTransferTarget))
+#define RB_IS_TRANSFER_TARGET(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), RB_TYPE_TRANSFER_TARGET))
+#define RB_TRANSFER_TARGET_GET_IFACE(o) (G_TYPE_INSTANCE_GET_INTERFACE ((o), RB_TYPE_TRANSFER_TARGET, RBTransferTargetInterface))
+
+typedef struct _RBTransferTarget RBTransferTarget;
+typedef struct _RBTransferTargetInterface RBTransferTargetInterface;
+
+struct _RBTransferTargetInterface
+{
+	GTypeInterface g_iface;
+
+	char*		(*build_dest_uri)	(RBTransferTarget *target,
+						 RhythmDBEntry *entry,
+						 const char *media_type,
+						 const char *extension);
+	gboolean	(*track_added)		(RBTransferTarget *target,
+						 RhythmDBEntry *entry,
+						 const char *uri,
+						 guint64 dest_size,
+						 const char *media_type);
+	gboolean	(*track_add_error)	(RBTransferTarget *target,
+						 RhythmDBEntry *entry,
+						 const char *uri,
+						 GError *error);
+	gboolean	(*should_transfer)	(RBTransferTarget *target,
+						 RhythmDBEntry *entry);
+};
+
+GType		rb_transfer_target_get_type	(void);
+
+char*		rb_transfer_target_build_dest_uri 	(RBTransferTarget *target,
+							 RhythmDBEntry *entry,
+							 const char *media_type,
+							 const char *extension);
+void		rb_transfer_target_track_added		(RBTransferTarget *target,
+							 RhythmDBEntry *entry,
+							 const char *uri,
+							 guint64 filesize,
+							 const char *media_type);
+void		rb_transfer_target_track_add_error	(RBTransferTarget *target,
+							 RhythmDBEntry *entry,
+							 const char *uri,
+							 GError *error);
+gboolean	rb_transfer_target_should_transfer	(RBTransferTarget *target,
+							 RhythmDBEntry *entry);
+
+GList *		rb_transfer_target_get_format_descriptions (RBTransferTarget *target);
+gboolean        rb_transfer_target_check_category	(RBTransferTarget *target,
+							 RhythmDBEntry *entry);
+gboolean        rb_transfer_target_check_duplicate	(RBTransferTarget *target,
+							 RhythmDBEntry *entry);
+
+RBTrackTransferBatch *rb_transfer_target_transfer	(RBTransferTarget *target, GList *entries);
+
+
+G_END_DECLS
+
+#endif	/* RB_TRANSFER_TARGET_H */
diff --git a/widgets/rb-entry-view.c b/widgets/rb-entry-view.c
index 471be38..6852c29 100644
--- a/widgets/rb-entry-view.c
+++ b/widgets/rb-entry-view.c
@@ -774,6 +774,8 @@ rb_entry_view_new (RhythmDB *db,
 					   "vadjustment", NULL,
 					   "hscrollbar_policy", GTK_POLICY_AUTOMATIC,
 					   "vscrollbar_policy", GTK_POLICY_AUTOMATIC,
+					   "hexpand", TRUE,
+					   "vexpand", TRUE,
 					   "shadow_type", GTK_SHADOW_IN,
 					   "db", db,
 					   "shell-player", RB_SHELL_PLAYER (shell_player),
diff --git a/widgets/rb-property-view.c b/widgets/rb-property-view.c
index 849c7d8..d952a4e 100644
--- a/widgets/rb-property-view.c
+++ b/widgets/rb-property-view.c
@@ -470,6 +470,8 @@ rb_property_view_new (RhythmDB *db,
 					       "vadjustment", NULL,
 					       "hscrollbar_policy", GTK_POLICY_AUTOMATIC,
 					       "vscrollbar_policy", GTK_POLICY_AUTOMATIC,
+					       "hexpand", TRUE,
+					       "vexpand", TRUE,
 					       "shadow_type", GTK_SHADOW_IN,
 					       "db", db,
 					       "prop", propid,



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