1 /*
2  *  Copyright (C) 2015 Jonathan Matthew <jonathan@d14n.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 #define __EXTENSIONS__
29 
30 #include "config.h"
31 
32 #include <string.h>
33 
34 #include <glib/gi18n.h>
35 #include <gtk/gtk.h>
36 
37 #include <gudev/gudev.h>
38 
39 #include "mediaplayerid.h"
40 
41 #include "rb-android-source.h"
42 #include "rb-debug.h"
43 #include "rb-util.h"
44 #include "rb-file-helpers.h"
45 #include "rhythmdb.h"
46 #include "rb-builder-helpers.h"
47 #include "rb-application.h"
48 #include "rb-transfer-target.h"
49 #include "rb-device-source.h"
50 #include "rb-sync-settings.h"
51 #include "rb-import-errors-source.h"
52 #include "rb-gst-media-types.h"
53 #include "rb-task-list.h"
54 #include "rb-encoder.h"
55 #include "rb-dialog.h"
56 
57 static void rb_android_device_source_init (RBDeviceSourceInterface *interface);
58 static void rb_android_transfer_target_init (RBTransferTargetInterface *interface);
59 
60 static void find_music_dirs (RBAndroidSource *source);
61 static void rescan_music_dirs (RBAndroidSource *source);
62 static void update_free_space_next (RBAndroidSource *source);
63 
64 enum
65 {
66 	PROP_0,
67 	PROP_VOLUME,
68 	PROP_MOUNT_ROOT,
69 	PROP_IGNORE_ENTRY_TYPE,
70 	PROP_ERROR_ENTRY_TYPE,
71 	PROP_DEVICE_INFO,
72 	PROP_DEVICE_SERIAL,
73 	PROP_GUDEV_DEVICE
74 };
75 
76 typedef struct
77 {
78 	RhythmDB *db;
79 
80 	gboolean loaded;
81 	RhythmDBImportJob *import_job;
82 	RBSource *import_errors;
83 	GCancellable *cancel;
84 	GQueue to_scan;
85 	int scanned;
86 
87 	RhythmDBEntryType *ignore_type;
88 	RhythmDBEntryType *error_type;
89 
90 	MPIDDevice *device_info;
91 	GUdevDevice *gudev_device;
92 	GVolume *volume;
93 	GObject *mount_root;
94 	gboolean ejecting;
95 
96 	GList *storage;
97 	guint64 storage_capacity;
98 	guint64 storage_free_space;
99 	GList *query_storage;
100 	guint64 storage_free_space_next;
101 	guint64 storage_capacity_next;
102 	guint rescan_id;
103 
104 	GtkWidget *grid;
105 	GtkWidget *info_bar;
106 } RBAndroidSourcePrivate;
107 
108 G_DEFINE_DYNAMIC_TYPE_EXTENDED (
109 	RBAndroidSource,
110 	rb_android_source,
111 	RB_TYPE_MEDIA_PLAYER_SOURCE,
112 	0,
113 	G_IMPLEMENT_INTERFACE_DYNAMIC (RB_TYPE_DEVICE_SOURCE, rb_android_device_source_init)
114 	G_IMPLEMENT_INTERFACE_DYNAMIC (RB_TYPE_TRANSFER_TARGET, rb_android_transfer_target_init))
115 
116 #define GET_PRIVATE(o)   (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_ANDROID_SOURCE, RBAndroidSourcePrivate))
117 
118 static void
free_space_cb(GObject * obj,GAsyncResult * res,gpointer data)119 free_space_cb (GObject *obj, GAsyncResult *res, gpointer data)
120 {
121 	RBAndroidSource *source = RB_ANDROID_SOURCE (data);
122 	RBAndroidSourcePrivate *priv = GET_PRIVATE(source);
123 	GFileInfo *info;
124 	GError *error = NULL;
125 
126 	info = g_file_query_filesystem_info_finish (G_FILE (obj), res, &error);
127 	if (info == NULL) {
128 		rb_debug ("error querying filesystem free space: %s", error->message);
129 		g_clear_error (&error);
130 	} else {
131 		priv->storage_free_space_next += g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE);
132 		priv->storage_capacity_next += g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE);
133 		rb_debug ("capacity: %" G_GUINT64_FORMAT ", free space: %" G_GUINT64_FORMAT,
134 		          priv->storage_capacity_next, priv->storage_free_space_next);
135 	}
136 
137 	priv->query_storage = priv->query_storage->next;
138 	if (priv->query_storage != NULL) {
139 		update_free_space_next (source);
140 	} else {
141 		priv->storage_free_space = priv->storage_free_space_next;
142 		priv->storage_capacity = priv->storage_capacity_next;
143 	}
144 }
145 
146 static void
update_free_space_next(RBAndroidSource * source)147 update_free_space_next (RBAndroidSource *source)
148 {
149 	RBAndroidSourcePrivate *priv = GET_PRIVATE(source);
150 	GFile *file;
151 	const char *attrs = G_FILE_ATTRIBUTE_FILESYSTEM_FREE "," G_FILE_ATTRIBUTE_FILESYSTEM_SIZE;
152 
153 	file = G_FILE (priv->query_storage->data);
154 	g_file_query_filesystem_info_async (file, attrs, G_PRIORITY_DEFAULT, NULL, free_space_cb, source);
155 }
156 
157 static void
update_free_space(RBAndroidSource * source)158 update_free_space (RBAndroidSource *source)
159 {
160 	RBAndroidSourcePrivate *priv = GET_PRIVATE(source);
161 
162 	if (priv->query_storage != NULL) {
163 		rb_debug ("already updating free space");
164 		return;
165 	}
166 
167 	if (priv->storage == NULL) {
168 		rb_debug ("no storage to query");
169 		return;
170 	}
171 
172 	priv->storage_free_space_next = 0;
173 	priv->storage_capacity_next = 0;
174 	priv->query_storage = priv->storage;
175 	update_free_space_next (source);
176 }
177 
178 
179 static void
music_dirs_done(RBAndroidSource * source)180 music_dirs_done (RBAndroidSource *source)
181 {
182 	RBAndroidSourcePrivate *priv = GET_PRIVATE(source);
183 
184 	if (priv->scanned > 1) {
185 		gtk_widget_hide (priv->info_bar);
186 		rhythmdb_import_job_start (priv->import_job);
187 
188 		if (priv->rescan_id != 0) {
189 			g_source_remove (priv->rescan_id);
190 		}
191 
192 		if (priv->storage != NULL) {
193 			rb_debug ("finished checking for music dirs");
194 			update_free_space (source);
195 		} else {
196 			rb_debug ("no music dirs found (%d)", priv->scanned);
197 		}
198 	} else {
199 		GtkWidget *label;
200 
201 		rb_debug ("no storage areas found");
202 		if (gtk_widget_get_visible (priv->info_bar) == FALSE) {
203 			label = gtk_label_new (_("No storage areas found on this device. You may need to unlock it and enable MTP."));
204 			gtk_container_add (GTK_CONTAINER (gtk_info_bar_get_content_area (GTK_INFO_BAR (priv->info_bar))), label);
205 			gtk_info_bar_set_message_type (GTK_INFO_BAR (priv->info_bar), GTK_MESSAGE_INFO);
206 			gtk_widget_show_all (priv->info_bar);
207 		}
208 		if (priv->rescan_id == 0)
209 			priv->rescan_id = g_timeout_add_seconds (5, (GSourceFunc) rescan_music_dirs, source);
210 	}
211 }
212 
213 
214 static void
enum_files_cb(GObject * obj,GAsyncResult * result,gpointer data)215 enum_files_cb (GObject *obj, GAsyncResult *result, gpointer data)
216 {
217 	RBAndroidSource *source = RB_ANDROID_SOURCE (data);
218 	RBAndroidSourcePrivate *priv = GET_PRIVATE(source);
219 	GFileEnumerator *e = G_FILE_ENUMERATOR (obj);
220 	GError *error = NULL;
221 	GFileInfo *info;
222 	GList *files;
223 	GList *l;
224 
225 	files = g_file_enumerator_next_files_finish (e, result, &error);
226 	if (error != NULL) {
227 		rb_debug ("error listing files: %s", error->message);
228 		music_dirs_done (source);
229 		return;
230 	}
231 
232 	if (files == NULL) {
233 		priv->scanned++;
234 		g_object_unref (e);
235 		find_music_dirs (source);
236 		return;
237 	}
238 
239 	for (l = files; l != NULL; l = l->next) {
240 		guint32 filetype;
241 		info = (GFileInfo *)l->data;
242 
243 		filetype = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_STANDARD_TYPE);
244 		if (filetype == G_FILE_TYPE_DIRECTORY) {
245 			GFile *dir;
246 			if (priv->scanned == 0) {
247 
248 				rb_debug ("got storage container %s", g_file_info_get_name (info));
249 				dir = g_file_get_child (g_file_enumerator_get_container (e), g_file_info_get_name (info));
250 				g_queue_push_tail (&priv->to_scan, dir);
251 			} else if (g_ascii_strcasecmp (g_file_info_get_name (info), "music") == 0) {
252 				GFile *storage;
253 				char *uri;
254 
255 				storage = g_file_enumerator_get_container (e);
256 				dir = g_file_get_child (storage, g_file_info_get_name (info));
257 				uri = g_file_get_uri (dir);
258 				rb_debug ("music dir found at %s", uri);
259 
260 				/* keep the container around for space/capacity calculation */
261 				priv->storage = g_list_append (priv->storage, dir);
262 
263 				rhythmdb_import_job_add_uri (priv->import_job, uri);
264 				g_free (uri);
265 			}
266 		}
267 
268 		g_object_unref (info);
269 	}
270 
271 	g_list_free (files);
272 
273 	g_file_enumerator_next_files_async (G_FILE_ENUMERATOR (obj), 64, G_PRIORITY_DEFAULT, priv->cancel, enum_files_cb, source);
274 }
275 
276 static void
enum_child_cb(GObject * obj,GAsyncResult * result,gpointer data)277 enum_child_cb (GObject *obj, GAsyncResult *result, gpointer data)
278 {
279 	RBAndroidSource *source = RB_ANDROID_SOURCE (data);
280 	RBAndroidSourcePrivate *priv = GET_PRIVATE(source);
281 	GFileEnumerator *e;
282 	GError *error = NULL;
283 
284 	e = g_file_enumerate_children_finish (G_FILE (obj), result, &error);
285 	if (e == NULL) {
286 		rb_debug ("enum error: %s", error->message);
287 		g_clear_error (&error);
288 		music_dirs_done (source);
289 		return;
290 	}
291 
292 	g_file_enumerator_next_files_async (e, 64, G_PRIORITY_DEFAULT, priv->cancel, enum_files_cb, source);
293 }
294 
295 static void
find_music_dirs(RBAndroidSource * source)296 find_music_dirs (RBAndroidSource *source)
297 {
298 	RBAndroidSourcePrivate *priv = GET_PRIVATE(source);
299 	const char *attrs =
300 		G_FILE_ATTRIBUTE_STANDARD_NAME ","
301 		G_FILE_ATTRIBUTE_STANDARD_TYPE;
302 
303 	gpointer dir;
304 
305 	dir = g_queue_pop_head (&priv->to_scan);
306 	if (dir == NULL) {
307 		music_dirs_done (source);
308 		return;
309 	}
310 
311 	rb_debug ("scanning %s", g_file_get_uri (G_FILE (dir)));
312 	g_file_enumerate_children_async (G_FILE (dir),
313 					 attrs,
314 					 G_FILE_QUERY_INFO_NONE,
315 					 G_PRIORITY_DEFAULT,
316 					 priv->cancel,
317 					 enum_child_cb,
318 					 source);
319 	g_object_unref (dir);
320 }
321 
322 static void
rescan_music_dirs(RBAndroidSource * source)323 rescan_music_dirs (RBAndroidSource *source)
324 {
325 	RBAndroidSourcePrivate *priv = GET_PRIVATE (source);
326 	GFile *root;
327 
328 	g_object_get (source, "mount-root", &root, NULL);
329 
330 	priv->scanned = 0;
331 	g_queue_push_tail (&priv->to_scan, root);
332 
333 	find_music_dirs (source);
334 }
335 
336 static void
import_complete_cb(RhythmDBImportJob * job,int total,RBAndroidSource * source)337 import_complete_cb (RhythmDBImportJob *job, int total, RBAndroidSource *source)
338 {
339 	RBAndroidSourcePrivate *priv = GET_PRIVATE (source);
340 	GSettings *settings;
341 	RBShell *shell;
342 
343 	if (priv->ejecting) {
344 		rb_device_source_default_eject (RB_DEVICE_SOURCE (source));
345 	} else {
346 		g_object_get (source, "shell", &shell, NULL);
347 		rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (priv->import_errors), RB_DISPLAY_PAGE (source));
348 		g_object_unref (shell);
349 
350 		g_object_set (source, "load-status", RB_SOURCE_LOAD_STATUS_LOADED, NULL);
351 
352 		g_object_get (source, "encoding-settings", &settings, NULL);
353 		rb_transfer_target_transfer (RB_TRANSFER_TARGET (source), settings, NULL, FALSE);
354 		g_object_unref (settings);
355 
356 		rb_media_player_source_purge_metadata_cache (RB_MEDIA_PLAYER_SOURCE (source));
357 	}
358 
359 	g_clear_object (&priv->import_job);
360 }
361 
362 static void
actually_load(RBAndroidSource * source)363 actually_load (RBAndroidSource *source)
364 {
365 	RBAndroidSourcePrivate *priv = GET_PRIVATE (source);
366 	RBTaskList *tasklist;
367 	RhythmDBEntryType *entry_type;
368 	RBShell *shell;
369 	GFile *root;
370 	char *name;
371 	char *label;
372 
373 	priv->loaded = TRUE;
374 	rb_media_player_source_load (RB_MEDIA_PLAYER_SOURCE (source));
375 
376 	/* identify storage containers and find music dirs within them */
377 	g_object_get (source, "mount-root", &root, "entry-type", &entry_type, NULL);
378 
379 	priv->cancel = g_cancellable_new ();
380 	priv->import_job = rhythmdb_import_job_new (priv->db, entry_type, priv->ignore_type, priv->error_type);
381 	g_signal_connect_object (priv->import_job, "complete", G_CALLBACK (import_complete_cb), source, 0);
382 
383 	priv->scanned = 0;
384 	g_queue_init (&priv->to_scan);
385 	g_queue_push_tail (&priv->to_scan, root);
386 	g_object_unref (entry_type);
387 
388 	find_music_dirs (source);
389 
390 	g_object_get (source, "name", &name, "shell", &shell, NULL);
391 	label = g_strdup_printf (_("Scanning %s"), name);
392 	g_object_set (priv->import_job, "task-label", label, NULL);
393 
394 	g_object_get (shell, "task-list", &tasklist, NULL);
395 	rb_task_list_add_task (tasklist, RB_TASK_PROGRESS (priv->import_job));
396 	g_object_unref (tasklist);
397 	g_object_unref (shell);
398 
399 	g_free (label);
400 	g_free (name);
401 }
402 
403 static void
volume_mount_cb(GObject * source_object,GAsyncResult * res,gpointer user_data)404 volume_mount_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
405 {
406 	RBAndroidSource *source = RB_ANDROID_SOURCE (user_data);
407 	GVolume *volume = G_VOLUME (source_object);
408 	GError *error = NULL;
409 
410 	rb_debug ("volume mount finished");
411 	if (g_volume_mount_finish (volume, res, &error)) {
412 		actually_load (source);
413 	} else {
414 		rb_error_dialog (NULL, _("Error mounting Android device"), "%s", error->message);
415 		g_clear_error (&error);
416 	}
417 }
418 
419 static gboolean
ensure_loaded(RBAndroidSource * source)420 ensure_loaded (RBAndroidSource *source)
421 {
422 	RBAndroidSourcePrivate *priv = GET_PRIVATE (source);
423 	RBSourceLoadStatus status;
424 	GMount *mount;
425 
426 	if (priv->loaded) {
427 		g_object_get (source, "load-status", &status, NULL);
428 		return (status == RB_SOURCE_LOAD_STATUS_LOADED);
429 	}
430 
431 	mount = g_volume_get_mount (priv->volume);
432 	if (mount != NULL) {
433 		rb_debug ("volume is mounted");
434 		g_object_unref (mount);
435 		actually_load (source);
436 		return FALSE;
437 	}
438 
439 	rb_debug ("mounting volume");
440 	g_volume_mount (priv->volume, G_MOUNT_MOUNT_NONE, NULL, NULL, volume_mount_cb, source);
441 	return FALSE;
442 }
443 
444 static void
delete_data_destroy(gpointer data)445 delete_data_destroy (gpointer data)
446 {
447 	g_list_free_full (data, (GDestroyNotify) rhythmdb_entry_unref);
448 }
449 
450 
451 static gboolean
can_delete_directory(RBAndroidSource * source,GFile * dir)452 can_delete_directory (RBAndroidSource *source, GFile *dir)
453 {
454 	GFile *root;
455 	char *path;
456 	int i;
457 	int c;
458 
459 	g_object_get (source, "mount-root", &root, NULL);
460 
461 	/*
462 	 * path here will be sdcard/Music/something for anything we want to delete
463 	 */
464 	path = g_file_get_relative_path (root, dir);
465 	c = 0;
466 	for (i = 0; path[i] != '\0'; i++) {
467 		if (path[i] == '/')
468 			c++;
469 	}
470 
471 	g_free (path);
472 	g_object_unref (root);
473 	return (c > 1);
474 }
475 
476 static void
delete_entries_task(GTask * task,gpointer source_object,gpointer task_data,GCancellable * cancellable)477 delete_entries_task (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable)
478 {
479 	RBAndroidSource *source = RB_ANDROID_SOURCE (source_object);
480 	RBAndroidSourcePrivate *priv = GET_PRIVATE (source);
481 	GList *l;
482 
483 	for (l = task_data; l != NULL; l = l->next) {
484 		RhythmDBEntry *entry;
485 		const char *uri;
486 		GFile *file;
487 		GFile *dir;
488 
489 		entry = l->data;
490 		uri = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
491 		file = g_file_new_for_uri (uri);
492 		g_file_delete (file, NULL, NULL);
493 
494 		/* now walk up the directory structure and delete empty dirs
495 		 * until we reach the root or one of the device's audio folders.
496 		 */
497 		dir = g_file_get_parent (file);
498 		while (can_delete_directory (source, dir)) {
499 			GFile *parent;
500 
501 			if (g_file_delete (dir, NULL, NULL) == FALSE) {
502 				break;
503 			}
504 
505 			parent = g_file_get_parent (dir);
506 			if (parent == NULL) {
507 				break;
508 			}
509 			g_object_unref (dir);
510 			dir = parent;
511 		}
512 
513 		g_object_unref (dir);
514 		g_object_unref (file);
515 
516 		rhythmdb_entry_delete (priv->db, entry);
517 	}
518 
519 	rhythmdb_commit (priv->db);
520 
521 	g_task_return_boolean (task, TRUE);
522 	g_object_unref (task);
523 }
524 
525 static void
impl_delete_entries(RBMediaPlayerSource * source,GList * entries,GAsyncReadyCallback callback,gpointer data)526 impl_delete_entries (RBMediaPlayerSource *source, GList *entries, GAsyncReadyCallback callback, gpointer data)
527 {
528 	GTask *task;
529 	GList *task_entries;
530 
531 	task = g_task_new (source, NULL, callback, data);
532 	task_entries = g_list_copy_deep (entries, (GCopyFunc) rhythmdb_entry_ref, NULL);
533 	g_task_set_task_data (task, task_entries, delete_data_destroy);
534 	g_task_run_in_thread (task, delete_entries_task);
535 }
536 
537 static void
impl_show_properties(RBMediaPlayerSource * source,GtkWidget * info_box,GtkWidget * notebook)538 impl_show_properties (RBMediaPlayerSource *source, GtkWidget *info_box, GtkWidget *notebook)
539 {
540 	RhythmDBQueryModel *model;
541 	GtkBuilder *builder;
542 	GtkWidget *widget;
543 	GObject *plugin;
544 	char *text;
545 
546 	g_object_get (source, "plugin", &plugin, NULL);
547 	builder = rb_builder_load_plugin_file (G_OBJECT (plugin), "android-info.ui", NULL);
548 	g_object_unref (plugin);
549 
550 	/* 'basic' tab stuff */
551 
552 	widget = GTK_WIDGET (gtk_builder_get_object (builder, "android-basic-info"));
553 	gtk_box_pack_start (GTK_BOX (info_box), widget, TRUE, TRUE, 0);
554 
555 	g_object_get (source, "base-query-model", &model, NULL);
556 	widget = GTK_WIDGET (gtk_builder_get_object (builder, "num-tracks"));
557 	text = g_strdup_printf ("%d", gtk_tree_model_iter_n_children (GTK_TREE_MODEL (model), NULL));
558 	gtk_label_set_text (GTK_LABEL (widget), text);
559 	g_free (text);
560 	g_object_unref (model);
561 
562 	g_object_unref (builder);
563 }
564 
565 static void
impl_get_entries(RBMediaPlayerSource * source,const char * category,GHashTable * map)566 impl_get_entries (RBMediaPlayerSource *source,
567 		  const char *category,
568 		  GHashTable *map)
569 {
570 	RhythmDBQueryModel *model;
571 	GtkTreeIter iter;
572 	gboolean podcast;
573 
574 	/* we don't have anything else to distinguish podcasts from regular
575 	 * tracks, so just use the genre.
576 	 */
577 	podcast = (g_str_equal (category, SYNC_CATEGORY_PODCAST));
578 
579 	g_object_get (source, "base-query-model", &model, NULL);
580 	if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (model), &iter) == FALSE) {
581 		g_object_unref (model);
582 		return;
583 	}
584 
585 	do {
586 		RhythmDBEntry *entry;
587 		const char *genre;
588 		entry = rhythmdb_query_model_iter_to_entry (model, &iter);
589 		genre = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_GENRE);
590 		if (g_str_equal (genre, "Podcast") == podcast) {
591 			_rb_media_player_source_add_to_map (map, entry);
592 		}
593 	} while (gtk_tree_model_iter_next (GTK_TREE_MODEL (model), &iter));
594 
595 	g_object_unref (model);
596 }
597 
598 static guint64
impl_get_capacity(RBMediaPlayerSource * source)599 impl_get_capacity (RBMediaPlayerSource *source)
600 {
601 	RBAndroidSourcePrivate *priv = GET_PRIVATE(source);
602 	return priv->storage_capacity;
603 }
604 
605 static guint64
impl_get_free_space(RBMediaPlayerSource * source)606 impl_get_free_space (RBMediaPlayerSource *source)
607 {
608 	RBAndroidSourcePrivate *priv = GET_PRIVATE(source);
609 	return priv->storage_free_space;
610 }
611 
612 static gboolean
impl_can_paste(RBSource * source)613 impl_can_paste (RBSource *source)
614 {
615 	return TRUE;
616 }
617 
618 static RBTrackTransferBatch *
impl_paste(RBSource * source,GList * entries)619 impl_paste (RBSource *source, GList *entries)
620 {
621 	gboolean defer;
622 	GSettings *settings;
623 	RBTrackTransferBatch *batch;
624 
625 	defer = (ensure_loaded (RB_ANDROID_SOURCE (source)) == FALSE);
626 	g_object_get (source, "encoding-settings", &settings, NULL);
627 	batch = rb_transfer_target_transfer (RB_TRANSFER_TARGET (source), settings, entries, defer);
628 	g_object_unref (settings);
629 	return batch;
630 }
631 
632 static gboolean
impl_can_delete(RBSource * source)633 impl_can_delete (RBSource *source)
634 {
635 	return TRUE;
636 }
637 
638 static void
impl_delete_selected(RBSource * source)639 impl_delete_selected (RBSource *source)
640 {
641 	RBEntryView *view;
642 	GList *sel;
643 
644 	view = rb_source_get_entry_view (source);
645 	sel = rb_entry_view_get_selected_entries (view);
646 
647 	impl_delete_entries (RB_MEDIA_PLAYER_SOURCE (source), sel, NULL, NULL);
648 	g_list_free_full (sel, (GDestroyNotify) rhythmdb_entry_unref);
649 }
650 
651 
652 static void
impl_eject(RBDeviceSource * source)653 impl_eject (RBDeviceSource *source)
654 {
655 	RBAndroidSourcePrivate *priv = GET_PRIVATE (source);
656 
657 	if (priv->import_job != NULL) {
658 		rhythmdb_import_job_cancel (priv->import_job);
659 		priv->ejecting = TRUE;
660 	} else {
661 		rb_device_source_default_eject (source);
662 	}
663 }
664 
665 
666 static char *
sanitize_path(const char * str)667 sanitize_path (const char *str)
668 {
669 	char *res = NULL;
670 	char *s;
671 
672 	/* Skip leading periods, otherwise files disappear... */
673 	while (*str == '.')
674 		str++;
675 
676 	s = g_strdup (str);
677 	rb_sanitize_path_for_msdos_filesystem (s);
678 	res = g_uri_escape_string (s, G_URI_RESERVED_CHARS_ALLOWED_IN_PATH_ELEMENT, TRUE);
679 	g_free (s);
680 	return res;
681 }
682 
683 
684 static char *
build_device_uri(RBAndroidSource * source,RhythmDBEntry * entry,const char * media_type,const char * extension)685 build_device_uri (RBAndroidSource *source, RhythmDBEntry *entry, const char *media_type, const char *extension)
686 {
687 	RBAndroidSourcePrivate *priv = GET_PRIVATE (source);
688 	const char *in_artist;
689 	char *artist, *album, *title;
690 	gulong track_number, disc_number;
691 	char *number;
692 	char *file = NULL;
693 	char *storage_uri;
694 	char *uri;
695 	char *ext;
696 	GFile *storage = NULL;
697 
698 	if (extension != NULL) {
699 		ext = g_strconcat (".", extension, NULL);
700 	} else {
701 		ext = g_strdup ("");
702 	}
703 
704 	in_artist = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ALBUM_ARTIST);
705 	if (in_artist[0] == '\0') {
706 		in_artist = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ARTIST);
707 	}
708 	artist = sanitize_path (in_artist);
709 	album = sanitize_path (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ALBUM));
710 	title = sanitize_path (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_TITLE));
711 
712 	/* we really do need to fix this so untagged entries actually have NULL rather than
713 	 * a translated string.
714 	 */
715 	if (strcmp (artist, _("Unknown")) == 0 && strcmp (album, _("Unknown")) == 0 &&
716 	    g_str_has_suffix (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION), title)) {
717 		/* file isn't tagged, so just use the filename as-is, replacing the extension */
718 		char *p;
719 
720 		p = g_utf8_strrchr (title, -1, '.');
721 		if (p != NULL) {
722 			*p = '\0';
723 		}
724 		file = g_strdup_printf ("%s%s", title, ext);
725 	}
726 
727 	if (file == NULL) {
728 		track_number = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_TRACK_NUMBER);
729 		disc_number = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DISC_NUMBER);
730 		if (disc_number > 0)
731 			number = g_strdup_printf ("%.02u.%.02u", (guint)disc_number, (guint)track_number);
732 		else
733 			number = g_strdup_printf ("%.02u", (guint)track_number);
734 
735 		/* artist/album/number - title */
736 		file = g_strdup_printf (G_DIR_SEPARATOR_S "%s" G_DIR_SEPARATOR_S "%s" G_DIR_SEPARATOR_S "%s%%20-%%20%s%s",
737 					artist, album, number, title, ext);
738 		g_free (number);
739 	}
740 
741 	g_free (artist);
742 	g_free (album);
743 	g_free (title);
744 	g_free (ext);
745 
746 	/* pick storage container to use somehow
747 	for (l = priv->storage; l != NULL; l = l->next) {
748 	}
749 	*/
750 	if (priv->storage)
751 		storage = priv->storage->data;
752 
753 	if (storage == NULL) {
754 		rb_debug ("couldn't find a container to store anything in");
755 		g_free (file);
756 		return NULL;
757 	}
758 
759 	storage_uri = g_file_get_uri (storage);
760 	uri = g_strconcat (storage_uri, file, NULL);
761 	g_free (file);
762 	g_free (storage_uri);
763 
764 	return uri;
765 }
766 
767 static char *
impl_build_dest_uri(RBTransferTarget * target,RhythmDBEntry * entry,const char * media_type,const char * extension)768 impl_build_dest_uri (RBTransferTarget *target,
769 		     RhythmDBEntry *entry,
770 		     const char *media_type,
771 		     const char *extension)
772 {
773 	return g_strdup (RB_ENCODER_DEST_TEMPFILE);
774 }
775 
776 static void
track_copy_cb(GObject * src,GAsyncResult * res,gpointer data)777 track_copy_cb (GObject *src, GAsyncResult *res, gpointer data)
778 {
779 	RBAndroidSource *source = RB_ANDROID_SOURCE (data);
780 	RBAndroidSourcePrivate *priv = GET_PRIVATE (source);
781 	RhythmDBEntryType *entry_type;
782 	RBShell *shell;
783 	RhythmDB *db;
784 	GFile *dest;
785 	char *uri;
786 	GError *error = NULL;
787 
788 	if (g_task_propagate_boolean (G_TASK (res), &error)) {
789 
790 		dest = G_FILE (src);
791 		uri = g_file_get_uri (dest);
792 
793 		g_object_get (source, "shell", &shell, NULL);
794 		g_object_get (shell, "db", &db, NULL);
795 		g_object_unref (shell);
796 
797 		g_object_get (source, "entry-type", &entry_type, NULL);
798 		rhythmdb_add_uri_with_types (db,
799 					     uri,
800 					     entry_type,
801 					     priv->ignore_type,
802 					     priv->error_type);
803 		g_object_unref (entry_type);
804 		g_object_unref (db);
805 		g_free (uri);
806 
807 		update_free_space (source);
808 	} else {
809 		rb_error_dialog (NULL, _("Error transferring track"), "%s", error->message);
810 	}
811 
812 	g_clear_error (&error);
813 	g_object_unref (src);
814 	g_object_unref (source);
815 }
816 
817 static void
copy_track_task(GTask * task,gpointer pdest,gpointer psource,GCancellable * cancel)818 copy_track_task (GTask *task, gpointer pdest, gpointer psource, GCancellable *cancel)
819 {
820 	GFile *source = G_FILE (psource);
821 	GFile *dest = G_FILE (pdest);
822 	GError *error = NULL;
823 	char *uri;
824 
825 	uri = g_file_get_uri (dest);
826 	rb_debug ("creating parent dirs for %s", uri);
827 	if (rb_uri_create_parent_dirs (uri, &error) == FALSE) {
828 		g_file_delete (source, NULL, NULL);
829 		g_free (uri);
830 		g_task_return_error (task, error);
831 		return;
832 	}
833 	rb_debug ("moving %s", uri);
834 	g_free (uri);
835 
836 	g_file_move (source,
837 		     dest,
838 		     G_FILE_COPY_OVERWRITE,
839 		     NULL,
840 		     NULL,
841 		     NULL,
842 		     &error);
843 	if (error) {
844 		g_file_delete (source, NULL, NULL);
845 		g_task_return_error (task, error);
846 	} else {
847 		g_task_return_boolean (task, TRUE);
848 	}
849 }
850 
851 static gboolean
impl_track_added(RBTransferTarget * target,RhythmDBEntry * entry,const char * dest,guint64 filesize,const char * media_type)852 impl_track_added (RBTransferTarget *target,
853 		  RhythmDBEntry *entry,
854 		  const char *dest,
855 		  guint64 filesize,
856 		  const char *media_type)
857 {
858 	RBAndroidSource *source = RB_ANDROID_SOURCE (target);
859 	char *realdest;
860 	GFile *dfile, *sfile;
861 	GTask *task;
862 
863 	realdest = build_device_uri (source, entry, media_type, rb_gst_media_type_to_extension (media_type));
864 	dfile = g_file_new_for_uri (realdest);
865 	sfile = g_file_new_for_uri (dest);
866 	g_free (realdest);
867 
868 	task = g_task_new (dfile, NULL, track_copy_cb, g_object_ref (source));
869 	g_task_set_task_data (task, sfile, g_object_unref);
870 	g_task_run_in_thread (task, copy_track_task);
871 	return FALSE;
872 }
873 
874 
875 static void
impl_selected(RBDisplayPage * page)876 impl_selected (RBDisplayPage *page)
877 {
878 	ensure_loaded (RB_ANDROID_SOURCE (page));
879 }
880 
881 static void
impl_delete_thyself(RBDisplayPage * page)882 impl_delete_thyself (RBDisplayPage *page)
883 {
884 	RBAndroidSourcePrivate *priv = GET_PRIVATE (page);
885 
886 	if (priv->import_errors != NULL) {
887 		rb_display_page_delete_thyself (RB_DISPLAY_PAGE (priv->import_errors));
888 		priv->import_errors = NULL;
889 	}
890 
891 	RB_DISPLAY_PAGE_CLASS (rb_android_source_parent_class)->delete_thyself (page);
892 }
893 
894 static void
impl_pack_content(RBBrowserSource * source,GtkWidget * content)895 impl_pack_content (RBBrowserSource *source, GtkWidget *content)
896 {
897 	RBAndroidSourcePrivate *priv = GET_PRIVATE (source);
898 	gtk_grid_attach (GTK_GRID (priv->grid), content, 0, 1, 1, 1);
899 }
900 
901 static void
rb_android_source_init(RBAndroidSource * source)902 rb_android_source_init (RBAndroidSource *source)
903 {
904 
905 }
906 
907 static void
impl_constructed(GObject * object)908 impl_constructed (GObject *object)
909 {
910 	RBAndroidSource *source;
911 	RBAndroidSourcePrivate *priv;
912 	RhythmDBEntryType *entry_type;
913 	RBShell *shell;
914 	char **output_formats;
915 
916 	source = RB_ANDROID_SOURCE (object);
917 	priv = GET_PRIVATE (source);
918 	priv->grid = gtk_grid_new ();
919 
920 	RB_CHAIN_GOBJECT_METHOD (rb_android_source_parent_class, constructed, object);
921 
922 	priv->info_bar = gtk_info_bar_new ();
923 	gtk_grid_attach (GTK_GRID (priv->grid), priv->info_bar, 0, 0, 1, 1);
924 
925 	gtk_container_add (GTK_CONTAINER (source), priv->grid);
926 	gtk_widget_show_all (priv->grid);
927 	gtk_widget_hide (priv->info_bar);
928 
929 	rb_device_source_set_display_details (RB_DEVICE_SOURCE (source));
930 
931 	g_object_get (source,
932 		      "shell", &shell,
933 		      "entry-type", &entry_type,
934 		      NULL);
935 
936 	g_object_get (shell, "db", &priv->db, NULL);
937 
938 	priv->import_errors = rb_import_errors_source_new (shell,
939 							   priv->error_type,
940 							   entry_type,
941 							   priv->ignore_type);
942 
943 	g_object_get (priv->device_info, "output-formats", &output_formats, NULL);
944 	if (output_formats != NULL) {
945 		GstEncodingTarget *target;
946 		int i;
947 
948 		target = gst_encoding_target_new ("android-device", "device", "", NULL);
949 		for (i = 0; output_formats[i] != NULL; i++) {
950 			const char *media_type = rb_gst_mime_type_to_media_type (output_formats[i]);
951 			if (media_type != NULL) {
952 				GstEncodingProfile *profile;
953 				profile = rb_gst_get_encoding_profile (media_type);
954 				if (profile != NULL) {
955 					gst_encoding_target_add_profile (target, profile);
956 				}
957 			}
958 		}
959 		g_object_set (source, "encoding-target", target, NULL);
960 	}
961 	g_strfreev (output_formats);
962 
963 	g_object_unref (shell);
964 }
965 
966 static void
impl_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)967 impl_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
968 {
969 	RBAndroidSourcePrivate *priv = GET_PRIVATE (object);
970 
971 	switch (prop_id) {
972 	case PROP_IGNORE_ENTRY_TYPE:
973 		priv->ignore_type = g_value_get_object (value);
974 		break;
975 	case PROP_ERROR_ENTRY_TYPE:
976 		priv->error_type = g_value_get_object (value);
977 		break;
978 	case PROP_DEVICE_INFO:
979 		priv->device_info = g_value_dup_object (value);
980 		break;
981 	case PROP_VOLUME:
982 		priv->volume = g_value_dup_object (value);
983 		break;
984 	case PROP_MOUNT_ROOT:
985 		priv->mount_root = g_value_dup_object (value);
986 		break;
987 	case PROP_GUDEV_DEVICE:
988 		priv->gudev_device = g_value_dup_object (value);
989 		break;
990 	default:
991 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
992 		break;
993 	}
994 }
995 
996 static void
impl_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)997 impl_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
998 {
999 	RBAndroidSourcePrivate *priv = GET_PRIVATE (object);
1000 
1001 	switch (prop_id) {
1002 	case PROP_IGNORE_ENTRY_TYPE:
1003 		g_value_set_object (value, priv->ignore_type);
1004 		break;
1005 	case PROP_ERROR_ENTRY_TYPE:
1006 		g_value_set_object (value, priv->error_type);
1007 		break;
1008 	case PROP_DEVICE_INFO:
1009 		g_value_set_object (value, priv->device_info);
1010 		break;
1011 	case PROP_VOLUME:
1012 		g_value_set_object (value, priv->volume);
1013 		break;
1014 	case PROP_MOUNT_ROOT:
1015 		g_value_set_object (value, priv->mount_root);
1016 		break;
1017 	case PROP_GUDEV_DEVICE:
1018 		g_value_set_object (value, priv->gudev_device);
1019 		break;
1020 	case PROP_DEVICE_SERIAL:
1021 		g_value_set_string (value, g_udev_device_get_property (priv->gudev_device, "ID_SERIAL"));
1022 		break;
1023 	default:
1024 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1025 		break;
1026 	}
1027 }
1028 
1029 static void
impl_dispose(GObject * object)1030 impl_dispose (GObject *object)
1031 {
1032 	RBAndroidSourcePrivate *priv = GET_PRIVATE (object);
1033 
1034 	if (priv->db != NULL) {
1035 		if (priv->ignore_type != NULL) {
1036 			rhythmdb_entry_delete_by_type (priv->db, priv->ignore_type);
1037 			g_clear_object (&priv->ignore_type);
1038 		}
1039 		if (priv->error_type != NULL) {
1040 			rhythmdb_entry_delete_by_type (priv->db, priv->error_type);
1041 			g_clear_object (&priv->error_type);
1042 		}
1043 
1044 		g_clear_object (&priv->db);
1045 	}
1046 
1047 	if (priv->import_job != NULL) {
1048 		rhythmdb_import_job_cancel (priv->import_job);
1049 		g_clear_object (&priv->import_job);
1050 	}
1051 	if (priv->rescan_id != 0) {
1052 		g_source_remove (priv->rescan_id);
1053 	}
1054 
1055 	g_clear_object (&priv->device_info);
1056 	g_clear_object (&priv->volume);
1057 	g_clear_object (&priv->mount_root);
1058 	g_clear_object (&priv->gudev_device);
1059 
1060 	G_OBJECT_CLASS (rb_android_source_parent_class)->dispose (object);
1061 }
1062 
1063 static void
impl_finalize(GObject * object)1064 impl_finalize (GObject *object)
1065 {
1066 	RBAndroidSourcePrivate *priv = GET_PRIVATE (object);
1067 
1068 	g_list_free_full (priv->storage, g_object_unref);
1069 
1070 	G_OBJECT_CLASS (rb_android_source_parent_class)->finalize (object);
1071 }
1072 
1073 
1074 static void
rb_android_device_source_init(RBDeviceSourceInterface * interface)1075 rb_android_device_source_init (RBDeviceSourceInterface *interface)
1076 {
1077 	interface->eject = impl_eject;
1078 }
1079 
1080 static void
rb_android_transfer_target_init(RBTransferTargetInterface * interface)1081 rb_android_transfer_target_init (RBTransferTargetInterface *interface)
1082 {
1083 	interface->build_dest_uri = impl_build_dest_uri;
1084 	interface->track_added = impl_track_added;
1085 }
1086 
1087 static void
rb_android_source_class_init(RBAndroidSourceClass * klass)1088 rb_android_source_class_init (RBAndroidSourceClass *klass)
1089 {
1090 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
1091 	RBDisplayPageClass *page_class = RB_DISPLAY_PAGE_CLASS (klass);
1092 	RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
1093 	RBBrowserSourceClass *browser_class = RB_BROWSER_SOURCE_CLASS (klass);
1094 	RBMediaPlayerSourceClass *mps_class = RB_MEDIA_PLAYER_SOURCE_CLASS (klass);
1095 
1096 	object_class->set_property = impl_set_property;
1097 	object_class->get_property = impl_get_property;
1098 	object_class->constructed = impl_constructed;
1099 	object_class->dispose = impl_dispose;
1100 	object_class->finalize = impl_finalize;
1101 
1102 	page_class->delete_thyself = impl_delete_thyself;
1103 	page_class->selected = impl_selected;
1104 
1105 	browser_class->pack_content = impl_pack_content;
1106 
1107 	source_class->can_delete = impl_can_delete;
1108 	source_class->delete_selected = impl_delete_selected;
1109 	source_class->can_move_to_trash = (RBSourceFeatureFunc) rb_false_function;
1110 	source_class->can_paste = impl_can_paste;
1111 	source_class->paste = impl_paste;
1112 	source_class->want_uri = rb_device_source_want_uri;
1113 	source_class->uri_is_source = rb_device_source_uri_is_source;
1114 
1115 	mps_class->get_entries = impl_get_entries;
1116 	mps_class->get_capacity = impl_get_capacity;
1117 	mps_class->get_free_space = impl_get_free_space;
1118 	mps_class->delete_entries = impl_delete_entries;
1119 	mps_class->show_properties = impl_show_properties;
1120 
1121 	g_object_class_install_property (object_class,
1122 					 PROP_ERROR_ENTRY_TYPE,
1123 					 g_param_spec_object ("error-entry-type",
1124 							      "Error entry type",
1125 							      "Entry type to use for import error entries added by this source",
1126 							      RHYTHMDB_TYPE_ENTRY_TYPE,
1127 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1128 	g_object_class_install_property (object_class,
1129 					 PROP_IGNORE_ENTRY_TYPE,
1130 					 g_param_spec_object ("ignore-entry-type",
1131 							      "Ignore entry type",
1132 							      "Entry type to use for ignore entries added by this source",
1133 							      RHYTHMDB_TYPE_ENTRY_TYPE,
1134 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1135 	g_object_class_install_property (object_class,
1136 					 PROP_DEVICE_INFO,
1137 					 g_param_spec_object ("device-info",
1138 							      "device info",
1139 							      "device information object",
1140 							      MPID_TYPE_DEVICE,
1141 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1142 	g_object_class_install_property (object_class,
1143 					 PROP_VOLUME,
1144 					 g_param_spec_object ("volume",
1145 							      "volume",
1146 							      "GVolume object",
1147 							      G_TYPE_VOLUME,
1148 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1149 	g_object_class_install_property (object_class,
1150 					 PROP_MOUNT_ROOT,
1151 					 g_param_spec_object ("mount-root",
1152 							      "mount root",
1153 							      "Mount root",
1154 							      G_TYPE_OBJECT,
1155 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1156 
1157 	g_object_class_install_property (object_class,
1158 					 PROP_GUDEV_DEVICE,
1159 					 g_param_spec_object ("gudev-device",
1160 							      "gudev-device",
1161 							      "GUdev device object",
1162 							      G_UDEV_TYPE_DEVICE,
1163 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1164 
1165 	g_object_class_override_property (object_class, PROP_DEVICE_SERIAL, "serial");
1166 
1167 	g_type_class_add_private (klass, sizeof (RBAndroidSourcePrivate));
1168 }
1169 
1170 static void
rb_android_source_class_finalize(RBAndroidSourceClass * klass)1171 rb_android_source_class_finalize (RBAndroidSourceClass *klass)
1172 {
1173 }
1174 
1175 void
_rb_android_source_register_type(GTypeModule * module)1176 _rb_android_source_register_type (GTypeModule *module)
1177 {
1178 	rb_android_source_register_type (module);
1179 }
1180 
1181