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