diff -u -u -r1.238 configure.ac
--- configure.ac	14 May 2006 01:56:39 -0000	1.238
+++ configure.ac	14 May 2006 06:12:45 -0000
@@ -852,7 +852,8 @@
 
 AM_CONDITIONAL([ENABLE_PYTHON],[test "x$enable_python" = "xyes"])
 
-
+AM_PATH_CHECK([], have_check=yes, have_check=no)
+AM_CONDITIONAL([HAVE_CHECK],[test "x$have_check" = "xyes"])
 
 dnl ================================================================
 dnl end-game
diff -u -u -r1.1 ipod-ui.xml
--- data/ui/ipod-ui.xml	16 Apr 2006 10:24:04 -0000	1.1
+++ data/ui/ipod-ui.xml	14 May 2006 06:12:45 -0000
@@ -2,5 +2,12 @@
   <popup name="iPodSourcePopup">
     <menuitem name="iPodSrcPopupRename" action="iPodSourceRename"/>
     <menuitem name="iPodSrcPopupEject" action="RemovableSourceEject"/>
+    <separator/>
+    <menuitem name="iPodSrcPopupNewPlaylist" action="iPodSourceNewPlaylist"/>
+  </popup>
+
+  <popup name="iPodPlaylistSourcePopup">
+    <menuitem name="RenameiPodPlaylistPopup" action="MusicPlaylistRenamePlaylist"/>
+    <menuitem name="DeleteiPodPlaylistPopup" action="MusicPlaylistDeletePlaylist"/>
   </popup>
 </ui>
diff -u -u -r1.17 rb-marshal.list
--- lib/rb-marshal.list	9 May 2006 23:04:29 -0000	1.17
+++ lib/rb-marshal.list	14 May 2006 06:12:46 -0000
@@ -19,5 +19,6 @@
 VOID:STRING,POINTER
 VOID:STRING,STRING
 VOID:STRING,STRING,STRING
+VOID:POINTER,DOUBLE
 VOID:STRING,STRING,STRING,UINT,BOOLEAN
 VOID:ULONG,FLOAT
diff -u -u -r1.2 Makefile.am
--- plugins/ipod/Makefile.am	12 May 2006 16:04:47 -0000	1.2
+++ plugins/ipod/Makefile.am	14 May 2006 06:12:46 -0000
@@ -1,14 +1,23 @@
 plugindir = $(PLUGINDIR)
+noinst_LTLIBRARIES = librbipodmanager.la
 plugin_LTLIBRARIES = libipod.la
 
+# build a separate lib so we can unittest it
+librbipodmanager_la_SOURCES =				\
+	rb-ipod.c					\
+	rb-ipod-manager.c
+
 libipod_la_SOURCES = \
 	rb-ipod-plugin.c			\
+	rb-ipod-playlist-source.c			\
 	$(top_srcdir)/sources/rb-ipod-source.c	\
 	$(top_srcdir)/sources/rb-ipod-source.h
 	
 libipod_la_LDFLAGS = $(PLUGIN_LIBTOOL_FLAGS)
 libipod_la_LIBADD = 					\
+	librbipodmanager.la				\
 	$(top_builddir)/lib/librb.la			\
+	$(top_builddir)/backends/librbbackends.la	\
 	$(HAL_LIBS)					\
 	$(IPOD_LIBS)
 
@@ -26,6 +35,7 @@
 	-I$(top_srcdir)/podcast                    	\
 	-I$(top_srcdir)/plugins				\
 	-I$(top_srcdir)/shell				\
+	-I$(top_srcdir)/backends			\
 	-DPIXMAP_DIR=\""$(datadir)/pixmaps"\"		\
 	-DSHARE_DIR=\"$(pkgdatadir)\"                   \
 	-DDATADIR=\""$(datadir)"\"			\
diff -u -u -r1.7 rhythmdb-private.h
--- rhythmdb/rhythmdb-private.h	20 Apr 2006 20:43:28 -0000	1.7
+++ rhythmdb/rhythmdb-private.h	14 May 2006 06:12:46 -0000
@@ -98,6 +98,9 @@
 
 	/*Podcast*/
 	RhythmDBPodcastFields *podcast;
+
+  /* iPod */
+  guint ipod_status;
 };
 
 G_END_DECLS
diff -u -u -r1.104 rhythmdb-tree.c
--- rhythmdb/rhythmdb-tree.c	13 May 2006 07:09:54 -0000	1.104
+++ rhythmdb/rhythmdb-tree.c	14 May 2006 06:12:48 -0000
@@ -406,6 +406,8 @@
 		/* Handle indexed properties. */
 		switch (ctx->propid)
 		{
+		
+    case RHYTHMDB_PROP_IPOD_STATUS:
 		case RHYTHMDB_PROP_TYPE:
 			g_assert_not_reached ();
 			break;
@@ -776,6 +778,7 @@
 
 		switch (i)
 		{
+    case RHYTHMDB_PROP_IPOD_STATUS:
 		case RHYTHMDB_PROP_TYPE:
 			break;
 		case RHYTHMDB_PROP_TITLE:
diff -u -u -r1.216 rhythmdb.c
--- rhythmdb/rhythmdb.c	12 May 2006 07:27:13 -0000	1.216
+++ rhythmdb/rhythmdb.c	14 May 2006 06:12:51 -0000
@@ -735,6 +735,7 @@
 	g_hash_table_destroy (db->priv->propname_map);
 
 	g_hash_table_destroy (db->priv->monitored_directories);
+	if (db->priv->changed_files_id > 0)
 	g_source_remove (db->priv->changed_files_id);
 	g_hash_table_destroy (db->priv->changed_files);
 
@@ -2857,6 +2858,9 @@
 		case RHYTHMDB_PROP_POST_TIME:
 			entry->podcast->post_time = g_value_get_ulong (value);
 			break;	
+    case RHYTHMDB_PROP_IPOD_STATUS:
+      entry->ipod_status = g_value_get_ulong (value);
+      break;
 		case RHYTHMDB_NUM_PROPERTIES:
 			g_assert_not_reached ();
 			break;
@@ -3931,6 +3935,7 @@
 			ENUM_ENTRY (RHYTHMDB_PROP_COPYRIGHT, "Podcast copyright (gchararray) [copyright]"),
 			ENUM_ENTRY (RHYTHMDB_PROP_IMAGE, "Podcast image(gchararray) [image]"),
 			ENUM_ENTRY (RHYTHMDB_PROP_POST_TIME, "Podcast time of post (gulong) [post-time]"),
+			ENUM_ENTRY (RHYTHMDB_PROP_IPOD_STATUS, "iPod status (gulong) [ipod-status]"),
 			{ 0, 0, 0 }
 		};
 		g_assert ((sizeof (values) / sizeof (values[0]) - 1) == RHYTHMDB_NUM_PROPERTIES);
@@ -4790,6 +4795,8 @@
 			return entry->podcast->status;		
 		else
 			return 0;
+  case RHYTHMDB_PROP_IPOD_STATUS:
+    return entry->ipod_status;
 	default:
 		g_assert_not_reached ();
 		return 0;
diff -u -u -r1.82 rhythmdb.h
--- rhythmdb/rhythmdb.h	2 May 2006 11:50:45 -0000	1.82
+++ rhythmdb/rhythmdb.h	14 May 2006 06:12:51 -0000
@@ -174,6 +174,9 @@
 	RHYTHMDB_PROP_IMAGE,
 	RHYTHMDB_PROP_POST_TIME,
 	
+  /* iPod properties */
+  RHYTHMDB_PROP_IPOD_STATUS,
+  
 	RHYTHMDB_NUM_PROPERTIES
 } RhythmDBPropType;
 
@@ -182,6 +185,11 @@
 	RHYTHMDB_PODCAST_STATUS_ERROR = 101,
 	RHYTHMDB_PODCAST_STATUS_WAITING = 102,
 	RHYTHMDB_PODCAST_STATUS_PAUSED = 103,
+};
+
+enum {
+  RHYTHMDB_IPOD_STATUS_COMPLETE = 100,
+  RHYTHMDB_IPOD_STATUS_ERROR = 101
 };
 
 GType rhythmdb_query_type_get_type (void);
diff -u -u -r1.50 rb-ipod-source.c
--- sources/rb-ipod-source.c	6 May 2006 23:28:03 -0000	1.50
+++ sources/rb-ipod-source.c	14 May 2006 06:12:54 -0000
@@ -2,6 +2,7 @@
  *  arch-tag: Implementation of ipod source object
  *
  *  Copyright (C) 2004 Christophe Fergeau  <teuf@gnome.org>
+ *		  2006 Alessandro Decina   <alessandro@nnva.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
@@ -21,7 +22,7 @@
 
 #include <config.h>
 
-
+#include <gtk/gtk.h>
 #include <gtk/gtktreeview.h>
 #include <string.h>
 #include "rhythmdb.h"
@@ -36,13 +37,38 @@
 #include <gpod/itdb.h>
 #include "eel-gconf-extensions.h"
 #include "rb-ipod-source.h"
+#include "rb-library-source.h"
+#include "rb-stock-icons.h"
 #include "rb-debug.h"
 #include "rb-file-helpers.h"
 #include "rb-plugin.h"
 #include "rb-removable-media-manager.h"
-#include "rb-static-playlist-source.h"
+#include "rb-ipod-playlist-source.h"
 #include "rb-util.h"
 #include "rhythmdb.h"
+#include "rb-sourcelist.h"
+#include "rb-sourcelist-model.h"
+#include "rb-ipod.h"
+#include "rb-ipod-manager.h"
+
+enum {
+	PROP_0,
+ 	PROP_MANAGER,
+};
+
+struct _RBiPodSourcePrivate
+{
+ 	RBiPodManager *manager;
+ 	RBShell *shell;
+ 	RhythmDB *db;
+ 	RBEntryView *entry_view;
+ 	RhythmDBEntryType entry_type;
+ 	GtkActionGroup *action_group;
+ 	GtkToggleAction *action_sync;
+ 	GHashTable *playlist_sources;
+};
+
+
 
 #ifdef IPOD_PHONE_SUPPORT
 #define PHONE_VENDOR_ID 0x22b8
@@ -54,37 +80,75 @@
 					    GObjectConstructParam *construct_properties);
 static void rb_ipod_source_dispose (GObject *object);
 
-static GObject *rb_ipod_source_constructor (GType type, guint n_construct_properties,
-			       GObjectConstructParam *construct_properties);
-static void rb_ipod_source_dispose (GObject *object);
+
+static void rb_ipod_source_get_property (GObject *object, guint propid,
+                                         GValue *value, GParamSpec *pspec);
+static void rb_ipod_source_set_property (GObject *object, guint propid,
+                                         const GValue *value, GParamSpec *pspec);
 
 static gboolean impl_show_popup (RBSource *source);
-static void impl_move_to_trash (RBSource *asource);
-static void rb_ipod_load_songs (RBiPodSource *source);
-static gchar *rb_ipod_get_mount_path (GnomeVFSVolume *volume);
+static GList * impl_get_ui_actions (RBSource *source);
 static void impl_delete_thyself (RBSource *source);
-static GList* impl_get_ui_actions (RBSource *source);
-#ifdef HAVE_HAL
-static gboolean hal_udi_is_ipod (const char *udi);
-#endif
+static gboolean load_songs_idle_cb (RBiPodSource *source);
+static void add_status_column (RBiPodSource *source,
+                               RBEntryView *view);
+static gchar *rb_ipod_get_mount_path (GnomeVFSVolume *volume);
+
+static void manager_track_read_cb (RBiPodManager *manager,
+                                   Itdb_Playlist *playlist, Itdb_Track *track,
+                                   RBiPodSource *source);
+static void manager_playlist_read_cb (RBiPodManager *manager,
+                                      Itdb_Playlist *playlist,
+                                      RBiPodSource *source);
 
 #ifdef ENABLE_TRACK_TRANSFER
-static void impl_paste (RBSource *source, GList *entries);
-static gboolean impl_receive_drag (RBSource *asource, GtkSelectionData *data);
-static gchar *
-ipod_get_filename_for_uri (const gchar *mount_point, const gchar *uri_str);
-static gchar *
-ipod_path_from_unix_path (const gchar *mount_point, const gchar *unix_path);
+static void action_new_playlist_cb (GtkAction *action, RBiPodSource *source);
+static void action_sync_toggled_cb (GtkToggleAction *action,
+                                    RBiPodSource *source);
+static gboolean impl_receive_drag (RBSource *source, GtkSelectionData *data);
+static void impl_delete (RBSource *source);
+static void manager_track_added_cb (RBiPodManager *manager,
+                                    Itdb_Playlist *playlist, Itdb_Track *track,
+                                    RBiPodSource *source);
+static void manager_track_removed_cb (RBiPodManager *manager,
+                                      Itdb_Playlist *playlist,
+                                      Itdb_Track *track,
+                                      RBiPodSource *source);
+static void manager_playlist_added_cb (RBiPodManager *manager,
+                                       Itdb_Playlist *playlist,
+                                       RBiPodSource *source);
+static void manager_playlist_removed_cb (RBiPodManager *manager,
+                                         Itdb_Playlist *playlist,
+                                         RBiPodSource *source);
+
+static void manager_dirty_cb (RBiPodManager *manager,
+                              gboolean dirty, RBiPodSource *source);
+
+static void manager_sync_started_cb (RBiPodManager *manager,
+                                     RBiPodSource *source);
+static void manager_track_transfer_progress_cb (RBiPodManager *manager,
+                                                RBiPodManagerTrackTransferInfo *info, gdouble progress,
+                                                RBiPodSource *source);
+static void manager_track_transfer_finished_cb (RBiPodManager *manager,
+                                                RBiPodManagerTrackTransferInfo *info,
+                                                RBiPodSource *source);
+static void manager_sync_finished_cb (RBiPodManager *manager,
+                                      gboolean canceled, RBiPodSource *source);
+
+static void manager_error_cb (RBiPodManager *manager,
+                              GError *error, RBiPodSource *source);
+static gboolean add_entry (RBiPodSource *source,
+                           RhythmDBEntry *entry, const gchar *uri);
+#endif
+
+#if 0
+static void impl_move_to_trash (RBSource *asource);
 #endif
-static void itdb_schedule_save (Itdb_iTunesDB *db);
 
 
-typedef struct
-{
-	Itdb_iTunesDB *ipod_db;
-	gchar *ipod_mount_path;
-	GHashTable *entry_map;
-} RBiPodSourcePrivate;
+#ifdef HAVE_HAL
+static gboolean hal_udi_is_ipod (const char *udi);
+#endif
 
 
 RB_PLUGIN_DEFINE_TYPE(RBiPodSource, 
@@ -94,63 +158,86 @@
 #define IPOD_SOURCE_GET_PRIVATE(o)   (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_IPOD_SOURCE, RBiPodSourcePrivate))
 
 
+#ifdef ENABLE_TRACK_TRANSFER
+static GtkActionEntry ui_actions[] = {
+	{"iPodSourceNewPlaylist", GNOME_MEDIA_PLAYLIST, N_("_New Playlist"),
+		NULL, N_("Create a new playlist"),
+		G_CALLBACK (action_new_playlist_cb)
+	}
+};
+
+static GtkToggleActionEntry toggle_ui_actions[] = {
+	{"iPodSourceSync", GTK_STOCK_REFRESH, N_("Synchronize"), NULL,
+		N_("Write changes to iPod"),
+		G_CALLBACK (action_sync_toggled_cb),
+		FALSE
+	}
+};
+#endif
+
 static void
 rb_ipod_source_class_init (RBiPodSourceClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 	RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
+#ifdef ENABLE_TRACK_TRANSFER
+	RBBrowserSourceClass *browser_source_class =
+		RB_BROWSER_SOURCE_CLASS (klass);
+
+	browser_source_class->impl_has_drop_support =
+		(RBBrowserSourceFeatureFunc) rb_true_function;
+#endif
 
 	object_class->constructor = rb_ipod_source_constructor;
+	object_class->get_property = rb_ipod_source_get_property;
+	object_class->set_property = rb_ipod_source_set_property;
 	object_class->dispose = rb_ipod_source_dispose;
 
+	g_object_class_install_property (object_class, PROP_MANAGER,
+			g_param_spec_object ("ipod-manager",
+				"iPod Manager",
+				"Rhythmbox iPod Manager",
+				RB_TYPE_IPOD_MANAGER,
+				G_PARAM_READWRITE));
+
 	source_class->impl_show_popup = impl_show_popup;
 	source_class->impl_delete_thyself = impl_delete_thyself;
-   	source_class->impl_can_move_to_trash = (RBSourceFeatureFunc) rb_true_function;
-   	source_class->impl_move_to_trash = impl_move_to_trash;
-	source_class->impl_can_rename = (RBSourceFeatureFunc) rb_true_function;
+
 	source_class->impl_get_ui_actions = impl_get_ui_actions;
 #ifdef ENABLE_TRACK_TRANSFER
   	source_class->impl_can_paste = (RBSourceFeatureFunc) rb_true_function;
-  	source_class->impl_paste = impl_paste;
+	source_class->impl_can_delete = (RBSourceFeatureFunc) rb_true_function;
   	source_class->impl_receive_drag = impl_receive_drag;
+	source_class->impl_delete = impl_delete;
+	source_class->impl_can_rename = (RBSourceFeatureFunc) rb_true_function;
+#endif
+#if 0
+	source_class->impl_can_move_to_trash = (RBSourceFeatureFunc) rb_true_function;
+   	source_class->impl_move_to_trash = impl_move_to_trash;
 #endif
 
 	g_type_class_add_private (klass, sizeof (RBiPodSourcePrivate));
 }
 
+#ifdef ENABLE_TRACK_TRANSFER
 static void
-rb_ipod_source_set_ipod_name (RBiPodSource *source, const char *name)
-{
-	Itdb_Playlist *mpl;
-	RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
-
-	mpl = itdb_playlist_mpl (priv->ipod_db);
-	rb_debug ("Renaming iPod from %s to %s", mpl->name, name);
-	if (strcmp (mpl->name, name) == 0) {
-		rb_debug ("iPod is already named %s", name);
-		return;
-	}
-	g_free (mpl->name);
-	mpl->name = g_strdup (name);
-	itdb_schedule_save (priv->ipod_db);
-}
-
-static void
-rb_ipod_source_name_changed_cb (RBiPodSource *source, GParamSpec *spec, 
-				gpointer data)
+name_changed_cb (RBiPodSource *source,
+		GParamSpec *spec, gpointer data)
 {
 	char *name;
 
 	g_object_get (RB_SOURCE (source), "name", &name, NULL);
-	rb_ipod_source_set_ipod_name (source, name);
+	rb_ipod_manager_rename (source->priv->manager, name);
 	g_free (name);
 }
+#endif
 
 static void
 rb_ipod_source_init (RBiPodSource *source)
 {
-	g_signal_connect (G_OBJECT (source), "notify::name", 
-			  (GCallback)rb_ipod_source_name_changed_cb, NULL);
+	source->priv = IPOD_SOURCE_GET_PRIVATE (source);
+	source->priv->playlist_sources =
+		g_hash_table_new (g_direct_hash, g_direct_equal);
 }
 
 static GObject *
@@ -158,40 +245,174 @@
 			    GObjectConstructParam *construct_properties)
 {
 	RBiPodSource *source;
-	RBEntryView *songs;
-	RBiPodSourcePrivate *priv;
+	GnomeVFSVolume *volume;
+	gchar *mount_path;
 
 	source = RB_IPOD_SOURCE (G_OBJECT_CLASS (rb_ipod_source_parent_class)->
-			constructor (type, n_construct_properties, construct_properties));
-	priv = IPOD_SOURCE_GET_PRIVATE (source);
+			constructor (type, n_construct_properties,
+				construct_properties));
+
+	g_object_get (G_OBJECT (source),
+			"shell", &source->priv->shell, "volume", &volume,
+			"entry-type", &source->priv->entry_type, NULL);
+	g_object_get (source->priv->shell, "db", &source->priv->db, NULL);
+
+	/* get the mount path and setup the iPodManager */
+	mount_path = rb_ipod_get_mount_path (volume);
+	source->priv->manager = rb_ipod_manager_new (mount_path,
+			source->priv->db);
+	g_free (mount_path);
+
+	/* append the status column to the entry view */
+	source->priv->entry_view =
+		rb_source_get_entry_view (RB_SOURCE (source));
+
+	rb_entry_view_append_column (source->priv->entry_view,
+			RB_ENTRY_VIEW_COL_RATING, FALSE);
+	rb_entry_view_append_column (source->priv->entry_view,
+			RB_ENTRY_VIEW_COL_LAST_PLAYED, FALSE);
+	add_status_column (source, source->priv->entry_view);
 
-	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);
+#ifdef ENABLE_TRACK_TRANSFER
+	/* setup UI actions */
+	source->priv->action_group =
+		_rb_source_register_action_group (RB_SOURCE (source),
+			"iPodSourceActions", ui_actions,
+			G_N_ELEMENTS (ui_actions), source);
+
+	gtk_action_group_add_toggle_actions (source->priv->action_group,
+			toggle_ui_actions, G_N_ELEMENTS (toggle_ui_actions),
+			source);
+
+	source->priv->action_sync = (GtkToggleAction *)
+		gtk_action_group_get_action (source->priv->action_group,
+			"iPodSourceSync");
+#endif
+	g_signal_connect_object (G_OBJECT (source->priv->manager),
+			"track-read",
+			G_CALLBACK (manager_track_read_cb),
+			source, 0);
+	g_signal_connect_object (G_OBJECT (source->priv->manager),
+			"playlist-read",
+			G_CALLBACK (manager_playlist_read_cb),
+			source, 0);
+#ifdef ENABLE_TRACK_TRANSFER
+	g_signal_connect_object (G_OBJECT (source->priv->manager),
+			"track-added",
+			G_CALLBACK (manager_track_added_cb),
+			source, 0);
+	g_signal_connect_object (G_OBJECT (source->priv->manager),
+			"track-transfer-progress",
+			G_CALLBACK (manager_track_transfer_progress_cb),
+			source, 0);
+	g_signal_connect_object (G_OBJECT (source->priv->manager),
+			"track-transfer-finished",
+			G_CALLBACK (manager_track_transfer_finished_cb),
+			source, 0);
+	g_signal_connect_object (G_OBJECT (source->priv->manager),
+			"track-removed",
+			G_CALLBACK (manager_track_removed_cb),
+			source, 0);
+	g_signal_connect_object (G_OBJECT (source->priv->manager),
+			"playlist-added",
+			G_CALLBACK (manager_playlist_added_cb),
+			source, 0);
+	g_signal_connect_object (G_OBJECT (source->priv->manager),
+			"playlist-removed",
+			G_CALLBACK (manager_playlist_removed_cb),
+			source, 0);
+	g_signal_connect_object (G_OBJECT (source->priv->manager),
+			"dirty",
+			G_CALLBACK (manager_dirty_cb),
+			source, 0);
+	g_signal_connect_object (G_OBJECT (source->priv->manager),
+			"sync-started",
+			G_CALLBACK (manager_sync_started_cb),
+			source, 0);
+	g_signal_connect_object (G_OBJECT (source->priv->manager),
+			"sync-finished",
+			G_CALLBACK (manager_sync_finished_cb),
+			source, 0);
+	g_signal_connect_object (G_OBJECT (source->priv->manager),
+			"error",
+			G_CALLBACK (manager_error_cb),
+			source, 0);
+#endif
 
-	rb_ipod_load_songs (source);
+	/* load the itdb in an idle handler */
+	g_idle_add ((GSourceFunc) load_songs_idle_cb, source);
 
 	return G_OBJECT (source);
 }
 
 static void 
+rb_ipod_source_get_property (GObject *object, guint propid,
+		GValue *value, GParamSpec *pspec)
+{
+	RBiPodSource *source = RB_IPOD_SOURCE (object);
+
+	switch (propid) {
+		case PROP_MANAGER:
+			g_value_set_object (value, source->priv->manager);
+			break;
+		default:
+			g_assert_not_reached ();
+	}
+}
+
+static void
+rb_ipod_source_set_property (GObject *object, guint propid,
+		const GValue *value, GParamSpec *pspec)
+{
+	RBiPodSource *source = RB_IPOD_SOURCE (object);
+
+	switch (propid) {
+		case PROP_MANAGER:
+			if (source->priv->manager)
+				g_object_unref (source->priv->manager);
+			source->priv->manager =
+				RB_IPOD_MANAGER (g_value_dup_object (value));
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object,
+					propid, pspec);
+	}
+}
+
+static void
+unref_playlist_source (gpointer key, gpointer value, gpointer user_data)
+{
+	rb_ipod_playlist_source_delete_no_remove (RB_IPOD_PLAYLIST_SOURCE (value));
+}
+
+static void
 rb_ipod_source_dispose (GObject *object)
 {
-	RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (object);
+	RBiPodSource *source = RB_IPOD_SOURCE (object);
 
- 	if (priv->ipod_db != NULL) {
- 		itdb_free (priv->ipod_db);
- 		priv->ipod_db = NULL;
+	if (source->priv->shell != NULL) {
+		g_object_unref (source->priv->shell);
+		source->priv->shell = NULL;
   	}
 
-	if (priv->ipod_mount_path) {
-		g_free (priv->ipod_mount_path);
-		priv->ipod_mount_path = NULL;
+	if (source->priv->db != NULL) {
+		g_object_unref (source->priv->db);
+		source->priv->db = NULL;
 	}
 
-	if (priv->entry_map) {
-		g_hash_table_destroy (priv->entry_map);
-		priv->entry_map = NULL;
+	if (source->priv->manager != NULL) {
+		g_object_unref (source->priv->manager);
+		source->priv->manager = NULL;
+	}
+
+	if (source->priv->action_group != NULL) {
+		GtkUIManager *uimanager;
+
+		g_object_get (source, "ui-manager", &uimanager, NULL);
+		gtk_ui_manager_remove_action_group (uimanager,
+				source->priv->action_group);
+		g_object_unref (uimanager);
+		source->priv->action_group = NULL;
  	}
 
 	G_OBJECT_CLASS (rb_ipod_source_parent_class)->dispose (object);
@@ -203,8 +424,6 @@
 	RBiPodSource *source;
 	RhythmDBEntryType entry_type;
 
-	g_assert (rb_ipod_is_volume_ipod (volume));
-
 	entry_type =  rhythmdb_entry_register_type ();
 
 	source = RB_IPOD_SOURCE (g_object_new (RB_TYPE_IPOD_SOURCE,
@@ -213,333 +432,134 @@
 					  "shell", shell,
 					  NULL));
 
-	rb_shell_register_entry_type_for_source (shell, RB_SOURCE (source), entry_type);
+	rb_shell_register_entry_type_for_source (shell, RB_SOURCE (source),
+			entry_type);
 
 	return RB_REMOVABLE_MEDIA_SOURCE (source);
 }
 
 static void 
-entry_set_string_prop (RhythmDB *db, RhythmDBEntry *entry,
-		       RhythmDBPropType propid, const char *str)
-{
-	GValue value = {0,};
-
-	if (!str)
-		str = _("Unknown");
+rb_ipod_source_status_cell_data_func (GtkTreeViewColumn *column,
+		GtkCellRenderer *renderer,
+		GtkTreeModel *tree_model,
+		GtkTreeIter *iter,
+		RBiPodSource *source)
 
-	g_value_init (&value, G_TYPE_STRING);
-	g_value_set_static_string (&value, str);
-	rhythmdb_entry_set_uninserted (RHYTHMDB (db), entry, propid, &value);
-	g_value_unset (&value);
-}
-
-
-static char *
-ipod_path_to_uri (const char *mount_point, const char *ipod_path)
 {
- 	char *rel_pc_path;
- 	char *full_pc_path;
- 	char *uri;
-	
- 	rel_pc_path = g_strdup (ipod_path);	
- 	itdb_filename_ipod2fs (rel_pc_path);
- 	full_pc_path = g_build_filename (mount_point, rel_pc_path, NULL);
- 	g_free (rel_pc_path);
- 	uri = g_filename_to_uri (full_pc_path, NULL, NULL);
- 	g_free (full_pc_path);
- 	return uri;
-}
-
-static void
-add_rb_playlist (RBiPodSource *source, Itdb_Playlist *playlist)
-{
-	RBShell *shell;
-	RBSource *playlist_source;
-	GList *it;
-	RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
-	RhythmDBEntryType entry_type;
-
-  	g_object_get (G_OBJECT (source), 
-		      "shell", &shell, 
-		      "entry-type", &entry_type,
-		      NULL);
-
-	playlist_source = rb_static_playlist_source_new (shell, 
-							 playlist->name, 
-							 FALSE,
-							 entry_type);
-
-	for (it = playlist->members; it != NULL; it = it->next) {
-		Itdb_Track *song;
-		char *filename;
+	RhythmDBEntry *entry;
+	guint value;
+	gchar *status_string;
 
-		song = (Itdb_Track *)it->data;
- 		filename = ipod_path_to_uri (priv->ipod_mount_path, 
- 					    song->ipod_path);
-		rb_static_playlist_source_add_location (RB_STATIC_PLAYLIST_SOURCE (playlist_source),
-							filename, -1);
-		g_free (filename);
-	}
+	gtk_tree_model_get (tree_model, iter, 0, &entry, -1);
 
-	rb_shell_append_source (shell, playlist_source, RB_SOURCE (source));
-	g_object_unref (G_OBJECT (shell));
+	value = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_IPOD_STATUS);
+	switch (value) {
+	case RHYTHMDB_IPOD_STATUS_COMPLETE:
+		g_object_set (G_OBJECT (renderer), "text",
+				_("Completed"), NULL);
+		value = 100;
+		break;
+	case RHYTHMDB_IPOD_STATUS_ERROR:
+		g_object_set (G_OBJECT (renderer), "text", _("Failed"), NULL);
+		value = 0;
+		break;
+	default:
+		status_string = g_strdup_printf ("%u %%", value);
+		g_object_set (G_OBJECT (renderer), "text",
+				status_string, NULL);
+		g_free (status_string);
+	}
+
+	g_object_set (G_OBJECT (renderer), "visible", TRUE, NULL);
+	g_object_set (G_OBJECT (renderer), "value", value, NULL);
+}
+
+static gint
+rb_ipod_source_status_cell_sort_func (RhythmDBEntry *a,
+		RhythmDBEntry *b,
+		RhythmDBQueryModel *model)
+{
+	gulong a_val, b_val;
+
+	a_val = rhythmdb_entry_get_ulong (a, RHYTHMDB_PROP_IPOD_STATUS);
+	b_val = rhythmdb_entry_get_ulong (b, RHYTHMDB_PROP_IPOD_STATUS);
+
+	if (a_val == b_val)
+		return 0;
+	return a_val > b_val ? 1 : -1;
 }
 
-
 static void
-load_ipod_playlists (RBiPodSource *source)
+add_status_column (RBiPodSource *source, RBEntryView *view)
 {
-	RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
-	GList *it;
-
-	for (it = priv->ipod_db->playlists; it != NULL; it = it->next) {
-		Itdb_Playlist *playlist;
-
-		playlist = (Itdb_Playlist *)it->data;
-		if (itdb_playlist_is_mpl (playlist)) {
-			continue;
-		}
-		if (playlist->is_spl) {
-			continue;
-		}
-
-		add_rb_playlist (source, playlist);
-	}
-
+	GtkTreeViewColumn *column;
+	GtkCellRenderer *renderer;
+	const gchar *status_strings[5];
+
+	status_strings[0] = _("Status");
+	status_strings[1] = _("Downloaded");
+	status_strings[2] = _("Failed");
+	status_strings[3] = "100 %";
+	status_strings[4] = NULL;
+
+	column = gtk_tree_view_column_new ();
+	renderer = gtk_cell_renderer_progress_new ();
+	gtk_tree_view_column_pack_start (column, renderer, TRUE);
+	gtk_tree_view_column_set_clickable (column, TRUE);
+	gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
+	rb_entry_view_set_fixed_column_width (view,
+			column, renderer, status_strings);
+	gtk_tree_view_column_set_cell_data_func (column, renderer,
+			(GtkTreeCellDataFunc)
+				rb_ipod_source_status_cell_data_func,
+			source, NULL);
+	rb_entry_view_append_column_custom (view, column,
+			_("Status"), "Status",
+			(GCompareDataFunc) rb_ipod_source_status_cell_sort_func,
+			0);
 }
 
-#ifdef ENABLE_TRACK_TRANSFER
-static Itdb_Track *
-create_ipod_song_from_entry (RhythmDBEntry *entry)
-{
-	Itdb_Track *track;
-
-	track = itdb_track_new ();
-
-	track->title = rhythmdb_entry_dup_string (entry, RHYTHMDB_PROP_TITLE);
-	track->album = rhythmdb_entry_dup_string (entry, RHYTHMDB_PROP_ALBUM);
-	track->artist = rhythmdb_entry_dup_string (entry, RHYTHMDB_PROP_ARTIST);
-	track->genre = rhythmdb_entry_dup_string (entry, RHYTHMDB_PROP_GENRE);
-	/*	track->filetype = rhythmdb_entry_dup_string (entry, RHYTHMDB_PROP);*/
-	track->size = rhythmdb_entry_get_uint64 (entry, RHYTHMDB_PROP_FILE_SIZE);
-	track->tracklen = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DURATION);
-	track->tracklen *= 1000;
-	track->cd_nr = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DISC_NUMBER);
-	track->track_nr = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_TRACK_NUMBER);
-	track->bitrate = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_BITRATE);
-	track->year = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DATE);
-	track->time_added = itdb_time_get_mac_time ();
-	track->time_played = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_LAST_PLAYED);
-	track->time_played = itdb_time_host_to_mac (track->time_played);
-	track->rating = rhythmdb_entry_get_double (entry, RHYTHMDB_PROP_RATING);
-	track->app_rating = track->rating;
-	track->playcount = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_PLAY_COUNT);
-
-	return track;
-}
-#endif
-
 static void
-add_ipod_song_to_db (RBiPodSource *source, RhythmDB *db, Itdb_Track *song)
+rb_ipod_source_playlist_add_track (RBiPodSource *source,
+		Itdb_Playlist *playlist,
+		Itdb_Track *track,
+		gboolean completed)
 {	
+	GValue value = {0};
 	RhythmDBEntry *entry;
-	RhythmDBEntryType entry_type;
-	RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
-	char *pc_path;
+	const gchar *uri = rb_ipod_track_get_ipod_uri (track);
 
-	/* Set URI */
-	g_object_get (G_OBJECT (source), "entry-type", &entry_type, 
-		      NULL);
-
-	pc_path = ipod_path_to_uri (priv->ipod_mount_path, 
-				    song->ipod_path);
-	entry = rhythmdb_entry_new (RHYTHMDB (db), entry_type,
-				    pc_path);
-
-	if (entry == NULL) {
-		rb_debug ("cannot create entry %s", pc_path);
-		g_free (pc_path);
+	if (itdb_playlist_is_mpl (playlist) == FALSE)
 		return;
-	}
-		
-	rb_debug ("Adding %s from iPod", pc_path);
-	g_free (pc_path);
-
-	/* Set track number */
-	if (song->track_nr != 0) {
-		GValue value = {0, };
-		g_value_init (&value, G_TYPE_ULONG);
-		g_value_set_ulong (&value, song->track_nr);
-		rhythmdb_entry_set_uninserted (RHYTHMDB (db), entry, 
-					       RHYTHMDB_PROP_TRACK_NUMBER, 
-					       &value);
-		g_value_unset (&value);
-	}
-
-	/* Set disc number */
-	if (song->cd_nr != 0) {
-		GValue value = {0, };
-		g_value_init (&value, G_TYPE_ULONG);
-		g_value_set_ulong (&value, song->cd_nr);
-		rhythmdb_entry_set_uninserted (RHYTHMDB (db), entry, 
-					       RHYTHMDB_PROP_DISC_NUMBER, 
-					       &value);
-		g_value_unset (&value);
-	}
-		
-	/* Set bitrate */
-	if (song->bitrate != 0) {
-		GValue value = {0, };
-		g_value_init (&value, G_TYPE_ULONG);
-		g_value_set_ulong (&value, song->bitrate);
-		rhythmdb_entry_set_uninserted (RHYTHMDB (db), entry, 
-					       RHYTHMDB_PROP_BITRATE, 
-					       &value);
-		g_value_unset (&value);
-	}
-		
-	/* Set length */
-	if (song->tracklen != 0) {
-		GValue value = {0, };
-		g_value_init (&value, G_TYPE_ULONG);
-		g_value_set_ulong (&value, song->tracklen/1000);
-		rhythmdb_entry_set_uninserted (RHYTHMDB (db), entry, 
-					       RHYTHMDB_PROP_DURATION, 
-					       &value);
-		g_value_unset (&value);
-	}
-		
-	/* Set file size */
-	if (song->size != 0) {
-		GValue value = {0, };
-		g_value_init (&value, G_TYPE_UINT64);
-		g_value_set_uint64 (&value, song->size);
-		rhythmdb_entry_set_uninserted (RHYTHMDB (db), entry, 
-					       RHYTHMDB_PROP_FILE_SIZE, 
-					       &value);
-		g_value_unset (&value);
-	}
 
-	/* Set playcount */
-	if (song->playcount != 0) {
-		GValue value = {0, };
-		g_value_init (&value, G_TYPE_ULONG);
-		g_value_set_ulong (&value, song->playcount);
-		rhythmdb_entry_set_uninserted (RHYTHMDB (db), entry,
-					       RHYTHMDB_PROP_PLAY_COUNT,
-					       &value);
-		g_value_unset (&value);
-	}
-
-	/* Set year */
-	if (song->year != 0) {
-		GDate *date = NULL;
-		GType type;
-		GValue value = {0, };
-			
-		date = g_date_new_dmy (1, G_DATE_JANUARY, song->year);
-
-		type = rhythmdb_get_property_type (RHYTHMDB(db),
-						   RHYTHMDB_PROP_DATE);
-			
-		g_value_init (&value, type);
-		g_value_set_ulong (&value, (date ? g_date_get_julian (date) : 0));
-			
-		rhythmdb_entry_set_uninserted (RHYTHMDB (db), entry,
-					       RHYTHMDB_PROP_DATE,
-					       &value);
-		g_value_unset (&value);
-		if (date)
-			g_date_free (date);
-	}
-
-	/* Set rating */
-	if (song->rating != 0) {
-		GValue value = {0, };
-		g_value_init (&value, G_TYPE_DOUBLE);
-		g_value_set_double (&value, song->rating/20.0);
-		rhythmdb_entry_set_uninserted (RHYTHMDB (db), entry,
-					       RHYTHMDB_PROP_RATING,
-					       &value);
-		g_value_unset (&value);
-	}
+	rb_debug ("adding %s to playlist %s", uri, playlist->name);
 		
-	/* Set last played */
-	if (song->time_played != 0) {
-		GValue value = {0, };
+	entry = rhythmdb_entry_new (source->priv->db,
+			source->priv->entry_type, uri);
+	rb_ipod_entry_set_from_track (source->priv->db, entry, track);
+	if (completed) {
 		g_value_init (&value, G_TYPE_ULONG);
-		g_value_set_ulong (&value, itdb_time_mac_to_host (song->time_played));
-		rhythmdb_entry_set_uninserted (RHYTHMDB (db), entry,
-					       RHYTHMDB_PROP_LAST_PLAYED,
-					       &value);
+		g_value_set_ulong (&value, RHYTHMDB_IPOD_STATUS_COMPLETE);
+		rhythmdb_entry_set_uninserted (source->priv->db, entry,
+				RHYTHMDB_PROP_IPOD_STATUS, &value);
 		g_value_unset (&value);
 	}
-		
-	/* Set title */		
-	entry_set_string_prop (RHYTHMDB (db), entry, 
-			       RHYTHMDB_PROP_TITLE, song->title);
-		
-	/* Set album, artist and genre from iTunesDB */
-	entry_set_string_prop (RHYTHMDB (db), entry, 
-			       RHYTHMDB_PROP_ARTIST, song->artist);
-		
-	entry_set_string_prop (RHYTHMDB (db), entry, 
-			       RHYTHMDB_PROP_ALBUM, song->album);
-		
-	entry_set_string_prop (RHYTHMDB (db), entry, 
-			       RHYTHMDB_PROP_GENRE, song->genre);
-
-
-	g_hash_table_insert (priv->entry_map, entry, song);
-
-	rhythmdb_commit (RHYTHMDB (db));
+	rhythmdb_commit (source->priv->db);
 }
 
 static gboolean
-load_ipod_db_idle_cb (RBiPodSource *source)
+load_songs_idle_cb (RBiPodSource *source)
 {
-	RBShell *shell;
-	RhythmDB *db;
- 	GList *it;
-	RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
-  
-  	g_object_get (G_OBJECT (source), "shell", &shell, NULL);
-  	g_object_get (G_OBJECT (shell), "db", &db, NULL);
-  	g_object_unref (G_OBJECT (shell));
-  
-  	g_assert (db != NULL);
- 	for (it = priv->ipod_db->tracks; it != NULL; it = it->next) {
-		add_ipod_song_to_db (source, db, (Itdb_Track *)it->data);
-	}
-
-	load_ipod_playlists (source);
+#ifdef ENABLE_TRACK_TRANSFER
+	g_signal_connect (G_OBJECT (source), "notify::name",
+			  G_CALLBACK (name_changed_cb), NULL);
+#endif
+	if (rb_ipod_manager_load_db (source->priv->manager, NULL) == FALSE)
+		rb_ipod_manager_create_db (source->priv->manager, NULL);
 
-	g_object_unref (G_OBJECT (db));
 	return FALSE;
 }
 
-static void
-rb_ipod_load_songs (RBiPodSource *source)
-{
-	RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
-	GnomeVFSVolume *volume;
-
-	g_object_get (G_OBJECT (source), "volume", &volume, NULL);
-	priv->ipod_mount_path = rb_ipod_get_mount_path (volume);
-
- 	priv->ipod_db = itdb_parse (priv->ipod_mount_path, NULL);
-	priv->entry_map = g_hash_table_new (g_direct_hash, g_direct_equal);
-	if ((priv->ipod_db != NULL) && (priv->entry_map != NULL)) {
-		/* FIXME: we could set a different icon depending on the iPod
-		 * model
-		 */
-		g_object_set (RB_SOURCE (source), 
-			      "name", itdb_playlist_mpl (priv->ipod_db)->name, 
-			      NULL);
-		g_idle_add ((GSourceFunc)load_ipod_db_idle_cb, source);
-	}
-}
-
 static gchar *
 rb_ipod_get_mount_path (GnomeVFSVolume *volume)
 {
@@ -618,12 +638,10 @@
 
 		result = hal_udi_is_ipod (udi);
 		g_free (udi);
-		if (result == FALSE) {
-			return FALSE;
-		}
+		if (result == TRUE)
+			return TRUE;
 	}
 #endif
-	
 	return rb_ipod_volume_has_ipod_db (volume);
 }
 
@@ -764,16 +782,16 @@
 
 	return result;
 }
-
 #endif
 
-static GList*
+static GList *
 impl_get_ui_actions (RBSource *source)
 {
 	GList *actions = NULL;
- 
-	actions = g_list_prepend (actions, g_strdup ("RemovableSourceEject"));
- 
+#ifdef ENABLE_TRACK_TRANSFER
+	actions = g_list_append (actions, g_strdup ("iPodSourceSync"));
+#endif
+	actions = g_list_append (actions, g_strdup ("RemovableSourceEject"));
 	return actions;
 }
 
@@ -785,490 +803,357 @@
 	return TRUE;
 }
 
-
 static void
-remove_track_from_db (Itdb_Track *track)
+impl_delete_thyself (RBSource *asource)
 {
-	GList *it;
+	RBiPodSource *source = RB_IPOD_SOURCE (asource);
 
-	for (it = track->itdb->playlists; it != NULL; it = it->next) {
-		itdb_playlist_remove_track ((Itdb_Playlist *)it->data, track);
-	}
-	itdb_track_remove (track);
+	rb_ipod_manager_sync_cancel (source->priv->manager);
+	rb_ipod_manager_write_itdb (source->priv->manager, NULL);
+
+	g_hash_table_foreach (source->priv->playlist_sources,
+			unref_playlist_source, NULL);
+	g_hash_table_destroy (source->priv->playlist_sources);
+	source->priv->playlist_sources = NULL;
+
+	RB_SOURCE_CLASS (rb_ipod_source_parent_class)->impl_delete_thyself (asource);
 }
 
 static void
-impl_move_to_trash (RBSource *asource)
+rb_ipod_source_add_playlist (RBiPodSource *source, Itdb_Playlist *playlist)
 {
-	GList *sel, *tem;
-	RBEntryView *songs;
-	RBShell *shell;
-	RhythmDB *db;
-	RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (asource);
-	RBiPodSource *source = RB_IPOD_SOURCE (asource);
-
-  	g_object_get (G_OBJECT (source), "shell", &shell, NULL);
-  	g_object_get (G_OBJECT (shell), "db", &db, NULL);
-  	g_object_unref (G_OBJECT (shell));
+	RBSource *playlist_source;
+	RBEntryView *view;
 
-	songs = rb_source_get_entry_view (RB_SOURCE (asource));
-	sel = rb_entry_view_get_selected_entries (songs);
-	for (tem = sel; tem != NULL; tem = tem->next) {
-		RhythmDBEntry *entry;
-		const gchar *uri;
-		Itdb_Track *track;
+	rb_debug ("adding playlist %s", playlist->name);
 
-		entry = (RhythmDBEntry *)tem->data;
-		uri = rhythmdb_entry_get_string (entry, 
-						 RHYTHMDB_PROP_LOCATION);
-		track = g_hash_table_lookup (priv->entry_map, entry);
-		if (track == NULL) {
-			g_warning ("Couldn't find track on ipod! (%s)", uri);
-			continue;
-		}
+	playlist_source =
+		rb_ipod_playlist_source_new (source, playlist);
+	view = rb_source_get_entry_view (playlist_source);
+	add_status_column (source, view);
+
+	g_hash_table_insert (source->priv->playlist_sources,
+			playlist, playlist_source);
+	rb_shell_append_source (source->priv->shell,
+			playlist_source, RB_SOURCE (source));
+}
 
- 		remove_track_from_db (track);
-		g_hash_table_remove (priv->entry_map, entry);
-		rhythmdb_entry_move_to_trash (db, entry);
-		rhythmdb_commit (db);
-	}
-	if (sel != NULL) {
-		itdb_write (priv->ipod_db, NULL);
-	}
+static void
+manager_playlist_read_cb (RBiPodManager *manager,
+		Itdb_Playlist *playlist, RBiPodSource *source)
+{
+	if (itdb_playlist_is_mpl (playlist))
+		return;
 
-	g_list_free (sel);
+	rb_ipod_source_add_playlist (source, playlist);
 }
 
 static void
-itdb_schedule_save (Itdb_iTunesDB *db)
+manager_track_read_cb (RBiPodManager *manager,
+		Itdb_Playlist *playlist,
+		Itdb_Track *track,
+		RBiPodSource *source)
 {
-       /* FIXME: should probably be delayed a bit to avoid doing
-        * it after each file when we are copying several files
-        * consecutively
-        * FIXME: or this function could be called itdb_set_dirty, and we'd
-        * have a timeout firing every 5 seconds and saving the db if it's
-        * dirty
-        */
-       itdb_write (db, NULL);
+	rb_ipod_source_playlist_add_track (source,
+			playlist, track, track->transferred);
 }
 
-
 #ifdef ENABLE_TRACK_TRANSFER
-static char *
-build_filename (RBSource *asource, RhythmDBEntry *entry)
+static void
+manager_track_added_cb (RBiPodManager *manager,
+		Itdb_Playlist *playlist,
+		Itdb_Track *track,
+		RBiPodSource *source)
 {
-	const char *uri;
-	char *dest;
-	char *dest_uri;
-
-	RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (asource);
-
-	uri = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
-	dest = ipod_get_filename_for_uri (priv->ipod_mount_path,  uri);
-	if (dest != NULL) {
-		dest_uri = g_filename_to_uri (dest, NULL, NULL);
-		g_free (dest);
-		return dest_uri;
-	}
+	rb_ipod_source_playlist_add_track (source,
+			playlist, track, track->transferred);
+}
 
-	return NULL;
+static void
+manager_track_removed_cb (RBiPodManager *manager,
+		Itdb_Playlist *playlist, Itdb_Track *track,
+		RBiPodSource *source)
+{
+	const gchar *uri;
+
+	uri = rb_ipod_track_get_source_uri (track);
+	if (uri == NULL)
+		uri = rb_ipod_track_get_ipod_uri (track);
+	rb_debug ("removed track %s from playlist %s", uri, playlist->name);
 }
 
 static void
-completed_cb (RhythmDBEntry *entry, const char *dest, RBiPodSource *source)
+manager_playlist_added_cb (RBiPodManager *manager,
+		Itdb_Playlist *playlist, RBiPodSource *source)
 {
-	RBShell *shell;
-	RhythmDB *db;
-	Itdb_Track *song;
-
-  	g_object_get (G_OBJECT (source), "shell", &shell, NULL);
-  	g_object_get (G_OBJECT (shell), "db", &db, NULL);
-  	g_object_unref (G_OBJECT (shell));
-
-	song = create_ipod_song_from_entry (entry);
-	if (song != NULL) {
-		RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
-		char *filename;
-
-		filename = g_filename_from_uri (dest, NULL, NULL);
-		song->ipod_path = ipod_path_from_unix_path (priv->ipod_mount_path, filename);
-		g_free (filename);
-		itdb_track_add (priv->ipod_db, song, -1);
-		itdb_playlist_add_track (itdb_playlist_mpl (priv->ipod_db),
-					 song, -1);
-
-		add_ipod_song_to_db (source, db, song);
-		itdb_schedule_save (priv->ipod_db);
-	}
-
-	g_object_unref (db);
+	rb_ipod_source_add_playlist (source, playlist);
 }
 
 static void
-impl_paste (RBSource *asource, GList *entries)
+manager_playlist_removed_cb (RBiPodManager *manager,
+		Itdb_Playlist *playlist, RBiPodSource *source)
 {
-	RBRemovableMediaManager *rm_mgr;
-	GList *l;
-	RBShell *shell;
-
-	g_object_get (G_OBJECT (asource), "shell", &shell, NULL);
-	g_object_get (G_OBJECT (shell),
-		      "removable-media-manager", &rm_mgr,
-		      NULL);
-	g_object_unref (G_OBJECT (shell));
+	rb_debug ("removed playlist %s", playlist->name);
+	g_hash_table_remove (source->priv->playlist_sources, playlist);
+}
 
-	for (l = entries; l != NULL; l = l->next) {
+static void
+manager_track_transfer_progress_cb (RBiPodManager *manager,
+		RBiPodManagerTrackTransferInfo *info,
+		gdouble progress,
+		RBiPodSource *source)
+{
+	Itdb_Track *track;
+	const gchar *uri;
 		RhythmDBEntry *entry;
-		RhythmDBEntryType entry_type;
-		RhythmDBEntryType ipod_entry_type;
-		char *dest;
-
-		entry = (RhythmDBEntry *)l->data;
-		entry_type = rhythmdb_entry_get_entry_type (entry);
-		g_object_get (G_OBJECT (asource),
-			      "entry-type", &ipod_entry_type,
-			      NULL);
-		if (entry_type == RHYTHMDB_ENTRY_TYPE_IRADIO_STATION ||
-		    entry_type == RHYTHMDB_ENTRY_TYPE_PODCAST_FEED ||
-//		    entry_type == RHYTHMDB_ENTRY_TYPE_PODCAST_EPISODE ||
-		    entry_type == ipod_entry_type)
-			continue;
+	GValue value = { 0 };
 
-		dest = build_filename (asource, entry);
-		if (dest == NULL) {
-			rb_debug ("could not create destination path for entry");
-			continue;
-		}
-		rb_removable_media_manager_queue_transfer (rm_mgr, entry,
-							   dest, NULL,
-							   (RBTranferCompleteCallback)completed_cb, asource);
-		g_free (dest);
+	track = info->track;
+	uri = rb_ipod_track_get_ipod_uri (track);
+	entry = rhythmdb_entry_lookup_by_location (source->priv->db, uri);
+	if (entry == NULL) {
+		/* the entry has been removed while synching...
+		 * the ipod manager should've canceled the transfer */
+		g_return_if_reached ();
+		return;
 	}
-	g_object_unref (G_OBJECT (rm_mgr));
+	g_value_init (&value, G_TYPE_ULONG);
+	g_value_set_ulong (&value, (gulong) (progress * 100));
+	rhythmdb_entry_set (source->priv->db, entry,
+			RHYTHMDB_PROP_IPOD_STATUS, &value);
+	g_value_unset (&value);
+	rhythmdb_commit (source->priv->db);
+	gtk_widget_queue_draw (GTK_WIDGET (source->priv->entry_view));
 }
 
-
-static gboolean
-impl_receive_drag (RBSource *asource, GtkSelectionData *data)
+static void
+manager_track_transfer_finished_cb (RBiPodManager *manager,
+		RBiPodManagerTrackTransferInfo *info,
+		RBiPodSource *source)
 {
-	RBBrowserSource *source = RB_BROWSER_SOURCE (asource);
-	GList *list, *i;
-	GList *entries = NULL;
-	RBShell *shell;
-	RhythmDB *db;
-
-	rb_debug ("parsing uri list");
-	list = rb_uri_list_parse ((const char *) data->data);
-
-  	g_object_get (G_OBJECT (source), "shell", &shell, NULL);
-  	g_object_get (G_OBJECT (shell), "db", &db, NULL);
-  	g_object_unref (G_OBJECT (shell));
-
-	for (i = list; i != NULL; i = g_list_next (i)) {
-		if (i->data != NULL) {
-			char *uri = i->data;
 			RhythmDBEntry *entry;
+	Itdb_Track *track;
+	GValue value = {0};
+	guint status;
 
-			entry = rhythmdb_entry_lookup_by_location (db, uri);
+	track = info->track;
 
-			if (entry == NULL) {
-				/* add to the library */
-				g_print ("Where does that come from?\n");
+	if (info->error) {
+		rb_debug ("track sync finished: %s",
+				info->error->message);
+		status = RHYTHMDB_IPOD_STATUS_ERROR;
 			} else {
-				/* add to list of entries to copy */
-				entries = g_list_prepend (entries, entry);
-			}
-
-			g_free (uri);
+		status = RHYTHMDB_IPOD_STATUS_COMPLETE;
 		}
+	entry = rhythmdb_entry_lookup_by_location (source->priv->db,
+			rb_ipod_track_get_ipod_uri (track));
+	if (entry != NULL) {
+		g_value_init (&value, G_TYPE_ULONG);
+		g_value_set_ulong (&value, status);
+		rhythmdb_entry_set (source->priv->db, entry,
+			RHYTHMDB_PROP_IPOD_STATUS, &value);
+		g_value_unset (&value);
+	} else {
+		/* the entry has been removed while it was being synched */
 	}
-	g_object_unref (db);
-	g_list_free (list);
-
-	if (entries) {
-		entries = g_list_reverse (entries);
-		if (rb_source_can_paste (asource))
-			rb_source_paste (asource, entries);
-		g_list_free (entries);
-	}
-
 
-	return TRUE;
+	gtk_widget_queue_draw (GTK_WIDGET (source->priv->entry_view));
 }
 
-
-/* Generation of the filename for the ipod */
-
-#define IPOD_MAX_PATH_LEN 56
-
-static gboolean
-test_dir_on_ipod (const char *mountpoint, const char *dirname)
+static void
+manager_dirty_cb (RBiPodManager *manager,
+		gboolean dirty, RBiPodSource *source)
 {
-	char *fullpath;
-	gboolean result;
-
-	fullpath  = g_build_filename (mountpoint, dirname, NULL);
-	result = g_file_test (fullpath, G_FILE_TEST_IS_DIR);
-	g_free (fullpath);
-	
-	return result;
+	rb_debug ("dirty %d", dirty);
 }
 
-static int
-ipod_mkdir_with_parents (const char *mountpoint, const char *dirname)
+static void
+manager_sync_started_cb (RBiPodManager *manager, RBiPodSource *source)
 {
-	char *fullpath;
-	int result;
-
-	fullpath  = g_build_filename (mountpoint, dirname, NULL);
-	result = g_mkdir_with_parents (fullpath, 0770);
-	g_free (fullpath);
-	
-	return result;
+	rb_debug ("sync started");
 }
 
-static gchar *
-build_ipod_dir_name (const char *mountpoint)
+static void
+manager_sync_finished_cb (RBiPodManager *manager, gboolean canceled,
+		RBiPodSource *source)
 {
-	/* FAT sucks, filename can be lowercase or uppercase, and if we try to
-	 * open the wrong one, we lose :-/
-	 */
-	char *dirname;
-	char *relpath;
-	gint32 suffix;
-	
-	suffix = g_random_int_range (0, 100);
-	dirname = g_strdup_printf ("F%02d", suffix);
-	relpath = g_build_filename (G_DIR_SEPARATOR_S, "iPod_Control",
-				    "Music", dirname, NULL);
-	g_free (dirname);
-
-	if (test_dir_on_ipod (mountpoint, relpath) != FALSE) {
-		return relpath;
-	}
-
-	g_free (relpath);
-	dirname = g_strdup_printf ("f%02d", g_random_int_range (0, 100));
-	relpath = g_build_filename (G_DIR_SEPARATOR_S, "iPod_Control",
-				    "Music", dirname, NULL);
-	g_free (dirname);
-
-	if (test_dir_on_ipod (mountpoint, relpath) != FALSE) {
-		return relpath;
+	if (canceled) {
+		rb_debug ("sync canceled");
+	} else {
+		rb_debug ("sync finished");
 	}
 
-	if (ipod_mkdir_with_parents (mountpoint, relpath) == 0) {
-		return relpath;
-	}
+	gtk_toggle_action_set_active (source->priv->action_sync, FALSE);
+}
 
-	g_free (relpath);
-	return NULL;
+static void
+manager_error_cb (RBiPodManager *manager,
+		GError *error, RBiPodSource *source)
+{
+	rb_debug ("manager error: %s", error->message);
 }
 
-static gchar *
-get_ipod_filename (const char *mount_point, const char *filename)
+static gboolean
+add_entry (RBiPodSource *source, RhythmDBEntry *entry, const gchar *uri)
 {
-	char *dirname;
-	char *result;
-	char *tmp;
+	RhythmDBEntryType entry_type;
+	Itdb_Track *track;
+	gboolean res;
+	gboolean is_podcast;
 
-	dirname = build_ipod_dir_name (mount_point);
-	if (dirname == NULL) {
-		return NULL;
+	entry_type = rhythmdb_entry_get_pointer (entry, RHYTHMDB_PROP_TYPE);
+	if (entry_type == RHYTHMDB_ENTRY_TYPE_PODCAST_POST) {
+		is_podcast = TRUE;
+	} else if (entry_type == RHYTHMDB_ENTRY_TYPE_SONG) {
+		is_podcast = FALSE;
+	} else {
+		rb_debug ("%s is not a song nor a podcast", uri);
+		return FALSE;
 	}
-	result = g_build_filename (dirname, filename, NULL);
-	g_free (dirname);
 
-	if (strlen (result) >= IPOD_MAX_PATH_LEN) {
-		char *ext;
+	track = itdb_track_new ();
+	rb_ipod_track_set_from_entry (source->priv->db, track, entry);
+	rb_ipod_track_set_source_uri (track, uri);
 
-		ext = strrchr (result, '.');
-		if (ext == NULL) {
-			result [IPOD_MAX_PATH_LEN - 1] = '\0';
-		} else {
-			memmove (&result[IPOD_MAX_PATH_LEN - strlen (ext) - 1] ,
-				 ext, strlen (ext) + 1);
-		}
-	}
+	if (is_podcast)
+		res = rb_ipod_manager_podcasts_add_track (source->priv->manager,
+				track);
+	else
+		res = rb_ipod_manager_add_track (source->priv->manager, track);
 
-	tmp = g_build_filename (mount_point, result, NULL);
-	g_free (result);
-	return tmp;
-}
+	if (res == FALSE)
+		itdb_track_free (track);
 
-#define MAX_TRIES 5
+	return res;
+}
 
-/* Strips non UTF8 characters from a string replacing them with _ */
-static gchar *
-utf8_to_ascii (const gchar *utf8)
+static void
+action_new_playlist_cb (GtkAction *action, RBiPodSource *source)
 {
-	GString *string;
-	const guchar *it = (const guchar *)utf8;
+	/* create a nameless playlist */
+	Itdb_Playlist *playlist = itdb_playlist_new ("", FALSE);
+	rb_ipod_playlist_add_data (playlist);
+	rb_ipod_manager_add_playlist (source->priv->manager, playlist);
+}
 
-	string = g_string_new ("");
-	while ((it != NULL) && (*it != '\0')) {
-		/* Do we have a 7 bit char ? */
-		if (*it < 0x80) {
-			g_string_append_c (string, *it);
+static void
+action_sync_toggled_cb (GtkToggleAction *action, RBiPodSource *source)
+{
+	if (gtk_toggle_action_get_active (action)) {
+		rb_debug ("sync action start");
+		if (rb_ipod_manager_sync (source->priv->manager, TRUE) == FALSE)
+			gtk_toggle_action_set_active (source->priv->action_sync,
+					FALSE);
 		} else {
-			g_string_append_c (string, '_');
-		}
-		it = (const guchar *)g_utf8_next_char (it);
+		rb_debug ("sync action cancel");
+		rb_ipod_manager_sync_cancel (source->priv->manager);
 	}
-
-	return g_string_free (string, FALSE);
 }
 
-/* Copied from eel-vfs-extensions.c from eel CVS HEAD on 2004-05-09
- * This function is (C) 1999, 2000 Eazel, Inc.
- */
-static char *
-eel_make_valid_utf8 (const char *name)
+static gboolean
+impl_receive_drag (RBSource *asource, GtkSelectionData *data)
 {
-	GString *string;
-	const char *remainder, *invalid;
-	int remaining_bytes, valid_bytes;
-
-	string = NULL;
-	remainder = name;
-	remaining_bytes = strlen (name);
-
-	while (remaining_bytes != 0) {
-		if (g_utf8_validate (remainder, remaining_bytes, &invalid)) {
-			break;
-		}
-		valid_bytes = invalid - remainder;
-
-		if (string == NULL) {
-			string = g_string_sized_new (remaining_bytes);
-		}
-		g_string_append_len (string, remainder, valid_bytes);
-		g_string_append_c (string, '_');
+	GList *uri_list = NULL;
+	RBiPodSource *source = RB_IPOD_SOURCE (asource);
+	gboolean res = FALSE;
 
-		remaining_bytes -= valid_bytes + 1;
-		remainder = invalid + 1;
-	}
+	rb_debug ("receive drag");
 
-	if (string == NULL) {
-		return g_strdup (name);
+	if (data->type == gdk_atom_intern ("text/uri-list", TRUE)) {
+		uri_list = rb_uri_list_parse ((gchar *) data->data);
 	}
 
-	g_string_append (string, remainder);
-	g_assert (g_utf8_validate (string->str, -1, NULL));
-
-	return g_string_free (string, FALSE);
-}
-
-
-static gchar *
-generate_ipod_filename (const gchar *mount_point, const gchar *filename)
-{
-	gchar *ipod_filename = NULL;
-	gchar *pc_filename;
-	gchar *tmp;
-	gint tries = 0;
-
-	/* First, we need a valid UTF-8 filename, strip all non-UTF-8 chars */
-	tmp = eel_make_valid_utf8 (filename);
-	/* The iPod doesn't seem to recognize non-ascii chars in filenames,
-	 * so we strip them
-	 */
-	pc_filename = utf8_to_ascii (tmp);
-	g_free (tmp);
-
-	g_assert (g_utf8_validate (pc_filename, -1, NULL));
-	/* Now we have a valid UTF-8 filename, try to find out where to put
-	 * it on the iPod
-	 */
-	do {
-		g_free (ipod_filename);
-		ipod_filename = get_ipod_filename (mount_point, pc_filename);
-		tries++;
-		if (tries > MAX_TRIES) {
-			break;
-		}
-	} while ((ipod_filename == NULL) 
-		 || (g_file_test (ipod_filename, G_FILE_TEST_EXISTS)));
+	if (uri_list) {
+		GList *walk;
+		RhythmDBEntry *entry;
 
-	g_free (pc_filename);
+		for (walk = uri_list; walk; walk = g_list_next (walk)) {
+			gchar *uri = (gchar *) walk->data;
 
-	if (tries > MAX_TRIES) {
-		/* FIXME: should create a unique filename */
-		return NULL;
-	} else {
-		return ipod_filename;
+			entry = rhythmdb_entry_lookup_by_location (source->priv->db,
+					uri);
+			if (entry == NULL) {
+				rb_debug ("skipping %s as it isn't in "
+						"library", uri);
+				continue;
 	}
-}
 
-static gchar *
-ipod_get_filename_for_uri (const gchar *mount_point, const gchar *uri_str)
-{
-	GnomeVFSURI *uri;
-	gchar *escaped;
-	gchar *filename;
-	gchar *result;
 
-	uri = gnome_vfs_uri_new (uri_str);
-	if (uri == NULL) {
-		return NULL;
-	}
-	escaped = gnome_vfs_uri_extract_short_path_name (uri);
-	gnome_vfs_uri_unref (uri);
-	if (escaped == NULL) {
-		return NULL;
+			if (add_entry (source, entry, uri) == TRUE) {
+				res = TRUE;
 	}
-	filename = gnome_vfs_unescape_string (escaped, G_DIR_SEPARATOR_S);
-	g_free (escaped);
-	if (filename == NULL) {
-		return NULL;
 	}
 
-	result = generate_ipod_filename (mount_point, filename);
-	g_free (filename);
+		rhythmdb_commit (source->priv->db);
+	}
 
-	return result;
+	return res;
 }
 
-/* End of generation of the filename on the iPod */
-
-static gchar *
-ipod_path_from_unix_path (const gchar *mount_point, const gchar *unix_path)
+static void
+impl_delete (RBSource *asource)
 {
-        gchar *ipod_path;
-
-        g_assert (g_utf8_validate (unix_path, -1, NULL));
-
-        if (!g_str_has_prefix (unix_path, mount_point)) {
-                return NULL;
-        }
+	RBiPodSource *source = RB_IPOD_SOURCE (asource);
+	GList *selected, *walk;
+	const gchar *uri;
+	RhythmDBEntry *entry;
 
-        ipod_path = g_strdup (unix_path + strlen (mount_point));
-        if (*ipod_path != G_DIR_SEPARATOR) {
-                gchar *tmp;
-                tmp = g_strdup_printf ("/%s", ipod_path);
-                g_free (ipod_path);
-                ipod_path = tmp;
+	selected =
+		rb_entry_view_get_selected_entries (source->priv->entry_view);
+	for (walk = selected; walk; walk = g_list_next (walk)) {
+		entry = (RhythmDBEntry *) walk->data;
+		uri = rhythmdb_entry_get_string (entry,
+				RHYTHMDB_PROP_LOCATION);
+
+		rb_debug ("delete %s", uri);
+		rb_ipod_manager_remove_uri (source->priv->manager, uri);
+		rhythmdb_entry_delete (source->priv->db, entry);
         }
 
-        /* Make sure the filename doesn't contain any ':' */
-        g_strdelimit (ipod_path, ":", ';');
-
-
-        /* Convert path to a Mac path where the dir separator is ':' */
-	itdb_filename_fs2ipod (ipod_path);
-
-        return ipod_path;
+	rhythmdb_commit (source->priv->db);
 }
 #endif
 
+#if 0
 static void
-impl_delete_thyself (RBSource *source)
+impl_move_to_trash (RBSource *asource)
 {
-	RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
+	GList *sel, *tem;
+	RBEntryView *songs;
+	RBShell *shell;
+	RhythmDB *db;
+	RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (asource);
+	RBiPodSource *source = RB_IPOD_SOURCE (asource);
 
-	itdb_free (priv->ipod_db);
-	priv->ipod_db = NULL;
+  	g_object_get (G_OBJECT (source), "shell", &shell, NULL);
+  	g_object_get (G_OBJECT (shell), "db", &db, NULL);
+  	g_object_unref (G_OBJECT (shell));
 
-	RB_SOURCE_CLASS (rb_ipod_source_parent_class)->impl_delete_thyself (source);
-}
+	songs = rb_source_get_entry_view (RB_SOURCE (asource));
+	sel = rb_entry_view_get_selected_entries (songs);
+	for (tem = sel; tem != NULL; tem = tem->next) {
+		RhythmDBEntry *entry;
+		const gchar *uri;
+		Itdb_Track *track;
+
+		entry = (RhythmDBEntry *)tem->data;
+		uri = rhythmdb_entry_get_string (entry,
+						 RHYTHMDB_PROP_LOCATION);
+		track = g_hash_table_lookup (priv->entry_map, entry);
+		if (track == NULL) {
+			g_warning ("Couldn't find track on ipod! (%s)", uri);
+			continue;
+		}
 
+ 		remove_track_from_db (track);
+		g_hash_table_remove (priv->entry_map, entry);
+		rhythmdb_entry_move_to_trash (db, entry);
+		rhythmdb_commit (db);
+	}
+	if (sel != NULL) {
+		itdb_write (priv->ipod_db, NULL);
+	}
+
+	g_list_free (sel);
+}
+#endif
diff -u -u -r1.6 rb-ipod-source.h
--- sources/rb-ipod-source.h	20 Apr 2006 20:43:30 -0000	1.6
+++ sources/rb-ipod-source.h	14 May 2006 06:12:54 -0000
@@ -22,6 +22,7 @@
 #ifndef __RB_IPOD_SOURCE_H
 #define __RB_IPOD_SOURCE_H
 
+#include <libgnomevfs/gnome-vfs.h>
 #include "rb-shell.h"
 #include "rb-removable-media-source.h"
 #include "rhythmdb.h"
@@ -35,9 +36,12 @@
 #define RB_IS_IPOD_SOURCE_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), RB_TYPE_IPOD_SOURCE))
 #define RB_IPOD_SOURCE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), RB_TYPE_IPOD_SOURCE, RBiPodSourceClass))
 
+typedef struct _RBiPodSourcePrivate RBiPodSourcePrivate;
+
 typedef struct
 {
 	RBRemovableMediaSource parent;
+	RBiPodSourcePrivate *priv;
 } RBiPodSource;
 
 typedef struct
diff -u -u -r1.169 rb-library-source.c
--- sources/rb-library-source.c	9 May 2006 10:11:01 -0000	1.169
+++ sources/rb-library-source.c	14 May 2006 06:12:54 -0000
@@ -1152,6 +1152,7 @@
 	for (l = entries; l != NULL; l = g_list_next (l)) {
 		RhythmDBEntry *entry = (RhythmDBEntry *)l->data;
 		RhythmDBEntryType entry_type;
+		RBSource *copy_source;
 		char *dest;
 
 		rb_debug ("pasting entry %s", rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION));
@@ -1162,7 +1163,9 @@
 			continue;
 		
 		/* see if the responsible source lets us copy */
-		if (!rb_source_can_copy (rb_shell_get_source_by_entry_type (shell, entry_type)))
+		copy_source = rb_shell_get_source_by_entry_type (shell,
+				entry_type);
+		if (copy_source && !rb_source_can_copy (copy_source))
 			continue;
 
 		dest = build_filename (source, entry);
diff -u -u -r1.10 Makefile.am
--- tests/Makefile.am	3 Nov 2005 02:53:28 -0000	1.10
+++ tests/Makefile.am	14 May 2006 06:12:55 -0000
@@ -1,8 +1,6 @@
 ## arch-tag: Automake rules for test programs
 ## Process this file with automake to produce Makefile.in
 
-if FALSE
-
 #TESTS = test-rhythmdb-simple test-rhythmdb-indexing \
 #		  test-rhythmdb-query test-rhythmdb-tree-serialization \
 #		  test-rhythmdb-tree-deserialization \
@@ -10,8 +8,17 @@
 
 TESTS = 
 
-noinst_PROGRAMS = test-cmdline $(TESTS)
+if HAVE_CHECK
+if USE_IPOD
+if ENABLE_TRACK_TRANSFER
+TESTS += test-rb-ipod-manager
+endif
+endif
+endif
+
+noinst_PROGRAMS = $(TESTS)
 
+if FALSE
 test_rhythmdb_simple_SOURCES = test-rhythmdb-simple.c
 test_rhythmdb_simple_LDADD =					\
 	$(top_builddir)/rhythmdb/librhythmdb.la			\
@@ -56,17 +63,33 @@
 	$(RHYTHMBOX_LIBS)
 
 test_cmdline_LDFLAGS = -export-dynamic
+endif
 
 INCLUDES = 							\
         -DGNOMELOCALEDIR=\""$(datadir)/locale"\"	        \
 	-DG_LOG_DOMAIN=\"Rhythmbox-tests\"			\
 	-I$(top_srcdir) 					\
 	$(RHYTHMBOX_CFLAGS)					\
+	$(CHECK_CFLAGS)						\
+	$(IPOD_CFLAGS)						\
 	-I$(top_srcdir)/lib					\
 	-I$(top_srcdir)/monkey-media				\
 	-I$(top_srcdir)/rhythmdb				\
-	-I$(top_srcdir)/widgets
-
-endif
+	-I$(top_srcdir)/widgets					\
+	-I$(top_srcdir)/player					\
+	-I$(top_srcdir)/plugins/ipod
+
+test_rb_ipod_manager_SOURCES = test-rb-ipod-manager.c
+test_rb_ipod_manager_LDADD = 					\
+	$(CHECK_LIBS) 						\
+	$(RHYTHMBOX_LIBS)					\
+	$(GNOME_MEDIA_PROFILES_LIBS)				\
+	$(IPOD_LIBS)						\
+	$(top_builddir)/plugins/ipod/librbipodmanager.la	\
+	$(top_builddir)/rhythmdb/librhythmdb.la			\
+	$(top_builddir)/widgets/librbwidgets.la			\
+	$(top_builddir)/metadata/librbmetadata.la 		\
+	$(top_builddir)/lib/librb.la 				\
+	$(top_builddir)/backends/librbbackends.la
 
 EXTRA_DIST = deserialization-test1.xml deserialization-test2.xml deserialization-test3.xml
--- /dev/null	2006-05-12 21:45:15.000000000 +1000
+++ tests/test-rb-ipod-manager.c	2006-05-14 16:10:44.000000000 +1000
@@ -0,0 +1,459 @@
+/*
+ * test-rb-ipod-manager.c - rb-ipod-manager test suite.
+ * Copyright 2006, Alessandro Decina
+ *
+ * Authors:
+ *   Alessandro Decina <alessandro@nnva.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License, version 2, as published by the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+*/
+
+#include <stdlib.h>
+#include <check.h>
+#include <glib.h>
+#include <glib-object.h>
+#include <libgnomevfs/gnome-vfs.h>
+#include <gst/gst.h>
+
+#include "rb-refstring.h"
+#include "rb-util.h"
+#include "rhythmdb-tree.h"
+#include "rb-ipod-manager.h"
+
+static gboolean wait_non_zero (int *signal_counter);
+static void read_case_setup (void);
+static void read_case_teardown (void);
+
+static RBiPodManager *manager;
+static gchar *mount_path; 
+static RhythmDB *db;
+static const gchar *tmpdir;
+gchar *foo;
+gchar *bar;
+
+static int nr_signal_track_added;
+static int nr_signal_track_removed;
+static int nr_signal_track_transfer_started;
+static int nr_signal_track_transfer_finished;
+
+static int nr_signal_playlist_added;
+static int nr_signal_playlist_removed;
+
+static int nr_signal_sync_started;
+static int nr_signal_sync_finished;
+
+static gboolean manager_is_dirty;
+
+static GError *track_transfer_error;
+static GError *sync_error;
+
+GMainLoop *loop;
+
+#define fail_unless_equals_int(a, b) \
+	fail_unless (a == b, "'" #a "' (%d) is not equal to '" #b \
+			"' (%d)", a, b);
+
+#define wait_signal(signal_name) wait_non_zero (&nr_signal_ ## signal_name)
+
+static gboolean
+quit_if_non_zero_cb (int *signal_counter)
+{
+	if (*signal_counter > 0)
+		g_main_loop_quit (loop);
+	
+	return TRUE;
+}
+
+static gboolean
+quit_if_zero_cb (int *signal_counter)
+{
+	if (*signal_counter == 0)
+		g_main_loop_quit (loop);
+
+	return FALSE;
+}
+
+static gboolean
+wait_non_zero (int *signal_counter)
+{	
+	guint idle, timeout;
+	if (*signal_counter > 0)
+		return TRUE;
+
+	loop = g_main_loop_new (g_main_context_default (), FALSE);
+	idle = g_idle_add ((GSourceFunc) quit_if_non_zero_cb, signal_counter);
+	timeout = g_timeout_add (1000, (GSourceFunc) quit_if_zero_cb,
+			signal_counter);
+
+	g_main_loop_run (loop);
+
+	g_source_remove (idle);
+	g_source_remove (timeout);
+	g_main_loop_unref (loop);
+
+	return signal_counter > 0;
+}
+
+static void
+track_added_cb (RBiPodManager *manager,
+		Itdb_Playlist *playlist, Itdb_Track *track, gpointer ignored)
+{
+	++nr_signal_track_added;
+
+	fail_unless (track != NULL);
+	fail_unless (rb_ipod_track_get_ipod_uri (track) != NULL);
+}
+
+static void
+track_removed_cb (RBiPodManager *manager,
+		Itdb_Playlist *playlist, Itdb_Track *track, gpointer ignored)
+{
+	++nr_signal_track_removed;
+
+	fail_unless (track != NULL);
+	fail_unless (rb_ipod_track_get_ipod_uri (track) != NULL);
+}
+
+static void
+dirty_cb (RBiPodManager *manager,
+		gboolean dirty, gpointer ignored)
+{
+	manager_is_dirty = dirty;
+}
+
+static void
+track_transfer_started_cb (RBiPodManager *manager,
+		RBiPodManagerTrackTransferInfo *info, gpointer ignored)
+{
+	++nr_signal_track_transfer_started;
+
+	fail_unless (info != NULL);
+	fail_unless (info->error == NULL);
+	fail_unless (info->track != NULL);
+}
+
+static void
+track_transfer_finished_cb (RBiPodManager *manager,
+		RBiPodManagerTrackTransferInfo *info, gpointer ignored)
+{
+	++nr_signal_track_transfer_finished;
+	if (info->error)
+		track_transfer_error = g_error_copy (info->error);
+}
+
+static void
+playlist_added_cb (RBiPodManager *manager,
+		Itdb_Playlist *playlist, gpointer ignored)
+{
+	++nr_signal_playlist_added;
+}
+
+static void
+playlist_removed_cb (RBiPodManager *manager, 
+		Itdb_Playlist *playlist, gpointer ignored)
+{
+	++nr_signal_playlist_removed;
+}
+
+static void
+sync_started_cb (RBiPodManager *manager, gpointer ignored)
+{
+	++nr_signal_sync_started;
+}
+
+static void
+sync_finished_cb (RBiPodManager *manager,
+		GError *error, gpointer ignored)
+{
+	++nr_signal_sync_finished;
+
+	if (error)
+		sync_error = g_error_copy (error);
+}
+
+static Itdb_Track *
+create_track (gchar *uri)
+{
+	Itdb_Track *track;
+	gchar *filename;
+	
+	track = itdb_track_new ();
+	rb_ipod_track_add_data (track);
+	filename = g_filename_from_uri (uri, NULL, NULL);
+	if (g_str_has_prefix (filename, mount_path))
+		rb_ipod_track_set_ipod_uri (track, uri);
+	else
+		rb_ipod_track_set_source_uri (track, uri);
+	g_free (filename);
+	
+	return track;
+}
+
+static Itdb_Playlist *
+create_playlist (gchar *name)
+{
+	Itdb_Playlist *pl = itdb_playlist_new (name, FALSE);
+	rb_ipod_playlist_add_data (pl);
+
+	return pl;
+}
+
+static void
+common_setup (void)
+{
+	mount_path = g_build_filename (tmpdir, "rb-ipod-manager-test", NULL);
+	foo = "file:///foo";
+	bar = g_strdup_printf ("file://%s/bar", mount_path);
+	db = rhythmdb_tree_new ("rb-ipod-manager-test-db");
+	manager = rb_ipod_manager_new (mount_path, db);
+	rhythmdb_entry_new (db, RHYTHMDB_ENTRY_TYPE_SONG, foo);
+	rhythmdb_entry_new (db, RHYTHMDB_ENTRY_TYPE_SONG, bar);
+	g_free (mount_path);
+	
+	nr_signal_track_added = 0;
+	nr_signal_track_removed = 0;
+	nr_signal_track_transfer_started = 0;
+	nr_signal_track_transfer_finished = 0;
+	
+	nr_signal_playlist_added = 0;
+	nr_signal_playlist_removed = 0;
+	
+	nr_signal_sync_started = 0;
+	nr_signal_sync_finished = 0;
+
+	manager_is_dirty = FALSE;
+	track_transfer_error = NULL;
+	sync_error = NULL;
+	
+	g_signal_connect (manager, "dirty",
+			G_CALLBACK (dirty_cb),
+			NULL);
+	
+	g_signal_connect (manager, "track-added",
+			G_CALLBACK (track_added_cb),
+			NULL);
+
+	g_signal_connect (manager, "track-removed",
+			G_CALLBACK (track_removed_cb),
+			NULL);
+	
+	g_signal_connect (manager, "track-transfer-started",
+			G_CALLBACK (track_transfer_started_cb),
+			NULL);
+
+	g_signal_connect (manager, "track-transfer-finished",
+			G_CALLBACK (track_transfer_finished_cb),
+			NULL);
+	
+	g_signal_connect (manager, "playlist-added",
+			G_CALLBACK (playlist_added_cb),
+			NULL);
+
+	g_signal_connect (manager, "playlist-removed",
+			G_CALLBACK (playlist_removed_cb),
+			NULL);
+	
+	g_signal_connect (manager, "sync-started",
+			G_CALLBACK (sync_started_cb),
+			NULL);
+
+	g_signal_connect (manager, "sync-finished",
+			G_CALLBACK (sync_finished_cb),
+			NULL);
+
+}
+
+static void
+common_teardown (void)
+{
+	g_free (bar);
+	g_clear_error (&track_transfer_error);
+	g_clear_error (&sync_error);
+
+	g_object_unref (db);
+	g_object_unref (manager);
+}
+
+static void
+write_case_setup (void) {
+	common_setup ();
+	fail_unless (rb_ipod_manager_create_db (manager, NULL) == TRUE);
+}
+
+static void
+write_case_teardown (void)
+{
+	common_teardown ();
+}
+
+static void
+read_case_setup (void)
+{
+	common_setup ();
+	fail_unless (rb_ipod_manager_load_db (manager, NULL) == TRUE);
+}
+
+static void
+read_case_teardown (void)
+{
+	common_teardown ();
+}
+
+START_TEST (test_write_track_add_remove)
+{
+	Itdb_Track *foo_track = create_track (foo);
+
+	fail_unless (rb_ipod_manager_add_track (manager, foo_track) == TRUE);
+	/* adding an uri twice fails */
+	fail_unless (rb_ipod_manager_add_track (manager, foo_track) == FALSE);
+	fail_unless_equals_int (nr_signal_track_added, 1);
+	/* when a track is added and _sync isn't called, the manager is dirty */
+	fail_unless (manager_is_dirty = TRUE);
+	
+	fail_unless (rb_ipod_manager_remove_uri (manager, foo) == TRUE);
+	/* removing an uri twice fails too */
+	fail_unless (rb_ipod_manager_remove_uri (manager, foo) == FALSE);
+	fail_unless_equals_int (nr_signal_track_removed, 1);
+	/* when the changes to the manager are undone, it gets clean again */
+	fail_unless (manager_is_dirty == FALSE);
+}
+END_TEST
+
+START_TEST (test_write_playlist_add_remove)
+{
+	Itdb_Playlist *pl1 = create_playlist ("PL1");
+	Itdb_Playlist *pl2 = create_playlist ("PL2");
+
+	fail_unless (rb_ipod_manager_add_playlist (manager, pl1) == TRUE);
+	/* can't add two playlists with the same name */
+	fail_unless (rb_ipod_manager_add_playlist (manager, pl1) == FALSE);
+	fail_unless (rb_ipod_manager_add_playlist (manager, pl2));
+	fail_unless_equals_int (nr_signal_playlist_added, 2);
+	fail_unless (manager_is_dirty == TRUE);
+
+	fail_unless (rb_ipod_manager_remove_playlist_name (manager, "PL1")
+			== TRUE);
+	/* you can't obviously remove a playlist twice */
+	fail_unless (rb_ipod_manager_remove_playlist_name (manager, "PL1")
+			== FALSE);
+	fail_unless (rb_ipod_manager_remove_playlist_name (manager, "PL2")
+			== TRUE);
+	fail_unless_equals_int (nr_signal_playlist_removed, 2);
+	/* we added and then removed two playlists, we're clean */
+	fail_unless (manager_is_dirty == FALSE);
+}
+END_TEST
+
+START_TEST (test_write_playlist_add_remove_track)
+{	
+	Itdb_Playlist *pl1 = create_playlist ("PL1");
+	Itdb_Track *foo_track = create_track (foo);
+	Itdb_Track *bar_track = create_track (bar);
+	
+	/* check if adding a playlist and a track to it works */
+	fail_unless (rb_ipod_manager_add_playlist (manager, pl1) == TRUE);
+	/* note: if a track isn't in the MPL, it's automatically added to it.
+	 * so we get two track-added signals here: the first for the MPL, the
+	 * second for PL1 */
+	fail_unless (rb_ipod_manager_playlist_add_track (manager,
+				pl1, foo_track) == TRUE);
+	fail_unless (rb_ipod_manager_playlist_add_track (manager,
+				pl1, foo_track) == FALSE);
+	fail_unless_equals_int (nr_signal_track_added, 2);
+	fail_unless (manager_is_dirty == TRUE);
+
+	fail_unless (rb_ipod_manager_add_track (manager, bar_track) == TRUE);
+	fail_unless_equals_int (nr_signal_track_added, 3);
+	/* if a track is already in the MPL, it won't be added again so just
+	 * one track-added is emitted after the _playlist_name_add_uri call */
+	fail_unless (rb_ipod_manager_playlist_add_track (manager,
+				pl1, bar_track) == TRUE);
+	fail_unless_equals_int (nr_signal_track_added, 4);
+	
+	fail_unless (rb_ipod_manager_playlist_remove_uri (manager,
+				pl1, foo) == TRUE);
+	fail_unless (rb_ipod_manager_playlist_remove_uri (manager,
+				pl1, foo) == FALSE);
+	fail_unless_equals_int (nr_signal_track_removed, 1);
+
+	/* removing a playlist also removes all its tracks */
+	fail_unless (rb_ipod_manager_remove_playlist_name (manager, "PL1")
+			== TRUE);
+	fail_unless (rb_ipod_manager_playlist_remove_uri (manager, 
+				pl1, bar) == FALSE);
+	fail_unless_equals_int (nr_signal_track_removed, 2);
+	
+	/* here the tracks are still in the MPL */
+	fail_unless (rb_ipod_manager_remove_uri (manager, foo) == TRUE);
+	fail_unless (rb_ipod_manager_remove_uri (manager, bar) == TRUE);
+	fail_unless_equals_int (nr_signal_track_removed, 4);
+	/* we emptied both PL1 and the MPL, so we're now clean */
+	fail_unless (manager_is_dirty == FALSE);
+	
+}
+END_TEST
+
+static Suite *
+manager_suite (void)
+{
+	Suite *suite = suite_create ("RBiPodManager");
+	/* suite to test write operations */
+	TCase *write_case = tcase_create ("Write");
+	/* suite to test read operations */
+	TCase *read_case = tcase_create ("Read");
+	suite_add_tcase (suite, write_case);
+	suite_add_tcase (suite, read_case);
+	
+	tcase_add_checked_fixture (write_case,
+			write_case_setup, write_case_teardown);
+	tcase_add_test (write_case, test_write_track_add_remove);
+	tcase_add_test (write_case, test_write_playlist_add_remove);
+	tcase_add_test (write_case, test_write_playlist_add_remove_track);
+#if 0	
+	tcase_add_checked_fixture (read_case,
+			read_case_setup, read_case_teardown);
+	tcase_add_test (test_read_check_db);
+#endif
+	
+	return suite;
+}
+
+int
+main (void)
+{
+	int failed;
+
+	g_type_init ();
+	g_thread_init (NULL);
+	rb_threads_init ();
+	rb_refstring_system_init ();
+	gnome_vfs_init ();
+	
+	tmpdir = g_get_tmp_dir ();
+	Suite *suite = manager_suite ();
+	SRunner *runner = srunner_create (suite);
+	srunner_run_all (runner, CK_NORMAL);
+	failed = srunner_ntests_failed (runner);
+
+	/* these aren't used anywhere, so there here to make it compile with warnings */
+	if (0) {
+		wait_non_zero (NULL);
+		read_case_setup ();
+		read_case_teardown ();
+	}
+	srunner_free (runner);
+
+	return failed == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+}
--- /dev/null	2006-05-12 21:45:15.000000000 +1000
+++ plugins/ipod/rb-ipod.h	2006-05-12 17:59:00.000000000 +1000
@@ -0,0 +1,97 @@
+/*
+ * rb-ipod.h - Rhythmbox iPod utility functions.
+ * Copyright 2006, Alessandro Decina
+ *
+ * Authors:
+ *   Alessandro Decina <alessandro@nnva.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License, version 2, as published by the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+*/
+
+#ifndef __RB_IPOD_H
+#define __RB_IPOD_H
+
+#include <gpod/itdb.h>
+#include "rhythmdb.h"
+
+enum {
+	RB_IPOD_PLAYLIST_PROP_NAME,
+};
+
+typedef struct {
+	gchar *source_uri;
+	gchar *ipod_uri;
+	gchar *mime_type;
+	guint status;
+	GList *added_tracks;
+	GList *removed_tracks;
+} RBiPodTrackData;
+
+typedef struct {
+	gchar *name;
+	gboolean is_mpl;
+	GList *added_tracks;
+	GList *removed_tracks;
+} RBiPodPlaylistData;
+
+void			rb_ipod_track_add_data 	(Itdb_Track *track);
+
+const gchar *		rb_ipod_track_get_source_uri 	(const Itdb_Track *track);
+
+void			rb_ipod_track_set_source_uri 	(Itdb_Track *track,
+							 const gchar *uri);
+
+const gchar *		rb_ipod_track_get_ipod_uri 	(const Itdb_Track *track);
+
+void 			rb_ipod_track_set_ipod_uri 	(Itdb_Track *track,
+							 const gchar *uri);
+
+guint 			rb_ipod_track_get_status
+							(Itdb_Track *track);
+void 			rb_ipod_track_set_status
+							(Itdb_Track *track,
+							 guint status);
+
+void			rb_ipod_entry_set_from_track 	(RhythmDB *db,
+							 RhythmDBEntry *entry,
+							 Itdb_Track *track);
+void			rb_ipod_track_set_from_entry 	(RhythmDB *db,
+							 Itdb_Track *track,
+							 RhythmDBEntry *entry);
+
+void 			rb_ipod_playlist_add_data 	(Itdb_Playlist *pl);
+
+RBiPodPlaylistData * 	rb_ipod_playlist_get_data 	(Itdb_Playlist *pl);
+
+void 			rb_ipod_playlist_set_prop 	(Itdb_Playlist *pl,
+							 guint propid,
+							 gconstpointer value);
+
+gpointer 		rb_ipod_playlist_get_prop 	(Itdb_Playlist *pl,
+							 guint propid);
+
+void 			rb_ipod_playlist_sync_props 	(Itdb_Playlist *pl);
+
+gboolean		rb_ipod_playlist_is_dirty 	(Itdb_Playlist *pl);
+
+gchar *			rb_ipod_path_to_uri 		(const gchar *mount_path,
+							 const gchar *track_path);
+
+const gchar *		rb_ipod_track_get_mime_type	(const Itdb_Track *track);
+void			rb_ipod_track_set_mime_type	(Itdb_Track *track,
+							 const gchar *mime_type);
+
+#endif /* __RB_IPOD_H */
+
--- /dev/null	2006-05-12 21:45:15.000000000 +1000
+++ plugins/ipod/rb-ipod.c	2006-05-12 17:52:49.000000000 +1000
@@ -0,0 +1,484 @@
+/*
+ * rb-ipod.c - Rhythmbox iPod utility functions.
+ * Copyright 2006, Alessandro Decina
+ *
+ * Authors:
+ *   Alessandro Decina <alessandro@nnva.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License, version 2, as published by the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+*/
+
+#include <libgnome/gnome-i18n.h>
+#include "rb-ipod.h"
+#include <string.h>
+
+typedef struct
+{
+	gchar *source_uri;
+	gchar *ipod_uri;
+	gchar *mime_type;
+	guint status;
+} RBiPodTrackPrivate;
+
+static void
+rb_ipod_track_free_private (RBiPodTrackPrivate *private)
+{
+	g_free (private->source_uri);
+	g_free (private->ipod_uri);
+	g_free (private->mime_type);
+	g_free (private);
+}
+
+static RBiPodTrackPrivate *
+rb_ipod_track_copy_private (RBiPodTrackPrivate *private)
+{
+	RBiPodTrackPrivate *new_priv;
+
+	new_priv = g_new0 (RBiPodTrackPrivate, 1);
+	new_priv->source_uri = g_strdup (private->source_uri);
+	new_priv->ipod_uri = g_strdup (private->ipod_uri);
+	new_priv->mime_type = g_strdup (private->mime_type);
+	new_priv->status = private->status;
+	
+	return new_priv;
+}
+
+void
+rb_ipod_track_add_data (Itdb_Track *track)
+{
+	g_return_if_fail (track != NULL);
+	g_return_if_fail (track->userdata == NULL);
+
+	track->userdata = g_new0 (RBiPodTrackPrivate, 1);
+	track->userdata_duplicate =
+		(ItdbUserDataDuplicateFunc) rb_ipod_track_copy_private;
+	track->userdata_destroy =
+		(ItdbUserDataDestroyFunc) rb_ipod_track_free_private;
+}
+
+const gchar *
+rb_ipod_track_get_source_uri (const Itdb_Track *track)
+{	
+	g_return_val_if_fail (track != NULL, NULL);
+	g_return_val_if_fail (track->userdata != NULL, NULL);
+	
+	return ((RBiPodTrackPrivate *) track->userdata)->source_uri;	
+}
+
+void
+rb_ipod_track_set_source_uri (Itdb_Track *track, const gchar *uri)
+{
+	RBiPodTrackPrivate *private;
+	
+	g_return_if_fail (track != NULL);
+	g_return_if_fail (track->userdata != NULL);
+
+	private = (RBiPodTrackPrivate *) track->userdata;
+	g_free (private->source_uri);
+	private->source_uri = g_strdup (uri);
+}
+
+const gchar *
+rb_ipod_track_get_ipod_uri (const Itdb_Track *track)
+{
+	g_return_val_if_fail (track != NULL, NULL);
+	g_return_val_if_fail (track->userdata != NULL, NULL);
+	
+	return ((RBiPodTrackPrivate *) track->userdata)->ipod_uri;	
+}
+
+void
+rb_ipod_track_set_ipod_uri (Itdb_Track *track, const gchar *uri)
+{
+	RBiPodTrackPrivate *private;
+
+	g_return_if_fail (track != NULL);
+	g_return_if_fail (track->userdata != NULL);
+	g_return_if_fail (uri != NULL);
+
+	private = (RBiPodTrackPrivate *) track->userdata;
+	g_free (private->ipod_uri);
+	private->ipod_uri = g_strdup (uri);
+}
+
+const gchar *
+rb_ipod_track_get_mime_type (const Itdb_Track *track)
+{	
+	g_return_val_if_fail (track != NULL, NULL);
+	g_return_val_if_fail (track->userdata != NULL, NULL);
+	
+	return ((RBiPodTrackPrivate *) track->userdata)->mime_type;
+}
+
+void
+rb_ipod_track_set_mime_type (Itdb_Track *track, const gchar *mime_type)
+{
+	RBiPodTrackPrivate *priv;
+	
+	g_return_if_fail (track != NULL);
+	g_return_if_fail (track->userdata != NULL);
+	
+	priv = (RBiPodTrackPrivate *) track->userdata;
+	g_free (priv->mime_type);
+	priv->mime_type = g_strdup (mime_type);
+}
+
+guint
+rb_ipod_track_get_status (Itdb_Track *track)
+{
+	g_return_val_if_fail (track != NULL, 0);
+	g_return_val_if_fail (track->userdata != NULL, 0);
+	
+	return ((RBiPodTrackPrivate *) track->userdata)->status;
+}
+
+void
+rb_ipod_track_set_status (Itdb_Track *track, guint status)
+{
+	RBiPodTrackPrivate *priv;
+	
+	g_return_if_fail (track != NULL);
+	g_return_if_fail (track->userdata != NULL);
+	
+	priv = (RBiPodTrackPrivate *) track->userdata;
+	priv->status = status;
+}
+
+gchar *
+rb_ipod_path_to_uri (const gchar *mount_path,
+		const gchar *track_path)
+{
+ 	gchar *rel_path;
+ 	gchar *abs_path;
+ 	gchar *uri;
+
+ 	rel_path = g_strdup (track_path);	
+ 	itdb_filename_ipod2fs (rel_path);
+	
+ 	abs_path = g_build_filename (mount_path, rel_path, NULL);
+ 	g_free (rel_path);
+	
+ 	uri = g_filename_to_uri (abs_path, NULL, NULL);
+ 	g_free (abs_path);
+ 
+	return uri;
+}
+
+static void 
+entry_set_string_prop (RhythmDB *db, RhythmDBEntry *entry,
+		       RhythmDBPropType propid, const char *str)
+{
+	GValue value = {0,};
+
+	if (!str)
+		str = _("Unknown");
+
+	g_value_init (&value, G_TYPE_STRING);
+	g_value_set_static_string (&value, str);
+	rhythmdb_entry_set_uninserted (RHYTHMDB (db), entry, propid, &value);
+	g_value_unset (&value);
+}
+
+void
+rb_ipod_entry_set_from_track (RhythmDB *db,
+		RhythmDBEntry *entry, Itdb_Track *track)
+{	
+	/* Set track number */
+	if (track->track_nr != 0) {
+		GValue value = { 0 };
+		g_value_init (&value, G_TYPE_ULONG);
+		g_value_set_ulong (&value, track->track_nr);
+		rhythmdb_entry_set_uninserted (RHYTHMDB (db), entry, 
+					       RHYTHMDB_PROP_TRACK_NUMBER, 
+					       &value);
+		g_value_unset (&value);
+	}
+
+	/* Set disc number */
+	if (track->cd_nr != 0) {
+		GValue value = {0, };
+		g_value_init (&value, G_TYPE_ULONG);
+		g_value_set_ulong (&value, track->cd_nr);
+		rhythmdb_entry_set_uninserted (RHYTHMDB (db), entry, 
+					       RHYTHMDB_PROP_DISC_NUMBER, 
+					       &value);
+		g_value_unset (&value);
+	}
+	
+	/* Set bitrate */
+	if (track->bitrate != 0) {
+		GValue value = {0, };
+		g_value_init (&value, G_TYPE_ULONG);
+		g_value_set_ulong (&value, track->bitrate);
+		rhythmdb_entry_set_uninserted (RHYTHMDB (db), entry, 
+					       RHYTHMDB_PROP_BITRATE, 
+					       &value);
+		g_value_unset (&value);
+	}
+	
+	/* Set length */
+	if (track->tracklen != 0) {
+		GValue value = {0, };
+		g_value_init (&value, G_TYPE_ULONG);
+		g_value_set_ulong (&value, track->tracklen/1000);
+		rhythmdb_entry_set_uninserted (RHYTHMDB (db), entry, 
+					       RHYTHMDB_PROP_DURATION, 
+					       &value);
+		g_value_unset (&value);
+	}
+	
+	/* Set file size */
+	if (track->size != 0) {
+		GValue value = {0, };
+		g_value_init (&value, G_TYPE_UINT64);
+		g_value_set_uint64 (&value, track->size);
+		rhythmdb_entry_set_uninserted (RHYTHMDB (db), entry, 
+					       RHYTHMDB_PROP_FILE_SIZE, 
+					       &value);
+		g_value_unset (&value);
+	}
+
+	/* Set playcount */
+	if (track->playcount != 0) {
+		GValue value = {0, };
+		g_value_init (&value, G_TYPE_ULONG);
+		g_value_set_ulong (&value, track->playcount);
+		rhythmdb_entry_set_uninserted (RHYTHMDB (db), entry,
+					       RHYTHMDB_PROP_PLAY_COUNT,
+					       &value);
+		g_value_unset (&value);
+	}
+
+	/* Set year */
+	if (track->year != 0) {
+		GDate *date = NULL;
+		GValue value = {0, };
+		
+		date = g_date_new_dmy (1, G_DATE_JANUARY, track->year);
+
+		g_value_init (&value, G_TYPE_ULONG);
+		g_value_set_ulong (&value, (date ? g_date_get_julian (date) : 0));
+		
+		rhythmdb_entry_set_uninserted (RHYTHMDB (db), entry,
+					       RHYTHMDB_PROP_YEAR,
+					       &value);
+		g_value_unset (&value);
+		if (date)
+			g_date_free (date);
+	}
+
+	/* Set rating */
+	if (track->rating != 0) {
+		GValue value = { 0 };
+		g_value_init (&value, G_TYPE_DOUBLE);
+		g_value_set_double (&value, track->rating / 20.0);
+		rhythmdb_entry_set_uninserted (RHYTHMDB (db),
+				entry, RHYTHMDB_PROP_RATING,
+				&value);
+		g_value_unset (&value);
+	}
+
+	/* Set last played */
+	if (track->time_played != 0) {
+		GValue value = { 0 };
+		g_value_init (&value, G_TYPE_ULONG);
+		g_value_set_ulong (&value, 
+				itdb_time_mac_to_host (track->time_played));
+		rhythmdb_entry_set_uninserted (RHYTHMDB (db), entry,
+				RHYTHMDB_PROP_LAST_PLAYED,
+				&value);
+		g_value_unset (&value);
+	}
+	
+	/* Set title */		
+	entry_set_string_prop (RHYTHMDB (db), entry, 
+			       RHYTHMDB_PROP_TITLE, track->title);
+	
+	/* Set album, artist and genre from iTunesDB */
+	entry_set_string_prop (RHYTHMDB (db), entry, 
+			       RHYTHMDB_PROP_ARTIST, track->artist);
+	
+	entry_set_string_prop (RHYTHMDB (db), entry, 
+			       RHYTHMDB_PROP_ALBUM, track->album);
+	
+	entry_set_string_prop (RHYTHMDB (db), entry, 
+			       RHYTHMDB_PROP_GENRE, track->genre);
+}
+
+void
+rb_ipod_track_set_from_entry (RhythmDB *db,
+		Itdb_Track *track, RhythmDBEntry *entry)
+{
+	rb_ipod_track_add_data (track);
+	track->track_nr = rhythmdb_entry_get_ulong (entry,
+			RHYTHMDB_PROP_TRACK_NUMBER);
+	track->cd_nr = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DISC_NUMBER);
+#if 0
+	track->bitrate = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_BITRATE);
+#endif
+	track->tracklen =
+		rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DURATION) * 1000;
+#if 0
+	track->size = rhythmdb_entry_get_uint64 (entry,
+			RHYTHMDB_PROP_FILE_SIZE);
+#endif
+	track->playcount = rhythmdb_entry_get_ulong (entry,
+			RHYTHMDB_PROP_PLAY_COUNT);
+	track->year = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_YEAR);
+	
+	track->rating =
+		rhythmdb_entry_get_double (entry, RHYTHMDB_PROP_RATING) * 20.0;
+	track->time_played =
+		itdb_time_host_to_mac (rhythmdb_entry_get_ulong (entry,
+					RHYTHMDB_PROP_LAST_PLAYED));
+	
+	/* copy the strings as they will be freed with itdb_track_free */
+	track->title = g_strdup (rhythmdb_entry_get_string (entry,
+				RHYTHMDB_PROP_TITLE));
+	track->artist = g_strdup (rhythmdb_entry_get_string (entry,
+				RHYTHMDB_PROP_ARTIST));
+	track->album = g_strdup (rhythmdb_entry_get_string (entry,
+				RHYTHMDB_PROP_ALBUM));
+	track->genre = g_strdup (rhythmdb_entry_get_string (entry,
+				RHYTHMDB_PROP_GENRE));	
+}
+
+static void
+free_track (gpointer a, gpointer ignored)
+{
+	itdb_track_free ((Itdb_Track *) a);
+}
+
+static void
+rb_ipod_playlist_free_data (RBiPodPlaylistData *data)
+{
+	if (data->is_mpl)
+		g_list_foreach (data->added_tracks, free_track, NULL);
+	g_list_free (data->added_tracks);
+
+	g_list_free (data->removed_tracks);
+}
+
+static RBiPodPlaylistData *
+rb_ipod_playlist_duplicate_data (RBiPodPlaylistData *data)
+{
+	g_return_val_if_reached (NULL);
+}
+
+void
+rb_ipod_playlist_add_data (Itdb_Playlist *playlist)
+{
+	g_return_if_fail (playlist != NULL);
+	g_return_if_fail (playlist->userdata == NULL);
+
+	playlist->userdata = g_new0 (RBiPodPlaylistData, 1);
+	if (itdb_playlist_is_mpl (playlist))
+		((RBiPodPlaylistData *) playlist->userdata)->is_mpl = TRUE;
+	playlist->userdata_duplicate =
+		(ItdbUserDataDuplicateFunc) rb_ipod_playlist_duplicate_data;
+	playlist->userdata_destroy =
+		(ItdbUserDataDestroyFunc) rb_ipod_playlist_free_data;
+}
+
+RBiPodPlaylistData *
+rb_ipod_playlist_get_data (Itdb_Playlist *playlist)
+{
+	g_return_val_if_fail (playlist != NULL, NULL);
+	g_return_val_if_fail (playlist->userdata != NULL, NULL);
+	
+	return (RBiPodPlaylistData *) playlist->userdata;
+}
+
+void
+rb_ipod_playlist_set_prop (Itdb_Playlist *playlist,
+		guint propid, gconstpointer value)
+{
+	RBiPodPlaylistData *pdata;
+	
+	g_return_if_fail (playlist != NULL);
+	g_return_if_fail (playlist->userdata != NULL);
+
+	pdata = rb_ipod_playlist_get_data (playlist);
+
+	switch (propid) {
+		case RB_IPOD_PLAYLIST_PROP_NAME: {
+			gchar *name = (gchar *) value;
+							 
+			g_free (pdata->name);
+			if (!strcmp (name, playlist->name))
+				pdata->name = NULL;
+			else
+				pdata->name = g_strdup ((gchar *) value);
+			break;
+		}
+		default:
+			g_return_if_reached ();
+	}
+}
+
+gpointer
+rb_ipod_playlist_get_prop (Itdb_Playlist *playlist, guint propid)
+{
+	RBiPodPlaylistData *pdata;
+
+	g_return_val_if_fail (playlist != NULL, NULL);
+	g_return_val_if_fail (playlist->userdata != NULL, NULL);
+
+	switch (propid) {
+		case RB_IPOD_PLAYLIST_PROP_NAME:
+			return pdata->name;
+			break;
+		default:
+			g_return_val_if_reached (NULL);
+	}
+
+	return NULL;
+}
+
+gboolean
+rb_ipod_playlist_is_dirty (Itdb_Playlist *playlist)
+{
+	RBiPodPlaylistData *pdata;
+	gboolean res = FALSE;
+	
+	g_return_val_if_fail (playlist != NULL, FALSE);
+	g_return_val_if_fail (playlist->userdata != NULL, FALSE);
+
+	pdata = rb_ipod_playlist_get_data (playlist);
+	
+	if (pdata->name && playlist->name &&
+			strcmp (playlist->name, pdata->name) != 0)
+		res = TRUE;
+
+	return res;
+}
+
+void
+rb_ipod_playlist_sync_props (Itdb_Playlist *playlist)
+{
+	RBiPodPlaylistData *pdata;
+	
+	g_return_if_fail (playlist != NULL);
+	g_return_if_fail (playlist->userdata != NULL);
+
+	pdata = rb_ipod_playlist_get_data (playlist);
+	
+	if (pdata->name) {
+		g_free (playlist->name);
+		playlist->name = pdata->name;
+		pdata->name = NULL;
+	}
+}
+	
--- /dev/null	2006-05-12 21:45:15.000000000 +1000
+++ plugins/ipod/rb-ipod-playlist-source.h	2006-05-12 17:52:49.000000000 +1000
@@ -0,0 +1,58 @@
+/*
+ *  Copyright (C) 2006 Alessandro Decina <alessandro@nnva.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.
+ *
+ *  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_IPOD_PLAYLIST_SOURCE_H
+#define __RB_IPOD_PLAYLIST_SOURCE_H
+
+#include "rb-ipod-source.h"
+#include "rb-static-playlist-source.h"
+
+G_BEGIN_DECLS
+
+#define RB_TYPE_IPOD_PLAYLIST_SOURCE         (rb_ipod_playlist_source_get_type ())
+#define RB_IPOD_PLAYLIST_SOURCE(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), RB_TYPE_IPOD_PLAYLIST_SOURCE, RBiPodPlaylistSource))
+#define RB_IPOD_PLAYLIST_SOURCE_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), RB_TYPE_IPOD_PLAYLIST_SOURCE, RBiPodPlaylistSourceClass))
+#define RB_IS_IPOD_PLAYLIST_SOURCE(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), RB_TYPE_IPOD_PLAYLIST_SOURCE))
+#define RB_IS_IPOD_PLAYLIST_SOURCE_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), RB_TYPE_IPOD_PLAYLIST_SOURCE))
+#define RB_IPOD_PLAYLIST_SOURCE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), RB_TYPE_IPOD_PLAYLIST_SOURCE, RBiPodPlaylistSourceClass))
+
+typedef struct _RBiPodPlaylistSourcePrivate RBiPodPlaylistSourcePrivate;
+
+typedef struct
+{
+	RBStaticPlaylistSource parent;
+	RBiPodPlaylistSourcePrivate *priv;
+} RBiPodPlaylistSource;
+
+typedef struct
+{
+	RBStaticPlaylistSourceClass parent_class;
+} RBiPodPlaylistSourceClass;
+
+GType		rb_ipod_playlist_source_get_type 	(void);
+
+RBSource *	rb_ipod_playlist_source_new		(RBiPodSource *source,
+							 Itdb_Playlist *plist);
+void 		rb_ipod_playlist_source_delete_no_remove 
+							(RBiPodPlaylistSource *source);
+G_END_DECLS
+
+#endif /* __RB_IPOD_PLAYLIST_SOURCE_H */
+
--- /dev/null	2006-05-12 21:45:15.000000000 +1000
+++ plugins/ipod/rb-ipod-manager.h	2006-05-12 19:07:25.000000000 +1000
@@ -0,0 +1,170 @@
+/*
+ * rb-ipod-manager.h - Rhythmbox iPod manager.
+ * Copyright 2006, Alessandro Decina
+ *
+ * Authors:
+ *   Alessandro Decina <alessandro@nnva.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License, version 2, as published by the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+*/
+
+#ifndef __RB_IPOD_MANAGER_H
+#define __RB_IPOD_MANAGER_H
+
+#include <glib-object.h>
+#include "rb-ipod.h"
+
+G_BEGIN_DECLS
+
+#define RB_TYPE_IPOD_MANAGER            (rb_ipod_manager_get_type ())
+#define RB_IPOD_MANAGER(o)              (G_TYPE_CHECK_INSTANCE_CAST ((o), RB_TYPE_IPOD_MANAGER, RBiPodManager))
+#define RB_IPOD_MANAGER_CLASS(k)        (G_TYPE_CHECK_CLASS_CAST((k), RB_TYPE_IPOD_MANAGER, RBiPodManagerClass))
+#define RB_IS_IPOD_MANAGER(o)           (G_TYPE_CHECK_INSTANCE_TYPE ((o), RB_TYPE_IPOD_MANAGER))
+#define RB_IS_IPOD_MANAGER_CLASS(k)     (G_TYPE_CHECK_CLASS_TYPE ((k), RB_TYPE_IPOD_MANAGER))
+#define RB_IPOD_MANAGER_GET_CLASS(o)    (G_TYPE_INSTANCE_GET_CLASS ((o), RB_TYPE_IPOD_MANAGER, RBiPodManagerClass))
+
+
+/* do i win the prize for the longest struct name? */
+typedef struct _RBiPodManagerTrackTransferInfoPrivate RBiPodManagerTrackTransferInfoPrivate;
+typedef struct _RBiPodManagerPrivate RBiPodManagerPrivate;
+
+enum {
+	RB_IPOD_MANAGER_ERROR,
+	RB_IPOD_MANAGER_ERROR_NOT_DIRTY,
+	RB_IPOD_MANAGER_ERROR_CANCELED,
+	RB_IPOD_MANAGER_ERROR_FAILED
+};
+
+typedef struct _RBiPodManagerTrackTransferInfo {
+	Itdb_Track *track;
+	GError *error;
+	RBiPodManagerTrackTransferInfoPrivate *priv;
+} RBiPodManagerTrackTransferInfo;	
+
+typedef struct _RBiPodManager
+{
+	GObject parent;
+	RBiPodManagerPrivate *priv;
+} RBiPodManager;
+
+typedef struct _RBiPodManagerClass {
+	GObjectClass parent_class;
+
+	/* signals */
+	void	(*track_added)			(RBiPodManager *manager,
+						 Itdb_Playlist *playlist,
+						 Itdb_Track *track);
+	
+	void	(*track_read)			(RBiPodManager *manager,
+						 Itdb_Playlist *playlist,
+						 Itdb_Track *track);
+	
+	void	(*track_removed)		(RBiPodManager *manager,
+						 Itdb_Playlist *playlist,
+						 Itdb_Track *track);
+	void 	(*playlist_added)		(RBiPodManager *manager,
+						 Itdb_Playlist *playlist);
+	
+	void 	(*playlist_read) 		(RBiPodManager *manager,
+					 	 Itdb_Playlist *playlist);
+
+	void 	(*playlist_removed) 		(RBiPodManager *manager,
+						 Itdb_Playlist *playlist);
+
+	void 	(*track_transfer_started) 	(RBiPodManager *manager,
+						 RBiPodManagerTrackTransferInfo *info);
+	
+	void 	(*track_transfer_progress) 	(RBiPodManager *manager,
+						 RBiPodManagerTrackTransferInfo *info,
+						 double progress);
+	
+	void 	(*track_transfer_finished) 	(RBiPodManager *manager,
+						 RBiPodManagerTrackTransferInfo *info);
+
+	void 	(*sync_started) 		(RBiPodManager *manager);
+
+	void 	(*sync_finished) 		(RBiPodManager *manager,
+						 GError *error);
+	
+	void 	(*dirty) 			(RBiPodManager *manager,
+						 gboolean dirty);
+	
+	void 	(*error) 			(RBiPodManager *manager, 
+						 GError *error);
+} RBiPodManagerClass;
+
+GType			rb_ipod_manager_get_type 	(void);
+
+RBiPodManager *		rb_ipod_manager_new 		(const gchar *path,
+							RhythmDB *db);
+
+gboolean		rb_ipod_manager_load_db		(RBiPodManager *manager,
+							 GError **error);
+
+gboolean		rb_ipod_manager_create_db 	(RBiPodManager *manager,
+							 GError **error);
+
+gboolean 		rb_ipod_manager_add_track 	(RBiPodManager *manager,
+							 Itdb_Track *track);
+gboolean		rb_ipod_manager_remove_track	(RBiPodManager *manager,
+							 Itdb_Track *track);
+
+gboolean 		rb_ipod_manager_remove_uri
+							(RBiPodManager *manager,
+							 const gchar *uri);
+
+gboolean 		rb_ipod_manager_add_playlist	(RBiPodManager *manager,
+							 Itdb_Playlist *pl);
+
+gboolean 		rb_ipod_manager_remove_playlist (RBiPodManager *manager,
+							 Itdb_Playlist *pl);
+
+gboolean 		rb_ipod_manager_remove_playlist_name
+							(RBiPodManager *manager,
+							 const gchar *name);
+
+void			rb_ipod_manager_playlist_rename (RBiPodManager *manager,
+							 Itdb_Playlist *pl,
+							 const gchar *new_name);
+
+void	 		rb_ipod_manager_rename 		(RBiPodManager *manager,
+							 const gchar *new_name);
+
+gboolean		rb_ipod_manager_podcasts_add_track
+							(RBiPodManager *manager,
+							 Itdb_Track *track);
+
+gboolean 		rb_ipod_manager_playlist_add_track
+							(RBiPodManager *manager,
+							 Itdb_Playlist *pl,
+							 Itdb_Track *track);
+
+gboolean		rb_ipod_manager_playlist_remove_track (RBiPodManager *manager,
+							       Itdb_Playlist *playlist,
+							       Itdb_Track *track);
+
+gboolean		rb_ipod_manager_playlist_remove_uri
+							(RBiPodManager *manager,
+							 Itdb_Playlist *pl,
+							 const gchar *uri);
+
+gboolean		rb_ipod_manager_sync		(RBiPodManager *manager,
+							 gboolean write_itdb);
+
+gboolean 		rb_ipod_manager_write_itdb 	(RBiPodManager *manager,
+							 GError **error);
+
+void 			rb_ipod_manager_sync_cancel 	(RBiPodManager *manager);
+#endif /* __RB_IPOD_MANAGER_H */
--- /dev/null	2006-05-12 21:45:15.000000000 +1000
+++ plugins/ipod/rb-ipod-manager.c	2006-05-14 16:50:00.000000000 +1000
@@ -0,0 +1,2094 @@
+/*
+ * rb-ipod-manager.c - Rhythmbox iPod manager.
+ * Copyright 2006, Alessandro Decina
+ *
+ * Authors:
+ *   Alessandro Decina <alessandro@nnva.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License, version 2, as published by the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+*/
+
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#include <gdk/gdk.h>
+#include "rb-ipod.h"
+#include "rb-ipod-manager.h"
+#include "rb-encoder.h"
+#include "rb-ipod-manager.h"
+#include "rb-file-helpers.h"
+#include "rb-marshal.h"
+#include "rb-debug.h"
+
+#define RB_IPOD_MANAGER_GET_PRIVATE(o)  \
+   (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_IPOD_MANAGER, \
+				 RBiPodManagerPrivate))
+
+#define MUSICDIRS_NUMBER 20
+
+typedef RBiPodManagerTrackTransferInfo TrackTransferInfo;
+typedef RBiPodManagerTrackTransferInfoPrivate TrackTransferInfoPrivate;
+
+enum {
+	PROP_0,
+	PROP_DB,
+	PROP_MOUNT_PATH
+};
+
+enum {
+	/* track signals */
+	TRACK_ADDED,
+	TRACK_READ,
+	TRACK_REMOVED,
+	TRACK_TRANSFER_STARTED,
+	TRACK_TRANSFER_PROGRESS,
+	TRACK_TRANSFER_FINISHED,
+	/* playlist signals */
+	PLAYLIST_ADDED,
+	PLAYLIST_READ,
+	PLAYLIST_REMOVED,
+	/* sync signals */
+	SYNC_STARTED,
+	SYNC_FINISHED,
+	DIRTY,
+	ERROR,
+	LAST_SIGNAL
+};
+
+/* the statuses a track can be in */
+enum {
+	TRACK_STATUS_ADDED,
+	TRACK_STATUS_TRANSFERRING,
+	TRACK_STATUS_REMOVED,
+	TRACK_STATUS_MEMBER
+};
+
+struct _RBiPodManagerPrivate {
+	Itdb_iTunesDB *itdb;
+	RhythmDB *db;
+
+	gchar *mount_path; 		/* ipod mount path */
+
+#ifdef ENABLE_TRACK_TRANSFER
+	gchar *mime_type;		/* mime type used when transcoding
+					   tracks */
+	gint dirty_counter;	/* number of changes still to be
+					   synchronized. when non zero the
+					   manager is dirty */
+	GList *transferring_tracks;	/* the tracks which are currently being
+					   transferred */
+
+	GList *added_playlists;		/* the playlists which have been added
+					   but not yet written to the itdb */
+	GList *removed_playlists;	/* the playlist which have been removed
+					   but not synchronized yet */
+
+	gboolean cancel_sync;		/* set when a synchronization needs to
+					   be stopped */
+	gboolean itdb_dirty;		/* set when the itdb is dirty and needs
+					   to be written on disk */
+	gboolean synching;		/* set when a synchronization is going
+					   on */
+	gboolean auto_write_itdb;
+#endif
+};
+
+#ifdef ENABLE_TRACK_TRANSFER
+/* each track that is being transferred has its own state, so multiple
+ * transfers can occur concurrently */
+struct _RBiPodManagerTrackTransferInfoPrivate
+{
+	RBiPodManager *manager;
+	RBEncoder *encoder;
+	gchar *mime_type;
+	gboolean cancel;
+	gboolean readd;
+};
+#endif
+
+G_DEFINE_TYPE (RBiPodManager, rb_ipod_manager, G_TYPE_OBJECT);
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+static void rb_ipod_manager_get_property (GObject *object,
+					  guint prop_id,
+					  GValue *value,
+					  GParamSpec *pspec);
+static void rb_ipod_manager_set_property (GObject *object,
+					  guint prop_id,
+					  const GValue *value,
+					  GParamSpec *pspec);
+static void rb_ipod_manager_dispose (GObject *object);
+static void rb_ipod_manager_finalize (GObject *object);
+
+#ifdef ENABLE_TRACK_TRANSFER
+static gboolean sync_step_idle_cb (RBiPodManager *manager);
+static void transfer_track (RBiPodManager *manager, Itdb_Track *track);
+static void transfer_track_cancel (TrackTransferInfo *info);
+static void remove_track (RBiPodManager *manager, Itdb_Track *track);
+static TrackTransferInfo *track_transfer_info_new (RBiPodManager *manager,
+						   Itdb_Track *track);
+static void track_transfer_info_free (TrackTransferInfo *info);
+
+static void encode_progress_cb (RBEncoder *encoder, double fraction,
+				TrackTransferInfo *info);
+static void encode_completed_cb (RBEncoder *encoder,
+				 TrackTransferInfo *info);
+static void encode_error_cb (RBEncoder *encoder, GError *error,
+			     TrackTransferInfo *info);
+
+static void increment_dirty_counter (RBiPodManager *manager);
+static void decrement_dirty_counter (RBiPodManager *manager);
+
+static GList *playlist_find_track (RBiPodManager *manager,
+				   Itdb_Playlist *playlist,
+				   gconstpointer data,
+				   GCompareFunc func,
+				   guint *status);
+static Itdb_Track *playlist_add_track_internal (RBiPodManager *manager,
+						Itdb_Playlist *playlist,
+						Itdb_Track *track);
+static void playlist_remove_all_tracks (RBiPodManager *manager,
+					Itdb_Playlist *playlist);
+static gboolean playlist_remove_track (RBiPodManager *manager,
+				       Itdb_Playlist *playlist,
+				       const gchar *uri,
+				       Itdb_Track *track);
+static void remove_track_from_all_playlists (RBiPodManager *manager,
+					     Itdb_Track *track);
+
+static gboolean write_itdb_internal (RBiPodManager *manager, GError **error);
+
+static gint cmp_pointer (gconstpointer a, gconstpointer b);
+static gint cmp_playlist_name (gconstpointer a, gconstpointer b);
+static gint cmp_track (gconstpointer a, gconstpointer b);
+static gint cmp_track_source_uri (gconstpointer a, gconstpointer b);
+static gint cmp_track_ipod_uri (gconstpointer a, gconstpointer b);
+
+static void playlist_track_added (RBiPodManager *manager,
+				  Itdb_Playlist *playlist,
+				  Itdb_Track *track,
+				  gboolean was_removed);
+static  void playlist_track_removed (RBiPodManager *manager,
+				     Itdb_Playlist *playlist,
+				     Itdb_Track *track,
+				     gboolean was_added);
+
+static gboolean uri_is_on_ipod (RBiPodManager *manager, const gchar *uri);
+
+static gchar *ipod_get_filename_for_uri (RBiPodManager *manager,
+					 const gchar *uri);
+static gchar *ipod_path_from_unix_path (const gchar *mount_path,
+					const gchar *filename);
+#endif
+
+static void
+rb_ipod_manager_class_init (RBiPodManagerClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+	object_class->get_property = rb_ipod_manager_get_property;
+	object_class->set_property = rb_ipod_manager_set_property;
+	object_class->dispose = rb_ipod_manager_dispose;
+	object_class->finalize = rb_ipod_manager_finalize;
+
+	g_object_class_install_property (object_class,
+					 PROP_MOUNT_PATH,
+					 g_param_spec_string ("mount-path",
+							      "mount path",
+							      "iPod mount path",
+							      NULL,
+							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+	g_object_class_install_property (object_class,
+					 PROP_DB,
+					 g_param_spec_object ("db",
+							      "RhythmDB",
+							      "RhythmDB object",
+							      RHYTHMDB_TYPE,
+							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+	signals[TRACK_READ] =
+		g_signal_new ("track-read",
+			      RB_TYPE_IPOD_MANAGER,
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (RBiPodManagerClass, track_read),
+			      NULL, NULL,
+			      rb_marshal_VOID__POINTER_POINTER,
+			      G_TYPE_NONE,
+			      2,
+			      G_TYPE_POINTER, G_TYPE_POINTER);
+	signals[PLAYLIST_READ] =
+		g_signal_new ("playlist-read",
+			      RB_TYPE_IPOD_MANAGER,
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (RBiPodManagerClass, playlist_read),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__POINTER,
+			      G_TYPE_NONE,
+			      1,
+			      G_TYPE_POINTER);
+#ifdef ENABLE_TRACK_TRANSFER
+	signals[TRACK_ADDED] =
+		g_signal_new ("track-added",
+			      RB_TYPE_IPOD_MANAGER,
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (RBiPodManagerClass, track_added),
+			      NULL, NULL,
+			      rb_marshal_VOID__POINTER_POINTER,
+			      G_TYPE_NONE,
+			      2,
+			      G_TYPE_POINTER, G_TYPE_POINTER);
+	signals[TRACK_REMOVED] =
+		g_signal_new ("track-removed",
+			      RB_TYPE_IPOD_MANAGER,
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (RBiPodManagerClass, track_removed),
+			      NULL, NULL,
+			      rb_marshal_VOID__POINTER_POINTER,
+			      G_TYPE_NONE,
+			      2,
+			      G_TYPE_POINTER, G_TYPE_POINTER);
+	signals[PLAYLIST_ADDED] =
+		g_signal_new ("playlist-added",
+			      RB_TYPE_IPOD_MANAGER,
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (RBiPodManagerClass, playlist_added),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__POINTER,
+			      G_TYPE_NONE,
+			      1,
+			      G_TYPE_POINTER);
+	signals[PLAYLIST_REMOVED] =
+		g_signal_new ("playlist-removed",
+			      RB_TYPE_IPOD_MANAGER,
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (RBiPodManagerClass, playlist_removed),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__POINTER,
+			      G_TYPE_NONE,
+			      1,
+			      G_TYPE_POINTER);
+	signals[TRACK_TRANSFER_STARTED] =
+		g_signal_new ("track-transfer-started",
+			      RB_TYPE_IPOD_MANAGER,
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (RBiPodManagerClass, track_transfer_started),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__POINTER,
+			      G_TYPE_NONE,
+			      1,
+			      G_TYPE_POINTER);
+	signals[TRACK_TRANSFER_PROGRESS] =
+		g_signal_new ("track-transfer-progress",
+			      RB_TYPE_IPOD_MANAGER,
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (RBiPodManagerClass, track_transfer_progress),
+			      NULL, NULL,
+			      rb_marshal_VOID__POINTER_DOUBLE,
+			      G_TYPE_NONE,
+			      2,
+			      G_TYPE_POINTER, G_TYPE_DOUBLE);
+	signals[TRACK_TRANSFER_FINISHED] =
+		g_signal_new ("track-transfer-finished",
+			      RB_TYPE_IPOD_MANAGER,
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (RBiPodManagerClass, track_transfer_finished),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__POINTER,
+			      G_TYPE_NONE,
+			      1,
+			      G_TYPE_POINTER);
+	signals[SYNC_STARTED] =
+		g_signal_new ("sync-started",
+			      RB_TYPE_IPOD_MANAGER,
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (RBiPodManagerClass, sync_started),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__VOID,
+			      G_TYPE_NONE,
+			      0);
+	signals[SYNC_FINISHED] =
+		g_signal_new ("sync-finished",
+			      RB_TYPE_IPOD_MANAGER,
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (RBiPodManagerClass, sync_finished),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__POINTER,
+			      G_TYPE_NONE,
+			      1, G_TYPE_POINTER);
+	signals[DIRTY] =
+		g_signal_new ("dirty",
+			      RB_TYPE_IPOD_MANAGER,
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (RBiPodManagerClass, dirty),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__BOOLEAN,
+			      G_TYPE_NONE,
+			      1, G_TYPE_BOOLEAN);
+	signals[ERROR] =
+		g_signal_new ("error",
+			      RB_TYPE_IPOD_MANAGER,
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (RBiPodManagerClass, error),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__POINTER,
+			      G_TYPE_NONE,
+			      1,
+			      G_TYPE_POINTER);
+#endif
+
+	g_type_class_add_private (klass, sizeof (RBiPodManagerPrivate));
+}
+
+static void
+rb_ipod_manager_init (RBiPodManager *manager)
+{
+	manager->priv = RB_IPOD_MANAGER_GET_PRIVATE (manager);
+	/* XXX: this needs to get fixed once we add support for AAC */
+	manager->priv->mime_type = g_strdup ("audio/mpeg");
+}
+
+static void
+rb_ipod_manager_dispose (GObject *object)
+{
+	RBiPodManager *manager = RB_IPOD_MANAGER (object);
+
+	if (manager->priv->db) {
+		g_object_unref (manager->priv->db);
+		manager->priv->db = NULL;
+	}
+}
+
+static void
+rb_ipod_manager_finalize (GObject *object)
+{
+	RBiPodManager *manager = RB_IPOD_MANAGER (object);
+
+	/* free all the tracks in the itdb. this will also free removed tracks
+	 * and removed playlist which have NOT been synchronized */
+	itdb_free (manager->priv->itdb);
+
+	g_free (manager->priv->mount_path);
+	g_free (manager->priv->mime_type);
+
+#ifdef ENABLE_TRACK_TRANSFER
+	g_list_free (manager->priv->transferring_tracks);
+
+	/* the same holds for added playlists */
+	g_list_foreach (manager->priv->added_playlists,
+			(GFunc) itdb_playlist_free, NULL);
+	g_list_free (manager->priv->added_playlists);
+
+	/* removed (but not synchronized) playlists were free'd with the itdb
+	 */
+	g_list_free (manager->priv->removed_playlists);
+#endif
+}
+
+static void
+rb_ipod_manager_set_property (GObject *object,
+		guint prop_id,
+		const GValue *value,
+		GParamSpec *pspec)
+{
+	RBiPodManager *manager = RB_IPOD_MANAGER (object);
+
+	switch (prop_id) {
+	case PROP_DB:
+		if (manager->priv->db)
+			g_object_unref (manager->priv->db);
+		manager->priv->db = RHYTHMDB (g_value_dup_object (value));
+		break;
+
+	case PROP_MOUNT_PATH:
+		g_free (manager->priv->mount_path);
+		manager->priv->mount_path = g_value_dup_string (value);
+		break;
+
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+	}
+}
+
+static void
+rb_ipod_manager_get_property (GObject *object,
+		guint prop_id,
+		GValue *value,
+		GParamSpec *pspec)
+{
+	RBiPodManager *manager = RB_IPOD_MANAGER (object);
+
+	switch (prop_id) {
+	case PROP_DB:
+		g_value_set_object (value, manager->priv->db);
+		break;
+
+	case PROP_MOUNT_PATH:
+		g_value_set_string (value, manager->priv->mount_path);
+		break;
+
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+	}
+}
+
+RBiPodManager *
+rb_ipod_manager_new (const gchar *mount_path, RhythmDB *db)
+{
+	RBiPodManager *manager;
+
+	manager = g_object_new (RB_TYPE_IPOD_MANAGER,
+				"mount-path", mount_path,
+				"db", db,
+				NULL);
+
+	return manager;
+}
+
+gboolean
+rb_ipod_manager_load_db (RBiPodManager *manager, GError **error)
+{
+	GList *walk, *node;
+	GError *err = NULL;
+	Itdb_Playlist *playlist;
+	Itdb_Track *track;
+
+	g_return_val_if_fail (manager != NULL, FALSE);
+	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+	g_return_val_if_fail (manager->priv->itdb == NULL, FALSE);
+
+	rb_debug ("loading the itdb");
+
+	/* TODO: this could take too long to complete: consider running all this
+	 * in a separate thread */
+	manager->priv->itdb = itdb_parse (manager->priv->mount_path, &err);
+	if (err) {
+		g_propagate_error (error, err);
+		return FALSE;
+	}
+
+	/* emit playlist-read signals */
+	for (walk = manager->priv->itdb->playlists; walk; walk = g_list_next (walk)) {
+		playlist = (Itdb_Playlist *) walk->data;
+		rb_ipod_playlist_add_data (playlist);
+
+		g_signal_emit (manager, signals[PLAYLIST_READ], 0, playlist);
+	}
+
+	/* emit track-read signals */
+	for (walk = manager->priv->itdb->playlists; walk; walk = g_list_next (walk)) {
+		playlist = (Itdb_Playlist *) walk->data;
+
+		for (node = playlist->members; node;
+				node = g_list_next (node)) {
+			track = (Itdb_Track *) node->data;
+	
+			if (itdb_playlist_is_mpl (playlist)) {
+				gchar *abs_uri =
+					rb_ipod_path_to_uri (manager->priv->mount_path,
+							track->ipod_path);
+				rb_ipod_track_add_data (track);
+				rb_ipod_track_set_ipod_uri (track, abs_uri);
+				track->transferred = TRUE;
+				g_free (abs_uri);
+			}
+
+			g_signal_emit (manager,
+					signals[TRACK_READ],
+					0, playlist, track);
+		}
+	}
+
+	return TRUE;
+}
+
+#ifdef ENABLE_TRACK_TRANSFER
+gboolean
+rb_ipod_manager_create_db (RBiPodManager *manager, GError **error)
+{
+	Itdb_Playlist *mpl;
+	Itdb_Playlist *podcasts;
+	int i;
+	gchar *musicdir_path;
+
+	g_return_val_if_fail (manager != NULL, FALSE);
+	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+	rb_debug ("creating the database");
+
+	manager->priv->itdb = itdb_new ();
+	itdb_set_mountpoint (manager->priv->itdb, manager->priv->mount_path);
+
+	/* create the master playlist */
+	mpl = itdb_playlist_new ("iPod", FALSE);
+	rb_ipod_playlist_add_data (mpl);
+	itdb_playlist_set_mpl (mpl);
+	itdb_playlist_add (manager->priv->itdb, mpl, -1);
+
+	/* create the podcasts playlist */
+	podcasts = itdb_playlist_new ("Podcasts", FALSE);
+	rb_ipod_playlist_add_data (podcasts);
+	itdb_playlist_set_podcasts (podcasts);
+	itdb_playlist_add (manager->priv->itdb, podcasts, -1);
+
+	/* create /ipod_mount_path/iPod_Control/Music/F??/ dirs */
+	musicdir_path = g_build_filename (manager->priv->mount_path,
+			"iPod_Control", "Music", NULL);
+
+	for (i = 0; i < MUSICDIRS_NUMBER; ++i) {
+		gchar *dir_str = g_strdup_printf ("F%02d", i);
+		gchar *dir_path = g_build_filename (musicdir_path,
+				dir_str, NULL);
+		g_free (dir_str);
+
+		if (g_mkdir_with_parents (dir_path, 600) == -1) {
+			g_set_error (error,
+					RB_IPOD_MANAGER_ERROR,
+					RB_IPOD_MANAGER_ERROR_FAILED,
+					"couldn't create musicdir %d: %s",
+					i, g_strerror (errno));
+			g_free (dir_path);
+			break;
+		}
+
+		g_free (dir_path);
+	}
+
+	g_free (musicdir_path);
+	manager->priv->itdb->musicdirs = MUSICDIRS_NUMBER;
+
+	if (error) {
+		itdb_free (manager->priv->itdb);
+		manager->priv->itdb = NULL;
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+/* emit playlist-added and update dirty_counter */
+static void
+playlist_added (RBiPodManager *manager,
+		Itdb_Playlist *playlist, gboolean was_removed)
+{
+	g_signal_emit (manager, signals[PLAYLIST_ADDED], 0, playlist);
+
+	if (rb_ipod_playlist_is_dirty (playlist))
+		increment_dirty_counter (manager);
+
+	if (was_removed == TRUE) {
+		rb_debug ("added previously removed playlist %s",
+				playlist->name);
+		decrement_dirty_counter (manager);
+	} else {
+		rb_debug ("added playlist %s", playlist->name);
+		increment_dirty_counter (manager);
+	}
+}
+
+/* add a playlist, making sure it hasn't been added already */
+static gboolean
+add_playlist_internal (RBiPodManager *manager,
+		const gchar *name, Itdb_Playlist *playlist)
+{
+	GList *node;
+	Itdb_Playlist *orig_playlist;
+	gconstpointer cmp_data;
+	GCompareFunc cmp_func;
+
+	if (playlist == NULL) {
+		/* compare playlists' names to check if one named `name'
+		 * already exists */
+		cmp_data = name;
+		cmp_func = cmp_playlist_name;
+	} else {
+		/* compare playlists by pointer */
+		cmp_data = playlist;
+		cmp_func = cmp_pointer;
+	}
+
+	if (g_list_find_custom (manager->priv->added_playlists,
+				cmp_data, cmp_func) != NULL) {
+		rb_debug ("playlist %s has already been added", name);
+
+		return FALSE;
+	}
+
+	if (g_list_find_custom (manager->priv->removed_playlists,
+				cmp_data, cmp_func) != NULL) {
+		/* adding a removed playlist: keep the previous playlist and
+		 * if necessary free the one passed as argument now */
+		orig_playlist = (Itdb_Playlist *) node->data;
+		manager->priv->removed_playlists =
+			g_list_delete_link (manager->priv->removed_playlists,
+					node);
+		if (playlist && orig_playlist != playlist)
+			/* free the passed playlist */
+			itdb_playlist_free (playlist);
+
+		playlist_added (manager, orig_playlist, TRUE);
+
+		return TRUE;
+	}
+
+	if (g_list_find_custom (manager->priv->itdb->playlists,
+				cmp_data, cmp_func) != NULL) {
+		rb_debug ("playlist %s is already on the ipod", name);
+
+		return FALSE;
+	}
+
+	if (playlist == NULL) {
+		/* adding a playlist by name, we have to create it */
+		playlist = itdb_playlist_new (name, FALSE);
+		rb_ipod_playlist_add_data (playlist);
+	}
+
+	manager->priv->added_playlists =
+		g_list_append (manager->priv->added_playlists,
+				playlist);
+
+	playlist_added (manager, playlist, FALSE);
+
+	return TRUE;
+}
+
+gboolean
+rb_ipod_manager_add_playlist (RBiPodManager *manager, Itdb_Playlist *playlist)
+{
+	g_return_val_if_fail (manager != NULL, FALSE);
+	g_return_val_if_fail (playlist != NULL, FALSE);
+
+	return add_playlist_internal (manager, playlist->name, playlist);
+}
+
+/* emit playlist-removed and update dirty_counter */
+static void
+playlist_removed (RBiPodManager *manager,
+		Itdb_Playlist *playlist, gboolean was_added)
+{
+	playlist_remove_all_tracks (manager, playlist);
+	g_signal_emit (manager, signals[PLAYLIST_REMOVED], 0, playlist);
+
+	if (rb_ipod_playlist_is_dirty (playlist) == TRUE)
+		decrement_dirty_counter (manager);
+
+	if (was_added) {
+		rb_debug ("removed previously added playlist %s",
+				playlist->name);
+		decrement_dirty_counter (manager);
+	} else {
+		rb_debug ("removed playlist %s", playlist->name);
+		increment_dirty_counter (manager);
+	}
+}
+
+static gboolean
+remove_playlist_internal (RBiPodManager *manager,
+		const gchar *name, Itdb_Playlist *playlist)
+{
+	GList *node;
+	gconstpointer cmp_data;
+	GCompareFunc cmp_func;
+
+	if (playlist == NULL) {
+		cmp_data = name;
+		cmp_func = cmp_playlist_name;
+	} else {
+		cmp_data = playlist;
+		cmp_func = cmp_pointer;
+	}
+
+	if (g_list_find_custom (manager->priv->removed_playlists,
+				cmp_data, cmp_func) != NULL) {
+		rb_debug ("playlist %s has already been removed", name);
+
+		return FALSE;
+	}
+
+	if ((node = g_list_find_custom (manager->priv->added_playlists,
+					cmp_data, cmp_func)) != NULL) {
+		playlist = (Itdb_Playlist *) node->data;
+
+		manager->priv->added_playlists =
+			g_list_delete_link (manager->priv->added_playlists,
+					node);
+		playlist_removed (manager, playlist, TRUE);
+
+		/* free the playlist */
+		itdb_playlist_free (playlist);
+
+		return TRUE;
+	}
+
+	if ((node = g_list_find_custom (manager->priv->itdb->playlists,
+					cmp_data, cmp_func)) == NULL) {
+		rb_debug ("playlist %s isn't on the ipod", name);
+
+		return FALSE;
+	}
+
+	playlist = (Itdb_Playlist *) node->data;
+	manager->priv->removed_playlists =
+		g_list_append (manager->priv->removed_playlists, playlist);
+
+	playlist_removed (manager, playlist, FALSE);
+
+	return TRUE;
+}
+
+gboolean
+rb_ipod_manager_remove_playlist (RBiPodManager *manager,
+		Itdb_Playlist *playlist)
+{
+	g_return_val_if_fail (manager != NULL, FALSE);
+	g_return_val_if_fail (playlist != NULL, FALSE);
+
+	return remove_playlist_internal (manager, playlist->name, playlist);
+}
+
+gboolean
+rb_ipod_manager_remove_playlist_name (RBiPodManager *manager,
+		const gchar *name)
+{
+	g_return_val_if_fail (manager != NULL, FALSE);
+	g_return_val_if_fail (name != NULL, FALSE);
+
+	return remove_playlist_internal (manager, name, NULL);
+}
+
+void
+rb_ipod_manager_playlist_rename (RBiPodManager *manager,
+		Itdb_Playlist *playlist, const gchar *new_name)
+{
+	gboolean was_dirty;
+
+	g_return_if_fail (manager != NULL);
+	g_return_if_fail (playlist != NULL);
+
+	/* record the previous state */
+	was_dirty = rb_ipod_playlist_is_dirty (playlist);
+	rb_ipod_playlist_set_prop (playlist,
+			RB_IPOD_PLAYLIST_PROP_NAME, new_name);
+	if (rb_ipod_playlist_is_dirty (playlist) == TRUE) {
+		/* if it was clean and now it's dirty, increment
+		 * dirty_counter */
+		if (was_dirty == FALSE) {
+			increment_dirty_counter (manager);
+		}
+	} else {
+		/* if's clean and was dirty, decrement dirty_counter */
+		if (was_dirty == TRUE) {
+			decrement_dirty_counter (manager);
+		}
+	}
+}
+
+void
+rb_ipod_manager_rename (RBiPodManager *manager, const gchar *new_name)
+{
+	Itdb_Playlist *mpl;
+
+	g_return_if_fail (manager != NULL);
+	g_return_if_fail (new_name != NULL);
+
+	mpl = itdb_playlist_mpl (manager->priv->itdb);
+	rb_ipod_manager_playlist_rename (manager, mpl, new_name);
+}
+
+gboolean
+rb_ipod_manager_playlist_add_track (RBiPodManager *manager,
+		Itdb_Playlist *playlist, Itdb_Track *track)
+{
+	gboolean added = FALSE;
+	GList *node;
+	const gchar *uri;
+	guint status;
+	Itdb_Playlist *mpl;
+	RBiPodPlaylistData *mpldata;
+	RBiPodPlaylistData *pdata;
+	Itdb_Track *mpl_track;
+
+	g_return_val_if_fail (manager != NULL, FALSE);
+	g_return_val_if_fail (playlist != NULL, FALSE);
+	g_return_val_if_fail (track != NULL, FALSE);
+
+	mpl = itdb_playlist_mpl (manager->priv->itdb);
+	mpldata = rb_ipod_playlist_get_data (mpl);
+	pdata = rb_ipod_playlist_get_data (playlist);
+
+	uri = rb_ipod_track_get_source_uri (track);
+	if (uri == NULL)
+		uri = rb_ipod_track_get_ipod_uri (track);
+	pdata = rb_ipod_playlist_get_data (playlist);
+
+	/* lookup track in the mpl, if we find it, we'll add it to playlist
+	 * and free the passed track */
+	if ((node = playlist_find_track (manager, mpl, track,
+					cmp_track, &status)) != NULL) {
+		if (status == TRACK_STATUS_TRANSFERRING)
+			mpl_track = ((TrackTransferInfo *) node->data)->track;
+		else
+			mpl_track = (Itdb_Track *) node->data;
+		if (status == TRACK_STATUS_REMOVED) {
+			/* readd track to the MPL */
+			mpldata->removed_tracks =
+				g_list_delete_link (mpldata->removed_tracks,
+						node);
+			playlist_track_added (manager, mpl, mpl_track, TRUE);
+			if (itdb_playlist_is_mpl (playlist) &&
+					mpl_track != track ) {
+				/* we've re-added the track which was in MPL's
+				 * removed_tracks list. So we actually added
+				 * back the track, but not the instance we
+				 * got as argument, which we free now */
+				itdb_track_free (track);
+				added = TRUE;
+			}
+		}
+	} else {
+		/* track isn't in the MPL, add it there first */
+		mpl_track = playlist_add_track_internal (manager, mpl, track);
+		added = TRUE;
+	}
+
+	/* if the passed playlist is the MPL there's nothing left to do */
+	if (itdb_playlist_is_mpl (playlist))
+		return added;
+
+	/* see if track is in the given playlist now */
+	if ((node = playlist_find_track (manager, playlist, mpl_track,
+					cmp_track, &status))  != NULL) {
+		if (status == TRACK_STATUS_REMOVED) {
+			/* it has been removed: readd it */
+			pdata->removed_tracks =
+				g_list_delete_link (pdata->removed_tracks,
+						node);
+			playlist_track_added (manager, playlist, track, TRUE);
+		} else {
+			/* it's in it and it hasn't been removed */
+			rb_debug ("track %s is already on playlist %s",
+					uri, playlist->name);
+			added = FALSE;
+		}
+	} else {
+		/* add track to playlist */
+		playlist_add_track_internal (manager, playlist, mpl_track);
+		added = TRUE;
+	}
+
+	if (added && mpl_track != track)
+		itdb_track_free (track);
+
+	return added;
+}
+
+gboolean
+rb_ipod_manager_playlist_remove_track (RBiPodManager *manager,
+		Itdb_Playlist *playlist, Itdb_Track *track)
+{
+	const gchar *uri;
+
+	g_return_val_if_fail (manager != NULL, FALSE);
+	g_return_val_if_fail (playlist != NULL, FALSE);
+	g_return_val_if_fail (track != NULL, FALSE);
+
+	uri = rb_ipod_track_get_source_uri (track);
+	if (uri == NULL)
+		uri = rb_ipod_track_get_ipod_uri (track);
+	return playlist_remove_track (manager, playlist, uri, track);
+
+	return FALSE;
+}
+
+gboolean
+rb_ipod_manager_playlist_remove_uri (RBiPodManager *manager,
+		Itdb_Playlist *playlist, const gchar *uri)
+{
+	g_return_val_if_fail (manager != NULL, FALSE);
+	g_return_val_if_fail (playlist != NULL, FALSE);
+	g_return_val_if_fail (uri != NULL, FALSE);
+
+	return playlist_remove_track (manager, playlist, uri, NULL);
+}
+
+gboolean
+rb_ipod_manager_add_track (RBiPodManager *manager, Itdb_Track *track)
+{
+	Itdb_Playlist *mpl;
+
+	g_return_val_if_fail (manager != NULL, FALSE);
+	g_return_val_if_fail (track != NULL, FALSE);
+
+	mpl = itdb_playlist_mpl (manager->priv->itdb);
+	return rb_ipod_manager_playlist_add_track (manager, mpl, track);
+}
+
+gboolean
+rb_ipod_manager_remove_track (RBiPodManager *manager, Itdb_Track *track)
+{
+	Itdb_Playlist *mpl;
+
+	g_return_val_if_fail (manager != NULL, FALSE);
+	g_return_val_if_fail (track != NULL, FALSE);
+
+	mpl = itdb_playlist_mpl (manager->priv->itdb);
+	return rb_ipod_manager_playlist_remove_track (manager, mpl, track);
+}
+
+gboolean
+rb_ipod_manager_podcasts_add_track (RBiPodManager *manager, Itdb_Track *track) {
+	Itdb_Playlist *podcasts;
+
+	g_return_val_if_fail (manager != NULL, FALSE);
+	g_return_val_if_fail (track != NULL, FALSE);
+
+	podcasts = itdb_playlist_podcasts (manager->priv->itdb);
+	return rb_ipod_manager_playlist_add_track (manager, podcasts, track);
+}
+
+gboolean
+rb_ipod_manager_remove_uri (RBiPodManager *manager, const gchar *uri)
+{
+	Itdb_Playlist *mpl;
+
+	g_return_val_if_fail (manager != NULL, FALSE);
+	g_return_val_if_fail (uri != NULL, FALSE);
+
+	mpl = itdb_playlist_mpl (manager->priv->itdb);
+	return rb_ipod_manager_playlist_remove_uri (manager, mpl, uri);
+}
+
+gboolean
+rb_ipod_manager_sync (RBiPodManager *manager, gboolean auto_write_itdb)
+{
+	g_return_val_if_fail (manager != NULL, FALSE);
+
+	if (manager->priv->synching ||
+			manager->priv->dirty_counter == 0) {
+		return FALSE;
+	}
+
+	manager->priv->cancel_sync = FALSE;
+	manager->priv->auto_write_itdb = auto_write_itdb;
+	g_idle_add ((GSourceFunc) sync_step_idle_cb, manager);
+
+	return TRUE;
+}
+
+gboolean
+rb_ipod_manager_write_itdb (RBiPodManager *manager, GError **error)
+{
+	gboolean res;
+
+	g_return_val_if_fail (manager != NULL, FALSE);
+	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+	if (manager->priv->itdb_dirty == TRUE) {
+		res = write_itdb_internal (manager, error);
+	} else {
+		g_set_error (error,
+				RB_IPOD_MANAGER_ERROR,
+				RB_IPOD_MANAGER_ERROR_NOT_DIRTY,
+				"not dirty");
+		res = FALSE;
+	}
+
+	return res;
+}
+
+void
+rb_ipod_manager_sync_cancel (RBiPodManager *manager)
+{
+	g_return_if_fail (manager != NULL);
+	manager->priv->cancel_sync = TRUE;
+}
+
+/* private funcs */
+
+/* keep track of dirty counter with increment_dirty_counter and
+ * decrement_dirty_counter. when dirty_counter is > 0 manager is
+ * dirty, otherwise it's clean */
+
+static void
+increment_dirty_counter (RBiPodManager *manager)
+{
+	if (++manager->priv->dirty_counter == 1)
+		g_signal_emit (manager, signals[DIRTY], 0, TRUE);
+}
+
+static void
+decrement_dirty_counter (RBiPodManager *manager)
+{
+	if (--manager->priv->dirty_counter == 0)
+		g_signal_emit (manager, signals[DIRTY], 0, FALSE);
+}
+
+/* compare a track's source uri with a given uri. If the track hasn't a
+ * source_uri, arbitrarily return -1 */
+static gint
+cmp_track_source_uri (gconstpointer a, gconstpointer b)
+{
+	const gchar *source_uri =
+		rb_ipod_track_get_source_uri ((Itdb_Track *) a);
+	const gchar *uri = (gchar *) b;
+
+	if (source_uri && uri)
+		return strcmp (source_uri, uri);
+
+	return -1;
+}
+
+
+/* compare a track's ipod uri with a given uri. If the track hasn't an
+ * ipod_uri, arbitrarily return -1 */
+static gint
+cmp_track_ipod_uri (gconstpointer a, gconstpointer b)
+{
+	const gchar *ipod_uri =
+		rb_ipod_track_get_ipod_uri ((Itdb_Track *) a);
+	const gchar *uri = (gchar *) b;
+
+	if (ipod_uri && uri)
+		return strcmp (ipod_uri, uri);
+
+	return -1;
+}
+
+/* compare two tracks first by their location in memory, then by their uris */
+static gint
+cmp_track (gconstpointer a, gconstpointer b)
+{
+	const gchar *b_uri;
+
+	if (a == b)
+		return 0;
+
+	b_uri = rb_ipod_track_get_source_uri ((Itdb_Track *) b);
+	if (cmp_track_source_uri (a, b_uri) == 0)
+		return 0;
+
+	b_uri = rb_ipod_track_get_ipod_uri ((Itdb_Track *) b);
+	if (cmp_track_ipod_uri (a, b_uri) == 0)
+		return 0;
+
+	return -1;
+}
+
+/* compare a playlist with a name */
+static gint
+cmp_playlist_name (gconstpointer a, gconstpointer b)
+{
+	return strcmp (((Itdb_Playlist *) a)->name,
+			(const gchar *) b);
+}
+
+static gint
+cmp_pointer (gconstpointer a, gconstpointer b)
+{
+	if (a < b) return -1;
+	else if (a == b) return 0;
+	else return 1;
+}
+
+/* utility to find a track in a playlist */
+static GList *
+playlist_find_track (RBiPodManager *manager,
+		Itdb_Playlist *playlist, gconstpointer data, GCompareFunc func,
+		guint *status)
+{
+	GList *node;
+	RBiPodPlaylistData *pdata = rb_ipod_playlist_get_data (playlist);
+
+	if (itdb_playlist_is_mpl (playlist)) {
+		GList *walk;
+		TrackTransferInfo *info = NULL;
+
+		for (walk = manager->priv->transferring_tracks; walk;
+				walk = g_list_next (walk)) {
+			info = (TrackTransferInfo *) walk->data;
+			if (func (info->track, data) == 0)
+				break;
+		}
+
+		if (walk != NULL) {
+			*status = TRACK_STATUS_TRANSFERRING;
+			return walk;
+		}
+	}
+
+	if ((node = g_list_find_custom (pdata->added_tracks,
+					data, func)) != NULL) {
+		*status = TRACK_STATUS_ADDED;
+		return node;
+	}
+
+	if ((node = g_list_find_custom (pdata->removed_tracks,
+					data, func)) != NULL) {
+		*status = TRACK_STATUS_REMOVED;
+		return node;
+	}
+
+	if ((node = g_list_find_custom (playlist->members,
+					data, func)) != NULL) {
+		*status = TRACK_STATUS_MEMBER;
+		return node;
+	}
+
+	return NULL;
+}
+
+/* emit track-added and update dirty_counter */
+static void
+playlist_track_added (RBiPodManager *manager, Itdb_Playlist *playlist,
+		Itdb_Track *track, gboolean was_removed)
+{
+	const gchar *uri;
+
+	uri = rb_ipod_track_get_source_uri (track);
+	if (uri == NULL)
+		uri = rb_ipod_track_get_ipod_uri (track);
+
+	g_signal_emit (manager, signals[TRACK_ADDED], 0, playlist, track);
+	if (was_removed) {
+		rb_debug ("added previously removed track %s to playlist %s",
+				uri, playlist->name);
+		decrement_dirty_counter (manager);
+	} else {
+		rb_debug ("added track %s to playlist %s",
+				uri, playlist->name);
+		increment_dirty_counter (manager);
+	}
+}
+
+/* append a track to playlist's added_tracks list. when adding it to the MPL
+ * also generate a filename */
+static Itdb_Track *
+playlist_add_track_internal (RBiPodManager *manager,
+		Itdb_Playlist *playlist, Itdb_Track *track)
+{
+	RBiPodPlaylistData *pdata = rb_ipod_playlist_get_data (playlist);
+
+	if (itdb_playlist_is_mpl (playlist) &&
+			rb_ipod_track_get_ipod_uri (track) == NULL) {
+		const gchar *source_uri;
+		gchar *ipod_uri;
+		gchar *target_filename;
+
+		source_uri = rb_ipod_track_get_source_uri (track);
+		target_filename =
+			ipod_get_filename_for_uri (manager, source_uri);
+		ipod_uri = g_filename_to_uri (target_filename, NULL, NULL);
+		rb_ipod_track_set_ipod_uri (track, ipod_uri);
+		g_free (ipod_uri);
+		track->ipod_path =
+			ipod_path_from_unix_path (manager->priv->mount_path,
+					target_filename);
+		g_free (target_filename);
+	}
+
+	pdata->added_tracks = g_list_append (pdata->added_tracks, track);
+	playlist_track_added (manager, playlist, track, FALSE);
+
+	return track;
+}
+
+/* emit track-removed and update dirty_counter */
+static void
+playlist_track_removed (RBiPodManager *manager, Itdb_Playlist *playlist,
+		Itdb_Track *track, gboolean was_added)
+{
+	const gchar *uri;
+
+	uri = rb_ipod_track_get_source_uri (track);
+	if (uri == NULL)
+		uri = rb_ipod_track_get_ipod_uri (track);
+
+	if (itdb_playlist_is_mpl (playlist) == TRUE)
+		remove_track_from_all_playlists (manager, track);
+
+	g_signal_emit (manager, signals[TRACK_REMOVED], 0, playlist, track);
+	if (was_added) {
+		rb_debug ("removed previously added track %s from playlist %s",
+				uri, playlist->name);
+		decrement_dirty_counter (manager);
+	} else {
+		rb_debug ("removed track %s from playlist %s",
+				uri, playlist->name);
+		increment_dirty_counter (manager);
+	}
+}
+
+/* remove a track from all the playlists. used when removing a track from the
+ * MPL */
+static void
+remove_track_from_all_playlists (RBiPodManager *manager, Itdb_Track *track)
+{
+	GList *walk;
+	const gchar *uri;
+
+	uri = rb_ipod_track_get_source_uri (track);
+	if (uri == NULL)
+		rb_ipod_track_get_ipod_uri (track);
+	rb_debug ("removing %s from all the playlists", uri);
+
+	for (walk = manager->priv->added_playlists; walk;
+			walk = g_list_next (walk)) {
+		playlist_remove_track (manager,
+				(Itdb_Playlist *) walk->data,
+				uri, track);
+	}
+
+	for (walk = manager->priv->itdb->playlists->next; walk;
+			walk = g_list_next (walk)) {
+		playlist_remove_track (manager,
+				(Itdb_Playlist *) walk->data,
+				uri, track);
+	}
+}
+
+/* remove a track from the playlist.
+ * if track != NULL, find and remove that specific pointer, otherwise lookup
+ * the track by its uri */
+static gboolean
+playlist_remove_track (RBiPodManager *manager, Itdb_Playlist *playlist,
+		const gchar *uri, Itdb_Track *track)
+{
+	GList *node;
+	gconstpointer cmp_data;
+	GCompareFunc cmp_func;
+	guint status;
+	RBiPodPlaylistData *pdata = rb_ipod_playlist_get_data (playlist);
+	gboolean removed = TRUE;
+
+	if (track == NULL) {
+		cmp_data = uri;
+		if (uri_is_on_ipod (manager, uri) == TRUE)
+			cmp_func = cmp_track_ipod_uri;
+		else
+			cmp_func = cmp_track_source_uri;
+	} else {
+		cmp_data = track;
+		cmp_func = cmp_pointer;
+	}
+
+	if ((node = playlist_find_track (manager, playlist, cmp_data,
+					cmp_func, &status)) != NULL) {
+		track = (Itdb_Track *) node->data;
+		switch (status) {
+			case TRACK_STATUS_TRANSFERRING: {
+				TrackTransferInfo *info =
+					(TrackTransferInfo *) node->data;
+		
+				transfer_track_cancel (info);
+				/* track is free'd */
+				break;
+			}
+			case TRACK_STATUS_ADDED:
+				pdata->added_tracks =
+					g_list_delete_link (pdata->added_tracks,
+							node);
+				playlist_track_removed (manager,
+						playlist, track, TRUE);
+				if (itdb_playlist_is_mpl (playlist) == TRUE)
+					itdb_track_free (track);
+				break;
+			case TRACK_STATUS_MEMBER:
+				pdata->removed_tracks =
+					g_list_append (pdata->removed_tracks,
+							track);
+				playlist_track_removed (manager,
+						playlist, track, FALSE);
+				break;
+			case TRACK_STATUS_REMOVED:
+				rb_debug ("%s has already been removed from "
+						"playlist %s", uri,
+						playlist->name);
+				removed = FALSE;
+				break;
+			default:
+				g_return_val_if_reached (FALSE);
+		}
+	} else {
+		removed = FALSE;
+	}
+
+	return removed;
+}
+
+/* remove all the tracks from a playlist. used when removing a playlist */
+static void
+playlist_remove_all_tracks (RBiPodManager *manager, Itdb_Playlist *playlist)
+{
+	Itdb_Track *track;
+	GList *walk;
+	RBiPodPlaylistData *pdata = rb_ipod_playlist_get_data (playlist);
+
+	/* walk pdata->added_tracks getting always its head as
+	 * playlist_track_removed will signal track-removed, and tracks could
+	 * be added in the signal handler */
+	walk = pdata->added_tracks;
+	while (walk) {
+		track = (Itdb_Track *) walk->data;
+		pdata->added_tracks =
+			g_list_delete_link (pdata->added_tracks, walk);
+		playlist_track_removed (manager, playlist, track, TRUE);
+		if (itdb_playlist_is_mpl (playlist) == TRUE)
+			itdb_track_free (track);
+
+		walk = pdata->added_tracks;
+	}
+
+	for (walk = playlist->members; walk; walk = g_list_next (walk)) {
+		track = (Itdb_Track *) walk->data;
+		pdata->removed_tracks =
+			g_list_append (pdata->removed_tracks, track);
+		playlist_track_removed (manager, playlist, track, FALSE);
+	}
+}
+
+/* write manager->priv->itdb on the iPod */
+static gboolean
+write_itdb_internal (RBiPodManager *manager, GError **error)
+{
+	GError  *local_error = NULL;
+
+	rb_debug ("writing itdb");
+	if (itdb_write (manager->priv->itdb, &local_error) == FALSE) {
+		rb_debug ("error writing itdb: %s", local_error->message);
+		goto end;
+	}
+
+	rb_debug ("writing shuffle db");
+	if (itdb_shuffle_write (manager->priv->itdb, error) == FALSE) {
+		rb_debug ("error writing shuffle db: %s",
+			local_error->message);
+	}
+
+end:
+	if (local_error) {
+		g_propagate_error (error, local_error);
+		return FALSE;
+	} else {
+		manager->priv->itdb_dirty = FALSE;
+		return TRUE;
+	}
+}
+
+/* called during synchronization to remove one of the tracks from mpl's
+ * removed_tracks list */
+static gboolean
+sync_step_remove_track (RBiPodManager *manager, RBiPodPlaylistData *pdata)
+{
+	GList *node;
+	Itdb_Track *track;
+
+	rb_debug ("sync step remove track");
+
+	node = pdata->removed_tracks;
+	track = (Itdb_Track *) node->data;
+	pdata->removed_tracks =
+		g_list_delete_link (pdata->removed_tracks, node);
+
+	remove_track (manager, track);
+	return TRUE;
+}
+
+/* during sync add one of the tracks from mpl's added_tracks list */
+static gboolean
+sync_step_add_track (RBiPodManager *manager, RBiPodPlaylistData *pdata)
+{
+	GList *node;
+	Itdb_Track *track;
+
+	rb_debug ("sync step add track");
+
+	node = pdata->added_tracks;
+	track = (Itdb_Track *) node->data;
+	pdata->added_tracks = g_list_delete_link (pdata->added_tracks, node);
+
+	transfer_track (manager, track);
+	/* transfer_track will call sync_step_idle_cb when the
+	 * transfer is finished */
+	return FALSE;
+}
+
+/* during sync update playlist->members removing the tracks in the playlist's
+ * removed_tracks list and adding the ones in the added_tracks list */
+static void
+sync_playlist (RBiPodManager *manager, Itdb_Playlist *playlist)
+{
+	GList *walk, *next;
+	Itdb_Track *track;
+	RBiPodPlaylistData *pdata = rb_ipod_playlist_get_data (playlist);
+	gboolean dirty = rb_ipod_playlist_is_dirty (playlist);
+
+	if (dirty) {
+		/* some properties have been changed: sync them */
+		rb_ipod_playlist_sync_props (playlist);
+		decrement_dirty_counter (manager);
+	}
+
+	/* add the added_track */
+	walk = pdata->added_tracks;
+	while (walk) {
+		track = (Itdb_Track *) walk->data;
+		next = walk->next;
+		/* only add the transferred ones */
+		if (track->transferred == TRUE) {
+			itdb_playlist_add_track (playlist, track, -1);
+			decrement_dirty_counter (manager);
+			pdata->added_tracks =
+				g_list_delete_link (pdata->added_tracks, walk);
+		}
+		walk = next;
+	}
+
+	/* remove the removed_tracks */
+	for (walk = pdata->removed_tracks; walk; walk = g_list_next (walk)) {
+		itdb_playlist_remove_track (playlist,
+				(Itdb_Track *) walk->data);
+		decrement_dirty_counter (manager);
+	}
+	pdata->removed_tracks = NULL;
+}
+
+/* update the itdb to reflect changes */
+static gboolean
+sync_db (RBiPodManager *manager)
+{
+	GList *walk;
+
+	for (walk = manager->priv->removed_playlists; walk;
+			walk = g_list_next (walk)) {
+		sync_playlist (manager, (Itdb_Playlist *) walk->data);
+		itdb_playlist_remove ((Itdb_Playlist *) walk->data);
+		decrement_dirty_counter (manager);
+	}
+	manager->priv->removed_playlists = NULL;
+
+	for (walk = manager->priv->itdb->playlists; walk;
+			walk = g_list_next (walk)) {
+		sync_playlist (manager, (Itdb_Playlist *) walk->data);
+	}
+
+	for (walk = manager->priv->added_playlists; walk;
+			walk = g_list_next (walk)) {
+		itdb_playlist_add (manager->priv->itdb,
+				(Itdb_Playlist *) walk->data, -1);
+		sync_playlist (manager, (Itdb_Playlist *) walk->data);
+		decrement_dirty_counter (manager);
+	}
+	manager->priv->added_playlists = NULL;
+
+	manager->priv->itdb_dirty = TRUE;
+	return TRUE;
+}
+
+/* execute a synchronization step */
+static gboolean
+sync_step_idle_cb (RBiPodManager *manager)
+{
+	gboolean res;
+	Itdb_Playlist *playlist;
+	RBiPodPlaylistData *pdata;
+
+	rb_debug ("sync step");
+
+	if (manager->priv->cancel_sync) {
+		rb_debug ("sync canceled");
+		manager->priv->synching = FALSE;
+		manager->priv->cancel_sync = FALSE;
+
+		return FALSE;
+	}
+
+	if (manager->priv->synching == FALSE) {
+		manager->priv->synching = TRUE;
+		g_signal_emit (manager, signals[SYNC_STARTED], 0);
+	}
+
+	playlist = itdb_playlist_mpl (manager->priv->itdb);
+	pdata = rb_ipod_playlist_get_data (playlist);
+
+	if (pdata->removed_tracks)
+		res = sync_step_remove_track (manager, pdata);
+	else if (pdata->added_tracks)
+		res = sync_step_add_track (manager, pdata);
+	else if (manager->priv->dirty_counter > 0)
+		res = sync_db (manager);
+	else {
+		GError *error = NULL;
+		manager->priv->synching = FALSE;
+
+		/* the synchronization is complete */
+		if (manager->priv->itdb_dirty &&
+				manager->priv->auto_write_itdb == TRUE)
+			write_itdb_internal (manager, &error);
+
+		rb_debug ("sync finished");
+		g_signal_emit (manager, signals[SYNC_FINISHED], 0, error);
+		res = FALSE;
+	}
+
+	return res;
+}
+
+/* for each transferring track, use a TrackTransferInfo structure to hold
+ * transfer specific infos. by using a structure per track we could parallelize
+ * transfers */
+static TrackTransferInfo *
+track_transfer_info_new (RBiPodManager *manager, Itdb_Track *track)
+{
+	TrackTransferInfo *info = g_new0 (TrackTransferInfo, 1);
+	info->priv = g_new0 (TrackTransferInfoPrivate, 1);
+	info->priv->manager = g_object_ref (manager);
+	info->priv->mime_type = g_strdup (manager->priv->mime_type);
+	info->priv->encoder = rb_encoder_new ();
+	g_signal_connect (info->priv->encoder, "progress",
+			G_CALLBACK (encode_progress_cb),
+			info);
+	g_signal_connect (info->priv->encoder, "completed",
+			G_CALLBACK (encode_completed_cb),
+			info);
+	g_signal_connect (info->priv->encoder, "error",
+			G_CALLBACK (encode_error_cb),
+			info);
+	info->track = track;
+
+	return info;
+}
+
+static void
+track_transfer_info_free (TrackTransferInfo *info)
+{
+	g_object_unref (info->priv->manager);
+	g_free (info->priv->mime_type);
+	g_free (info->priv);
+	if (info->error)
+		g_error_free (info->error);
+	g_free (info);
+}
+
+/* transfer a track to the ipod */
+static void
+transfer_track (RBiPodManager *manager, Itdb_Track *track)
+{
+	RhythmDBEntry *entry;
+	const gchar *ipod_uri;
+	TrackTransferInfo *info;
+	gint i;
+	const gchar *mime_type;
+	gboolean keep_original_mime = FALSE;
+	/* XXX: this is a suckish hack not to transcode id3 muxed mp3s..
+	 * fix this in RBEncoder */
+	static gchar *mp3_mime_types[] = {
+		"application/x-id3", /* this could contain flac for example */
+		"audio/mpeg" /* which version? which part? */
+	};
+
+	info = track_transfer_info_new (manager, track);
+	manager->priv->transferring_tracks =
+		g_list_append (manager->priv->transferring_tracks, info);
+
+	g_signal_emit (manager, signals[TRACK_TRANSFER_STARTED], 0, info);
+
+	entry = rhythmdb_entry_lookup_by_location (manager->priv->db,
+			rb_ipod_track_get_source_uri (track));
+
+	if (entry == NULL) {
+		Itdb_Playlist *mpl = itdb_playlist_mpl (manager->priv->itdb);
+
+		rb_debug ("entry %s doesn't exist anymore",
+				rb_ipod_track_get_source_uri (track));
+
+		manager->priv->transferring_tracks =
+			g_list_remove (manager->priv->transferring_tracks, info);
+
+		g_set_error (&info->error,
+				RB_IPOD_MANAGER_ERROR,
+				RB_IPOD_MANAGER_ERROR_FAILED,
+				"entry doesn't exist anymore");
+
+		g_signal_emit (manager,
+				signals[TRACK_TRANSFER_FINISHED],
+				0, info);
+
+		remove_track_from_all_playlists (manager, info->track);
+		playlist_track_removed (manager, mpl, track, TRUE);
+		itdb_track_free (info->track);
+
+		g_idle_add ((GSourceFunc) sync_step_idle_cb,
+				info->priv->manager);
+
+		track_transfer_info_free (info);
+		return;
+	}
+
+	ipod_uri = rb_ipod_track_get_ipod_uri (track);
+	mime_type = rhythmdb_entry_get_string (entry,
+			RHYTHMDB_PROP_MIMETYPE);
+
+	for (i = 0; i < G_N_ELEMENTS (mp3_mime_types); ++i) {
+		if (strcmp (mp3_mime_types[i], mime_type) == 0) {
+			keep_original_mime = TRUE;
+			break;
+		}
+	}
+
+	if (keep_original_mime == FALSE)
+		mime_type = manager->priv->mime_type;
+
+	rb_encoder_encode (info->priv->encoder, entry, ipod_uri, mime_type);
+}
+
+static void
+remove_uri (RBiPodManager *manager, const gchar *uri)
+{
+	gchar *filename;
+
+	rb_debug ("removing uri %s", uri);
+
+	filename = g_filename_from_uri (uri, NULL, NULL);
+	unlink (filename);
+	free (filename);
+}
+
+static void
+remove_track (RBiPodManager *manager, Itdb_Track *track)
+{
+	const gchar *file_path;
+	Itdb_Playlist *playlist;
+
+	g_return_if_fail (track->transferred == TRUE);
+
+	file_path = rb_ipod_track_get_ipod_uri (track);
+	rb_debug ("remove track: %s", file_path);
+	remove_uri (manager, file_path);
+
+	playlist = itdb_playlist_mpl (manager->priv->itdb);
+	itdb_playlist_remove_track (playlist, track);
+	itdb_track_remove (track);
+	manager->priv->itdb_dirty = TRUE;
+
+	decrement_dirty_counter (manager);
+}
+
+static void
+transfer_track_cancel (TrackTransferInfo *info)
+{
+	g_set_error (&info->error,
+			RB_IPOD_MANAGER_ERROR,
+			RB_IPOD_MANAGER_ERROR_CANCELED,
+			"track transfer canceled");
+
+	rb_encoder_cancel (info->priv->encoder);
+}
+
+static void
+encode_progress_cb (RBEncoder *encoder, gdouble progress, TrackTransferInfo *info)
+{
+	if (info->priv->manager->priv->cancel_sync) {
+		info->priv->readd = TRUE;
+		return transfer_track_cancel (info);
+	}
+
+	g_signal_emit (info->priv->manager,
+			signals[TRACK_TRANSFER_PROGRESS],
+			0, info, progress);
+}
+
+static void
+encode_completed_cb (RBEncoder *encoder, TrackTransferInfo *info)
+{
+	RBiPodManager *manager = info->priv->manager;
+	Itdb_Playlist *mpl = itdb_playlist_mpl (manager->priv->itdb);
+	RBiPodPlaylistData *mpldata = rb_ipod_playlist_get_data (mpl);
+	const gchar *source_uri;
+	const gchar *ipod_uri;
+	gchar *ipod_filename;
+	struct stat buf;
+
+	source_uri = rb_ipod_track_get_source_uri (info->track);
+	ipod_uri = rb_ipod_track_get_ipod_uri (info->track);
+
+	manager->priv->transferring_tracks =
+		g_list_remove (manager->priv->transferring_tracks, info);
+
+	if (info->error) {
+		if (g_error_matches (info->error, RB_IPOD_MANAGER_ERROR,
+					RB_IPOD_MANAGER_ERROR_CANCELED))
+			rb_debug ("%s transfer canceled", source_uri);
+		else
+			rb_debug ("%s transfer error: %s",
+					source_uri, info->error->message);
+
+		remove_uri (manager, ipod_uri);
+
+		g_signal_emit (manager,
+				signals[TRACK_TRANSFER_FINISHED],
+				0, info);
+
+		if (info->priv->readd) {
+			mpldata->added_tracks = g_list_prepend (mpldata->added_tracks, info->track);
+		} else {
+			remove_track_from_all_playlists (manager, info->track);
+			playlist_track_removed (manager, mpl, info->track, TRUE);
+			itdb_track_free (info->track);
+		}
+
+		track_transfer_info_free (info);
+		g_idle_add ((GSourceFunc) sync_step_idle_cb, manager);
+		return;
+	}
+
+	rb_debug ("%s -> %s transfer completed", source_uri, ipod_uri);
+	/* mark the track as transferred */
+	info->track->transferred = TRUE;
+
+	info->track->filetype = g_strdup ("MPEG audio file");
+
+	ipod_filename = g_filename_from_uri (ipod_uri, NULL, NULL);
+	if (stat (ipod_filename, &buf) != -1) {
+		info->track->size = buf.st_size;
+	} else {
+		rb_debug ("couldn't stat file %s", ipod_filename);
+		/* go ahead !? */
+	}
+	g_free (ipod_filename);
+
+	/* add the track to the itdb */
+	itdb_track_add (manager->priv->itdb, info->track, -1);
+	itdb_playlist_add_track (mpl, info->track, -1);
+	manager->priv->itdb_dirty = TRUE;
+
+	g_signal_emit (manager,
+		       signals[TRACK_TRANSFER_FINISHED],
+		       0, info);
+
+	decrement_dirty_counter (manager);
+	g_idle_add ((GSourceFunc) sync_step_idle_cb, manager);
+
+	track_transfer_info_free (info);
+}
+
+static void
+encode_error_cb (RBEncoder *encoder, GError *error,
+		TrackTransferInfo * info)
+{
+	/* if we have a previous error, don't clobber it. this may happen if an
+	 * error occurs after the encoding has been canceled */
+	if (info->error == NULL)
+		info->error = g_error_copy (error);
+}
+
+static gboolean
+uri_is_on_ipod (RBiPodManager *manager, const gchar *uri)
+{
+	gboolean res;
+	gchar *filename = g_filename_from_uri (uri, NULL, NULL);
+
+	if (g_str_has_prefix (filename, manager->priv->mount_path))
+		res = TRUE;
+	else
+		res = FALSE;
+
+	g_free (filename);
+
+	return res;
+}
+
+/* Generation of the filename for the ipod */
+/* (C) 2006 Christophe Fergeau <teuf@gnome.org> */
+
+#define IPOD_MAX_PATH_LEN 56
+
+static gboolean
+test_dir_on_ipod (const char *mountpoint, const char *dirname)
+{
+	char *fullpath;
+	gboolean result;
+
+	fullpath  = g_build_filename (mountpoint, dirname, NULL);
+	result = g_file_test (fullpath, G_FILE_TEST_IS_DIR);
+	g_free (fullpath);
+
+	return result;
+}
+
+static int
+ipod_mkdir_with_parents (const char *mountpoint, const char *dirname)
+{
+	char *fullpath;
+	int result;
+
+	fullpath  = g_build_filename (mountpoint, dirname, NULL);
+	result = g_mkdir_with_parents (fullpath, 0770);
+	g_free (fullpath);
+
+	return result;
+}
+
+static gchar *
+build_ipod_dir_name (RBiPodManager *manager)
+{
+	/* FAT sucks, filename can be lowercase or uppercase, and if we try to
+	 * open the wrong one, we lose :-/
+	 */
+	char *dirname;
+	char *relpath;
+	gint32 suffix;
+
+	g_return_val_if_fail (itdb_musicdirs_number (manager->priv->itdb) > 0,
+			NULL);
+
+	suffix = g_random_int_range (0,
+			itdb_musicdirs_number (manager->priv->itdb));
+	dirname = g_strdup_printf ("F%02d", suffix);
+	relpath = g_build_filename (G_DIR_SEPARATOR_S, "iPod_Control",
+				    "Music", dirname, NULL);
+	g_free (dirname);
+
+	if (test_dir_on_ipod (manager->priv->mount_path, relpath) != FALSE) {
+		return relpath;
+	}
+
+	g_free (relpath);
+	dirname = g_strdup_printf ("f%02d", g_random_int_range (0, 100));
+	relpath = g_build_filename (G_DIR_SEPARATOR_S, "iPod_Control",
+				    "Music", dirname, NULL);
+	g_free (dirname);
+
+	if (test_dir_on_ipod (manager->priv->mount_path, relpath) != FALSE) {
+		return relpath;
+	}
+
+	if (ipod_mkdir_with_parents (manager->priv->mount_path, relpath) == 0) {
+		return relpath;
+	}
+
+	g_free (relpath);
+	return NULL;
+}
+
+static gchar *
+get_ipod_filename (RBiPodManager *manager, const char *filename)
+{
+	char *dirname;
+	char *result;
+	char *tmp;
+	gchar *name;
+	char *ext;
+
+	name = g_strdup (filename);
+	if (g_strrstr (name, ".mp3") == NULL) {
+		if ((ext = strrchr (name, '.')) != NULL)
+			*ext = '\0';
+		tmp = g_strconcat (name, ".mp3", NULL);
+		g_free (name);
+		name = tmp;
+	}
+
+	dirname = build_ipod_dir_name (manager);
+	if (dirname == NULL) {
+		return NULL;
+	}
+
+	result = g_build_filename (dirname, name, NULL);
+	g_free (dirname);
+	g_free (name);
+
+	if (strlen (result) >= IPOD_MAX_PATH_LEN) {
+		char *ext;
+
+		ext = strrchr (result, '.');
+		if (ext == NULL) {
+			result [IPOD_MAX_PATH_LEN - 1] = '\0';
+		} else {
+			memmove (&result[IPOD_MAX_PATH_LEN - strlen (ext) - 1] ,
+				 ext, strlen (ext) + 1);
+		}
+	}
+
+	tmp = g_build_filename (manager->priv->mount_path, result, NULL);
+	g_free (result);
+	return tmp;
+}
+
+#define MAX_TRIES 5
+
+/* Strips non UTF8 characters from a string replacing them with _ */
+static gchar *
+utf8_to_ascii (const gchar *utf8)
+{
+	GString *string;
+	const guchar *it = (const guchar *)utf8;
+
+	string = g_string_new ("");
+	while ((it != NULL) && (*it != '\0')) {
+		/* Do we have a 7 bit char ? */
+		if (*it < 0x80) {
+			g_string_append_c (string, *it);
+		} else {
+			g_string_append_c (string, '_');
+		}
+		it = (const guchar *)g_utf8_next_char (it);
+	}
+
+	return g_string_free (string, FALSE);
+}
+
+/* Copied from eel-vfs-extensions.c from eel CVS HEAD on 2004-05-09
+ * This function is (C) 1999, 2000 Eazel, Inc.
+ */
+static char *
+eel_make_valid_utf8 (const char *name)
+{
+	GString *string;
+	const char *remainder, *invalid;
+	int remaining_bytes, valid_bytes;
+
+	string = NULL;
+	remainder = name;
+	remaining_bytes = strlen (name);
+
+	while (remaining_bytes != 0) {
+		if (g_utf8_validate (remainder, remaining_bytes, &invalid)) {
+			break;
+		}
+		valid_bytes = invalid - remainder;
+
+		if (string == NULL) {
+			string = g_string_sized_new (remaining_bytes);
+		}
+		g_string_append_len (string, remainder, valid_bytes);
+		g_string_append_c (string, '_');
+
+		remaining_bytes -= valid_bytes + 1;
+		remainder = invalid + 1;
+	}
+
+	if (string == NULL) {
+		return g_strdup (name);
+	}
+
+	g_string_append (string, remainder);
+	g_assert (g_utf8_validate (string->str, -1, NULL));
+
+	return g_string_free (string, FALSE);
+}
+
+
+static gchar *
+generate_ipod_filename (RBiPodManager *manager, const gchar *filename)
+{
+	gchar *ipod_filename = NULL;
+	gchar *pc_filename;
+	gchar *tmp;
+	gint tries = 0;
+
+	/* First, we need a valid UTF-8 filename, strip all non-UTF-8 chars */
+	tmp = eel_make_valid_utf8 (filename);
+	/* The iPod doesn't seem to recognize non-ascii chars in filenames,
+	 * so we strip them
+	 */
+	pc_filename = utf8_to_ascii (tmp);
+	g_free (tmp);
+
+	g_assert (g_utf8_validate (pc_filename, -1, NULL));
+	/* Now we have a valid UTF-8 filename, try to find out where to put
+	 * it on the iPod
+	 */
+	do {
+		g_free (ipod_filename);
+		ipod_filename = get_ipod_filename (manager, pc_filename);
+		tries++;
+		if (tries > MAX_TRIES) {
+			break;
+		}
+	} while ((ipod_filename == NULL)
+		 || (g_file_test (ipod_filename, G_FILE_TEST_EXISTS)));
+
+	g_free (pc_filename);
+
+	if (tries > MAX_TRIES) {
+		/* FIXME: should create a unique filename */
+		return NULL;
+	} else {
+		return ipod_filename;
+	}
+}
+
+static gchar *
+ipod_get_filename_for_uri (RBiPodManager *manager, const gchar *uri_str)
+{
+	GnomeVFSURI *uri;
+	gchar *escaped;
+	gchar *filename;
+	gchar *result;
+
+	uri = gnome_vfs_uri_new (uri_str);
+	if (uri == NULL) {
+		return NULL;
+	}
+	escaped = gnome_vfs_uri_extract_short_path_name (uri);
+	gnome_vfs_uri_unref (uri);
+	if (escaped == NULL) {
+		return NULL;
+	}
+	filename = gnome_vfs_unescape_string (escaped, G_DIR_SEPARATOR_S);
+	g_free (escaped);
+	if (filename == NULL) {
+		return NULL;
+	}
+
+	result = generate_ipod_filename (manager, filename);
+	g_free (filename);
+
+	return result;
+}
+
+/* End of generation of the filename on the iPod */
+
+static gchar *
+ipod_path_from_unix_path (const gchar *mount_point, const gchar *unix_path)
+{
+        gchar *ipod_path;
+
+        g_assert (g_utf8_validate (unix_path, -1, NULL));
+
+        if (!g_str_has_prefix (unix_path, mount_point)) {
+                return NULL;
+        }
+
+        ipod_path = g_strdup (unix_path + strlen (mount_point));
+        if (*ipod_path != G_DIR_SEPARATOR) {
+                gchar *tmp;
+                tmp = g_strdup_printf ("/%s", ipod_path);
+                g_free (ipod_path);
+                ipod_path = tmp;
+        }
+
+        /* Make sure the filename doesn't contain any ':' */
+        g_strdelimit (ipod_path, ":", ';');
+
+        /* Convert path to a Mac path where the dir separator is ':' */
+	itdb_filename_fs2ipod (ipod_path);
+
+        return ipod_path;
+}
+#endif
