hythmbox-2.98/plugins/ipod/rb-ipod-source.c

No issues found

   1 /*
   2  *  Copyright (C) 2004, 2007 Christophe Fergeau  <teuf@gnome.org>
   3  *
   4  *  This program is free software; you can redistribute it and/or modify
   5  *  it under the terms of the GNU General Public License as published by
   6  *  the Free Software Foundation; either version 2 of the License, or
   7  *  (at your option) any later version.
   8  *
   9  *  The Rhythmbox authors hereby grant permission for non-GPL compatible
  10  *  GStreamer plugins to be used and distributed together with GStreamer
  11  *  and Rhythmbox. This permission is above and beyond the permissions granted
  12  *  by the GPL license by which Rhythmbox is covered. If you modify this code
  13  *  you may extend this exception to your version of the code, but you are not
  14  *  obligated to do so. If you do not wish to do so, delete this exception
  15  *  statement from your version.
  16  *
  17  *  This program is distributed in the hope that it will be useful,
  18  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  19  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  20  *  GNU General Public License for more details.
  21  *
  22  *  You should have received a copy of the GNU General Public License
  23  *  along with this program; if not, write to the Free Software
  24  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
  25  *
  26  */
  27 
  28 #include "config.h"
  29 
  30 #include <errno.h>
  31 #include <string.h>
  32 
  33 #include <glib/gi18n.h>
  34 #include <glib/gstdio.h>
  35 #include <gtk/gtk.h>
  36 #include <gpod/itdb.h>
  37 
  38 #include "rb-ipod-source.h"
  39 #include "rb-ipod-db.h"
  40 #include "rb-ipod-helpers.h"
  41 #include "rb-debug.h"
  42 #include "rb-file-helpers.h"
  43 #include "rb-builder-helpers.h"
  44 #include "rb-removable-media-manager.h"
  45 #include "rb-device-source.h"
  46 #include "rb-ipod-static-playlist-source.h"
  47 #include "rb-util.h"
  48 #include "rhythmdb.h"
  49 #include "rb-cut-and-paste-code.h"
  50 #include "rb-media-player-source.h"
  51 #include "rb-sync-settings.h"
  52 #include "rb-playlist-source.h"
  53 #include "rb-playlist-manager.h"
  54 #include "rb-podcast-manager.h"
  55 #include "rb-podcast-entry-types.h"
  56 #include "rb-stock-icons.h"
  57 #include "rb-gst-media-types.h"
  58 #include "rb-transfer-target.h"
  59 #include "rb-ext-db.h"
  60 #include "rb-dialog.h"
  61 
  62 static void rb_ipod_device_source_init (RBDeviceSourceInterface *interface);
  63 static void rb_ipod_source_transfer_target_init (RBTransferTargetInterface *interface);
  64 
  65 static void rb_ipod_source_constructed (GObject *object);
  66 static void rb_ipod_source_dispose (GObject *object);
  67 
  68 static void impl_delete (RBSource *asource);
  69 static RBTrackTransferBatch *impl_paste (RBSource *source, GList *entries);
  70 static void rb_ipod_load_songs (RBiPodSource *source);
  71 
  72 static gboolean impl_show_popup (RBDisplayPage *page);
  73 static void impl_delete_thyself (RBDisplayPage *page);
  74 static void impl_selected (RBDisplayPage *page);
  75 
  76 static gboolean impl_track_added (RBTransferTarget *target,
  77 				  RhythmDBEntry *entry,
  78 				  const char *dest,
  79 				  guint64 filesize,
  80 				  const char *media_type);
  81 static char* impl_build_dest_uri (RBTransferTarget *target,
  82 				  RhythmDBEntry *entry,
  83 				  const char *media_type,
  84 				  const char *extension);
  85 static gchar* ipod_get_filename_for_uri (const gchar *mount_point,
  86 					 const gchar *uri_str,
  87 					 const gchar *media_type,
  88 					 const gchar *extension);
  89 static gchar* ipod_path_from_unix_path (const gchar *mount_point,
  90 					const gchar *unix_path);
  91 
  92 static guint64 impl_get_capacity (RBMediaPlayerSource *source);
  93 static guint64 impl_get_free_space (RBMediaPlayerSource *source);
  94 static void impl_get_entries (RBMediaPlayerSource *source, const char *category, GHashTable *map);
  95 static void impl_delete_entries (RBMediaPlayerSource *source, GList *entries, RBMediaPlayerSourceDeleteCallback callback, gpointer callback_data, GDestroyNotify destroy_data);
  96 static void impl_add_playlist (RBMediaPlayerSource *source, gchar *name, GList *entries);
  97 static void impl_remove_playlists (RBMediaPlayerSource *source);
  98 static void impl_show_properties (RBMediaPlayerSource *source, GtkWidget *info_box, GtkWidget *notebook);
  99 
 100 static void rb_ipod_source_set_property (GObject *object,
 101 					 guint prop_id,
 102 					 const GValue *value,
 103 					 GParamSpec *pspec);
 104 static void rb_ipod_source_get_property (GObject *object,
 105 					 guint prop_id,
 106 					 GValue *value,
 107 					 GParamSpec *pspec);
 108 static gboolean ensure_loaded (RBiPodSource *source);
 109 
 110 
 111 static RhythmDB *get_db_for_source (RBiPodSource *source);
 112 
 113 struct _PlayedEntry {
 114 	RhythmDBEntry *entry;
 115 	guint play_count;
 116 };
 117 
 118 typedef struct _PlayedEntry PlayedEntry;
 119 
 120 typedef struct
 121 {
 122 	GMount *mount;
 123 	RbIpodDb *ipod_db;
 124 	GHashTable *entry_map;
 125 
 126 	MPIDDevice *device_info;
 127 
 128 	gboolean needs_shuffle_db;
 129 	RBIpodStaticPlaylistSource *podcast_pl;
 130 
 131 	guint load_idle_id;
 132 
 133 	RBExtDB *art_store;
 134 
 135 	GQueue *offline_plays;
 136 
 137 	/* init dialog */
 138 	GtkWidget *init_dialog;
 139 	GtkWidget *model_combo;
 140 	GtkWidget *name_entry;
 141 } RBiPodSourcePrivate;
 142 
 143 typedef struct {
 144 	RBiPodSourcePrivate *priv;
 145 	GdkPixbuf *pixbuf;
 146 } RBiPodSongArtworkAddData;
 147 
 148 enum
 149 {
 150 	PROP_0,
 151 	PROP_DEVICE_INFO,
 152 	PROP_DEVICE_SERIAL,
 153 	PROP_MOUNT
 154 };
 155 
 156 G_DEFINE_DYNAMIC_TYPE_EXTENDED(
 157 	RBiPodSource,
 158 	rb_ipod_source,
 159 	RB_TYPE_MEDIA_PLAYER_SOURCE,
 160 	0,
 161 	G_IMPLEMENT_INTERFACE_DYNAMIC (RB_TYPE_DEVICE_SOURCE, rb_ipod_device_source_init)
 162 	G_IMPLEMENT_INTERFACE_DYNAMIC (RB_TYPE_TRANSFER_TARGET, rb_ipod_source_transfer_target_init))
 163 
 164 #define IPOD_SOURCE_GET_PRIVATE(o)   (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_IPOD_SOURCE, RBiPodSourcePrivate))
 165 
 166 static void
 167 rb_ipod_source_class_init (RBiPodSourceClass *klass)
 168 {
 169 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 170 	RBDisplayPageClass *page_class = RB_DISPLAY_PAGE_CLASS (klass);
 171 	RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
 172 	RBMediaPlayerSourceClass *mps_class = RB_MEDIA_PLAYER_SOURCE_CLASS (klass);
 173 
 174 	object_class->constructed = rb_ipod_source_constructed;
 175 	object_class->dispose = rb_ipod_source_dispose;
 176 
 177 	object_class->set_property = rb_ipod_source_set_property;
 178 	object_class->get_property = rb_ipod_source_get_property;
 179 
 180 	page_class->delete_thyself = impl_delete_thyself;
 181 	page_class->show_popup = impl_show_popup;
 182 	page_class->selected = impl_selected;
 183 
 184 	source_class->impl_can_move_to_trash = (RBSourceFeatureFunc) rb_false_function;
 185 	source_class->impl_can_rename = (RBSourceFeatureFunc) rb_true_function;
 186 	source_class->impl_can_delete = (RBSourceFeatureFunc) rb_true_function;
 187 	source_class->impl_delete = impl_delete;
 188 
 189 	source_class->impl_can_paste = (RBSourceFeatureFunc) rb_true_function;
 190 	source_class->impl_paste = impl_paste;
 191 	source_class->impl_want_uri = rb_device_source_want_uri;
 192 	source_class->impl_uri_is_source = rb_device_source_uri_is_source;
 193 
 194 	mps_class->impl_get_entries = impl_get_entries;
 195 	mps_class->impl_get_capacity = impl_get_capacity;
 196 	mps_class->impl_get_free_space = impl_get_free_space;
 197 	mps_class->impl_delete_entries = impl_delete_entries;
 198 	mps_class->impl_add_playlist = impl_add_playlist;
 199 	mps_class->impl_remove_playlists = impl_remove_playlists;
 200 	mps_class->impl_show_properties = impl_show_properties;
 201 
 202 	g_object_class_install_property (object_class,
 203 					 PROP_DEVICE_INFO,
 204 					 g_param_spec_object ("device-info",
 205 							      "device info",
 206 							      "device information object",
 207 							      MPID_TYPE_DEVICE,
 208 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 209 	g_object_class_install_property (object_class,
 210 					 PROP_MOUNT,
 211 					 g_param_spec_object ("mount",
 212 							      "mount",
 213 							      "GMount object",
 214 							      G_TYPE_MOUNT,
 215 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 216 	g_object_class_override_property (object_class, PROP_DEVICE_SERIAL, "serial");
 217 
 218 	g_type_class_add_private (klass, sizeof (RBiPodSourcePrivate));
 219 }
 220 
 221 static void
 222 rb_ipod_device_source_init (RBDeviceSourceInterface *interface)
 223 {
 224 	/* nothing */
 225 }
 226 
 227 static void
 228 rb_ipod_source_transfer_target_init (RBTransferTargetInterface *interface)
 229 {
 230 	interface->track_added = impl_track_added;
 231 	interface->build_dest_uri = impl_build_dest_uri;
 232 }
 233 
 234 static void
 235 rb_ipod_source_class_finalize (RBiPodSourceClass *klass)
 236 {
 237 }
 238 
 239 static void
 240 rb_ipod_source_set_property (GObject *object,
 241 			     guint prop_id,
 242 			     const GValue *value,
 243 			     GParamSpec *pspec)
 244 {
 245 	RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (object);
 246 
 247 	switch (prop_id) {
 248 	case PROP_DEVICE_INFO:
 249 		priv->device_info = g_value_dup_object (value);
 250 		break;
 251 	case PROP_MOUNT:
 252 		priv->mount = g_value_dup_object (value);
 253 		break;
 254 	default:
 255 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 256 		break;
 257 	}
 258 }
 259 
 260 static void
 261 rb_ipod_source_get_property (GObject *object,
 262 			     guint prop_id,
 263 			     GValue *value,
 264 			     GParamSpec *pspec)
 265 {
 266 	RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (object);
 267 
 268 	switch (prop_id) {
 269 	case PROP_DEVICE_INFO:
 270 		g_value_set_object (value, priv->device_info);
 271 		break;
 272 	case PROP_DEVICE_SERIAL:
 273 		{
 274 			char *serial;
 275 			g_object_get (priv->device_info, "serial", &serial, NULL);
 276 			g_value_take_string (value, serial);
 277 		}
 278 		break;
 279 	case PROP_MOUNT:
 280 		g_value_set_object (value, priv->mount);
 281 		break;
 282 	default:
 283 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 284 		break;
 285 	}
 286 }
 287 
 288 static void
 289 rb_ipod_source_set_ipod_name (RBiPodSource *source, const char *name)
 290 {
 291 	RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
 292 
 293 	if (priv->ipod_db == NULL) {
 294 		rb_debug ("can't change ipod name with no ipod db");
 295 		return;
 296 	}
 297 
 298 	rb_ipod_db_set_ipod_name (priv->ipod_db, name);
 299 }
 300 
 301 static void
 302 rb_ipod_source_name_changed_cb (RBiPodSource *source, GParamSpec *spec,
 303 				gpointer data)
 304 {
 305 	char *name;
 306 
 307 	g_object_get (source, "name", &name, NULL);
 308 	rb_ipod_source_set_ipod_name (source, name);
 309 	g_free (name);
 310 }
 311 
 312 static void
 313 rb_ipod_source_init (RBiPodSource *source)
 314 {
 315 }
 316 
 317 static void
 318 finish_construction (RBiPodSource *source)
 319 {
 320 	RBEntryView *songs;
 321 	RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
 322 	GstEncodingTarget *target;
 323 
 324 
 325 	songs = rb_source_get_entry_view (RB_SOURCE (source));
 326 	rb_entry_view_append_column (songs, RB_ENTRY_VIEW_COL_RATING, FALSE);
 327 	rb_entry_view_append_column (songs, RB_ENTRY_VIEW_COL_LAST_PLAYED, FALSE);
 328         rb_entry_view_append_column (songs, RB_ENTRY_VIEW_COL_FIRST_SEEN, FALSE);
 329 
 330 	priv->art_store = rb_ext_db_new ("album-art");
 331 
 332 	/* is there model-specific data we need to pay attention to here?
 333 	 * maybe load a target from the device too?
 334 	 */
 335 	target = gst_encoding_target_new ("ipod", "device", "ipod", NULL);
 336 	gst_encoding_target_add_profile (target, rb_gst_get_encoding_profile ("audio/mpeg"));
 337 	gst_encoding_target_add_profile (target, rb_gst_get_encoding_profile ("audio/x-aac"));
 338 	g_object_set (source, "encoding-target", target, NULL);
 339 
 340 }
 341 
 342 static void
 343 first_time_dialog_response_cb (GtkDialog *dialog, int response, RBiPodSource *source)
 344 {
 345 	const Itdb_IpodInfo *info;
 346 	GtkTreeModel *tree_model;
 347 	GtkTreeIter iter;
 348 	char *mountpoint;
 349 	char *ipod_name;
 350 	GFile *root;
 351 	GError *error = NULL;
 352 	RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
 353 
 354 	priv->init_dialog = NULL;
 355 
 356 	if (response != GTK_RESPONSE_ACCEPT) {
 357 		gtk_widget_destroy (GTK_WIDGET (dialog));
 358 		rb_display_page_delete_thyself (RB_DISPLAY_PAGE (source));
 359 		return;
 360 	}
 361 
 362 	/* get model number and name */
 363 	tree_model = gtk_combo_box_get_model (GTK_COMBO_BOX (priv->model_combo));
 364 	if (!gtk_combo_box_get_active_iter (GTK_COMBO_BOX (priv->model_combo), &iter)) {
 365 		gtk_widget_destroy (GTK_WIDGET (dialog));
 366 		rb_display_page_delete_thyself (RB_DISPLAY_PAGE (source));
 367 		return;
 368 	}
 369 	gtk_tree_model_get (tree_model, &iter, /* COL_INFO */ 0, &info, -1);
 370 	ipod_name = g_strdup (gtk_entry_get_text (GTK_ENTRY (priv->name_entry)));
 371 
 372 	/* get mountpoint again */
 373 	root = g_mount_get_root (priv->mount);
 374 	if (root == NULL) {
 375 		gtk_widget_destroy (GTK_WIDGET (dialog));
 376 		return;
 377 	}
 378 	mountpoint = g_file_get_path (root);
 379 	g_object_unref (root);
 380 
 381 	rb_debug ("attempting to init ipod on '%s', with model '%s' and name '%s'",
 382 		  mountpoint, info->model_number, ipod_name);
 383 	if (!itdb_init_ipod (mountpoint, info->model_number, ipod_name, &error)) {
 384 		rb_error_dialog (NULL, _("Unable to initialize new iPod"), "%s", error->message);
 385 		g_error_free (error);
 386 		rb_display_page_delete_thyself (RB_DISPLAY_PAGE (source));
 387 	} else {
 388 		finish_construction (source);
 389 	}
 390 
 391 	gtk_widget_destroy (GTK_WIDGET (dialog));
 392 	g_free (mountpoint);
 393 	g_free (ipod_name);
 394 }
 395 
 396 static gboolean
 397 create_init_dialog (RBiPodSource *source)
 398 {
 399 	GFile *root;
 400 	char *mountpoint;
 401 	char *builder_file;
 402 	GtkBuilder *builder;
 403 	GObject *plugin;
 404 	RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
 405 
 406 	root = g_mount_get_root (priv->mount);
 407 	if (root == NULL) {
 408 		return FALSE;
 409 	}
 410 	mountpoint = g_file_get_path (root);
 411 	g_object_unref (root);
 412 
 413 	if (mountpoint == NULL) {
 414 		return FALSE;
 415 	}
 416 
 417 	g_object_get (source, "plugin", &plugin, NULL);
 418 	builder_file = rb_find_plugin_data_file (G_OBJECT (plugin), "ipod-init.ui");
 419 	g_object_unref (plugin);
 420 
 421 	builder = rb_builder_load (builder_file, NULL);
 422 	g_free (builder_file);
 423 	if (builder == NULL) {
 424 		g_free (mountpoint);
 425 		return FALSE;
 426 	}
 427 
 428 	priv->init_dialog = GTK_WIDGET (gtk_builder_get_object (builder, "ipod_init"));
 429 	priv->model_combo = GTK_WIDGET (gtk_builder_get_object (builder, "model_combo"));
 430 	priv->name_entry = GTK_WIDGET (gtk_builder_get_object (builder, "name_entry"));
 431 	rb_ipod_helpers_fill_model_combo (priv->model_combo, mountpoint);
 432 
 433 	g_signal_connect (priv->init_dialog,
 434 			  "response",
 435 			  G_CALLBACK (first_time_dialog_response_cb),
 436 			  source);
 437 
 438 	g_object_unref (builder);
 439 	g_free (mountpoint);
 440 	return TRUE;
 441 }
 442 
 443 static void
 444 rb_ipod_source_constructed (GObject *object)
 445 {
 446 	RBiPodSource *source;
 447 	GMount *mount;
 448 
 449 	RB_CHAIN_GOBJECT_METHOD (rb_ipod_source_parent_class, constructed, object);
 450 	source = RB_IPOD_SOURCE (object);
 451 
 452 	g_object_get (source, "mount", &mount, NULL);
 453 
 454 	rb_device_source_set_display_details (RB_DEVICE_SOURCE (source));
 455 
 456 	if (rb_ipod_helpers_needs_init (mount)) {
 457 		if (create_init_dialog (source) == FALSE) {
 458 			rb_display_page_delete_thyself (RB_DISPLAY_PAGE (source));
 459 		}
 460 	} else {
 461 		finish_construction (source);
 462 	}
 463 }
 464 
 465 static void
 466 rb_ipod_source_dispose (GObject *object)
 467 {
 468 	RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (object);
 469 
 470 	if (priv->ipod_db) {
 471 		g_object_unref (G_OBJECT (priv->ipod_db));
 472 		priv->ipod_db = NULL;
 473 	}
 474 
 475 	if (priv->entry_map) {
 476 		g_hash_table_destroy (priv->entry_map);
 477 		priv->entry_map = NULL;
 478  	}
 479 
 480 	if (priv->load_idle_id != 0) {
 481 		g_source_remove (priv->load_idle_id);
 482 		priv->load_idle_id = 0;
 483 	}
 484 
 485 	if (priv->offline_plays) {
 486 		g_queue_foreach (priv->offline_plays,
 487 				 (GFunc)g_free, NULL);
 488 		g_queue_free (priv->offline_plays);
 489 		priv->offline_plays = NULL;
 490 	}
 491 
 492 	if (priv->mount) {
 493 		g_object_unref (priv->mount);
 494 		priv->mount = NULL;
 495 	}
 496 
 497 	if (priv->art_store) {
 498 		g_object_unref (priv->art_store);
 499 		priv->art_store = NULL;
 500 	}
 501 
 502 	if (priv->init_dialog) {
 503 		gtk_widget_destroy (priv->init_dialog);
 504 		priv->init_dialog = NULL;
 505 	}
 506 
 507 	G_OBJECT_CLASS (rb_ipod_source_parent_class)->dispose (object);
 508 }
 509 
 510 RBMediaPlayerSource *
 511 rb_ipod_source_new (GObject *plugin,
 512 		    RBShell *shell,
 513 		    GMount *mount,
 514 		    MPIDDevice *device_info)
 515 {
 516 	RBiPodSource *source;
 517 	RhythmDBEntryType *entry_type;
 518 	RhythmDB *db;
 519 	GVolume *volume;
 520 	GSettings *settings;
 521 	char *name;
 522 	char *path;
 523 
 524 	volume = g_mount_get_volume (mount);
 525 	path = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE);
 526 	if (path == NULL)
 527 		path = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_UUID);
 528 	g_object_unref (volume);
 529 
 530 	g_object_get (shell, "db", &db, NULL);
 531 	name = g_strdup_printf ("ipod: %s", path);
 532 	entry_type = g_object_new (RHYTHMDB_TYPE_ENTRY_TYPE,
 533 				   "db", db,
 534 				   "name", name,
 535 				   "save-to-disk", FALSE,
 536 				   "category", RHYTHMDB_ENTRY_NORMAL,
 537 				   NULL);
 538 	rhythmdb_register_entry_type (db, entry_type);
 539 	g_object_unref (db);
 540 	g_free (name);
 541 	g_free (path);
 542 
 543 	settings = g_settings_new ("org.gnome.rhythmbox.plugins.ipod");
 544 	source = RB_IPOD_SOURCE (g_object_new (RB_TYPE_IPOD_SOURCE,
 545 				               "plugin", plugin,
 546 					       "entry-type", entry_type,
 547 					       "mount", mount,
 548 					       "shell", shell,
 549 					       "device-info", device_info,
 550 					       "load-status", RB_SOURCE_LOAD_STATUS_LOADING,
 551 					       "settings", g_settings_get_child (settings, "source"),
 552 					       "toolbar-path", "/iPodSourceToolBar",
 553 					       NULL));
 554 	g_object_unref (settings);
 555 
 556 	rb_shell_register_entry_type_for_source (shell, RB_SOURCE (source), entry_type);
 557         g_object_unref (entry_type);
 558 
 559 	return RB_MEDIA_PLAYER_SOURCE (source);
 560 }
 561 
 562 static void
 563 entry_set_string_prop (RhythmDB *db, RhythmDBEntry *entry,
 564 		       RhythmDBPropType propid, const char *str)
 565 {
 566 	GValue value = {0,};
 567 
 568 	if (!str)
 569 		str = _("Unknown");
 570 
 571 	g_value_init (&value, G_TYPE_STRING);
 572 	g_value_set_static_string (&value, str);
 573 	rhythmdb_entry_set (RHYTHMDB (db), entry, propid, &value);
 574 	g_value_unset (&value);
 575 }
 576 
 577 static char *
 578 ipod_path_to_uri (const char *mount_point, const char *ipod_path)
 579 {
 580  	char *rel_pc_path;
 581  	char *full_pc_path;
 582  	char *uri;
 583 
 584  	rel_pc_path = g_strdup (ipod_path);
 585  	itdb_filename_ipod2fs (rel_pc_path);
 586  	full_pc_path = g_build_filename (mount_point, rel_pc_path, NULL);
 587  	g_free (rel_pc_path);
 588  	uri = g_filename_to_uri (full_pc_path, NULL, NULL);
 589  	g_free (full_pc_path);
 590  	return uri;
 591 }
 592 
 593 static void
 594 set_podcast_icon (RBIpodStaticPlaylistSource *source)
 595 {
 596 	GdkPixbuf *pixbuf;
 597 	gint       size;
 598 
 599 	gtk_icon_size_lookup (RB_SOURCE_ICON_SIZE, &size, NULL);
 600 	pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
 601 					   RB_STOCK_PODCAST,
 602 					   size,
 603 					   0, NULL);
 604 
 605 	if (pixbuf != NULL) {
 606 		g_object_set (source, "pixbuf", pixbuf, NULL);
 607 		g_object_unref (pixbuf);
 608 	}
 609 }
 610 
 611 static RBIpodStaticPlaylistSource *
 612 add_rb_playlist (RBiPodSource *source, Itdb_Playlist *playlist)
 613 {
 614 	RBShell *shell;
 615 	RBIpodStaticPlaylistSource *playlist_source;
 616 	GList *it;
 617 	RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
 618 	RhythmDBEntryType *entry_type;
 619 
 620 	g_object_get (source,
 621 			  "shell", &shell,
 622 			  "entry-type", &entry_type,
 623 			  NULL);
 624 
 625 	playlist_source = rb_ipod_static_playlist_source_new (shell,
 626                                                               source,
 627                                                               priv->ipod_db,
 628                                                               playlist,
 629                                                               entry_type);
 630 	g_object_unref (entry_type);
 631 
 632 	for (it = playlist->members; it != NULL; it = it->next) {
 633 		Itdb_Track *song;
 634 		char *filename;
 635 		const char *mount_path;
 636 
 637 		song = (Itdb_Track *)it->data;
 638 		mount_path = rb_ipod_db_get_mount_path (priv->ipod_db);
 639 		filename = ipod_path_to_uri (mount_path, song->ipod_path);
 640 		rb_static_playlist_source_add_location (RB_STATIC_PLAYLIST_SOURCE (playlist_source),
 641 							filename, -1);
 642 		g_free (filename);
 643 	}
 644 
 645         /* RBSource derives from GtkWidget so its initial reference is
 646          * floating. Since we need a ref for ourselves and we don't want it to
 647          * be stolen by a GtkContainer, we sink the floating reference.
 648          */
 649 	g_object_ref_sink (G_OBJECT(playlist_source));
 650 	playlist->userdata = playlist_source;
 651 	playlist->userdata_destroy = g_object_unref;
 652 	playlist->userdata_duplicate = g_object_ref;
 653 
 654 	if (itdb_playlist_is_podcasts(playlist)) {
 655 		priv->podcast_pl = playlist_source;
 656 		set_podcast_icon (playlist_source);
 657 	}
 658 	rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (playlist_source), RB_DISPLAY_PAGE (source));
 659 	g_object_unref (shell);
 660 
 661 	return playlist_source;
 662 }
 663 
 664 static void
 665 load_ipod_playlists (RBiPodSource *source)
 666 {
 667 	RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
 668 	GList *it;
 669 
 670 	for (it = rb_ipod_db_get_playlists (priv->ipod_db);
 671 	     it != NULL;
 672 	     it = it->next) {
 673 		Itdb_Playlist *playlist;
 674 
 675 		playlist = (Itdb_Playlist *)it->data;
 676 		if (itdb_playlist_is_mpl (playlist)) {
 677 			continue;
 678 		} else if (playlist->is_spl) {
 679 			continue;
 680 		}
 681 
 682 		add_rb_playlist (source, playlist);
 683 	}
 684 
 685 }
 686 
 687 static Itdb_Track *
 688 create_ipod_song_from_entry (RhythmDBEntry *entry, guint64 filesize, const char *media_type)
 689 {
 690 	Itdb_Track *track;
 691 
 692 	track = itdb_track_new ();
 693 
 694 	track->title = rhythmdb_entry_dup_string (entry, RHYTHMDB_PROP_TITLE);
 695 	track->album = rhythmdb_entry_dup_string (entry, RHYTHMDB_PROP_ALBUM);
 696 	track->artist = rhythmdb_entry_dup_string (entry, RHYTHMDB_PROP_ARTIST);
 697 	track->albumartist = rhythmdb_entry_dup_string (entry, RHYTHMDB_PROP_ALBUM_ARTIST);
 698 	track->sort_artist = rhythmdb_entry_dup_string (entry,
 699 	                                                RHYTHMDB_PROP_ARTIST_SORTNAME);
 700 	track->sort_album = rhythmdb_entry_dup_string (entry,
 701 	                                                RHYTHMDB_PROP_ALBUM_SORTNAME);
 702 	track->sort_albumartist = rhythmdb_entry_dup_string (entry,
 703 	                                       	       	     RHYTHMDB_PROP_ALBUM_ARTIST_SORTNAME);
 704 	track->genre = rhythmdb_entry_dup_string (entry, RHYTHMDB_PROP_GENRE);
 705 	track->filetype = g_strdup (media_type);		/* XXX mapping required? */
 706 	track->size = filesize;
 707 	track->tracklen = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DURATION);
 708 	track->tracklen *= 1000;
 709 	track->cd_nr = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DISC_NUMBER);
 710 	track->track_nr = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_TRACK_NUMBER);
 711 	track->bitrate = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_BITRATE);
 712 	track->year = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_YEAR);
 713 	track->time_added = time (NULL);
 714 	track->time_played = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_LAST_PLAYED);
 715 	track->rating = rhythmdb_entry_get_double (entry, RHYTHMDB_PROP_RATING);
 716 	track->rating *= ITDB_RATING_STEP;
 717 	track->app_rating = track->rating;
 718 	track->playcount = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_PLAY_COUNT);
 719 
 720 	if (rhythmdb_entry_get_entry_type (entry) == RHYTHMDB_ENTRY_TYPE_PODCAST_POST) {
 721 		track->mediatype = ITDB_MEDIATYPE_PODCAST;
 722 		track->time_released = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_POST_TIME);
 723 	} else {
 724 		track->mediatype = ITDB_MEDIATYPE_AUDIO;
 725 	}
 726 
 727 	return track;
 728 }
 729 
 730 static void add_offline_played_entry (RBiPodSource *source,
 731 				      RhythmDBEntry *entry,
 732 				      guint play_count)
 733 {
 734 	PlayedEntry *played_entry;
 735 	RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
 736 
 737 	if (play_count == 0) {
 738 		return;
 739 	}
 740 
 741 	if (priv->offline_plays == NULL) {
 742 		priv->offline_plays = g_queue_new();
 743 	}
 744 
 745 	played_entry = g_new0 (PlayedEntry, 1);
 746 	played_entry->entry = entry;
 747 	played_entry->play_count = play_count;
 748 
 749 	g_queue_push_tail (priv->offline_plays, played_entry);
 750 }
 751 
 752 static void
 753 add_ipod_song_to_db (RBiPodSource *source, RhythmDB *db, Itdb_Track *song)
 754 {
 755 	RhythmDBEntry *entry;
 756 	RhythmDBEntryType *entry_type;
 757 	RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
 758 	char *pc_path;
 759 	const char *mount_path;
 760 
 761 	/* Set URI */
 762 	g_object_get (source, "entry-type", &entry_type, NULL);
 763 	mount_path = rb_ipod_db_get_mount_path (priv->ipod_db);
 764 	pc_path = ipod_path_to_uri (mount_path, song->ipod_path);
 765 	entry = rhythmdb_entry_new (RHYTHMDB (db), entry_type, pc_path);
 766 	g_object_unref (entry_type);
 767 
 768 	if (entry == NULL) {
 769 		rb_debug ("cannot create entry %s", pc_path);
 770 		g_free (pc_path);
 771 		return;
 772 	}
 773 
 774 	if ((song->mediatype != ITDB_MEDIATYPE_AUDIO)
 775 	    && (song->mediatype != ITDB_MEDIATYPE_PODCAST)) {
 776 		rb_debug ("iPod track is neither an audio track nor a podcast, skipping");
 777 		return;
 778 	}
 779 
 780 	rb_debug ("Adding %s from iPod", pc_path);
 781 	g_free (pc_path);
 782 
 783 	/* Set track number */
 784 	if (song->track_nr != 0) {
 785 		GValue value = {0, };
 786 		g_value_init (&value, G_TYPE_ULONG);
 787 		g_value_set_ulong (&value, song->track_nr);
 788 		rhythmdb_entry_set (RHYTHMDB (db), entry,
 789 					       RHYTHMDB_PROP_TRACK_NUMBER,
 790 					       &value);
 791 		g_value_unset (&value);
 792 	}
 793 
 794 	/* Set disc number */
 795 	if (song->cd_nr != 0) {
 796 		GValue value = {0, };
 797 		g_value_init (&value, G_TYPE_ULONG);
 798 		g_value_set_ulong (&value, song->cd_nr);
 799 		rhythmdb_entry_set (RHYTHMDB (db), entry,
 800 					       RHYTHMDB_PROP_DISC_NUMBER,
 801 					       &value);
 802 		g_value_unset (&value);
 803 	}
 804 
 805 	/* Set bitrate */
 806 	if (song->bitrate != 0) {
 807 		GValue value = {0, };
 808 		g_value_init (&value, G_TYPE_ULONG);
 809 		g_value_set_ulong (&value, song->bitrate);
 810 		rhythmdb_entry_set (RHYTHMDB (db), entry,
 811 					       RHYTHMDB_PROP_BITRATE,
 812 					       &value);
 813 		g_value_unset (&value);
 814 	}
 815 
 816 	/* Set length */
 817 	if (song->tracklen != 0) {
 818 		GValue value = {0, };
 819 		g_value_init (&value, G_TYPE_ULONG);
 820 		g_value_set_ulong (&value, song->tracklen/1000);
 821 		rhythmdb_entry_set (RHYTHMDB (db), entry,
 822 					       RHYTHMDB_PROP_DURATION,
 823 					       &value);
 824 		g_value_unset (&value);
 825 	}
 826 
 827 	/* Set file size */
 828 	if (song->size != 0) {
 829 		GValue value = {0, };
 830 		g_value_init (&value, G_TYPE_UINT64);
 831 		g_value_set_uint64 (&value, song->size);
 832 		rhythmdb_entry_set (RHYTHMDB (db), entry,
 833 					       RHYTHMDB_PROP_FILE_SIZE,
 834 					       &value);
 835 		g_value_unset (&value);
 836 	}
 837 
 838 	/* Set playcount */
 839 	if (song->playcount != 0) {
 840 		GValue value = {0, };
 841 		g_value_init (&value, G_TYPE_ULONG);
 842 		g_value_set_ulong (&value, song->playcount);
 843 		rhythmdb_entry_set (RHYTHMDB (db), entry,
 844 					       RHYTHMDB_PROP_PLAY_COUNT,
 845 					       &value);
 846 		g_value_unset (&value);
 847 	}
 848 
 849 	/* Set year */
 850 	if (song->year != 0) {
 851 		GDate *date = NULL;
 852 		GType type;
 853 		GValue value = {0, };
 854 
 855 		date = g_date_new_dmy (1, G_DATE_JANUARY, song->year);
 856 
 857 		type = rhythmdb_get_property_type (RHYTHMDB(db),
 858 						   RHYTHMDB_PROP_DATE);
 859 
 860 		g_value_init (&value, type);
 861 		g_value_set_ulong (&value, (date ? g_date_get_julian (date) : 0));
 862 
 863 		rhythmdb_entry_set (RHYTHMDB (db), entry,
 864 					       RHYTHMDB_PROP_DATE,
 865 					       &value);
 866 		g_value_unset (&value);
 867 		if (date)
 868 			g_date_free (date);
 869 	}
 870 
 871 	/* Set rating */
 872 	if (song->rating != 0) {
 873 		GValue value = {0, };
 874 		g_value_init (&value, G_TYPE_DOUBLE);
 875 		g_value_set_double (&value, song->rating/ITDB_RATING_STEP);
 876 		rhythmdb_entry_set (RHYTHMDB (db), entry,
 877 					       RHYTHMDB_PROP_RATING,
 878 					       &value);
 879 		g_value_unset (&value);
 880 	}
 881 
 882 	/* Set last added time */
 883 	if (song->time_added != 0) {
 884 		GValue value = {0, };
 885 		g_value_init (&value, G_TYPE_ULONG);
 886 		g_value_set_ulong (&value, song->time_added);
 887 		rhythmdb_entry_set (RHYTHMDB (db), entry,
 888 					       RHYTHMDB_PROP_FIRST_SEEN,
 889 					       &value);
 890 		g_value_unset (&value);
 891 	}
 892 
 893 	/* Set last played */
 894 	if (song->time_played != 0) {
 895 		GValue value = {0, };
 896 		g_value_init (&value, G_TYPE_ULONG);
 897 		g_value_set_ulong (&value, song->time_played);
 898 		rhythmdb_entry_set (RHYTHMDB (db), entry,
 899 					       RHYTHMDB_PROP_LAST_PLAYED,
 900 					       &value);
 901 		g_value_unset (&value);
 902 	}
 903 
 904 	/* Set title */
 905 	entry_set_string_prop (RHYTHMDB (db), entry,
 906 			       RHYTHMDB_PROP_TITLE, song->title);
 907 
 908 	/* Set album, artist and genre from iTunesDB */
 909 	entry_set_string_prop (RHYTHMDB (db), entry,
 910 			       RHYTHMDB_PROP_ARTIST, song->artist);
 911 
 912 	if (song->albumartist != NULL) {
 913                 entry_set_string_prop (RHYTHMDB (db), entry,
 914                                        RHYTHMDB_PROP_ALBUM_ARTIST,
 915                                        song->albumartist);
 916 	}
 917 
 918         if (song->sort_artist != NULL) {
 919                 entry_set_string_prop (RHYTHMDB (db), entry,
 920                                        RHYTHMDB_PROP_ARTIST_SORTNAME,
 921                                        song->sort_artist);
 922         }
 923 
 924         if (song->sort_album != NULL) {
 925                 entry_set_string_prop (RHYTHMDB (db), entry,
 926                                        RHYTHMDB_PROP_ALBUM_SORTNAME,
 927                                        song->sort_album);
 928         }
 929 
 930 	if (song->sort_albumartist != NULL) {
 931                 entry_set_string_prop (RHYTHMDB (db), entry,
 932                                        RHYTHMDB_PROP_ALBUM_ARTIST_SORTNAME,
 933                                        song->sort_albumartist);
 934 	}
 935 
 936 	entry_set_string_prop (RHYTHMDB (db), entry,
 937 			       RHYTHMDB_PROP_ALBUM, song->album);
 938 
 939 	entry_set_string_prop (RHYTHMDB (db), entry,
 940 			       RHYTHMDB_PROP_GENRE, song->genre);
 941 
 942 	g_hash_table_insert (priv->entry_map, entry, song);
 943 
 944 	if (song->recent_playcount != 0) {
 945 		add_offline_played_entry (source, entry,
 946 					  song->recent_playcount);
 947 	}
 948 
 949 	rhythmdb_commit (RHYTHMDB (db));
 950 }
 951 
 952 static RhythmDB *
 953 get_db_for_source (RBiPodSource *source)
 954 {
 955 	RBShell *shell;
 956 	RhythmDB *db;
 957 
 958 	g_object_get (source, "shell", &shell, NULL);
 959 	g_object_get (shell, "db", &db, NULL);
 960 	g_object_unref (shell);
 961 
 962 	return db;
 963 }
 964 
 965 static gint
 966 compare_timestamps (gconstpointer a, gconstpointer b, gpointer data)
 967 {
 968 	PlayedEntry *lhs = (PlayedEntry *)a;
 969 	PlayedEntry *rhs = (PlayedEntry *)b;
 970 
 971 	gulong lhs_timestamp;
 972 	gulong rhs_timestamp;
 973 
 974 	lhs_timestamp =  rhythmdb_entry_get_ulong (lhs->entry,
 975 						   RHYTHMDB_PROP_LAST_PLAYED);
 976 
 977 	rhs_timestamp =  rhythmdb_entry_get_ulong (rhs->entry,
 978 						   RHYTHMDB_PROP_LAST_PLAYED);
 979 
 980 
 981 	return (int) (lhs_timestamp - rhs_timestamp);
 982 }
 983 
 984 static void
 985 remove_playcount_file (RBiPodSource *source)
 986 {
 987         RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
 988         char *itunesdb_dir;
 989         char *playcounts_file;
 990         int result = -1;
 991 	const char *mountpoint;
 992 
 993 	mountpoint = rb_ipod_db_get_mount_path (priv->ipod_db);
 994         itunesdb_dir = itdb_get_itunes_dir (mountpoint);
 995         playcounts_file = itdb_get_path (itunesdb_dir, "Play Counts");
 996         if (playcounts_file != NULL)
 997 		result = g_unlink (playcounts_file);
 998         if (result == 0) {
 999                 rb_debug ("iPod Play Counts file successfully deleted");
1000         } else {
1001 		if (playcounts_file != NULL)
1002 			rb_debug ("Failed to remove iPod Play Counts file: %s",
1003 				  strerror (errno));
1004 		else
1005 			rb_debug ("Failed to remove non-existant iPod Play Counts file");
1006         }
1007         g_free (itunesdb_dir);
1008         g_free (playcounts_file);
1009 
1010 }
1011 
1012 static void
1013 send_offline_plays_notification (RBiPodSource *source)
1014 {
1015 	RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
1016 	RhythmDB *db;
1017 	GValue val = {0, };
1018 
1019 	if (priv->offline_plays == NULL) {
1020 		return;
1021 	}
1022 
1023 	/* audioscrobbler expects data to arrive with increasing timestamps,
1024 	 * dunno if the sorting should be done in the audioscrobbler plugin,
1025 	 * or if this kind of "insider knowledge" is OK here
1026 	 */
1027 	g_queue_sort (priv->offline_plays,
1028 		      (GCompareDataFunc)compare_timestamps,
1029 		      NULL);
1030 
1031 	db = get_db_for_source (source);
1032 	g_value_init (&val, G_TYPE_ULONG);
1033 
1034 	while (!g_queue_is_empty (priv->offline_plays)) {
1035 		gulong last_play;
1036 		PlayedEntry *entry;
1037 		entry = (PlayedEntry*)g_queue_pop_head (priv->offline_plays);
1038 		last_play = rhythmdb_entry_get_ulong (entry->entry,
1039 						      RHYTHMDB_PROP_LAST_PLAYED);
1040 		g_value_set_ulong (&val, last_play);
1041 		rhythmdb_emit_entry_extra_metadata_notify (db, entry->entry,
1042 							   "rb:offlinePlay",
1043 							   &val);
1044 		g_free (entry);
1045 	}
1046 	g_value_unset (&val);
1047 	g_object_unref (G_OBJECT (db));
1048 
1049 	remove_playcount_file (source);
1050 }
1051 
1052 static void
1053 rb_ipod_source_entry_changed_cb (RhythmDB *db,
1054 				 RhythmDBEntry *entry,
1055 				 GArray *changes,
1056 				 RBiPodSource *source)
1057 {
1058 	int i;
1059 
1060 	/* Ignore entries which are not iPod entries */
1061 	RhythmDBEntryType *entry_type;
1062 	RhythmDBEntryType *ipod_entry_type;
1063 	RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
1064 
1065 	entry_type = rhythmdb_entry_get_entry_type (entry);
1066 	g_object_get (source, "entry-type", &ipod_entry_type, NULL);
1067 	if (entry_type != ipod_entry_type) {
1068 		g_object_unref (ipod_entry_type);
1069 		return;
1070 	}
1071 	g_object_unref (ipod_entry_type);
1072 
1073 	/* If an interesting property was changed, update it on the iPod */
1074 	/* If the iPod database is being saved in a separate thread, this
1075 	 * might not be 100% thread-safe, but at worst we'll modify a field
1076 	 * at the time it's being saved which will get a wrong value, but
1077 	 * that's the worst that can happen and that's pretty theoretical,
1078 	 * I don't think avoiding it is worth the effort.
1079 	 */
1080 	for (i = 0; i < changes->len; i++) {
1081 		GValue *v = &g_array_index (changes, GValue, i);
1082 		RhythmDBEntryChange *change = g_value_get_boxed (v);
1083 		switch (change->prop) {
1084 		case RHYTHMDB_PROP_RATING: {
1085 			Itdb_Track *track;
1086 			double old_rating;
1087 			double new_rating;
1088 
1089 			old_rating = g_value_get_double (&change->old);
1090 			new_rating = g_value_get_double (&change->new);
1091 			if (old_rating != new_rating) {
1092 				track = g_hash_table_lookup (priv->entry_map,
1093 							     entry);
1094 				track->rating = new_rating * ITDB_RATING_STEP;
1095 				track->app_rating = track->rating;
1096 				rb_debug ("rating changed, saving db");
1097 				rb_ipod_db_save_async (priv->ipod_db);
1098 			} else {
1099 				rb_debug ("rating didn't change");
1100 			}
1101 			break;
1102 		}
1103 		case RHYTHMDB_PROP_PLAY_COUNT: {
1104 			Itdb_Track *track;
1105 			gulong old_playcount;
1106 			gulong new_playcount;
1107 
1108 			old_playcount = g_value_get_ulong (&change->old);
1109 			new_playcount = g_value_get_ulong (&change->new);
1110 			if (old_playcount != new_playcount) {
1111 				track = g_hash_table_lookup (priv->entry_map,
1112 							     entry);
1113 				track->playcount = new_playcount;
1114 				rb_debug ("playcount changed, saving db");
1115 				rb_ipod_db_save_async (priv->ipod_db);
1116 			} else {
1117 				rb_debug ("playcount didn't change");
1118 			}
1119 			break;
1120 		}
1121 		case RHYTHMDB_PROP_LAST_PLAYED: {
1122 			Itdb_Track *track;
1123 			gulong old_lastplay;
1124 			gulong new_lastplay;
1125 
1126 			old_lastplay = g_value_get_ulong (&change->old);
1127 			new_lastplay = g_value_get_ulong (&change->new);
1128 			if (old_lastplay != new_lastplay) {
1129 				track = g_hash_table_lookup (priv->entry_map,
1130 							     entry);
1131 				track->time_played = new_lastplay;
1132 				rb_debug ("last play time changed, saving db");
1133 				rb_ipod_db_save_async (priv->ipod_db);
1134 			} else {
1135 				rb_debug ("last play time didn't change");
1136 			}
1137 			break;
1138 		}
1139 		default:
1140 			rb_debug ("Ignoring property %d", change->prop);
1141 			break;
1142 		}
1143 	}
1144 }
1145 
1146 static gboolean
1147 load_ipod_db_idle_cb (RBiPodSource *source)
1148 {
1149 	RhythmDB *db;
1150  	GList *it;
1151 	RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
1152 
1153 	GDK_THREADS_ENTER ();
1154 
1155 	db = get_db_for_source (source);
1156 
1157 	g_assert (db != NULL);
1158  	for (it = rb_ipod_db_get_tracks (priv->ipod_db);
1159 	     it != NULL;
1160 	     it = it->next) {
1161 		add_ipod_song_to_db (source, db, (Itdb_Track *)it->data);
1162 	}
1163 
1164 	load_ipod_playlists (source);
1165 	send_offline_plays_notification (source);
1166 
1167 	g_signal_connect_object(G_OBJECT(db), "entry-changed",
1168 				G_CALLBACK (rb_ipod_source_entry_changed_cb),
1169 				source, 0);
1170 
1171 	g_object_unref (db);
1172 
1173 	g_object_set (source, "load-status", RB_SOURCE_LOAD_STATUS_LOADED, NULL);
1174 
1175 	rb_transfer_target_transfer (RB_TRANSFER_TARGET (source), NULL, FALSE);
1176 
1177 	GDK_THREADS_LEAVE ();
1178 	priv->load_idle_id = 0;
1179 	return FALSE;
1180 }
1181 
1182 static void
1183 rb_ipod_load_songs (RBiPodSource *source)
1184 {
1185 	RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
1186 
1187 	priv->ipod_db = rb_ipod_db_new (priv->mount);
1188 	priv->entry_map = g_hash_table_new (g_direct_hash, g_direct_equal);
1189 
1190 	if ((priv->ipod_db != NULL) && (priv->entry_map != NULL)) {
1191 		const char *name;
1192 		name = rb_ipod_db_get_ipod_name (priv->ipod_db);
1193 		if (name) {
1194 			g_object_set (RB_SOURCE (source),
1195 				      "name", name,
1196 				      NULL);
1197 		}
1198                 g_signal_connect (G_OBJECT (source), "notify::name",
1199 		  	          (GCallback)rb_ipod_source_name_changed_cb,
1200                                   NULL);
1201 		priv->load_idle_id = g_idle_add ((GSourceFunc)load_ipod_db_idle_cb, source);
1202 	}
1203 }
1204 
1205 static gboolean
1206 impl_show_popup (RBDisplayPage *page)
1207 {
1208 	_rb_display_page_show_popup (page, "/iPodSourcePopup");
1209 	return TRUE;
1210 }
1211 
1212 typedef struct {
1213 	RBMediaPlayerSource *source;
1214 	RBMediaPlayerSourceDeleteCallback callback;
1215 	gpointer callback_data;
1216 	GDestroyNotify destroy_data;
1217 	GList *files;
1218 } DeleteFileData;
1219 
1220 static gboolean
1221 delete_done_cb (DeleteFileData *data)
1222 {
1223 	if (data->callback) {
1224 		data->callback (data->source, data->callback_data);
1225 	}
1226 	if (data->destroy_data) {
1227 		data->destroy_data (data->callback_data);
1228 	}
1229 	g_object_unref (data->source);
1230 	rb_list_deep_free (data->files);
1231 	return FALSE;
1232 }
1233 
1234 static gpointer
1235 delete_thread (DeleteFileData *data)
1236 {
1237 	GList *i;
1238 	rb_debug ("deleting %d files", g_list_length (data->files));
1239 	for (i = data->files; i != NULL; i = i->next) {
1240 		g_unlink ((const char *)i->data);
1241 	}
1242 	rb_debug ("done deleting %d files", g_list_length (data->files));
1243 	g_idle_add ((GSourceFunc) delete_done_cb, data);
1244 	return NULL;
1245 }
1246 
1247 static void
1248 impl_delete_entries (RBMediaPlayerSource *source, GList *entries, RBMediaPlayerSourceDeleteCallback callback, gpointer cb_data, GDestroyNotify destroy_data)
1249 {
1250 	RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
1251 	RhythmDB *db = get_db_for_source ((RBiPodSource *)source);
1252 	GList *i;
1253 	GList *filenames = NULL;
1254 	DeleteFileData *data = g_new0 (DeleteFileData, 1);
1255 
1256 	for (i = entries; i != NULL; i = i->next) {
1257 		const char *uri;
1258 		char *filename;
1259 		Itdb_Track *track;
1260 		RhythmDBEntry *entry;
1261 
1262 		entry = i->data;
1263 		uri = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
1264 		track = g_hash_table_lookup (priv->entry_map, entry);
1265 		if (track == NULL) {
1266 			g_warning ("Couldn't find track on ipod! (%s)", uri);
1267 			continue;
1268 		}
1269 
1270 		rb_ipod_db_remove_track (priv->ipod_db, track);
1271 		g_hash_table_remove (priv->entry_map, entry);
1272 		filename = g_filename_from_uri (uri, NULL, NULL);
1273 
1274 		if (filename != NULL) {
1275 			filenames = g_list_prepend (filenames, filename);
1276 		}
1277 		rhythmdb_entry_delete (db, entry);
1278 	}
1279 
1280 	rhythmdb_commit (db);
1281 	g_object_unref (db);
1282 
1283 	data->source = g_object_ref (source);
1284 	data->callback = callback;
1285 	data->callback_data = cb_data;
1286 	data->destroy_data = destroy_data;
1287 	data->files = filenames;
1288 
1289 	g_thread_new ("ipod-delete", (GThreadFunc) delete_thread, data);
1290 }
1291 
1292 static RBTrackTransferBatch *
1293 impl_paste (RBSource *source, GList *entries)
1294 {
1295 	gboolean defer;
1296 
1297 	defer = (ensure_loaded (RB_IPOD_SOURCE (source)) == FALSE);
1298 	return rb_transfer_target_transfer (RB_TRANSFER_TARGET (source), entries, defer);
1299 }
1300 
1301 static void
1302 impl_delete (RBSource *source)
1303 {
1304 	GList *sel;
1305 	RBEntryView *songs;
1306 
1307 	songs = rb_source_get_entry_view (source);
1308 	sel = rb_entry_view_get_selected_entries (songs);
1309 	impl_delete_entries (RB_MEDIA_PLAYER_SOURCE (source), sel, NULL, NULL, NULL);
1310 	rb_list_destroy_free (sel, (GDestroyNotify) rhythmdb_entry_unref);
1311 }
1312 
1313 static void
1314 impl_add_playlist (RBMediaPlayerSource *source,
1315 		   char *name,
1316 		   GList *entries)	/* GList of RhythmDBEntry * on the device to go into the playlist */
1317 {
1318 	RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
1319 	RBIpodStaticPlaylistSource *playlist_source;
1320 	Itdb_Playlist *ipod_playlist;
1321 	GList *iter;
1322 
1323 	ipod_playlist = itdb_playlist_new (name, FALSE);
1324 	rb_ipod_db_add_playlist (priv->ipod_db, ipod_playlist);
1325 	playlist_source = add_rb_playlist (RB_IPOD_SOURCE (source), ipod_playlist);
1326 
1327 	for (iter = entries; iter != NULL; iter = iter->next) {
1328 		rb_static_playlist_source_add_entry (RB_STATIC_PLAYLIST_SOURCE (playlist_source), iter->data, -1);
1329 	}
1330 }
1331 
1332 static void
1333 impl_remove_playlists (RBMediaPlayerSource *source)
1334 {
1335 	RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
1336 	GList *playlists;
1337 	GList *p;
1338 
1339 	playlists = rb_ipod_db_get_playlists (priv->ipod_db);
1340 
1341 	for (p = playlists; p != NULL; p = p->next) {
1342 		Itdb_Playlist *playlist = (Itdb_Playlist *)p->data;
1343 		/* XXX might need to exclude more playlists here.. */
1344 		if (!itdb_playlist_is_mpl (playlist) &&
1345 		    !itdb_playlist_is_podcasts (playlist) &&
1346 		    !playlist->is_spl) {
1347 
1348 			/* destroy the playlist source */
1349 			rb_display_page_delete_thyself (RB_DISPLAY_PAGE (playlist->userdata));
1350 
1351 			/* remove playlist from ipod */
1352 			rb_ipod_db_remove_playlist (priv->ipod_db, playlist);
1353 		}
1354 	}
1355 
1356 	g_list_free (playlists);
1357 }
1358 
1359 static char *
1360 impl_build_dest_uri (RBTransferTarget *target,
1361 		     RhythmDBEntry *entry,
1362 		     const char *media_type,
1363 		     const char *extension)
1364 {
1365 	RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (target);
1366 	const char *uri;
1367 	char *dest;
1368 	const char *mount_path;
1369 
1370 	if (priv->ipod_db == NULL) {
1371 		return NULL;
1372 	}
1373 
1374 	uri = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
1375 	mount_path = rb_ipod_db_get_mount_path (priv->ipod_db);
1376 	dest = ipod_get_filename_for_uri (mount_path,  uri,
1377 					  media_type, extension);
1378 	if (dest != NULL) {
1379 		char *dest_uri;
1380 
1381 		dest_uri = g_filename_to_uri (dest, NULL, NULL);
1382 		g_free (dest);
1383 		return dest_uri;
1384 	}
1385 
1386 	return NULL;
1387 }
1388 
1389 Itdb_Playlist *
1390 rb_ipod_source_get_playlist (RBiPodSource *source,
1391 			     gchar *name)
1392 {
1393 	RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
1394 	Itdb_Playlist *ipod_playlist;
1395 
1396 	ipod_playlist = rb_ipod_db_get_playlist_by_name (priv->ipod_db, name);
1397 
1398 	/* Playlist doesn't exist on the iPod, create it */
1399 	if (ipod_playlist == NULL) {
1400 		ipod_playlist = itdb_playlist_new (name, FALSE);
1401 		rb_ipod_db_add_playlist (priv->ipod_db, ipod_playlist);
1402 		add_rb_playlist (source, ipod_playlist);
1403 	}
1404 
1405 	return ipod_playlist;
1406 }
1407 
1408 static void
1409 add_to_podcasts (RBiPodSource *source, Itdb_Track *song)
1410 {
1411 	RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
1412 	gchar *filename;
1413 	const gchar *mount_path;
1414 
1415         /* Set various flags indicating the Itdb_Track is a podcast */
1416         song->skip_when_shuffling = 0x01;
1417         song->remember_playback_position = 0x01;
1418         song->mark_unplayed = 0x02;
1419         song->flag4 = 0x03;
1420 
1421 	if (priv->podcast_pl == NULL) {
1422 		/* No Podcast playlist on the iPod, create a new one */
1423 		Itdb_Playlist *ipod_playlist;
1424 		ipod_playlist = itdb_playlist_new (_("Podcasts"), FALSE);
1425 		itdb_playlist_set_podcasts (ipod_playlist);
1426 		rb_ipod_db_add_playlist (priv->ipod_db, ipod_playlist);
1427 		add_rb_playlist (source, ipod_playlist);
1428 	}
1429 
1430 	mount_path = rb_ipod_db_get_mount_path (priv->ipod_db);
1431 	filename = ipod_path_to_uri (mount_path, song->ipod_path);
1432 	rb_static_playlist_source_add_location (RB_STATIC_PLAYLIST_SOURCE (priv->podcast_pl), filename, -1);
1433 	g_free (filename);
1434 }
1435 
1436 static gboolean
1437 rb_add_artwork_whole_album_cb (GtkTreeModel *query_model,
1438 			       GtkTreePath *path,
1439 			       GtkTreeIter *iter,
1440 			       RBiPodSongArtworkAddData *artwork_data)
1441 {
1442 	RhythmDBEntry *entry;
1443 	Itdb_Track *song;
1444 
1445 	entry = rhythmdb_query_model_iter_to_entry (RHYTHMDB_QUERY_MODEL (query_model), iter);
1446 
1447 	song = g_hash_table_lookup (artwork_data->priv->entry_map, entry);
1448 	rhythmdb_entry_unref (entry);
1449 	g_return_val_if_fail (song != NULL, FALSE);
1450 
1451 	if (song->has_artwork == 0x01) {
1452 		return FALSE;
1453 	}
1454 
1455 	rb_ipod_db_set_thumbnail (artwork_data->priv->ipod_db, song, artwork_data->pixbuf);
1456 
1457 	return FALSE;
1458 }
1459 
1460 static void
1461 art_request_cb (RBExtDBKey *key, const char *filename, GValue *data, RBiPodSource *source)
1462 {
1463 	RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
1464 	Itdb_Device *device;
1465 	GdkPixbuf *pixbuf;
1466 	GtkTreeModel *query_model;
1467 	RBiPodSongArtworkAddData artwork_data;
1468         RhythmDBEntryType *entry_type;
1469 	RhythmDB *db;
1470 	const char *artist;
1471 	const char *album;
1472 
1473 	if (data == NULL || G_VALUE_HOLDS (data, GDK_TYPE_PIXBUF) == FALSE) {
1474 		return;
1475         }
1476 	pixbuf = GDK_PIXBUF (g_value_get_object (data));
1477 
1478 	device = rb_ipod_db_get_device (priv->ipod_db);
1479 	if (device == NULL || itdb_device_supports_artwork (device) == FALSE) {
1480 		return;
1481 	}
1482 
1483         g_object_get (source, "entry-type", &entry_type, NULL);
1484 
1485 	db = get_db_for_source (source);
1486 	query_model = GTK_TREE_MODEL (rhythmdb_query_model_new_empty (db));
1487 	artist = rb_ext_db_key_get_field (key, "artist");
1488 	album = rb_ext_db_key_get_field (key, "album");
1489 	/* XXX album-artist? */
1490 
1491 	rhythmdb_do_full_query (db, RHYTHMDB_QUERY_RESULTS (query_model),
1492                                 RHYTHMDB_QUERY_PROP_EQUALS,
1493                                 RHYTHMDB_PROP_TYPE, entry_type,
1494 				RHYTHMDB_QUERY_PROP_EQUALS,
1495 				RHYTHMDB_PROP_ARTIST, artist,
1496 				RHYTHMDB_QUERY_PROP_EQUALS,
1497 				RHYTHMDB_PROP_ALBUM, album,
1498 				RHYTHMDB_QUERY_END);
1499 
1500 	artwork_data.priv = priv;
1501 	artwork_data.pixbuf = pixbuf;
1502 
1503 	gtk_tree_model_foreach (query_model,
1504 				(GtkTreeModelForeachFunc) rb_add_artwork_whole_album_cb,
1505 				&artwork_data);
1506         g_object_unref (entry_type);
1507 	g_object_unref (query_model);
1508 	g_object_unref (db);
1509 }
1510 
1511 static gboolean
1512 impl_track_added (RBTransferTarget *target,
1513 		  RhythmDBEntry *entry,
1514 		  const char *dest,
1515 		  guint64 filesize,
1516 		  const char *media_type)
1517 {
1518 	RBiPodSource *source = RB_IPOD_SOURCE (target);
1519 	RhythmDB *db;
1520 	Itdb_Track *song;
1521 
1522 	db = get_db_for_source (source);
1523 
1524 	song = create_ipod_song_from_entry (entry, filesize, media_type);
1525 	if (song != NULL) {
1526 		RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
1527 		char *filename;
1528 		const char *mount_path;
1529 		Itdb_Device *device;
1530 
1531 		filename = g_filename_from_uri (dest, NULL, NULL);
1532 		mount_path = rb_ipod_db_get_mount_path (priv->ipod_db);
1533 		song->ipod_path = ipod_path_from_unix_path (mount_path,
1534 							    filename);
1535 		g_free (filename);
1536 
1537 		if (song->mediatype == ITDB_MEDIATYPE_PODCAST) {
1538 			add_to_podcasts (source, song);
1539 		}
1540 		device = rb_ipod_db_get_device (priv->ipod_db);
1541 		if (device && itdb_device_supports_artwork (device)) {
1542 			RBExtDBKey *key;
1543 			key = rb_ext_db_key_create_lookup ("album", song->album);
1544 			rb_ext_db_key_add_field (key, "artist", song->artist);
1545 			if (song->albumartist) {
1546 				rb_ext_db_key_add_field (key, "artist", song->albumartist);
1547 			}
1548 
1549 			rb_ext_db_request (priv->art_store,
1550 					   key,
1551 					   (RBExtDBRequestCallback) art_request_cb,
1552 					   g_object_ref (source),
1553 					   g_object_unref);
1554 			rb_ext_db_key_free (key);
1555 		}
1556 		add_ipod_song_to_db (source, db, song);
1557 		rb_ipod_db_add_track (priv->ipod_db, song);
1558 	}
1559 
1560 	g_object_unref (db);
1561 
1562 	return FALSE;
1563 }
1564 
1565 /* Generation of the filename for the ipod */
1566 
1567 #define IPOD_MAX_PATH_LEN 56
1568 
1569 static gboolean
1570 test_dir_on_ipod (const char *mountpoint, const char *dirname)
1571 {
1572 	char *fullpath;
1573 	gboolean result;
1574 
1575 	fullpath  = g_build_filename (mountpoint, dirname, NULL);
1576 	result = g_file_test (fullpath, G_FILE_TEST_IS_DIR);
1577 	g_free (fullpath);
1578 
1579 	return result;
1580 }
1581 
1582 static int
1583 ipod_mkdir_with_parents (const char *mountpoint, const char *dirname)
1584 {
1585 	char *fullpath;
1586 	int result;
1587 
1588 	fullpath  = g_build_filename (mountpoint, dirname, NULL);
1589 	result = g_mkdir_with_parents (fullpath, 0770);
1590 	g_free (fullpath);
1591 
1592 	return result;
1593 }
1594 
1595 static gchar *
1596 build_ipod_dir_name (const char *mountpoint)
1597 {
1598 	/* FAT sucks, filename can be lowercase or uppercase, and if we try to
1599 	 * open the wrong one, we lose :-/
1600 	 */
1601 	char *dirname;
1602 	char *relpath;
1603 	char *ctrl_path, *ctrl_dir;
1604 	gint32 suffix;
1605 
1606 	/* Get the control directory first */
1607 	ctrl_path = itdb_get_control_dir (mountpoint);
1608 	if (ctrl_path == NULL) {
1609 		g_debug ("Couldn't find control directory for iPod at '%s'", mountpoint);
1610 		return NULL;
1611 	}
1612 	ctrl_dir = g_path_get_basename (ctrl_path);
1613 	if (ctrl_dir == NULL || *ctrl_dir == '.') {
1614 		g_free (ctrl_dir);
1615 		g_debug ("Couldn't find control directory for iPod at '%s' (got full path '%s'", mountpoint, ctrl_path);
1616 		g_free (ctrl_path);
1617 		return NULL;
1618 	}
1619 	g_free (ctrl_path);
1620 
1621 	suffix = g_random_int_range (0, 49);
1622 	dirname = g_strdup_printf ("F%02d", suffix);
1623 	relpath = g_build_filename (G_DIR_SEPARATOR_S, ctrl_dir,
1624 				    "Music", dirname, NULL);
1625 	g_free (dirname);
1626 
1627 	if (test_dir_on_ipod (mountpoint, relpath) != FALSE) {
1628 		g_free (ctrl_dir);
1629 		return relpath;
1630 	}
1631 
1632 	g_free (relpath);
1633 	dirname = g_strdup_printf ("f%02d", suffix);
1634 	relpath = g_build_filename (G_DIR_SEPARATOR_S, ctrl_dir,
1635 				    "Music", dirname, NULL);
1636 	g_free (dirname);
1637 	g_free (ctrl_dir);
1638 
1639 	if (test_dir_on_ipod (mountpoint, relpath) != FALSE) {
1640 		return relpath;
1641 	}
1642 
1643 	if (ipod_mkdir_with_parents (mountpoint, relpath) == 0) {
1644 		return relpath;
1645 	}
1646 
1647 	g_free (relpath);
1648 	return NULL;
1649 }
1650 
1651 static gchar *
1652 get_ipod_filename (const char *mount_point, const char *filename)
1653 {
1654 	char *dirname;
1655 	char *result;
1656 	char *tmp;
1657 
1658 	dirname = build_ipod_dir_name (mount_point);
1659 	if (dirname == NULL) {
1660 		return NULL;
1661 	}
1662 	result = g_build_filename (dirname, filename, NULL);
1663 	g_free (dirname);
1664 
1665 	if (strlen (result) >= IPOD_MAX_PATH_LEN) {
1666 		char *ext, *suffix;
1667 
1668 		ext = strrchr (result, '.');
1669 		if (ext == NULL) {
1670 			suffix = result + IPOD_MAX_PATH_LEN - 4;
1671 			result [IPOD_MAX_PATH_LEN - 1] = '\0';
1672 		} else {
1673 			suffix = result + IPOD_MAX_PATH_LEN - 4 - strlen(ext);
1674 			memmove (&result[IPOD_MAX_PATH_LEN - strlen (ext) - 1] ,
1675 				 ext, strlen (ext) + 1);
1676 		}
1677 
1678 		/* Add suffix to reduce the chance of a name collision with truncated name */
1679 		suffix[0] = '~';
1680 		suffix[1] = 'A' + g_random_int_range (0, 26);
1681 		suffix[2] = 'A' + g_random_int_range (0, 26);
1682 	}
1683 
1684 	tmp = g_build_filename (mount_point, result, NULL);
1685 	g_free (result);
1686 	return tmp;
1687 }
1688 
1689 #define MAX_TRIES 5
1690 
1691 /* Strips non UTF8 characters from a string replacing them with _ */
1692 static gchar *
1693 utf8_to_ascii (const gchar *utf8)
1694 {
1695 	GString *string;
1696 	const guchar *it = (const guchar *)utf8;
1697 
1698 	string = g_string_new ("");
1699 	while ((it != NULL) && (*it != '\0')) {
1700 		/* Do we have a 7 bit char ? */
1701 		if (*it < 0x80) {
1702 			g_string_append_c (string, *it);
1703 		} else {
1704 			g_string_append_c (string, '_');
1705 		}
1706 		it = (const guchar *)g_utf8_next_char (it);
1707 	}
1708 
1709 	return g_string_free (string, FALSE);
1710 }
1711 
1712 static gchar *
1713 generate_ipod_filename (const gchar *mount_point, const gchar *filename)
1714 {
1715 	gchar *ipod_filename = NULL;
1716 	gchar *pc_filename;
1717 	gchar *tmp;
1718 	gint tries = 0;
1719 
1720 	/* First, we need a valid UTF-8 filename, strip all non-UTF-8 chars */
1721 	tmp = rb_make_valid_utf8 (filename, '_');
1722 	/* The iPod doesn't seem to recognize non-ascii chars in filenames,
1723 	 * so we strip them
1724 	 */
1725 	pc_filename = utf8_to_ascii (tmp);
1726 	g_free (tmp);
1727 
1728 	g_assert (g_utf8_validate (pc_filename, -1, NULL));
1729 	/* Now we have a valid UTF-8 filename, try to find out where to put
1730 	 * it on the iPod
1731 	 */
1732 	do {
1733 		g_free (ipod_filename);
1734 		ipod_filename = get_ipod_filename (mount_point, pc_filename);
1735 		tries++;
1736 		if (tries > MAX_TRIES) {
1737 			break;
1738 		}
1739 	} while ((ipod_filename == NULL)
1740 		 || (g_file_test (ipod_filename, G_FILE_TEST_EXISTS)));
1741 
1742 	g_free (pc_filename);
1743 
1744 	if (tries > MAX_TRIES) {
1745 		/* FIXME: should create a unique filename */
1746 		return NULL;
1747 	} else {
1748 		return ipod_filename;
1749 	}
1750 }
1751 
1752 static gchar *
1753 ipod_get_filename_for_uri (const gchar *mount_point,
1754 			   const gchar *uri_str,
1755 			   const gchar *media_type,
1756 			   const gchar *extension)
1757 {
1758 	gchar *escaped;
1759 	gchar *filename;
1760 	gchar *result;
1761 
1762 	escaped = rb_uri_get_short_path_name (uri_str);
1763 	if (escaped == NULL) {
1764 		return NULL;
1765 	}
1766 	filename = g_uri_unescape_string (escaped, NULL);
1767 	g_free (escaped);
1768 	if (filename == NULL) {
1769 		return NULL;
1770 	}
1771 
1772 	/* replace the old extension or append it */
1773 	/* FIXME: we really need a mapping (audio/mpeg->mp3) and not
1774 	 * just rely on the user's audio profile havign the "right" one */
1775 	escaped = g_utf8_strrchr (filename, -1, '.');
1776 	if (escaped != NULL) {
1777 		*escaped = 0;
1778 	}
1779 
1780 	if (extension != NULL) {
1781 		escaped = g_strdup_printf ("%s.%s", filename, extension);
1782 		g_free (filename);
1783 	} else {
1784 		escaped = filename;
1785 	}
1786 
1787 	result = generate_ipod_filename (mount_point, escaped);
1788 	g_free (escaped);
1789 
1790 	return result;
1791 }
1792 
1793 /* End of generation of the filename on the iPod */
1794 
1795 static gchar *
1796 ipod_path_from_unix_path (const gchar *mount_point, const gchar *unix_path)
1797 {
1798 	gchar *ipod_path;
1799 
1800 	g_assert (g_utf8_validate (unix_path, -1, NULL));
1801 
1802 	if (!g_str_has_prefix (unix_path, mount_point)) {
1803 		return NULL;
1804 	}
1805 
1806 	ipod_path = g_strdup (unix_path + strlen (mount_point));
1807 	if (*ipod_path != G_DIR_SEPARATOR) {
1808 		gchar *tmp;
1809 		tmp = g_strdup_printf ("/%s", ipod_path);
1810 		g_free (ipod_path);
1811 		ipod_path = tmp;
1812 	}
1813 
1814 	/* Make sure the filename doesn't contain any ':' */
1815 	g_strdelimit (ipod_path, ":", ';');
1816 
1817 	/* Convert path to a Mac path where the dir separator is ':' */
1818 	itdb_filename_fs2ipod (ipod_path);
1819 
1820 	return ipod_path;
1821 }
1822 
1823 static gboolean
1824 ensure_loaded (RBiPodSource *source)
1825 {
1826 	RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
1827 	RBSourceLoadStatus status;
1828 	
1829 	if (priv->ipod_db == NULL) {
1830 		rb_ipod_load_songs (source);
1831 		rb_media_player_source_load (RB_MEDIA_PLAYER_SOURCE (source));
1832 		return FALSE;
1833 	} else {
1834 		g_object_get (source, "load-status", &status, NULL);
1835 		return (status == RB_SOURCE_LOAD_STATUS_LOADED);
1836 	}
1837 }
1838 
1839 static void
1840 impl_selected (RBDisplayPage *page)
1841 {
1842 	ensure_loaded (RB_IPOD_SOURCE (page));
1843 }
1844 
1845 static void
1846 impl_delete_thyself (RBDisplayPage *page)
1847 {
1848 	RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (page);
1849 	GList *p;
1850 
1851 	if (priv->ipod_db == NULL) {
1852 		RB_DISPLAY_PAGE_CLASS (rb_ipod_source_parent_class)->delete_thyself (page);
1853 		return;
1854 	}
1855 
1856 	for (p = rb_ipod_db_get_playlists (priv->ipod_db);
1857 	     p != NULL;
1858 	     p = p->next) {
1859 		Itdb_Playlist *playlist = (Itdb_Playlist *)p->data;
1860 		if (!itdb_playlist_is_mpl (playlist) && !playlist->is_spl) {
1861 			RBSource *rb_playlist;
1862 
1863 			rb_playlist = RB_SOURCE (playlist->userdata);
1864 			rb_display_page_delete_thyself (RB_DISPLAY_PAGE (rb_playlist));
1865 		}
1866 	}
1867 
1868 	g_object_unref (G_OBJECT (priv->ipod_db));
1869 	priv->ipod_db = NULL;
1870 
1871 	RB_DISPLAY_PAGE_CLASS (rb_ipod_source_parent_class)->delete_thyself (page);
1872 }
1873 
1874 Itdb_Playlist *
1875 rb_ipod_source_new_playlist (RBiPodSource *source)
1876 {
1877 	RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
1878 	Itdb_Playlist *ipod_playlist;
1879 
1880 	if (priv->ipod_db == NULL) {
1881 		rb_debug ("can't create new ipod playlist with no ipod db");
1882 		return NULL;
1883 	}
1884 
1885 	ipod_playlist = itdb_playlist_new (_("New playlist"), FALSE);
1886 	rb_ipod_db_add_playlist (priv->ipod_db, ipod_playlist);
1887 	add_rb_playlist (source, ipod_playlist);
1888 	return ipod_playlist;
1889 }
1890 
1891 void
1892 rb_ipod_source_remove_playlist (RBiPodSource *ipod_source,
1893 				RBSource *source)
1894 {
1895 	RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (ipod_source);
1896 	RBIpodStaticPlaylistSource *playlist_source = RB_IPOD_STATIC_PLAYLIST_SOURCE (source);
1897 	Itdb_Playlist *playlist;
1898 
1899 	rb_display_page_delete_thyself (RB_DISPLAY_PAGE (source));
1900 
1901 	g_object_get (playlist_source, "itdb-playlist", &playlist, NULL);
1902 	rb_ipod_db_remove_playlist (priv->ipod_db, playlist);
1903 }
1904 
1905 
1906 Itdb_Track *
1907 rb_ipod_source_lookup_track (RBiPodSource *source,
1908 			     RhythmDBEntry *entry)
1909 {
1910 	RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
1911 
1912 	return g_hash_table_lookup (priv->entry_map, entry);
1913 }
1914 
1915 static gboolean
1916 ipod_name_changed_cb (GtkWidget     *widget,
1917  		      GdkEventFocus *event,
1918 		      gpointer       user_data)
1919 {
1920 	g_object_set (RB_SOURCE (user_data), "name",
1921 		      gtk_entry_get_text (GTK_ENTRY (widget)),
1922 		      NULL);
1923 	return FALSE;
1924 }
1925 
1926 
1927 static void
1928 impl_show_properties (RBMediaPlayerSource *source, GtkWidget *info_box, GtkWidget *notebook)
1929 {
1930 	RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
1931 	GHashTableIter iter;
1932 	int num_podcasts;
1933 	gpointer key, value;
1934 	GtkBuilder *builder;
1935 	GtkWidget *widget;
1936 	char *text;
1937 	const gchar *mp;
1938 	char *builder_file;
1939 	Itdb_Device *ipod_dev;
1940 	GObject *plugin;
1941 	GList *output_formats;
1942 	GList *t;
1943 	GString *str;
1944 
1945 	/* probably should display an error on the basic page in most of these error cases.. */
1946 
1947 	if (priv->ipod_db == NULL) {
1948 		rb_debug ("can't show ipod properties with no ipod db");
1949 		return;
1950 	}
1951 
1952 	g_object_get (source, "plugin", &plugin, NULL);
1953 	builder_file = rb_find_plugin_data_file (plugin, "ipod-info.ui");
1954 	g_object_unref (plugin);
1955 
1956 	if (builder_file == NULL) {
1957 		g_warning ("Couldn't find ipod-info.ui");
1958 		return;
1959 	}
1960 
1961 	builder = rb_builder_load (builder_file, NULL);
1962 	g_free (builder_file);
1963 
1964  	if (builder == NULL) {
1965  		rb_debug ("Couldn't load ipod-info.ui");
1966  		return;
1967  	}
1968 
1969 	ipod_dev = rb_ipod_db_get_device (priv->ipod_db);
1970 
1971 	/* basic tab stuff */
1972 	widget = GTK_WIDGET (gtk_builder_get_object (builder, "ipod-basic-info"));
1973 	gtk_box_pack_start (GTK_BOX (info_box), widget, TRUE, TRUE, 0);
1974 
1975 	widget = GTK_WIDGET (gtk_builder_get_object (builder, "ipod-name-entry"));
1976 	gtk_entry_set_text (GTK_ENTRY (widget), rb_ipod_db_get_ipod_name (priv->ipod_db));
1977 	g_signal_connect (widget, "focus-out-event",
1978  			  (GCallback)ipod_name_changed_cb, source);
1979 
1980 	num_podcasts = 0;
1981 	g_hash_table_iter_init (&iter, priv->entry_map);
1982 	while (g_hash_table_iter_next (&iter, &key, &value)) {
1983 		Itdb_Track *track = value;
1984 		if (track->mediatype == ITDB_MEDIATYPE_PODCAST) {
1985 			num_podcasts++;
1986 		}
1987 	}
1988 
1989 	/* TODO these need to be updated as entries are added and removed. */
1990 	widget = GTK_WIDGET (gtk_builder_get_object (builder, "ipod-num-tracks"));
1991 	text = g_strdup_printf ("%d", g_hash_table_size (priv->entry_map) - num_podcasts);
1992 	gtk_label_set_text (GTK_LABEL (widget), text);
1993 	g_free (text);
1994 
1995 	widget = GTK_WIDGET (gtk_builder_get_object (builder, "ipod-num-podcasts"));
1996 	text = g_strdup_printf ("%d", num_podcasts);
1997 	gtk_label_set_text (GTK_LABEL (widget), text);
1998 	g_free (text);
1999 
2000 	/* TODO probably needs to ignore the master playlist? */
2001 	widget = GTK_WIDGET (gtk_builder_get_object (builder, "ipod-num-playlists"));
2002 	text = g_strdup_printf ("%d", g_list_length (rb_ipod_db_get_playlists (priv->ipod_db)));
2003 	gtk_label_set_text (GTK_LABEL (widget), text);
2004 	g_free (text);
2005 
2006 	/* 'advanced' tab stuff */
2007 	widget = GTK_WIDGET (gtk_builder_get_object (builder, "ipod-advanced-tab"));
2008 	gtk_notebook_append_page (GTK_NOTEBOOK (notebook), widget, gtk_label_new (_("Advanced")));
2009 
2010 	widget = GTK_WIDGET (gtk_builder_get_object (builder, "label-mount-point-value"));
2011 	mp = rb_ipod_db_get_mount_path (priv->ipod_db);
2012 	gtk_label_set_text (GTK_LABEL (widget), mp);
2013 
2014 	widget = GTK_WIDGET (gtk_builder_get_object (builder, "label-device-node-value"));
2015 	text = rb_ipod_helpers_get_device (RB_SOURCE(source));
2016 	gtk_label_set_text (GTK_LABEL (widget), text);
2017 	g_free (text);
2018 
2019 	widget = GTK_WIDGET (gtk_builder_get_object (builder, "label-ipod-model-value"));
2020 	gtk_label_set_text (GTK_LABEL (widget), itdb_device_get_sysinfo (ipod_dev, "ModelNumStr"));
2021 
2022 	widget = GTK_WIDGET (gtk_builder_get_object (builder, "label-database-version-value"));
2023 	text = g_strdup_printf ("%u", rb_ipod_db_get_database_version (priv->ipod_db));
2024 	gtk_label_set_text (GTK_LABEL (widget), text);
2025 	g_free (text);
2026 
2027 	g_object_get (priv->device_info, "serial", &text, NULL);
2028 	widget = GTK_WIDGET (gtk_builder_get_object (builder, "label-serial-number-value"));
2029 	gtk_label_set_text (GTK_LABEL (widget), text);
2030 	g_free (text);
2031 
2032 	widget = GTK_WIDGET (gtk_builder_get_object (builder, "label-firmware-version-value"));
2033 	gtk_label_set_text (GTK_LABEL (widget), itdb_device_get_sysinfo (ipod_dev, "VisibleBuildID"));
2034 
2035 	str = g_string_new ("");
2036 	output_formats = rb_transfer_target_get_format_descriptions (RB_TRANSFER_TARGET (source));
2037 	for (t = output_formats; t != NULL; t = t->next) {
2038 		if (t != output_formats) {
2039 			g_string_append (str, "\n");
2040 		}
2041 		g_string_append (str, t->data);
2042 	}
2043 	rb_list_deep_free (output_formats);
2044 
2045 	widget = GTK_WIDGET (gtk_builder_get_object (builder, "label-audio-formats-value"));
2046 	gtk_label_set_text (GTK_LABEL (widget), str->str);
2047 	g_string_free (str, TRUE);
2048 
2049 	g_object_unref (builder);
2050 }
2051 
2052 static const gchar *
2053 get_mount_point	(RBiPodSource *source)
2054 {
2055 	RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
2056 	return rb_ipod_db_get_mount_path (priv->ipod_db);
2057 }
2058 
2059 static guint64
2060 impl_get_capacity (RBMediaPlayerSource *source)
2061 {
2062 	RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
2063 	if (priv->ipod_db) {
2064 		return rb_ipod_helpers_get_capacity (get_mount_point (RB_IPOD_SOURCE (source)));
2065 	} else {
2066 		return 0;
2067 	}
2068 }
2069 
2070 static guint64
2071 impl_get_free_space (RBMediaPlayerSource *source)
2072 {
2073 	RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
2074 	if (priv->ipod_db) {
2075 		return rb_ipod_helpers_get_free_space (get_mount_point (RB_IPOD_SOURCE (source)));
2076 	} else {
2077 		return 0;
2078 	}
2079 }
2080 
2081 static void
2082 impl_get_entries (RBMediaPlayerSource *source, const char *category, GHashTable *map)
2083 {
2084 	RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
2085 	GHashTableIter iter;
2086 	gpointer key, value;
2087 	Itdb_Mediatype media_type;
2088 
2089 	/* map the sync category to an itdb media type */
2090 	if (g_str_equal (category, SYNC_CATEGORY_MUSIC)) {
2091 		media_type = ITDB_MEDIATYPE_AUDIO;
2092 	} else if (g_str_equal (category, SYNC_CATEGORY_PODCAST)) {
2093 		media_type = ITDB_MEDIATYPE_PODCAST;
2094 	} else {
2095 		g_warning ("unsupported ipod sync category %s", category);
2096 		return;
2097 	}
2098 
2099 	/* extract all entries matching the media type for the sync category */
2100 	g_hash_table_iter_init (&iter, priv->entry_map);
2101 	while (g_hash_table_iter_next (&iter, &key, &value)) {
2102 		Itdb_Track *track = value;
2103 		if (track->mediatype == media_type) {
2104 			RhythmDBEntry *entry = key;
2105 			_rb_media_player_source_add_to_map (map, entry);
2106 		}
2107 	}
2108 }
2109 
2110 void
2111 _rb_ipod_source_register_type (GTypeModule *module)
2112 {
2113 	rb_ipod_source_register_type (module);
2114 }