2006-12-12 James Livingston <doclivingston@gmail.com>
[rhythmbox.git] / sources / rb-ipod-source.c
blob7436a5cb253be899cf88b3ade3919993b03717cb
1 /*
2 * arch-tag: Implementation of ipod source object
4 * Copyright (C) 2004 Christophe Fergeau <teuf@gnome.org>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
22 #include "config.h"
24 #include <string.h>
26 #include <glib/gi18n.h>
27 #include <gtk/gtk.h>
28 #ifdef HAVE_HAL
29 #include <libhal.h>
30 #include <dbus/dbus.h>
31 #endif
32 #include <libgnomevfs/gnome-vfs-utils.h>
33 #include <libgnomevfs/gnome-vfs-volume.h>
34 #include <libgnomevfs/gnome-vfs-volume-monitor.h>
35 #include <gpod/itdb.h>
37 #include "eel-gconf-extensions.h"
38 #include "rb-ipod-source.h"
39 #include "rb-debug.h"
40 #include "rb-file-helpers.h"
41 #include "rb-plugin.h"
42 #include "rb-removable-media-manager.h"
43 #include "rb-static-playlist-source.h"
44 #include "rb-util.h"
45 #include "rhythmdb.h"
46 #include "rb-cut-and-paste-code.h"
48 #ifdef IPOD_SUPPORT
49 #define PHONE_VENDOR_ID 0x22b8
50 #define PHONE_PRODUCT_ID 0x4810
51 #endif
53 static GObject *rb_ipod_source_constructor (GType type,
54 guint n_construct_properties,
55 GObjectConstructParam *construct_properties);
56 static void rb_ipod_source_dispose (GObject *object);
58 static GObject *rb_ipod_source_constructor (GType type, guint n_construct_properties,
59 GObjectConstructParam *construct_properties);
60 static void rb_ipod_source_dispose (GObject *object);
62 static gboolean impl_show_popup (RBSource *source);
63 static void impl_move_to_trash (RBSource *asource);
64 static void rb_ipod_load_songs (RBiPodSource *source);
65 static gchar *rb_ipod_get_mount_path (GnomeVFSVolume *volume);
66 static void impl_delete_thyself (RBSource *source);
67 static GList* impl_get_ui_actions (RBSource *source);
68 #ifdef HAVE_HAL
69 static gboolean hal_udi_is_ipod (const char *udi);
70 #endif
72 #ifdef ENABLE_IPOD_WRITING
73 static void impl_paste (RBSource *source, GList *entries);
74 static gboolean impl_receive_drag (RBSource *asource, GtkSelectionData *data);
75 static gchar *
76 ipod_get_filename_for_uri (const gchar *mount_point, const gchar *uri_str);
77 static gchar *
78 ipod_path_from_unix_path (const gchar *mount_point, const gchar *unix_path);
79 #endif
80 static void itdb_schedule_save (Itdb_iTunesDB *db);
82 typedef struct
84 Itdb_iTunesDB *ipod_db;
85 gchar *ipod_mount_path;
86 GHashTable *entry_map;
88 GList *playlists;
90 guint load_idle_id;
91 } RBiPodSourcePrivate;
93 RB_PLUGIN_DEFINE_TYPE(RBiPodSource,
94 rb_ipod_source,
95 RB_TYPE_REMOVABLE_MEDIA_SOURCE)
97 #define IPOD_SOURCE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_IPOD_SOURCE, RBiPodSourcePrivate))
99 static void
100 rb_ipod_source_class_init (RBiPodSourceClass *klass)
102 GObjectClass *object_class = G_OBJECT_CLASS (klass);
103 RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
105 object_class->constructor = rb_ipod_source_constructor;
106 object_class->dispose = rb_ipod_source_dispose;
108 source_class->impl_show_popup = impl_show_popup;
109 source_class->impl_delete_thyself = impl_delete_thyself;
110 source_class->impl_can_move_to_trash = (RBSourceFeatureFunc) rb_true_function;
111 source_class->impl_move_to_trash = impl_move_to_trash;
112 source_class->impl_can_rename = (RBSourceFeatureFunc) rb_true_function;
113 source_class->impl_get_ui_actions = impl_get_ui_actions;
114 #ifdef ENABLE_IPOD_WRITING
115 source_class->impl_can_paste = (RBSourceFeatureFunc) rb_true_function;
116 source_class->impl_paste = impl_paste;
117 source_class->impl_receive_drag = impl_receive_drag;
118 #endif
120 g_type_class_add_private (klass, sizeof (RBiPodSourcePrivate));
123 static void
124 rb_ipod_source_set_ipod_name (RBiPodSource *source, const char *name)
126 Itdb_Playlist *mpl;
127 RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
129 mpl = itdb_playlist_mpl (priv->ipod_db);
130 if (mpl->name != NULL) {
131 rb_debug ("Renaming iPod from %s to %s", mpl->name, name);
132 if (strcmp (mpl->name, name) == 0) {
133 rb_debug ("iPod is already named %s", name);
134 return;
137 g_free (mpl->name);
138 mpl->name = g_strdup (name);
139 itdb_schedule_save (priv->ipod_db);
142 static void
143 rb_ipod_source_name_changed_cb (RBiPodSource *source, GParamSpec *spec,
144 gpointer data)
146 char *name;
148 g_object_get (source, "name", &name, NULL);
149 rb_ipod_source_set_ipod_name (source, name);
150 g_free (name);
153 static void
154 rb_ipod_source_init (RBiPodSource *source)
156 g_signal_connect (G_OBJECT (source), "notify::name",
157 (GCallback)rb_ipod_source_name_changed_cb, NULL);
160 static GObject *
161 rb_ipod_source_constructor (GType type, guint n_construct_properties,
162 GObjectConstructParam *construct_properties)
164 RBiPodSource *source;
165 RBEntryView *songs;
166 RBiPodSourcePrivate *priv;
168 source = RB_IPOD_SOURCE (G_OBJECT_CLASS (rb_ipod_source_parent_class)->
169 constructor (type, n_construct_properties, construct_properties));
170 priv = IPOD_SOURCE_GET_PRIVATE (source);
172 songs = rb_source_get_entry_view (RB_SOURCE (source));
173 rb_entry_view_append_column (songs, RB_ENTRY_VIEW_COL_RATING, FALSE);
174 rb_entry_view_append_column (songs, RB_ENTRY_VIEW_COL_LAST_PLAYED, FALSE);
176 rb_ipod_load_songs (source);
178 return G_OBJECT (source);
181 static void
182 rb_ipod_source_dispose (GObject *object)
184 RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (object);
186 if (priv->ipod_db != NULL) {
187 itdb_free (priv->ipod_db);
188 priv->ipod_db = NULL;
191 if (priv->ipod_mount_path) {
192 g_free (priv->ipod_mount_path);
193 priv->ipod_mount_path = NULL;
196 if (priv->entry_map) {
197 g_hash_table_destroy (priv->entry_map);
198 priv->entry_map = NULL;
201 if (priv->load_idle_id != 0) {
202 g_source_remove (priv->load_idle_id);
203 priv->load_idle_id = 0;
206 G_OBJECT_CLASS (rb_ipod_source_parent_class)->dispose (object);
209 RBRemovableMediaSource *
210 rb_ipod_source_new (RBShell *shell,
211 GnomeVFSVolume *volume)
213 RBiPodSource *source;
214 RhythmDBEntryType entry_type;
215 RhythmDB *db;
217 g_assert (rb_ipod_is_volume_ipod (volume));
219 g_object_get (shell, "db", &db, NULL);
220 entry_type = rhythmdb_entry_register_type (db, NULL);
221 entry_type->save_to_disk = FALSE;
222 entry_type->category = RHYTHMDB_ENTRY_NORMAL;
223 g_object_unref (db);
225 source = RB_IPOD_SOURCE (g_object_new (RB_TYPE_IPOD_SOURCE,
226 "entry-type", entry_type,
227 "volume", volume,
228 "shell", shell,
229 "sourcelist-group", RB_SOURCELIST_GROUP_REMOVABLE,
230 NULL));
232 rb_shell_register_entry_type_for_source (shell, RB_SOURCE (source), entry_type);
234 return RB_REMOVABLE_MEDIA_SOURCE (source);
237 static void
238 entry_set_string_prop (RhythmDB *db, RhythmDBEntry *entry,
239 RhythmDBPropType propid, const char *str)
241 GValue value = {0,};
243 if (!str)
244 str = _("Unknown");
246 g_value_init (&value, G_TYPE_STRING);
247 g_value_set_static_string (&value, str);
248 rhythmdb_entry_set (RHYTHMDB (db), entry, propid, &value);
249 g_value_unset (&value);
252 static char *
253 ipod_path_to_uri (const char *mount_point, const char *ipod_path)
255 char *rel_pc_path;
256 char *full_pc_path;
257 char *uri;
259 rel_pc_path = g_strdup (ipod_path);
260 itdb_filename_ipod2fs (rel_pc_path);
261 full_pc_path = g_build_filename (mount_point, rel_pc_path, NULL);
262 g_free (rel_pc_path);
263 uri = g_filename_to_uri (full_pc_path, NULL, NULL);
264 g_free (full_pc_path);
265 return uri;
268 static void
269 add_rb_playlist (RBiPodSource *source, Itdb_Playlist *playlist)
271 RBShell *shell;
272 RBSource *playlist_source;
273 GList *it;
274 RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
275 RhythmDBEntryType entry_type;
277 g_object_get (source,
278 "shell", &shell,
279 "entry-type", &entry_type,
280 NULL);
282 playlist_source = rb_static_playlist_source_new (shell,
283 playlist->name,
284 FALSE,
285 entry_type);
286 g_boxed_free (RHYTHMDB_TYPE_ENTRY_TYPE, entry_type);
288 for (it = playlist->members; it != NULL; it = it->next) {
289 Itdb_Track *song;
290 char *filename;
292 song = (Itdb_Track *)it->data;
293 filename = ipod_path_to_uri (priv->ipod_mount_path,
294 song->ipod_path);
295 rb_static_playlist_source_add_location (RB_STATIC_PLAYLIST_SOURCE (playlist_source),
296 filename, -1);
297 g_free (filename);
300 priv->playlists = g_list_prepend (priv->playlists, playlist_source);
302 rb_shell_append_source (shell, playlist_source, RB_SOURCE (source));
303 g_object_unref (shell);
306 static void
307 load_ipod_playlists (RBiPodSource *source)
309 RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
310 GList *it;
312 for (it = priv->ipod_db->playlists; it != NULL; it = it->next) {
313 Itdb_Playlist *playlist;
315 playlist = (Itdb_Playlist *)it->data;
316 if (itdb_playlist_is_mpl (playlist)) {
317 continue;
319 if (playlist->is_spl) {
320 continue;
323 add_rb_playlist (source, playlist);
328 #ifdef ENABLE_IPOD_WRITING
329 static Itdb_Track *
330 create_ipod_song_from_entry (RhythmDBEntry *entry)
332 Itdb_Track *track;
334 track = itdb_track_new ();
336 track->title = rhythmdb_entry_dup_string (entry, RHYTHMDB_PROP_TITLE);
337 track->album = rhythmdb_entry_dup_string (entry, RHYTHMDB_PROP_ALBUM);
338 track->artist = rhythmdb_entry_dup_string (entry, RHYTHMDB_PROP_ARTIST);
339 track->genre = rhythmdb_entry_dup_string (entry, RHYTHMDB_PROP_GENRE);
340 /* track->filetype = rhythmdb_entry_dup_string (entry, RHYTHMDB_PROP);*/
341 track->size = rhythmdb_entry_get_uint64 (entry, RHYTHMDB_PROP_FILE_SIZE);
342 track->tracklen = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DURATION);
343 track->tracklen *= 1000;
344 track->cd_nr = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DISC_NUMBER);
345 track->track_nr = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_TRACK_NUMBER);
346 track->bitrate = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_BITRATE);
347 track->year = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DATE);
348 track->time_added = itdb_time_get_mac_time ();
349 track->time_played = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_LAST_PLAYED);
350 track->time_played = itdb_time_host_to_mac (track->time_played);
351 track->rating = rhythmdb_entry_get_double (entry, RHYTHMDB_PROP_RATING);
352 track->app_rating = track->rating;
353 track->playcount = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_PLAY_COUNT);
355 return track;
357 #endif
359 static void
360 add_ipod_song_to_db (RBiPodSource *source, RhythmDB *db, Itdb_Track *song)
362 RhythmDBEntry *entry;
363 RhythmDBEntryType entry_type;
364 RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
365 char *pc_path;
367 /* Set URI */
368 g_object_get (source, "entry-type", &entry_type,
369 NULL);
371 pc_path = ipod_path_to_uri (priv->ipod_mount_path,
372 song->ipod_path);
373 entry = rhythmdb_entry_new (RHYTHMDB (db), entry_type,
374 pc_path);
375 g_boxed_free (RHYTHMDB_TYPE_ENTRY_TYPE, entry_type);
377 if (entry == NULL) {
378 rb_debug ("cannot create entry %s", pc_path);
379 g_free (pc_path);
380 return;
383 rb_debug ("Adding %s from iPod", pc_path);
384 g_free (pc_path);
386 /* Set track number */
387 if (song->track_nr != 0) {
388 GValue value = {0, };
389 g_value_init (&value, G_TYPE_ULONG);
390 g_value_set_ulong (&value, song->track_nr);
391 rhythmdb_entry_set (RHYTHMDB (db), entry,
392 RHYTHMDB_PROP_TRACK_NUMBER,
393 &value);
394 g_value_unset (&value);
397 /* Set disc number */
398 if (song->cd_nr != 0) {
399 GValue value = {0, };
400 g_value_init (&value, G_TYPE_ULONG);
401 g_value_set_ulong (&value, song->cd_nr);
402 rhythmdb_entry_set (RHYTHMDB (db), entry,
403 RHYTHMDB_PROP_DISC_NUMBER,
404 &value);
405 g_value_unset (&value);
408 /* Set bitrate */
409 if (song->bitrate != 0) {
410 GValue value = {0, };
411 g_value_init (&value, G_TYPE_ULONG);
412 g_value_set_ulong (&value, song->bitrate);
413 rhythmdb_entry_set (RHYTHMDB (db), entry,
414 RHYTHMDB_PROP_BITRATE,
415 &value);
416 g_value_unset (&value);
419 /* Set length */
420 if (song->tracklen != 0) {
421 GValue value = {0, };
422 g_value_init (&value, G_TYPE_ULONG);
423 g_value_set_ulong (&value, song->tracklen/1000);
424 rhythmdb_entry_set (RHYTHMDB (db), entry,
425 RHYTHMDB_PROP_DURATION,
426 &value);
427 g_value_unset (&value);
430 /* Set file size */
431 if (song->size != 0) {
432 GValue value = {0, };
433 g_value_init (&value, G_TYPE_UINT64);
434 g_value_set_uint64 (&value, song->size);
435 rhythmdb_entry_set (RHYTHMDB (db), entry,
436 RHYTHMDB_PROP_FILE_SIZE,
437 &value);
438 g_value_unset (&value);
441 /* Set playcount */
442 if (song->playcount != 0) {
443 GValue value = {0, };
444 g_value_init (&value, G_TYPE_ULONG);
445 g_value_set_ulong (&value, song->playcount);
446 rhythmdb_entry_set (RHYTHMDB (db), entry,
447 RHYTHMDB_PROP_PLAY_COUNT,
448 &value);
449 g_value_unset (&value);
452 /* Set year */
453 if (song->year != 0) {
454 GDate *date = NULL;
455 GType type;
456 GValue value = {0, };
458 date = g_date_new_dmy (1, G_DATE_JANUARY, song->year);
460 type = rhythmdb_get_property_type (RHYTHMDB(db),
461 RHYTHMDB_PROP_DATE);
463 g_value_init (&value, type);
464 g_value_set_ulong (&value, (date ? g_date_get_julian (date) : 0));
466 rhythmdb_entry_set (RHYTHMDB (db), entry,
467 RHYTHMDB_PROP_DATE,
468 &value);
469 g_value_unset (&value);
470 if (date)
471 g_date_free (date);
474 /* Set rating */
475 if (song->rating != 0) {
476 GValue value = {0, };
477 g_value_init (&value, G_TYPE_DOUBLE);
478 g_value_set_double (&value, song->rating/20.0);
479 rhythmdb_entry_set (RHYTHMDB (db), entry,
480 RHYTHMDB_PROP_RATING,
481 &value);
482 g_value_unset (&value);
485 /* Set last played */
486 if (song->time_played != 0) {
487 GValue value = {0, };
488 g_value_init (&value, G_TYPE_ULONG);
489 g_value_set_ulong (&value, itdb_time_mac_to_host (song->time_played));
490 rhythmdb_entry_set (RHYTHMDB (db), entry,
491 RHYTHMDB_PROP_LAST_PLAYED,
492 &value);
493 g_value_unset (&value);
496 /* Set title */
497 entry_set_string_prop (RHYTHMDB (db), entry,
498 RHYTHMDB_PROP_TITLE, song->title);
500 /* Set album, artist and genre from iTunesDB */
501 entry_set_string_prop (RHYTHMDB (db), entry,
502 RHYTHMDB_PROP_ARTIST, song->artist);
504 entry_set_string_prop (RHYTHMDB (db), entry,
505 RHYTHMDB_PROP_ALBUM, song->album);
507 entry_set_string_prop (RHYTHMDB (db), entry,
508 RHYTHMDB_PROP_GENRE, song->genre);
510 g_hash_table_insert (priv->entry_map, entry, song);
512 rhythmdb_commit (RHYTHMDB (db));
515 static RhythmDB *
516 get_db_for_source (RBiPodSource *source)
518 RBShell *shell;
519 RhythmDB *db;
521 g_object_get (source, "shell", &shell, NULL);
522 g_object_get (shell, "db", &db, NULL);
523 g_object_unref (shell);
525 return db;
528 static gboolean
529 load_ipod_db_idle_cb (RBiPodSource *source)
531 RhythmDB *db;
532 GList *it;
533 RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
535 GDK_THREADS_ENTER ();
537 db = get_db_for_source (source);
539 g_assert (db != NULL);
540 for (it = priv->ipod_db->tracks; it != NULL; it = it->next) {
541 add_ipod_song_to_db (source, db, (Itdb_Track *)it->data);
544 load_ipod_playlists (source);
546 g_object_unref (db);
548 GDK_THREADS_LEAVE ();
549 priv->load_idle_id = 0;
550 return FALSE;
553 static void
554 rb_ipod_load_songs (RBiPodSource *source)
556 RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
557 GnomeVFSVolume *volume;
559 g_object_get (source, "volume", &volume, NULL);
560 priv->ipod_mount_path = rb_ipod_get_mount_path (volume);
562 priv->ipod_db = itdb_parse (priv->ipod_mount_path, NULL);
563 priv->entry_map = g_hash_table_new (g_direct_hash, g_direct_equal);
564 if ((priv->ipod_db != NULL) && (priv->entry_map != NULL)) {
565 Itdb_Playlist *mpl;
567 /* FIXME: we could set a different icon depending on the iPod
568 * model
570 mpl = itdb_playlist_mpl (priv->ipod_db);
571 if (mpl && mpl->name) {
572 g_object_set (RB_SOURCE (source),
573 "name", mpl->name,
574 NULL);
576 priv->load_idle_id = g_idle_add ((GSourceFunc)load_ipod_db_idle_cb, source);
579 g_object_unref (volume);
582 static gchar *
583 rb_ipod_get_mount_path (GnomeVFSVolume *volume)
585 gchar *path;
586 gchar *uri;
588 uri = gnome_vfs_volume_get_activation_uri (volume);
589 path = g_filename_from_uri (uri, NULL, NULL);
590 g_assert (path != NULL);
591 g_free (uri);
593 return path;
596 static gchar *
597 rb_ipod_get_itunesdb_path (GnomeVFSVolume *volume)
599 gchar *mount_point_uri;
600 gchar *mount_point;
601 gchar *result;
603 mount_point_uri = gnome_vfs_volume_get_activation_uri (volume);
604 if (mount_point_uri == NULL) {
605 return NULL;
607 mount_point = g_filename_from_uri (mount_point_uri, NULL, NULL);
608 g_free (mount_point_uri);
609 if (mount_point == NULL) {
610 return NULL;
613 #ifdef IPOD_SUPPORT
614 result = itdb_get_itunesdb_path (mount_point);
615 #else
616 result = g_build_filename (mount_point,
617 "iPod_Control/iTunes/iTunesDB",
618 NULL);
619 #endif
621 g_free (mount_point);
622 return result;
625 static gboolean
626 rb_ipod_volume_has_ipod_db (GnomeVFSVolume *volume)
628 char *itunesdb_path;
629 gboolean result;
631 itunesdb_path = rb_ipod_get_itunesdb_path (volume);
633 if (itunesdb_path != NULL) {
634 result = g_file_test (itunesdb_path, G_FILE_TEST_EXISTS);
635 } else {
636 result = FALSE;
638 g_free (itunesdb_path);
640 return result;
643 gboolean
644 rb_ipod_is_volume_ipod (GnomeVFSVolume *volume)
646 #ifdef HAVE_HAL
647 gchar *udi;
648 #endif
649 if (gnome_vfs_volume_get_volume_type (volume) != GNOME_VFS_VOLUME_TYPE_MOUNTPOINT) {
650 return FALSE;
653 #ifdef HAVE_HAL
654 udi = gnome_vfs_volume_get_hal_udi (volume);
655 if (udi != NULL) {
656 gboolean result;
658 result = hal_udi_is_ipod (udi);
659 g_free (udi);
660 if (result == FALSE) {
661 return FALSE;
664 #endif
666 return rb_ipod_volume_has_ipod_db (volume);
669 #ifdef HAVE_HAL_0_5
671 static gboolean
672 hal_udi_is_ipod (const char *udi)
674 LibHalContext *ctx;
675 DBusConnection *conn;
676 char *parent_udi;
677 char *parent_name;
678 gboolean result;
679 DBusError error;
680 gboolean inited = FALSE;
682 result = FALSE;
683 dbus_error_init (&error);
685 conn = NULL;
686 parent_udi = NULL;
687 parent_name = NULL;
689 ctx = libhal_ctx_new ();
690 if (ctx == NULL) {
691 /* FIXME: should we return an error somehow so that we can
692 * fall back to a check for iTunesDB presence instead ?
694 rb_debug ("cannot connect to HAL");
695 goto end;
697 conn = dbus_bus_get (DBUS_BUS_SYSTEM, &error);
698 if (conn == NULL || dbus_error_is_set (&error))
699 goto end;
701 libhal_ctx_set_dbus_connection (ctx, conn);
702 if (!libhal_ctx_init (ctx, &error) || dbus_error_is_set (&error))
703 goto end;
705 inited = TRUE;
706 parent_udi = libhal_device_get_property_string (ctx, udi,
707 "info.parent", &error);
708 if (parent_udi == NULL || dbus_error_is_set (&error))
709 goto end;
711 parent_name = libhal_device_get_property_string (ctx, parent_udi,
712 "storage.model", &error);
713 #ifdef IPOD_SUPPORT
715 char *spider_udi;
716 int vnd_id = 0;
717 int product_id = 0;
719 spider_udi = g_strdup(parent_udi);
720 while (vnd_id == 0 && product_id == 0 && spider_udi != NULL) {
721 char *old_udi = spider_udi;
722 spider_udi = libhal_device_get_property_string (ctx, spider_udi,
723 "info.parent", &error);
724 if (dbus_error_is_set (&error)) {
725 dbus_error_free (&error);
726 dbus_error_init (&error);
727 spider_udi = NULL;
728 break;
730 g_free(old_udi);
732 vnd_id = libhal_device_get_property_int (ctx, spider_udi,
733 "usb.vendor_id", &error);
734 if (dbus_error_is_set(&error)) {
735 dbus_error_free (&error);
736 dbus_error_init (&error);
737 vnd_id = 0;
740 product_id = libhal_device_get_property_int (ctx, spider_udi,
741 "usb.product_id", &error);
742 if (dbus_error_is_set(&error)) {
743 dbus_error_free (&error);
744 dbus_error_init (&error);
745 product_id = 0;
748 g_free (spider_udi);
750 if (vnd_id == PHONE_VENDOR_ID && product_id == PHONE_PRODUCT_ID) {
751 result = TRUE;
754 #endif
755 if (parent_name == NULL || dbus_error_is_set (&error))
756 goto end;
758 if (strcmp (parent_name, "iPod") == 0)
759 result = TRUE;
761 end:
762 g_free (parent_udi);
763 g_free (parent_name);
765 if (dbus_error_is_set (&error)) {
766 rb_debug ("Error: %s\n", error.message);
767 dbus_error_free (&error);
768 dbus_error_init (&error);
771 if (ctx) {
772 if (inited)
773 libhal_ctx_shutdown (ctx, &error);
774 libhal_ctx_free(ctx);
777 dbus_error_free (&error);
779 return result;
782 #elif HAVE_HAL_0_2
784 static gboolean
785 hal_udi_is_ipod (const char *udi)
787 LibHalContext *ctx;
788 char *parent_udi;
789 char *parent_name;
790 gboolean result;
792 result = FALSE;
793 ctx = hal_initialize (NULL, FALSE);
794 if (ctx == NULL) {
795 /* FIXME: should we return an error somehow so that we can
796 * fall back to a check for iTunesDB presence instead ?
798 return FALSE;
800 parent_udi = hal_device_get_property_string (ctx, udi,
801 "info.parent");
802 parent_name = hal_device_get_property_string (ctx, parent_udi,
803 "storage.model");
804 g_free (parent_udi);
806 if (parent_name != NULL && strcmp (parent_name, "iPod") == 0) {
807 result = TRUE;
810 g_free (parent_name);
811 hal_shutdown (ctx);
813 return result;
816 #endif
818 static GList*
819 impl_get_ui_actions (RBSource *source)
821 GList *actions = NULL;
823 actions = g_list_prepend (actions, g_strdup ("RemovableSourceEject"));
825 return actions;
828 static gboolean
829 impl_show_popup (RBSource *source)
831 _rb_source_show_popup (RB_SOURCE (source), "/iPodSourcePopup");
832 return TRUE;
835 static void
836 remove_track_from_db (Itdb_Track *track)
838 GList *it;
840 for (it = track->itdb->playlists; it != NULL; it = it->next) {
841 itdb_playlist_remove_track ((Itdb_Playlist *)it->data, track);
843 itdb_track_remove (track);
846 static void
847 impl_move_to_trash (RBSource *asource)
849 GList *sel, *tem;
850 RBEntryView *songs;
851 RhythmDB *db;
852 RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (asource);
853 RBiPodSource *source = RB_IPOD_SOURCE (asource);
855 db = get_db_for_source (source);
857 songs = rb_source_get_entry_view (RB_SOURCE (asource));
858 sel = rb_entry_view_get_selected_entries (songs);
859 for (tem = sel; tem != NULL; tem = tem->next) {
860 RhythmDBEntry *entry;
861 const gchar *uri;
862 Itdb_Track *track;
864 entry = (RhythmDBEntry *)tem->data;
865 uri = rhythmdb_entry_get_string (entry,
866 RHYTHMDB_PROP_LOCATION);
867 track = g_hash_table_lookup (priv->entry_map, entry);
868 if (track == NULL) {
869 g_warning ("Couldn't find track on ipod! (%s)", uri);
870 continue;
873 remove_track_from_db (track);
874 g_hash_table_remove (priv->entry_map, entry);
875 rhythmdb_entry_move_to_trash (db, entry);
876 rhythmdb_commit (db);
879 if (sel != NULL) {
880 itdb_write (priv->ipod_db, NULL);
883 g_object_unref (db);
885 g_list_free (sel);
888 static void
889 itdb_schedule_save (Itdb_iTunesDB *db)
891 /* FIXME: should probably be delayed a bit to avoid doing
892 * it after each file when we are copying several files
893 * consecutively
894 * FIXME: or this function could be called itdb_set_dirty, and we'd
895 * have a timeout firing every 5 seconds and saving the db if it's
896 * dirty
898 itdb_write (db, NULL);
901 #ifdef ENABLE_IPOD_WRITING
902 static char *
903 build_filename (RBSource *asource, RhythmDBEntry *entry)
905 const char *uri;
906 char *dest;
907 char *dest_uri;
909 RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (asource);
911 uri = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
912 dest = ipod_get_filename_for_uri (priv->ipod_mount_path, uri);
913 if (dest != NULL) {
914 dest_uri = g_filename_to_uri (dest, NULL, NULL);
915 g_free (dest);
916 return dest_uri;
919 return NULL;
922 static void
923 completed_cb (RhythmDBEntry *entry, const char *dest, RBiPodSource *source)
925 RhythmDB *db;
926 Itdb_Track *song;
928 db = get_db_for_source (source);
930 song = create_ipod_song_from_entry (entry);
931 if (song != NULL) {
932 RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
933 char *filename;
935 filename = g_filename_from_uri (dest, NULL, NULL);
936 song->ipod_path = ipod_path_from_unix_path (priv->ipod_mount_path, filename);
937 g_free (filename);
938 itdb_track_add (priv->ipod_db, song, -1);
939 itdb_playlist_add_track (itdb_playlist_mpl (priv->ipod_db),
940 song, -1);
942 add_ipod_song_to_db (source, db, song);
943 itdb_schedule_save (priv->ipod_db);
946 g_object_unref (db);
949 static void
950 impl_paste (RBSource *asource, GList *entries)
952 RBRemovableMediaManager *rm_mgr;
953 GList *l;
954 RBShell *shell;
956 g_object_get (asource, "shell", &shell, NULL);
957 g_object_get (shell,
958 "removable-media-manager", &rm_mgr,
959 NULL);
960 g_object_unref (shell);
962 for (l = entries; l != NULL; l = l->next) {
963 RhythmDBEntry *entry;
964 RhythmDBEntryType entry_type;
965 RhythmDBEntryType ipod_entry_type;
966 char *dest;
968 entry = (RhythmDBEntry *)l->data;
969 entry_type = rhythmdb_entry_get_entry_type (entry);
970 g_object_get (asource,
971 "entry-type", &ipod_entry_type,
972 NULL);
973 if (entry_type == ipod_entry_type ||
974 entry_type->category != RHYTHMDB_ENTRY_NORMAL) {
975 g_boxed_free (RHYTHMDB_TYPE_ENTRY_TYPE, entry_type);
976 continue;
979 dest = build_filename (asource, entry);
980 if (dest == NULL) {
981 rb_debug ("could not create destination path for entry");
982 g_boxed_free (RHYTHMDB_TYPE_ENTRY_TYPE, entry_type);
983 continue;
985 rb_removable_media_manager_queue_transfer (rm_mgr, entry,
986 dest, NULL,
987 (RBTranferCompleteCallback)completed_cb, asource);
988 g_free (dest);
989 g_boxed_free (RHYTHMDB_TYPE_ENTRY_TYPE, entry_type);
992 g_object_unref (rm_mgr);
995 static gboolean
996 impl_receive_drag (RBSource *asource, GtkSelectionData *data)
998 RBBrowserSource *source = RB_BROWSER_SOURCE (asource);
999 GList *list, *i;
1000 GList *entries = NULL;
1001 RhythmDB *db;
1002 gboolean is_id;
1004 rb_debug ("parsing uri list");
1005 list = rb_uri_list_parse ((const char *) data->data);
1006 is_id = (data->type == gdk_atom_intern ("application/x-rhythmbox-entry", TRUE));
1008 db = get_db_for_source (RB_IPOD_SOURCE (source));
1010 for (i = list; i != NULL; i = g_list_next (i)) {
1011 if (i->data != NULL) {
1012 char *uri = i->data;
1013 RhythmDBEntry *entry;
1015 entry = rhythmdb_entry_lookup_from_string (db, uri, is_id);
1017 if (entry == NULL) {
1018 /* add to the library */
1019 g_print ("Where does that come from?\n");
1020 } else {
1021 /* add to list of entries to copy */
1022 entries = g_list_prepend (entries, entry);
1025 g_free (uri);
1028 g_object_unref (db);
1029 g_list_free (list);
1031 if (entries) {
1032 entries = g_list_reverse (entries);
1033 if (rb_source_can_paste (asource))
1034 rb_source_paste (asource, entries);
1035 g_list_free (entries);
1038 return TRUE;
1041 /* Generation of the filename for the ipod */
1043 #define IPOD_MAX_PATH_LEN 56
1045 static gboolean
1046 test_dir_on_ipod (const char *mountpoint, const char *dirname)
1048 char *fullpath;
1049 gboolean result;
1051 fullpath = g_build_filename (mountpoint, dirname, NULL);
1052 result = g_file_test (fullpath, G_FILE_TEST_IS_DIR);
1053 g_free (fullpath);
1055 return result;
1058 static int
1059 ipod_mkdir_with_parents (const char *mountpoint, const char *dirname)
1061 char *fullpath;
1062 int result;
1064 fullpath = g_build_filename (mountpoint, dirname, NULL);
1065 result = g_mkdir_with_parents (fullpath, 0770);
1066 g_free (fullpath);
1068 return result;
1071 static gchar *
1072 build_ipod_dir_name (const char *mountpoint)
1074 /* FAT sucks, filename can be lowercase or uppercase, and if we try to
1075 * open the wrong one, we lose :-/
1077 char *dirname;
1078 char *relpath;
1079 gint32 suffix;
1081 suffix = g_random_int_range (0, 100);
1082 dirname = g_strdup_printf ("F%02d", suffix);
1083 relpath = g_build_filename (G_DIR_SEPARATOR_S, "iPod_Control",
1084 "Music", dirname, NULL);
1085 g_free (dirname);
1087 if (test_dir_on_ipod (mountpoint, relpath) != FALSE) {
1088 return relpath;
1091 g_free (relpath);
1092 dirname = g_strdup_printf ("f%02d", g_random_int_range (0, 100));
1093 relpath = g_build_filename (G_DIR_SEPARATOR_S, "iPod_Control",
1094 "Music", dirname, NULL);
1095 g_free (dirname);
1097 if (test_dir_on_ipod (mountpoint, relpath) != FALSE) {
1098 return relpath;
1101 if (ipod_mkdir_with_parents (mountpoint, relpath) == 0) {
1102 return relpath;
1105 g_free (relpath);
1106 return NULL;
1109 static gchar *
1110 get_ipod_filename (const char *mount_point, const char *filename)
1112 char *dirname;
1113 char *result;
1114 char *tmp;
1116 dirname = build_ipod_dir_name (mount_point);
1117 if (dirname == NULL) {
1118 return NULL;
1120 result = g_build_filename (dirname, filename, NULL);
1121 g_free (dirname);
1123 if (strlen (result) >= IPOD_MAX_PATH_LEN) {
1124 char *ext;
1126 ext = strrchr (result, '.');
1127 if (ext == NULL) {
1128 result [IPOD_MAX_PATH_LEN - 1] = '\0';
1129 } else {
1130 memmove (&result[IPOD_MAX_PATH_LEN - strlen (ext) - 1] ,
1131 ext, strlen (ext) + 1);
1135 tmp = g_build_filename (mount_point, result, NULL);
1136 g_free (result);
1137 return tmp;
1140 #define MAX_TRIES 5
1142 /* Strips non UTF8 characters from a string replacing them with _ */
1143 static gchar *
1144 utf8_to_ascii (const gchar *utf8)
1146 GString *string;
1147 const guchar *it = (const guchar *)utf8;
1149 string = g_string_new ("");
1150 while ((it != NULL) && (*it != '\0')) {
1151 /* Do we have a 7 bit char ? */
1152 if (*it < 0x80) {
1153 g_string_append_c (string, *it);
1154 } else {
1155 g_string_append_c (string, '_');
1157 it = (const guchar *)g_utf8_next_char (it);
1160 return g_string_free (string, FALSE);
1163 static gchar *
1164 generate_ipod_filename (const gchar *mount_point, const gchar *filename)
1166 gchar *ipod_filename = NULL;
1167 gchar *pc_filename;
1168 gchar *tmp;
1169 gint tries = 0;
1171 /* First, we need a valid UTF-8 filename, strip all non-UTF-8 chars */
1172 tmp = rb_make_valid_utf8 (filename, '_');
1173 /* The iPod doesn't seem to recognize non-ascii chars in filenames,
1174 * so we strip them
1176 pc_filename = utf8_to_ascii (tmp);
1177 g_free (tmp);
1179 g_assert (g_utf8_validate (pc_filename, -1, NULL));
1180 /* Now we have a valid UTF-8 filename, try to find out where to put
1181 * it on the iPod
1183 do {
1184 g_free (ipod_filename);
1185 ipod_filename = get_ipod_filename (mount_point, pc_filename);
1186 tries++;
1187 if (tries > MAX_TRIES) {
1188 break;
1190 } while ((ipod_filename == NULL)
1191 || (g_file_test (ipod_filename, G_FILE_TEST_EXISTS)));
1193 g_free (pc_filename);
1195 if (tries > MAX_TRIES) {
1196 /* FIXME: should create a unique filename */
1197 return NULL;
1198 } else {
1199 return ipod_filename;
1203 static gchar *
1204 ipod_get_filename_for_uri (const gchar *mount_point, const gchar *uri_str)
1206 gchar *escaped;
1207 gchar *filename;
1208 gchar *result;
1210 escaped = rb_uri_get_short_path_name (uri_str);
1211 if (escaped == NULL) {
1212 return NULL;
1214 filename = gnome_vfs_unescape_string (escaped, G_DIR_SEPARATOR_S);
1215 g_free (escaped);
1216 if (filename == NULL) {
1217 return NULL;
1220 result = generate_ipod_filename (mount_point, filename);
1221 g_free (filename);
1223 return result;
1226 /* End of generation of the filename on the iPod */
1228 static gchar *
1229 ipod_path_from_unix_path (const gchar *mount_point, const gchar *unix_path)
1231 gchar *ipod_path;
1233 g_assert (g_utf8_validate (unix_path, -1, NULL));
1235 if (!g_str_has_prefix (unix_path, mount_point)) {
1236 return NULL;
1239 ipod_path = g_strdup (unix_path + strlen (mount_point));
1240 if (*ipod_path != G_DIR_SEPARATOR) {
1241 gchar *tmp;
1242 tmp = g_strdup_printf ("/%s", ipod_path);
1243 g_free (ipod_path);
1244 ipod_path = tmp;
1247 /* Make sure the filename doesn't contain any ':' */
1248 g_strdelimit (ipod_path, ":", ';');
1250 /* Convert path to a Mac path where the dir separator is ':' */
1251 itdb_filename_fs2ipod (ipod_path);
1253 return ipod_path;
1255 #endif
1257 static void
1258 impl_delete_thyself (RBSource *source)
1260 RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
1261 GList *p;
1263 for (p = priv->playlists; p != NULL; p = p->next) {
1264 RBSource *playlist = RB_SOURCE (p->data);
1265 rb_source_delete_thyself (playlist);
1267 g_list_free (priv->playlists);
1268 priv->playlists = NULL;
1270 itdb_free (priv->ipod_db);
1271 priv->ipod_db = NULL;
1273 RB_SOURCE_CLASS (rb_ipod_source_parent_class)->impl_delete_thyself (source);