No issues found
Tool | Failure ID | Location | Function | Message | Data |
---|---|---|---|---|---|
clang-analyzer | no-output-found | rb-mtp-source.c | Message(text='Unable to locate XML output from invoke-clang-analyzer') | None |
1 /*
2 * Copyright (C) 2006 Peter Grundstrรถm <pete@openfestis.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 <string.h>
31 #include <gtk/gtk.h>
32 #include <glib/gi18n.h>
33 #include <gst/gst.h>
34
35 #if defined(HAVE_GUDEV)
36 #define G_UDEV_API_IS_SUBJECT_TO_CHANGE
37 #include <gudev/gudev.h>
38 #endif
39
40 #include "rhythmdb.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-static-playlist-source.h"
46 #include "rb-transfer-target.h"
47 #include "rb-device-source.h"
48 #include "rb-util.h"
49 #include "rb-refstring.h"
50 #include "rhythmdb.h"
51 #include "rb-dialog.h"
52 #include "rb-shell-player.h"
53 #include "rb-player.h"
54 #include "rb-encoder.h"
55 #include "rb-sync-settings.h"
56 #include "rb-gst-media-types.h"
57 #include "rb-ext-db.h"
58
59 #include "rb-mtp-source.h"
60 #include "rb-mtp-thread.h"
61
62 static void rb_mtp_source_constructed (GObject *object);
63 static void rb_mtp_source_dispose (GObject *object);
64 static void rb_mtp_source_finalize (GObject *object);
65 static void rb_mtp_device_source_init (RBDeviceSourceInterface *interface);
66 static void rb_mtp_source_transfer_target_init (RBTransferTargetInterface *interface);
67
68 static void rb_mtp_source_set_property (GObject *object,
69 guint prop_id,
70 const GValue *value,
71 GParamSpec *pspec);
72 static void rb_mtp_source_get_property (GObject *object,
73 guint prop_id,
74 GValue *value,
75 GParamSpec *pspec);
76
77 static void impl_delete (RBSource *asource);
78 static RBTrackTransferBatch *impl_paste (RBSource *asource, GList *entries);
79 static gboolean impl_show_popup (RBDisplayPage *page);
80 static gboolean impl_uri_is_source (RBSource *asource, const char *uri);
81
82 static gboolean impl_track_added (RBTransferTarget *target,
83 RhythmDBEntry *entry,
84 const char *dest,
85 guint64 filesize,
86 const char *media_type);
87 static gboolean impl_track_add_error (RBTransferTarget *target,
88 RhythmDBEntry *entry,
89 const char *dest,
90 GError *error);
91 static char *impl_build_dest_uri (RBTransferTarget *target,
92 RhythmDBEntry *entry,
93 const char *media_type,
94 const char *extension);
95
96 static void impl_eject (RBDeviceSource *source);
97 static gboolean impl_can_eject (RBDeviceSource *source);
98
99 static void impl_selected (RBDisplayPage *page);
100 static void mtp_device_open_cb (LIBMTP_mtpdevice_t *device, RBMtpSource *source);
101 static void mtp_tracklist_cb (LIBMTP_track_t *tracks, RBMtpSource *source);
102 static RhythmDB * get_db_for_source (RBMtpSource *source);
103
104 static void impl_get_entries (RBMediaPlayerSource *source, const char *category, GHashTable *map);
105 static guint64 impl_get_capacity (RBMediaPlayerSource *source);
106 static guint64 impl_get_free_space (RBMediaPlayerSource *source);
107 static void impl_delete_entries (RBMediaPlayerSource *source,
108 GList *entries,
109 RBMediaPlayerSourceDeleteCallback callback,
110 gpointer callback_data,
111 GDestroyNotify destroy_data);
112 static void impl_show_properties (RBMediaPlayerSource *source, GtkWidget *info_box, GtkWidget *notebook);
113
114 static void prepare_player_source_cb (RBPlayer *player,
115 const char *stream_uri,
116 GstElement *src,
117 RBMtpSource *source);
118 static void prepare_encoder_source_cb (RBEncoderFactory *factory,
119 const char *stream_uri,
120 GObject *src,
121 RBMtpSource *source);
122 static void prepare_encoder_sink_cb (RBEncoderFactory *factory,
123 const char *stream_uri,
124 GObject *sink,
125 RBMtpSource *source);
126 #if defined(HAVE_GUDEV)
127 static GMount *find_mount_for_device (GUdevDevice *device);
128 #endif
129
130 typedef struct
131 {
132 gboolean tried_open;
133 RBMtpThread *device_thread;
134 LIBMTP_raw_device_t raw_device;
135 GHashTable *entry_map;
136 GHashTable *track_transfer_map;
137 #if defined(HAVE_GUDEV)
138 GUdevDevice *udev_device;
139 GVolume *remount_volume;
140 #else
141 char *udi;
142 #endif
143 uint16_t supported_types[LIBMTP_FILETYPE_UNKNOWN+1];
144 gboolean album_art_supported;
145 RBExtDB *art_store;
146
147 /* device information */
148 char *manufacturer;
149 char *serial;
150 char *device_version;
151 char *model_name;
152 guint64 capacity;
153 guint64 free_space; /* updated by callbacks */
154
155 } RBMtpSourcePrivate;
156
157 G_DEFINE_DYNAMIC_TYPE_EXTENDED(
158 RBMtpSource,
159 rb_mtp_source,
160 RB_TYPE_MEDIA_PLAYER_SOURCE,
161 0,
162 G_IMPLEMENT_INTERFACE_DYNAMIC (RB_TYPE_DEVICE_SOURCE, rb_mtp_device_source_init)
163 G_IMPLEMENT_INTERFACE_DYNAMIC (RB_TYPE_TRANSFER_TARGET, rb_mtp_source_transfer_target_init))
164
165 #define MTP_SOURCE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_MTP_SOURCE, RBMtpSourcePrivate))
166
167 enum
168 {
169 PROP_0,
170 PROP_RAW_DEVICE,
171 PROP_UDEV_DEVICE,
172 PROP_UDI,
173 PROP_DEVICE_SERIAL
174 };
175
176 static void
177 rb_mtp_source_class_init (RBMtpSourceClass *klass)
178 {
179 GObjectClass *object_class = G_OBJECT_CLASS (klass);
180 RBDisplayPageClass *page_class = RB_DISPLAY_PAGE_CLASS (klass);
181 RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
182 RBMediaPlayerSourceClass *mps_class = RB_MEDIA_PLAYER_SOURCE_CLASS (klass);
183
184 object_class->constructed = rb_mtp_source_constructed;
185 object_class->dispose = rb_mtp_source_dispose;
186 object_class->finalize = rb_mtp_source_finalize;
187 object_class->set_property = rb_mtp_source_set_property;
188 object_class->get_property = rb_mtp_source_get_property;
189
190 page_class->show_popup = impl_show_popup;
191 page_class->selected = impl_selected;
192
193 source_class->impl_can_rename = (RBSourceFeatureFunc) rb_true_function;
194 source_class->impl_can_delete = (RBSourceFeatureFunc) rb_true_function;
195 source_class->impl_can_paste = (RBSourceFeatureFunc) rb_true_function;
196 source_class->impl_can_move_to_trash = (RBSourceFeatureFunc) rb_false_function;
197 source_class->impl_can_copy = (RBSourceFeatureFunc) rb_true_function;
198 source_class->impl_can_cut = (RBSourceFeatureFunc) rb_false_function;
199 source_class->impl_delete = impl_delete;
200 source_class->impl_paste = impl_paste;
201 source_class->impl_uri_is_source = impl_uri_is_source;
202
203 mps_class->impl_get_entries = impl_get_entries;
204 mps_class->impl_get_capacity = impl_get_capacity;
205 mps_class->impl_get_free_space = impl_get_free_space;
206 mps_class->impl_delete_entries = impl_delete_entries;
207 mps_class->impl_show_properties = impl_show_properties;
208
209 g_object_class_install_property (object_class,
210 PROP_RAW_DEVICE,
211 g_param_spec_pointer ("raw-device",
212 "raw-device",
213 "libmtp raw device",
214 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
215 #if defined(HAVE_GUDEV)
216 g_object_class_install_property (object_class,
217 PROP_UDEV_DEVICE,
218 g_param_spec_object ("udev-device",
219 "udev-device",
220 "GUdev device object",
221 G_UDEV_TYPE_DEVICE,
222 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
223 #else
224 g_object_class_install_property (object_class,
225 PROP_UDI,
226 g_param_spec_string ("udi",
227 "udi",
228 "udi",
229 NULL,
230 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
231 #endif
232 g_object_class_override_property (object_class, PROP_DEVICE_SERIAL, "serial");
233
234 g_type_class_add_private (klass, sizeof (RBMtpSourcePrivate));
235 }
236
237 static void
238 rb_mtp_device_source_init (RBDeviceSourceInterface *interface)
239 {
240 interface->can_eject = impl_can_eject;
241 interface->eject = impl_eject;
242 }
243
244 static void
245 rb_mtp_source_transfer_target_init (RBTransferTargetInterface *interface)
246 {
247 interface->build_dest_uri = impl_build_dest_uri;
248 interface->track_added = impl_track_added;
249 interface->track_add_error = impl_track_add_error;
250 }
251
252 static void
253 rb_mtp_source_class_finalize (RBMtpSourceClass *klass)
254 {
255 }
256
257 static void
258 rb_mtp_source_name_changed_cb (RBMtpSource *source,
259 GParamSpec *spec,
260 gpointer data)
261 {
262 RBMtpSourcePrivate *priv = MTP_SOURCE_GET_PRIVATE (source);
263 char *name = NULL;
264
265 g_object_get (source, "name", &name, NULL);
266 rb_mtp_thread_set_device_name (priv->device_thread, name);
267 g_free (name);
268 }
269
270 static void
271 rb_mtp_source_init (RBMtpSource *source)
272 {
273 RBMtpSourcePrivate *priv = MTP_SOURCE_GET_PRIVATE (source);
274
275 priv->entry_map = g_hash_table_new_full (g_direct_hash,
276 g_direct_equal,
277 NULL,
278 (GDestroyNotify) LIBMTP_destroy_track_t);
279
280 priv->track_transfer_map = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
281 }
282
283
284 static void
285 open_device (RBMtpSource *source)
286 {
287 RBMtpSourcePrivate *priv = MTP_SOURCE_GET_PRIVATE (source);
288
289 rb_debug ("actually opening device");
290 priv->device_thread = rb_mtp_thread_new ();
291 rb_mtp_thread_open_device (priv->device_thread,
292 &priv->raw_device,
293 (RBMtpOpenCallback)mtp_device_open_cb,
294 g_object_ref (source),
295 g_object_unref);
296 }
297
298 #if defined(HAVE_GUDEV)
299 static void
300 unmount_done_cb (GObject *object, GAsyncResult *result, gpointer psource)
301 {
302 GMount *mount;
303 RBMtpSource *source;
304 gboolean ok;
305 GError *error = NULL;
306 RBMtpSourcePrivate *priv;
307
308 mount = G_MOUNT (object);
309 source = RB_MTP_SOURCE (psource);
310 priv = MTP_SOURCE_GET_PRIVATE (source);
311
312 ok = g_mount_unmount_with_operation_finish (mount, result, &error);
313 if (ok) {
314 rb_debug ("successfully unmounted mtp device");
315 priv->remount_volume = g_mount_get_volume (mount);
316
317 open_device (source);
318 } else {
319 g_warning ("Unable to unmount MTP device: %s", error->message);
320 g_error_free (error);
321 }
322
323 g_object_unref (mount);
324 g_object_unref (source);
325 }
326
327 #endif
328
329 static gboolean
330 ensure_loaded (RBMtpSource *source)
331 {
332 RBMtpSourcePrivate *priv = MTP_SOURCE_GET_PRIVATE (source);
333 #if defined(HAVE_GUDEV)
334 GMount *mount;
335 #endif
336 if (priv->tried_open) {
337 RBSourceLoadStatus status;
338 g_object_get (source, "load-status", &status, NULL);
339 return (status == RB_SOURCE_LOAD_STATUS_LOADED);
340 }
341 priv->tried_open = TRUE;
342
343 /* try to open the device. if gvfs has mounted it, unmount it first */
344 #if defined(HAVE_GUDEV)
345 mount = find_mount_for_device (priv->udev_device);
346 if (mount != NULL) {
347 rb_debug ("device is already mounted, waiting until activated");
348 g_mount_unmount_with_operation (mount,
349 G_MOUNT_UNMOUNT_NONE,
350 NULL,
351 NULL,
352 unmount_done_cb,
353 g_object_ref (source));
354 /* mount gets unreffed in callback */
355 } else {
356 rb_debug ("device isn't mounted");
357 open_device (source);
358 }
359 #else
360 open_device (source);
361 #endif
362 return FALSE;
363 }
364
365 static void
366 impl_selected (RBDisplayPage *page)
367 {
368 ensure_loaded (RB_MTP_SOURCE (page));
369 }
370
371 static void
372 rb_mtp_source_constructed (GObject *object)
373 {
374 RBMtpSource *source;
375 RBEntryView *tracks;
376 RBShell *shell;
377 RBShellPlayer *shell_player;
378 GObject *player_backend;
379 GtkIconTheme *theme;
380 GdkPixbuf *pixbuf;
381 gint size;
382
383 RB_CHAIN_GOBJECT_METHOD (rb_mtp_source_parent_class, constructed, object);
384 source = RB_MTP_SOURCE (object);
385
386 tracks = rb_source_get_entry_view (RB_SOURCE (source));
387 rb_entry_view_append_column (tracks, RB_ENTRY_VIEW_COL_RATING, FALSE);
388 rb_entry_view_append_column (tracks, RB_ENTRY_VIEW_COL_LAST_PLAYED, FALSE);
389
390 /* the source element needs our cooperation */
391 g_object_get (source, "shell", &shell, NULL);
392 g_object_get (shell, "shell-player", &shell_player, NULL);
393 g_object_get (shell_player, "player", &player_backend, NULL);
394 g_object_unref (shell_player);
395
396 g_signal_connect_object (player_backend,
397 "prepare-source",
398 G_CALLBACK (prepare_player_source_cb),
399 source, 0);
400
401 g_object_unref (player_backend);
402 g_object_unref (shell);
403
404 g_signal_connect_object (rb_encoder_factory_get (),
405 "prepare-source",
406 G_CALLBACK (prepare_encoder_source_cb),
407 source, 0);
408 g_signal_connect_object (rb_encoder_factory_get (),
409 "prepare-sink",
410 G_CALLBACK (prepare_encoder_sink_cb),
411 source, 0);
412
413 /* icon */
414 theme = gtk_icon_theme_get_default ();
415 gtk_icon_size_lookup (GTK_ICON_SIZE_LARGE_TOOLBAR, &size, NULL);
416 pixbuf = gtk_icon_theme_load_icon (theme, "multimedia-player", size, 0, NULL);
417
418 g_object_set (source, "pixbuf", pixbuf, NULL);
419 g_object_unref (pixbuf);
420
421 }
422
423 static void
424 rb_mtp_source_set_property (GObject *object,
425 guint prop_id,
426 const GValue *value,
427 GParamSpec *pspec)
428 {
429 RBMtpSourcePrivate *priv = MTP_SOURCE_GET_PRIVATE (object);
430 LIBMTP_raw_device_t *raw_device;
431
432 switch (prop_id) {
433 case PROP_RAW_DEVICE:
434 raw_device = g_value_get_pointer (value);
435 priv->raw_device = *raw_device;
436 break;
437 #if defined(HAVE_GUDEV)
438 case PROP_UDEV_DEVICE:
439 priv->udev_device = g_value_dup_object (value);
440 break;
441 #else
442 case PROP_UDI:
443 priv->udi = g_value_dup_string (value);
444 break;
445 #endif
446 default:
447 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
448 break;
449 }
450 }
451
452 static void
453 rb_mtp_source_get_property (GObject *object,
454 guint prop_id,
455 GValue *value,
456 GParamSpec *pspec)
457 {
458 RBMtpSourcePrivate *priv = MTP_SOURCE_GET_PRIVATE (object);
459
460 switch (prop_id) {
461 case PROP_RAW_DEVICE:
462 g_value_set_pointer (value, &priv->raw_device);
463 break;
464 #if defined(HAVE_GUDEV)
465 case PROP_UDEV_DEVICE:
466 g_value_set_object (value, priv->udev_device);
467 break;
468 #else
469 case PROP_UDI:
470 g_value_set_string (value, priv->udi);
471 break;
472 #endif
473 case PROP_DEVICE_SERIAL:
474 g_value_set_string (value, priv->serial);
475 break;
476 default:
477 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
478 break;
479 }
480 }
481
482 #if defined(HAVE_GUDEV)
483 static void
484 remount_done_cb (GObject *object, GAsyncResult *result, gpointer no)
485 {
486 gboolean ok;
487 GError *error = NULL;
488
489 ok = g_volume_mount_finish (G_VOLUME (object), result, &error);
490 if (ok) {
491 rb_debug ("volume remounted successfully");
492 } else {
493 g_warning ("Unable to remount MTP device: %s", error->message);
494 g_error_free (error);
495 }
496 g_object_unref (object);
497 }
498 #endif
499
500 static void
501 rb_mtp_source_dispose (GObject *object)
502 {
503 RBMtpSource *source = RB_MTP_SOURCE (object);
504 RBMtpSourcePrivate *priv = MTP_SOURCE_GET_PRIVATE (source);
505 RhythmDBEntryType *entry_type;
506 RhythmDB *db;
507
508 if (priv->device_thread != NULL) {
509 g_object_unref (priv->device_thread);
510 priv->device_thread = NULL;
511 }
512
513 #if defined(HAVE_GUDEV)
514 if (priv->remount_volume != NULL) {
515 rb_debug ("remounting gvfs volume for mtp device");
516 /* the callback will unref remount_volume */
517 g_volume_mount (priv->remount_volume,
518 G_MOUNT_MOUNT_NONE,
519 NULL,
520 NULL,
521 remount_done_cb,
522 NULL);
523 priv->remount_volume = NULL;
524 }
525 #endif
526 if (priv->art_store != NULL) {
527 g_object_unref (priv->art_store);
528 priv->art_store = NULL;
529 }
530
531 db = get_db_for_source (source);
532
533 g_object_get (G_OBJECT (source), "entry-type", &entry_type, NULL);
534 rhythmdb_entry_delete_by_type (db, entry_type);
535 g_object_unref (entry_type);
536
537 rhythmdb_commit (db);
538 g_object_unref (db);
539
540 G_OBJECT_CLASS (rb_mtp_source_parent_class)->dispose (object);
541 }
542
543 static void
544 rb_mtp_source_finalize (GObject *object)
545 {
546 RBMtpSourcePrivate *priv = MTP_SOURCE_GET_PRIVATE (object);
547
548 g_hash_table_destroy (priv->entry_map);
549 g_hash_table_destroy (priv->track_transfer_map); /* probably need to destroy the tracks too.. */
550
551 #if defined(HAVE_GUDEV)
552 if (priv->udev_device) {
553 g_object_unref (G_OBJECT (priv->udev_device));
554 }
555 #else
556 g_free (priv->udi);
557 #endif
558 g_free (priv->manufacturer);
559 g_free (priv->device_version);
560 g_free (priv->model_name);
561 g_free (priv->serial);
562
563 G_OBJECT_CLASS (rb_mtp_source_parent_class)->finalize (object);
564 }
565
566 RBSource *
567 rb_mtp_source_new (RBShell *shell,
568 GObject *plugin,
569 #if defined(HAVE_GUDEV)
570 GUdevDevice *udev_device,
571 #else
572 const char *udi,
573 #endif
574 LIBMTP_raw_device_t *device)
575 {
576 RBMtpSource *source = NULL;
577 RhythmDBEntryType *entry_type;
578 RhythmDB *db = NULL;
579 GSettings *settings;
580 char *name = NULL;
581
582 g_object_get (shell, "db", &db, NULL);
583 name = g_strdup_printf ("MTP-%u-%d", device->bus_location, device->devnum);
584
585 entry_type = g_object_new (RHYTHMDB_TYPE_ENTRY_TYPE,
586 "db", db,
587 "name", name,
588 "save-to-disk", FALSE,
589 "category", RHYTHMDB_ENTRY_NORMAL,
590 NULL);
591 g_free (name);
592 g_object_unref (db);
593
594 settings = g_settings_new ("org.gnome.rhythmbox.plugins.mtpdevice");
595 source = RB_MTP_SOURCE (g_object_new (RB_TYPE_MTP_SOURCE,
596 "plugin", plugin,
597 "entry-type", entry_type,
598 "shell", shell,
599 "visibility", TRUE,
600 "raw-device", device,
601 #if defined(HAVE_GUDEV)
602 "udev-device", udev_device,
603 #else
604 "udi", udi,
605 #endif
606 "load-status", RB_SOURCE_LOAD_STATUS_LOADING,
607 "settings", g_settings_get_child (settings, "source"),
608 "toolbar-path", "/MTPSourceToolBar",
609 "name", _("Media Player"),
610 NULL));
611 g_object_unref (settings);
612
613 rb_shell_register_entry_type_for_source (shell, RB_SOURCE (source), entry_type);
614
615 return RB_SOURCE (source);
616 }
617
618 static void
619 update_free_space_cb (LIBMTP_mtpdevice_t *device, RBMtpSource *source)
620 {
621 RBMtpSourcePrivate *priv = MTP_SOURCE_GET_PRIVATE (source);
622 LIBMTP_devicestorage_t *storage;
623 int ret;
624
625 ret = LIBMTP_Get_Storage (device, LIBMTP_STORAGE_SORTBY_NOTSORTED);
626 if (ret != 0) {
627 rb_mtp_thread_report_errors (priv->device_thread, FALSE);
628 }
629
630 /* probably need a lock for this.. */
631 priv->free_space = 0;
632 for (storage = device->storage; storage != NULL; storage = storage->next) {
633 priv->free_space += storage->FreeSpaceInBytes;
634 }
635 }
636
637 static void
638 queue_free_space_update (RBMtpSource *source)
639 {
640 RBMtpSourcePrivate *priv = MTP_SOURCE_GET_PRIVATE (source);
641 rb_mtp_thread_queue_callback (priv->device_thread,
642 (RBMtpThreadCallback) update_free_space_cb, source, NULL);
643 }
644
645 static void
646 entry_set_string_prop (RhythmDB *db,
647 RhythmDBEntry *entry,
648 RhythmDBPropType propid,
649 const char *str)
650 {
651 GValue value = {0,};
652
653 if (str == NULL || (g_utf8_validate (str, -1, NULL) == FALSE)) {
654 str = _("Unknown");
655 }
656
657 g_value_init (&value, G_TYPE_STRING);
658 g_value_set_static_string (&value, str);
659 rhythmdb_entry_set (RHYTHMDB (db), entry, propid, &value);
660 g_value_unset (&value);
661 }
662
663 static RhythmDBEntry *
664 add_mtp_track_to_db (RBMtpSource *source,
665 RhythmDB *db,
666 LIBMTP_track_t *track)
667 {
668 RhythmDBEntry *entry = NULL;
669 RhythmDBEntryType *entry_type;
670 RBMtpSourcePrivate *priv = MTP_SOURCE_GET_PRIVATE (source);
671 char *name = NULL;
672
673 /* ignore everything except audio (allow audio/video types too, since they're probably pretty common) */
674 if (!(LIBMTP_FILETYPE_IS_AUDIO (track->filetype) || LIBMTP_FILETYPE_IS_AUDIOVIDEO (track->filetype))) {
675 rb_debug ("ignoring non-audio item %d (filetype %s)",
676 track->item_id,
677 LIBMTP_Get_Filetype_Description (track->filetype));
678 return NULL;
679 }
680
681 /* Set URI */
682 g_object_get (G_OBJECT (source), "entry-type", &entry_type, NULL);
683 name = g_strdup_printf ("xrbmtp://%i/%s", track->item_id, track->filename);
684 entry = rhythmdb_entry_new (RHYTHMDB (db), entry_type, name);
685 g_free (name);
686 g_object_unref (entry_type);
687
688 if (entry == NULL) {
689 rb_debug ("cannot create entry %i", track->item_id);
690 g_object_unref (G_OBJECT (db));
691 return NULL;
692 }
693
694 /* Set track number */
695 if (track->tracknumber != 0) {
696 GValue value = {0, };
697 g_value_init (&value, G_TYPE_ULONG);
698 g_value_set_ulong (&value, track->tracknumber);
699 rhythmdb_entry_set (RHYTHMDB (db), entry,
700 RHYTHMDB_PROP_TRACK_NUMBER,
701 &value);
702 g_value_unset (&value);
703 }
704
705 /* Set length */
706 if (track->duration != 0) {
707 GValue value = {0, };
708 g_value_init (&value, G_TYPE_ULONG);
709 g_value_set_ulong (&value, track->duration/1000);
710 rhythmdb_entry_set (RHYTHMDB (db), entry,
711 RHYTHMDB_PROP_DURATION,
712 &value);
713 g_value_unset (&value);
714 }
715
716 /* Set file size */
717 if (track->filesize != 0) {
718 GValue value = {0, };
719 g_value_init (&value, G_TYPE_UINT64);
720 g_value_set_uint64 (&value, track->filesize);
721 rhythmdb_entry_set (RHYTHMDB (db), entry,
722 RHYTHMDB_PROP_FILE_SIZE,
723 &value);
724 g_value_unset (&value);
725 }
726
727 /* Set playcount */
728 if (track->usecount != 0) {
729 GValue value = {0, };
730 g_value_init (&value, G_TYPE_ULONG);
731 g_value_set_ulong (&value, track->usecount);
732 rhythmdb_entry_set (RHYTHMDB (db), entry,
733 RHYTHMDB_PROP_PLAY_COUNT,
734 &value);
735 g_value_unset (&value);
736 }
737 /* Set rating */
738 if (track->rating != 0) {
739 GValue value = {0, };
740 g_value_init (&value, G_TYPE_DOUBLE);
741 g_value_set_double (&value, track->rating/20);
742 rhythmdb_entry_set (RHYTHMDB (db), entry,
743 RHYTHMDB_PROP_RATING,
744 &value);
745 g_value_unset (&value);
746 }
747 /* Set release date */
748 if (track->date != NULL && track->date[0] != '\0') {
749 GTimeVal tv;
750 if (g_time_val_from_iso8601 (track->date, &tv)) {
751 GDate d;
752 GValue value = {0, };
753 g_value_init (&value, G_TYPE_ULONG);
754 g_date_set_time_val (&d, &tv);
755 g_value_set_ulong (&value, g_date_get_julian (&d));
756 rhythmdb_entry_set (RHYTHMDB (db), entry, RHYTHMDB_PROP_DATE, &value);
757 g_value_unset (&value);
758 }
759 }
760
761 /* Set title */
762 entry_set_string_prop (RHYTHMDB (db), entry, RHYTHMDB_PROP_TITLE, track->title);
763
764 /* Set album, artist and genre from MTP */
765 entry_set_string_prop (RHYTHMDB (db), entry, RHYTHMDB_PROP_ARTIST, track->artist);
766 entry_set_string_prop (RHYTHMDB (db), entry, RHYTHMDB_PROP_ALBUM, track->album);
767 entry_set_string_prop (RHYTHMDB (db), entry, RHYTHMDB_PROP_GENRE, track->genre);
768
769 g_hash_table_insert (priv->entry_map, entry, track);
770 rhythmdb_commit (RHYTHMDB (db));
771
772 return entry;
773 }
774
775 typedef struct {
776 RBMtpSource *source;
777 char *name;
778 guint16 *types;
779 guint16 num_types;
780 } DeviceOpenedData;
781
782 static gboolean
783 device_opened_idle (DeviceOpenedData *data)
784 {
785 RBMtpSourcePrivate *priv = MTP_SOURCE_GET_PRIVATE (data->source);
786 int i;
787 GstEncodingTarget *target;
788 GList *profiles = NULL;
789
790 if (data->name != NULL) {
791 g_object_set (data->source, "name", data->name, NULL);
792 }
793
794 /* when the source name changes after this, try to update the device name */
795 g_signal_connect (G_OBJECT (data->source), "notify::name",
796 (GCallback)rb_mtp_source_name_changed_cb, NULL);
797
798 rb_media_player_source_load (RB_MEDIA_PLAYER_SOURCE (data->source));
799
800 for (i = 0; i < data->num_types; i++) {
801 const char *mediatype;
802 gboolean prepend;
803 if (i <= LIBMTP_FILETYPE_UNKNOWN) {
804 priv->supported_types[data->types[i]] = 1;
805 }
806
807 mediatype = NULL;
808 prepend = FALSE;
809 switch (data->types[i]) {
810 case LIBMTP_FILETYPE_WAV:
811 /*mediatype = "audio/x-wav";*/
812 /* don't bother including this? */
813 break;
814 case LIBMTP_FILETYPE_MP3:
815 mediatype = "audio/mpeg";
816 prepend = TRUE; /* always goes first if supported */
817 break;
818 case LIBMTP_FILETYPE_WMA:
819 mediatype = "audio/x-wma";
820 break;
821 case LIBMTP_FILETYPE_OGG:
822 mediatype = "audio/x-vorbis";
823 break;
824 case LIBMTP_FILETYPE_MP4:
825 case LIBMTP_FILETYPE_M4A:
826 case LIBMTP_FILETYPE_AAC:
827 mediatype = "audio/x-aac";
828 break;
829 case LIBMTP_FILETYPE_WMV:
830 mediatype = "audio/x-ms-wmv"; /* media type? */
831 break;
832 case LIBMTP_FILETYPE_ASF:
833 mediatype = "video/x-ms-asf"; /* media type? */
834 break;
835 case LIBMTP_FILETYPE_FLAC:
836 mediatype = "audio/x-flac";
837 break;
838
839 case LIBMTP_FILETYPE_JPEG:
840 rb_debug ("JPEG (album art) supported");
841 priv->album_art_supported = TRUE;
842 break;
843
844 default:
845 rb_debug ("unknown libmtp filetype %s supported", LIBMTP_Get_Filetype_Description (data->types[i]));
846 break;
847 }
848
849 if (mediatype != NULL) {
850 GstEncodingProfile *profile;
851 profile = rb_gst_get_encoding_profile (mediatype);
852 if (profile != NULL) {
853 rb_debug ("media type %s supported", mediatype);
854 if (prepend) {
855 profiles = g_list_prepend (profiles, profile);
856 } else {
857 profiles = g_list_append (profiles, profile);
858 }
859 } else {
860 rb_debug ("no encoding profile for supported media type %s", mediatype);
861 }
862 }
863 }
864
865 if (priv->album_art_supported) {
866 priv->art_store = rb_ext_db_new ("album-art");
867 }
868
869 target = gst_encoding_target_new ("mtpdevice", "device", "", profiles);
870 g_object_set (data->source, "encoding-target", target, NULL);
871
872 g_object_unref (data->source);
873 free (data->types);
874 g_free (data->name);
875 g_free (data);
876
877 return FALSE;
878 }
879
880 static gboolean
881 device_open_failed_idle (RBMtpSource *source)
882 {
883 /* libmtp doesn't give us a useful error message in this case, so
884 * all we can offer is this generic message.
885 */
886 RBMtpSourcePrivate *priv = MTP_SOURCE_GET_PRIVATE (source);
887 rb_error_dialog (NULL,
888 _("Media player device error"),
889 /* Translators: first %s is the device manufacturer,
890 * second is the product name.
891 */
892 _("Unable to open the %s %s device"),
893 priv->raw_device.device_entry.vendor,
894 priv->raw_device.device_entry.product);
895 rb_display_page_delete_thyself (RB_DISPLAY_PAGE (source));
896 g_object_unref (source);
897 return FALSE;
898 }
899
900 static gboolean
901 device_open_ignore_idle (DeviceOpenedData *data)
902 {
903 rb_display_page_delete_thyself (RB_DISPLAY_PAGE (data->source));
904 g_object_unref (data->source);
905 free (data->types);
906 g_free (data->name);
907 g_free (data);
908 return FALSE;
909 }
910
911 /* this callback runs on the device handling thread, so it can call libmtp directly */
912 static void
913 mtp_device_open_cb (LIBMTP_mtpdevice_t *device, RBMtpSource *source)
914 {
915 RBMtpSourcePrivate *priv = MTP_SOURCE_GET_PRIVATE (source);
916 gboolean has_audio = FALSE;
917 DeviceOpenedData *data;
918
919 if (device == NULL) {
920 /* can't delete the source on this thread, so move it to the main thread */
921 g_idle_add ((GSourceFunc) device_open_failed_idle, g_object_ref (source));
922 return;
923 }
924
925 /* set the source name to match the device, ignoring some
926 * particular broken device names.
927 */
928 data = g_new0 (DeviceOpenedData, 1);
929 data->source = g_object_ref (source);
930 data->name = LIBMTP_Get_Friendlyname (device);
931 if (data->name == NULL || strcmp (data->name, "?????") == 0) {
932 g_free (data->name);
933 data->name = LIBMTP_Get_Modelname (device);
934 }
935 if (data->name == NULL) {
936 data->name = g_strdup (_("Digital Audio Player"));
937 }
938
939 /* get some other device information that doesn't change */
940 priv->manufacturer = LIBMTP_Get_Manufacturername (device);
941 priv->device_version = LIBMTP_Get_Deviceversion (device);
942 priv->model_name = LIBMTP_Get_Modelname (device);
943 priv->serial = LIBMTP_Get_Serialnumber (device);
944
945 /* calculate the device capacity */
946 priv->capacity = 0;
947 if (LIBMTP_Get_Storage (device, LIBMTP_STORAGE_SORTBY_NOTSORTED) == 0) {
948 LIBMTP_devicestorage_t *storage;
949 for (storage = device->storage;
950 storage != NULL;
951 storage = storage->next) {
952 priv->capacity += storage->MaxCapacity;
953 }
954 }
955
956 update_free_space_cb (device, RB_MTP_SOURCE (source));
957
958 /* figure out the set of formats supported by the device, ensuring there's at least
959 * one audio format aside from WAV. the purpose of this is to exclude cameras and other
960 * MTP devices that aren't interesting to us.
961 */
962 if (LIBMTP_Get_Supported_Filetypes (device, &data->types, &data->num_types) != 0) {
963 rb_mtp_thread_report_errors (priv->device_thread, FALSE);
964 } else {
965 int i;
966 for (i = 0; i < data->num_types; i++) {
967 if (data->types[i] != LIBMTP_FILETYPE_WAV && LIBMTP_FILETYPE_IS_AUDIO (data->types[i])) {
968 has_audio = TRUE;
969 break;
970 }
971 }
972 }
973
974 if (has_audio == FALSE) {
975 rb_debug ("device doesn't support any audio formats");
976 g_idle_add ((GSourceFunc) device_open_ignore_idle, data);
977 return;
978 }
979
980 g_idle_add ((GSourceFunc) device_opened_idle, data);
981
982 /* now get the track list */
983 rb_mtp_thread_get_track_list (priv->device_thread, (RBMtpTrackListCallback) mtp_tracklist_cb, g_object_ref (source), g_object_unref);
984 }
985
986 static gboolean
987 device_loaded_idle (RBMtpSource *source)
988 {
989 g_object_set (source, "load-status", RB_SOURCE_LOAD_STATUS_LOADED, NULL);
990 rb_transfer_target_transfer (RB_TRANSFER_TARGET (source), NULL, FALSE);
991 return FALSE;
992 }
993
994 static void
995 mtp_tracklist_cb (LIBMTP_track_t *tracks, RBMtpSource *source)
996 {
997 RhythmDB *db = NULL;
998 LIBMTP_track_t *track;
999
1000 /* add tracks to database */
1001 db = get_db_for_source (source);
1002 for (track = tracks; track != NULL; track = track->next) {
1003 add_mtp_track_to_db (source, db, track);
1004 }
1005 g_object_unref (db);
1006
1007 g_idle_add ((GSourceFunc) device_loaded_idle, source);
1008 }
1009
1010 static char *
1011 gdate_to_char (GDate* date)
1012 {
1013 return g_strdup_printf ("%04i%02i%02iT000000.0",
1014 g_date_get_year (date),
1015 g_date_get_month (date),
1016 g_date_get_day (date));
1017 }
1018
1019 static LIBMTP_filetype_t
1020 media_type_to_filetype (RBMtpSource *source, const char *media_type)
1021 {
1022 RBMtpSourcePrivate *priv = MTP_SOURCE_GET_PRIVATE (source);
1023
1024 if (!strcmp (media_type, "audio/mpeg")) {
1025 return LIBMTP_FILETYPE_MP3;
1026 } else if (!strcmp (media_type, "audio/x-wav")) {
1027 return LIBMTP_FILETYPE_WAV;
1028 } else if (!strcmp (media_type, "audio/x-vorbis")) {
1029 return LIBMTP_FILETYPE_OGG;
1030 } else if (!strcmp (media_type, "audio/x-aac")) {
1031 /* try a few different filetypes that might work */
1032 if (priv->supported_types[LIBMTP_FILETYPE_M4A])
1033 return LIBMTP_FILETYPE_M4A;
1034 else if (!priv->supported_types[LIBMTP_FILETYPE_AAC] && priv->supported_types[LIBMTP_FILETYPE_MP4])
1035 return LIBMTP_FILETYPE_MP4;
1036 else
1037 return LIBMTP_FILETYPE_AAC;
1038
1039 } else if (!strcmp (media_type, "audio/x-wma")) {
1040 return LIBMTP_FILETYPE_WMA;
1041 } else if (!strcmp (media_type, "video/x-ms-asf")) {
1042 return LIBMTP_FILETYPE_ASF;
1043 } else if (!strcmp (media_type, "audio/x-flac")) {
1044 return LIBMTP_FILETYPE_FLAC;
1045 } else {
1046 rb_debug ("\"%s\" is not a supported media_type", media_type);
1047 return LIBMTP_FILETYPE_UNKNOWN;
1048 }
1049 }
1050
1051 static void
1052 impl_delete (RBSource *source)
1053 {
1054 GList *sel;
1055 RBEntryView *songs;
1056
1057 songs = rb_source_get_entry_view (source);
1058 sel = rb_entry_view_get_selected_entries (songs);
1059 impl_delete_entries (RB_MEDIA_PLAYER_SOURCE (source), sel, NULL, NULL, NULL);
1060 rb_list_destroy_free (sel, (GDestroyNotify) rhythmdb_entry_unref);
1061 }
1062
1063 static gboolean
1064 impl_uri_is_source (RBSource *source, const char *uri)
1065 {
1066 RBMtpSourcePrivate *priv = MTP_SOURCE_GET_PRIVATE (source);
1067 char *source_uri;
1068 gboolean result;
1069
1070 if (g_str_has_prefix (uri, "gphoto2://") == FALSE)
1071 return FALSE;
1072
1073 source_uri = g_strdup_printf ("gphoto2://[usb:%03d,%03d]/",
1074 priv->raw_device.bus_location,
1075 priv->raw_device.devnum);
1076 result = g_str_has_prefix (uri, source_uri);
1077 g_free (source_uri);
1078 return result;
1079 }
1080
1081 static RBTrackTransferBatch *
1082 impl_paste (RBSource *source, GList *entries)
1083 {
1084 gboolean defer;
1085 defer = (ensure_loaded (RB_MTP_SOURCE (source)) == FALSE);
1086 return rb_transfer_target_transfer (RB_TRANSFER_TARGET (source), entries, defer);
1087 }
1088
1089 static gboolean
1090 impl_show_popup (RBDisplayPage *page)
1091 {
1092 _rb_display_page_show_popup (page, "/MTPSourcePopup");
1093 return TRUE;
1094 }
1095
1096 static RhythmDB *
1097 get_db_for_source (RBMtpSource *source)
1098 {
1099 RBShell *shell = NULL;
1100 RhythmDB *db = NULL;
1101
1102 g_object_get (source, "shell", &shell, NULL);
1103 g_object_get (shell, "db", &db, NULL);
1104 g_object_unref (shell);
1105
1106 return db;
1107 }
1108
1109 static void
1110 art_request_cb (RBExtDBKey *key, const char *filename, GValue *data, RBMtpSource *source)
1111 {
1112 RBMtpSourcePrivate *priv = MTP_SOURCE_GET_PRIVATE (source);
1113
1114 if (G_VALUE_HOLDS (data, GDK_TYPE_PIXBUF)) {
1115 GdkPixbuf *pixbuf;
1116 const char *album_name;
1117
1118 pixbuf = GDK_PIXBUF (g_value_get_object (data));
1119
1120 album_name = rb_ext_db_key_get_field (key, "album");
1121 rb_mtp_thread_set_album_image (priv->device_thread, album_name, pixbuf);
1122 queue_free_space_update (source);
1123 }
1124 }
1125
1126 static gboolean
1127 impl_track_added (RBTransferTarget *target,
1128 RhythmDBEntry *entry,
1129 const char *dest,
1130 guint64 filesize,
1131 const char *media_type)
1132 {
1133 LIBMTP_track_t *track = NULL;
1134 RBMtpSourcePrivate *priv = MTP_SOURCE_GET_PRIVATE (target);
1135 RhythmDB *db;
1136
1137 track = g_hash_table_lookup (priv->track_transfer_map, dest);
1138 if (track == NULL) {
1139 rb_debug ("track-added called, but can't find a track for dest URI %s", dest);
1140 return FALSE;
1141 }
1142 g_hash_table_remove (priv->track_transfer_map, dest);
1143
1144 if (strcmp (track->album, _("Unknown")) != 0) {
1145 rb_mtp_thread_add_to_album (priv->device_thread, track, track->album);
1146
1147 if (priv->album_art_supported) {
1148 RBExtDBKey *key;
1149
1150 /* need to do this in an idle handler? */
1151 key = rb_ext_db_key_create_lookup ("album", track->album);
1152 rb_ext_db_key_add_field (key, "artist", track->artist);
1153 rb_ext_db_request (priv->art_store,
1154 key,
1155 (RBExtDBRequestCallback) art_request_cb,
1156 g_object_ref (target),
1157 (GDestroyNotify) g_object_unref);
1158 rb_ext_db_key_free (key);
1159 }
1160 }
1161
1162 db = get_db_for_source (RB_MTP_SOURCE (target));
1163 add_mtp_track_to_db (RB_MTP_SOURCE (target), db, track);
1164 g_object_unref (db);
1165
1166 queue_free_space_update (RB_MTP_SOURCE (target));
1167 return FALSE;
1168 }
1169
1170 static gboolean
1171 impl_track_add_error (RBTransferTarget *target,
1172 RhythmDBEntry *entry,
1173 const char *dest,
1174 GError *error)
1175 {
1176 RBMtpSourcePrivate *priv = MTP_SOURCE_GET_PRIVATE (target);
1177 /* we don't actually do anything with the error here, we just need to clean up the transfer map */
1178 LIBMTP_track_t *track = g_hash_table_lookup (priv->track_transfer_map, dest);
1179 if (track != NULL) {
1180 LIBMTP_destroy_track_t (track);
1181 g_hash_table_remove (priv->track_transfer_map, dest);
1182 } else {
1183 rb_debug ("track-add-error called, but can't find a track for dest URI %s", dest);
1184 }
1185
1186 return TRUE;
1187 }
1188
1189 static void
1190 sanitize_for_mtp (char *str)
1191 {
1192 rb_sanitize_path_for_msdos_filesystem (str);
1193 g_strdelimit (str, "/", '_');
1194 }
1195
1196 static void
1197 prepare_encoder_sink_cb (RBEncoderFactory *factory,
1198 const char *stream_uri,
1199 GObject *sink,
1200 RBMtpSource *source)
1201 {
1202 RBMtpSourcePrivate *priv = MTP_SOURCE_GET_PRIVATE (source);
1203 RhythmDBEntry *entry;
1204 RhythmDB *db;
1205 LIBMTP_track_t *track;
1206 char **bits;
1207 char *extension;
1208 char *track_str;
1209 LIBMTP_filetype_t filetype;
1210 gulong track_id;
1211 GDate d;
1212 char **folder_path;
1213
1214 /* make sure this stream is for a file on our device */
1215 if (g_str_has_prefix (stream_uri, "xrbmtp://") == FALSE)
1216 return;
1217
1218 /* extract the entry ID, extension, and MTP filetype from the URI */
1219 bits = g_strsplit (stream_uri + strlen ("xrbmtp://"), "/", 3);
1220 track_id = strtoul (bits[0], NULL, 0);
1221 extension = g_strdup (bits[1]);
1222 filetype = strtoul (bits[2], NULL, 0);
1223 g_strfreev (bits);
1224
1225 db = get_db_for_source (source);
1226 entry = rhythmdb_entry_lookup_by_id (db, track_id);
1227 g_object_unref (db);
1228 if (entry == NULL) {
1229 g_free (extension);
1230 return;
1231 }
1232
1233 track = LIBMTP_new_track_t ();
1234 track->title = rhythmdb_entry_dup_string (entry, RHYTHMDB_PROP_TITLE);
1235 track->album = rhythmdb_entry_dup_string (entry, RHYTHMDB_PROP_ALBUM);
1236 track->artist = rhythmdb_entry_dup_string (entry, RHYTHMDB_PROP_ARTIST);
1237 track->genre = rhythmdb_entry_dup_string (entry, RHYTHMDB_PROP_GENRE);
1238
1239 /* build up device filename */
1240 if (rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DISC_NUMBER) > 0) {
1241 track_str = g_strdup_printf ("%.2lu.%.2lu ",
1242 rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DISC_NUMBER),
1243 rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_TRACK_NUMBER));
1244 } else {
1245 track_str = g_strdup_printf ("%.2lu ",
1246 rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_TRACK_NUMBER));
1247 }
1248
1249 track->filename = g_strdup_printf ("%s%s - %s.%s",
1250 track_str,
1251 rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ARTIST),
1252 rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_TITLE),
1253 extension);
1254 g_free (track_str);
1255 g_free (extension);
1256
1257 /* construct folder path: artist/album */
1258 folder_path = g_new0 (char *, 3);
1259 folder_path[0] = rhythmdb_entry_dup_string (entry, RHYTHMDB_PROP_ALBUM_ARTIST);
1260 if (folder_path[0] == NULL || folder_path[0][0] == '\0') {
1261 g_free (folder_path[0]);
1262 folder_path[0] = rhythmdb_entry_dup_string (entry, RHYTHMDB_PROP_ARTIST);
1263 }
1264 folder_path[1] = rhythmdb_entry_dup_string (entry, RHYTHMDB_PROP_ALBUM);
1265
1266 /* ensure the filename is safe for FAT filesystems and doesn't contain slashes */
1267 sanitize_for_mtp (track->filename);
1268 sanitize_for_mtp (folder_path[0]);
1269 sanitize_for_mtp (folder_path[1]);
1270
1271 if (rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DATE) > 0) {
1272 g_date_set_julian (&d, rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DATE));
1273 track->date = gdate_to_char (&d);
1274 }
1275 track->tracknumber = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_TRACK_NUMBER);
1276 track->duration = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DURATION) * 1000;
1277 track->rating = rhythmdb_entry_get_double (entry, RHYTHMDB_PROP_RATING) * 20;
1278 track->usecount = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_PLAY_COUNT);
1279
1280 track->filetype = filetype;
1281
1282 g_object_set (sink,
1283 "device-thread", priv->device_thread,
1284 "folder-path", folder_path,
1285 "mtp-track", track,
1286 NULL);
1287 rhythmdb_entry_unref (entry);
1288 g_strfreev (folder_path);
1289
1290 g_hash_table_insert (priv->track_transfer_map, g_strdup (stream_uri), track);
1291 }
1292
1293 static char *
1294 impl_build_dest_uri (RBTransferTarget *target,
1295 RhythmDBEntry *entry,
1296 const char *media_type,
1297 const char *extension)
1298 {
1299 gulong id;
1300 char *uri;
1301 LIBMTP_filetype_t filetype;
1302
1303 if (media_type == NULL) {
1304 media_type = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MEDIA_TYPE);
1305 }
1306 filetype = media_type_to_filetype (RB_MTP_SOURCE (target), media_type);
1307 rb_debug ("using libmtp filetype %d (%s) for source media type %s",
1308 filetype,
1309 LIBMTP_Get_Filetype_Description (filetype),
1310 media_type);
1311
1312 /* the prepare-sink callback needs the entry ID to set up the
1313 * upload data, and we want to use the supplied extension for
1314 * the filename on the device.
1315 *
1316 * this is pretty ugly - it'd be much nicer to have a source-defined
1317 * structure that got passed around (or was accessible from) the various
1318 * hooks and methods called during the track transfer process. probably
1319 * something to address in my horribly stalled track transfer rewrite..
1320 *
1321 * the structure would either be created when queuing the track for transfer,
1322 * or here; passed to any prepare-source or prepare-sink callbacks for the
1323 * encoder; and then passed to whatever gets called when the transfer is complete.
1324 */
1325 id = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_ENTRY_ID);
1326 if (extension == NULL) {
1327 extension = "";
1328 }
1329 uri = g_strdup_printf ("xrbmtp://%lu/%s/%d", id, extension, filetype);
1330 return uri;
1331 }
1332
1333
1334 static void
1335 impl_get_entries (RBMediaPlayerSource *source, const char *category, GHashTable *map)
1336 {
1337 RBMtpSourcePrivate *priv = MTP_SOURCE_GET_PRIVATE (source);
1338 GHashTableIter iter;
1339 gpointer key, value;
1340 gboolean podcast;
1341
1342 /* sync category mapping is a bit hackish here, as MTP doesn't categorise
1343 * tracks itself. matching specific genres is about the best we can do.
1344 */
1345 podcast = (g_str_equal (category, SYNC_CATEGORY_PODCAST));
1346
1347 g_hash_table_iter_init (&iter, priv->entry_map);
1348 while (g_hash_table_iter_next (&iter, &key, &value)) {
1349 LIBMTP_track_t *track = value;
1350
1351 if ((g_strcmp0 (track->genre, "Podcast") == 0) == podcast) {
1352 RhythmDBEntry *entry = key;
1353 _rb_media_player_source_add_to_map (map, entry);
1354 }
1355 }
1356 }
1357
1358 static guint64
1359 impl_get_capacity (RBMediaPlayerSource *source)
1360 {
1361 RBMtpSourcePrivate *priv = MTP_SOURCE_GET_PRIVATE (source);
1362 return priv->capacity;
1363 }
1364
1365 static guint64
1366 impl_get_free_space (RBMediaPlayerSource *source)
1367 {
1368 RBMtpSourcePrivate *priv = MTP_SOURCE_GET_PRIVATE (source);
1369 /* probably need a lock for this */
1370 return priv->free_space;
1371 }
1372
1373 typedef struct {
1374 gboolean actually_free;
1375 GHashTable *check_folders;
1376 RBMediaPlayerSource *source;
1377 RBMediaPlayerSourceDeleteCallback callback;
1378 gpointer callback_data;
1379 GDestroyNotify destroy_data;
1380 } TracksDeletedCallbackData;
1381
1382 static void
1383 free_delete_data (TracksDeletedCallbackData *data)
1384 {
1385 if (data->actually_free == FALSE) {
1386 return;
1387 }
1388
1389 g_hash_table_destroy (data->check_folders);
1390 g_object_unref (data->source);
1391 if (data->destroy_data) {
1392 data->destroy_data (data->callback_data);
1393 }
1394 g_free (data);
1395 }
1396
1397 static gboolean
1398 delete_done_idle_cb (TracksDeletedCallbackData *data)
1399 {
1400 if (data->callback) {
1401 data->callback (data->source, data->callback_data);
1402 }
1403
1404 data->actually_free = TRUE;
1405 free_delete_data (data);
1406 return FALSE;
1407 }
1408
1409 static void
1410 delete_done_cb (LIBMTP_mtpdevice_t *device, TracksDeletedCallbackData *data)
1411 {
1412 LIBMTP_folder_t *folders;
1413 LIBMTP_file_t *files;
1414
1415 data->actually_free = FALSE;
1416 update_free_space_cb (device, RB_MTP_SOURCE (data->source));
1417
1418 /* if any of the folders we just deleted from are now empty, delete them */
1419 folders = LIBMTP_Get_Folder_List (device);
1420 files = LIBMTP_Get_Filelisting_With_Callback (device, NULL, NULL);
1421 if (folders != NULL) {
1422 GHashTableIter iter;
1423 gpointer key;
1424 g_hash_table_iter_init (&iter, data->check_folders);
1425 while (g_hash_table_iter_next (&iter, &key, NULL)) {
1426 LIBMTP_folder_t *f;
1427 LIBMTP_folder_t *c;
1428 LIBMTP_file_t *file;
1429 uint32_t folder_id = GPOINTER_TO_UINT(key);
1430
1431 while (folder_id != device->default_music_folder && folder_id != 0) {
1432
1433 f = LIBMTP_Find_Folder (folders, folder_id);
1434 if (f == NULL) {
1435 rb_debug ("unable to find folder %u", folder_id);
1436 break;
1437 }
1438
1439 /* don't delete folders with children that we didn't just delete */
1440 for (c = f->child; c != NULL; c = c->sibling) {
1441 if (g_hash_table_lookup (data->check_folders,
1442 GUINT_TO_POINTER (c->folder_id)) == NULL) {
1443 break;
1444 }
1445 }
1446 if (c != NULL) {
1447 rb_debug ("folder %s has children", f->name);
1448 break;
1449 }
1450
1451 /* don't delete folders that contain files */
1452 for (file = files; file != NULL; file = file->next) {
1453 if (file->parent_id == folder_id) {
1454 break;
1455 }
1456 }
1457
1458 if (file != NULL) {
1459 rb_debug ("folder %s contains at least one file: %s", f->name, file->filename);
1460 break;
1461 }
1462
1463 /* ok, the folder is empty */
1464 rb_debug ("deleting empty folder %s", f->name);
1465 LIBMTP_Delete_Object (device, f->folder_id);
1466
1467 /* if the folder we just deleted has siblings, the parent
1468 * can't be empty.
1469 */
1470 if (f->sibling != NULL) {
1471 rb_debug ("folder %s has siblings, can't delete parent", f->name);
1472 break;
1473 }
1474 folder_id = f->parent_id;
1475 }
1476 }
1477
1478 LIBMTP_destroy_folder_t (folders);
1479 } else {
1480 rb_debug ("unable to get device folder list");
1481 }
1482
1483 /* clean up the file list */
1484 while (files != NULL) {
1485 LIBMTP_file_t *n;
1486
1487 n = files->next;
1488 LIBMTP_destroy_file_t (files);
1489 files = n;
1490 }
1491
1492 g_idle_add ((GSourceFunc) delete_done_idle_cb, data);
1493 }
1494
1495 static void
1496 impl_delete_entries (RBMediaPlayerSource *source,
1497 GList *entries,
1498 RBMediaPlayerSourceDeleteCallback callback,
1499 gpointer user_data,
1500 GDestroyNotify destroy_data)
1501 {
1502 RBMtpSourcePrivate *priv = MTP_SOURCE_GET_PRIVATE (source);
1503 RhythmDB *db;
1504 GList *i;
1505 TracksDeletedCallbackData *cb_data;
1506
1507 cb_data = g_new0 (TracksDeletedCallbackData, 1);
1508 cb_data->source = g_object_ref (source);
1509 cb_data->callback_data = user_data;
1510 cb_data->callback = callback;
1511 cb_data->destroy_data = destroy_data;
1512 cb_data->check_folders = g_hash_table_new (g_direct_hash, g_direct_equal);
1513
1514 db = get_db_for_source (RB_MTP_SOURCE (source));
1515 for (i = entries; i != NULL; i = i->next) {
1516 LIBMTP_track_t *track;
1517 const char *uri;
1518 const char *album_name;
1519 RhythmDBEntry *entry;
1520
1521 entry = i->data;
1522 uri = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
1523 track = g_hash_table_lookup (priv->entry_map, entry);
1524 if (track == NULL) {
1525 rb_debug ("Couldn't find track on mtp-device! (%s)", uri);
1526 continue;
1527 }
1528
1529 album_name = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ALBUM);
1530 if (g_strcmp0 (album_name, _("Unknown")) != 0) {
1531 rb_mtp_thread_remove_from_album (priv->device_thread, track, album_name);
1532 }
1533 rb_mtp_thread_delete_track (priv->device_thread, track);
1534
1535 g_hash_table_insert (cb_data->check_folders,
1536 GUINT_TO_POINTER (track->parent_id),
1537 GINT_TO_POINTER (1));
1538
1539 g_hash_table_remove (priv->entry_map, entry);
1540 rhythmdb_entry_delete (db, entry);
1541 }
1542
1543 /* callback when all tracks have been deleted */
1544 rb_mtp_thread_queue_callback (priv->device_thread,
1545 (RBMtpThreadCallback) delete_done_cb,
1546 cb_data,
1547 (GDestroyNotify) free_delete_data);
1548
1549 rhythmdb_commit (db);
1550 }
1551
1552 static void
1553 impl_show_properties (RBMediaPlayerSource *source, GtkWidget *info_box, GtkWidget *notebook)
1554 {
1555 RBMtpSourcePrivate *priv = MTP_SOURCE_GET_PRIVATE (source);
1556 GtkBuilder *builder;
1557 GtkWidget *widget;
1558 GHashTableIter iter;
1559 gpointer key, value;
1560 int num_podcasts;
1561 char *device_name;
1562 char *builder_file;
1563 GObject *plugin;
1564 char *text;
1565 GList *output_formats;
1566 GList *t;
1567 GString *str;
1568
1569 g_object_get (source, "plugin", &plugin, NULL);
1570 builder_file = rb_find_plugin_data_file (G_OBJECT (plugin), "mtp-info.ui");
1571 g_object_unref (plugin);
1572
1573 if (builder_file == NULL) {
1574 g_warning ("Couldn't find mtp-info.ui");
1575 return;
1576 }
1577
1578 builder = rb_builder_load (builder_file, NULL);
1579 g_free (builder_file);
1580
1581 if (builder == NULL) {
1582 rb_debug ("Couldn't load mtp-info.ui");
1583 return;
1584 }
1585
1586 /* 'basic' tab stuff */
1587
1588 widget = GTK_WIDGET (gtk_builder_get_object (builder, "mtp-basic-info"));
1589 gtk_box_pack_start (GTK_BOX (info_box), widget, TRUE, TRUE, 0);
1590
1591 widget = GTK_WIDGET (gtk_builder_get_object (builder, "entry-mtp-name"));
1592 g_object_get (source, "name", &device_name, NULL);
1593 gtk_entry_set_text (GTK_ENTRY (widget), device_name);
1594 g_free (device_name);
1595 g_signal_connect (widget, "focus-out-event",
1596 (GCallback)rb_mtp_source_name_changed_cb, source);
1597
1598 num_podcasts = 0;
1599 g_hash_table_iter_init (&iter, priv->entry_map);
1600 while (g_hash_table_iter_next (&iter, &key, &value)) {
1601 LIBMTP_track_t *track = value;
1602 if (g_strcmp0 (track->genre, "Podcast") == 0) {
1603 num_podcasts++;
1604 }
1605 }
1606
1607 widget = GTK_WIDGET (gtk_builder_get_object (builder, "mtp-num-tracks"));
1608 text = g_strdup_printf ("%d", g_hash_table_size (priv->entry_map) - num_podcasts);
1609 gtk_label_set_text (GTK_LABEL (widget), text);
1610 g_free (text);
1611
1612 widget = GTK_WIDGET (gtk_builder_get_object (builder, "mtp-num-podcasts"));
1613 text = g_strdup_printf ("%d", num_podcasts);
1614 gtk_label_set_text (GTK_LABEL (widget), text);
1615 g_free (text);
1616
1617 widget = GTK_WIDGET (gtk_builder_get_object (builder, "mtp-num-playlists"));
1618 text = g_strdup_printf ("%d", 0); /* correct, but wrong */
1619 gtk_label_set_text (GTK_LABEL (widget), text);
1620 g_free (text);
1621
1622 /* 'advanced' tab stuff */
1623 widget = GTK_WIDGET (gtk_builder_get_object (builder, "mtp-advanced-tab"));
1624 gtk_notebook_append_page (GTK_NOTEBOOK (notebook), widget, gtk_label_new (_("Advanced")));
1625
1626 widget = GTK_WIDGET (gtk_builder_get_object (builder, "label-mtp-model-value"));
1627 gtk_label_set_text (GTK_LABEL (widget), priv->model_name);
1628
1629 widget = GTK_WIDGET (gtk_builder_get_object (builder, "label-serial-number-value"));
1630 gtk_label_set_text (GTK_LABEL (widget), priv->serial);
1631
1632 widget = GTK_WIDGET (gtk_builder_get_object (builder, "label-firmware-version-value"));
1633 gtk_label_set_text (GTK_LABEL (widget), priv->device_version);
1634
1635 widget = GTK_WIDGET (gtk_builder_get_object (builder, "label-manufacturer-value"));
1636 gtk_label_set_text (GTK_LABEL (widget), priv->manufacturer);
1637
1638 str = g_string_new ("");
1639 output_formats = rb_transfer_target_get_format_descriptions (RB_TRANSFER_TARGET (source));
1640 for (t = output_formats; t != NULL; t = t->next) {
1641 if (t != output_formats) {
1642 g_string_append (str, "\n");
1643 }
1644 g_string_append (str, t->data);
1645 }
1646 rb_list_deep_free (output_formats);
1647
1648 widget = GTK_WIDGET (gtk_builder_get_object (builder, "label-audio-formats-value"));
1649 gtk_label_set_text (GTK_LABEL (widget), str->str);
1650 g_string_free (str, TRUE);
1651
1652 g_object_unref (builder);
1653 }
1654
1655 static void
1656 prepare_source (RBMtpSource *source, const char *stream_uri, GObject *src)
1657 {
1658 RBMtpSourcePrivate *priv = MTP_SOURCE_GET_PRIVATE (source);
1659 RhythmDBEntry *entry;
1660 RhythmDB *db;
1661
1662 /* make sure this stream is for a file on our device */
1663 if (g_str_has_prefix (stream_uri, "xrbmtp://") == FALSE)
1664 return;
1665
1666 db = get_db_for_source (source);
1667 entry = rhythmdb_entry_lookup_by_location (db, stream_uri);
1668 g_object_unref (db);
1669 if (entry == NULL)
1670 return;
1671
1672 if (_rb_source_check_entry_type (RB_SOURCE (source), entry) == FALSE) {
1673 rhythmdb_entry_unref (entry);
1674 return;
1675 }
1676
1677 rb_debug ("setting device-thread for stream %s", stream_uri);
1678 g_object_set (src, "device-thread", priv->device_thread, NULL);
1679 rhythmdb_entry_unref (entry);
1680 }
1681
1682 static void
1683 prepare_player_source_cb (RBPlayer *player,
1684 const char *stream_uri,
1685 GstElement *src,
1686 RBMtpSource *source)
1687 {
1688 prepare_source (source, stream_uri, G_OBJECT (src));
1689 }
1690
1691 static void
1692 prepare_encoder_source_cb (RBEncoderFactory *factory,
1693 const char *stream_uri,
1694 GObject *src,
1695 RBMtpSource *source)
1696 {
1697 prepare_source (source, stream_uri, src);
1698 }
1699
1700 static gboolean
1701 impl_can_eject (RBDeviceSource *source)
1702 {
1703 return TRUE;
1704 }
1705
1706 static void
1707 impl_eject (RBDeviceSource *source)
1708 {
1709 rb_display_page_delete_thyself (RB_DISPLAY_PAGE (source));
1710 }
1711
1712 #if defined(HAVE_GUDEV)
1713
1714 static GMount *
1715 find_mount_for_device (GUdevDevice *device)
1716 {
1717 GMount *mount = NULL;
1718 const char *device_file;
1719 GVolumeMonitor *volmon;
1720 GList *mounts;
1721 GList *i;
1722
1723 device_file = g_udev_device_get_device_file (device);
1724 if (device_file == NULL) {
1725 return NULL;
1726 }
1727
1728 volmon = g_volume_monitor_get ();
1729 mounts = g_volume_monitor_get_mounts (volmon);
1730 g_object_unref (volmon);
1731
1732 for (i = mounts; i != NULL; i = i->next) {
1733 GVolume *v;
1734
1735 v = g_mount_get_volume (G_MOUNT (i->data));
1736 if (v != NULL) {
1737 char *devname = NULL;
1738 gboolean match;
1739
1740 devname = g_volume_get_identifier (v, G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE);
1741 g_object_unref (v);
1742 if (devname == NULL)
1743 continue;
1744
1745 match = g_str_equal (devname, device_file);
1746 g_free (devname);
1747
1748 if (match) {
1749 mount = G_MOUNT (i->data);
1750 g_object_ref (G_OBJECT (mount));
1751 break;
1752 }
1753 }
1754 }
1755 g_list_foreach (mounts, (GFunc)g_object_unref, NULL);
1756 g_list_free (mounts);
1757 return mount;
1758 }
1759
1760 #endif
1761
1762 void
1763 _rb_mtp_source_register_type (GTypeModule *module)
1764 {
1765 rb_mtp_source_register_type (module);
1766 }