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