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 }