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