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