1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2  *
3  *  Copyright (C) 2004 James Livingston  <doclivingston@gmail.com>
4  *
5  *  This program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2 of the License, or
8  *  (at your option) any later version.
9  *
10  *  The Rhythmbox authors hereby grant permission for non-GPL compatible
11  *  GStreamer plugins to be used and distributed together with GStreamer
12  *  and Rhythmbox. This permission is above and beyond the permissions granted
13  *  by the GPL license by which Rhythmbox is covered. If you modify this code
14  *  you may extend this exception to your version of the code, but you are not
15  *  obligated to do so. If you do not wish to do so, delete this exception
16  *  statement from your version.
17  *
18  *  This program is distributed in the hope that it will be useful,
19  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
20  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  *  GNU General Public License for more details.
22  *
23  *  You should have received a copy of the GNU General Public License
24  *  along with this program; if not, write to the Free Software
25  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
26  *
27  */
28 
29 #define __EXTENSIONS__
30 
31 #include "config.h"
32 
33 #include <string.h>
34 
35 #include <gtk/gtk.h>
36 #include <glib/gi18n.h>
37 
38 #include <totem-pl-parser.h>
39 
40 #include "mediaplayerid.h"
41 
42 #include "rb-generic-player-source.h"
43 #include "rb-generic-player-playlist-source.h"
44 #include "rb-removable-media-manager.h"
45 #include "rb-transfer-target.h"
46 #include "rb-device-source.h"
47 #include "rb-debug.h"
48 #include "rb-util.h"
49 #include "rb-file-helpers.h"
50 #include "rhythmdb.h"
51 #include "rb-dialog.h"
52 #include "rhythmdb-import-job.h"
53 #include "rb-import-errors-source.h"
54 #include "rb-builder-helpers.h"
55 #include "rb-gst-media-types.h"
56 #include "rb-sync-settings.h"
57 #include "rb-missing-plugins.h"
58 #include "rb-application.h"
59 #include "rb-display-page-menu.h"
60 #include "rb-task-list.h"
61 
62 static void rb_generic_player_device_source_init (RBDeviceSourceInterface *interface);
63 static void rb_generic_player_source_transfer_target_init (RBTransferTargetInterface *interface);
64 
65 static void impl_constructed (GObject *object);
66 static void impl_dispose (GObject *object);
67 static void impl_set_property (GObject *object,
68 			       guint prop_id,
69 			       const GValue *value,
70 			       GParamSpec *pspec);
71 static void impl_get_property (GObject *object,
72 			       guint prop_id,
73 			       GValue *value,
74 			       GParamSpec *pspec);
75 
76 static void load_songs (RBGenericPlayerSource *source);
77 
78 static void impl_delete_thyself (RBDisplayPage *page);
79 static void impl_selected (RBDisplayPage *page);
80 
81 static gboolean impl_can_paste (RBSource *source);
82 static RBTrackTransferBatch *impl_paste (RBSource *source, GList *entries);
83 static gboolean impl_can_delete (RBSource *source);
84 static void impl_delete_selected (RBSource *source);
85 
86 static void impl_eject (RBDeviceSource *source);
87 
88 static char* impl_build_dest_uri (RBTransferTarget *target,
89 				  RhythmDBEntry *entry,
90 				  const char *media_type,
91 				  const char *extension);
92 static guint64 impl_get_capacity (RBMediaPlayerSource *source);
93 static guint64 impl_get_free_space (RBMediaPlayerSource *source);
94 static void impl_get_entries (RBMediaPlayerSource *source, const char *category, GHashTable *map);
95 static void impl_delete_entries (RBMediaPlayerSource *source,
96 				 GList *entries,
97 				 GAsyncReadyCallback callback,
98 				 gpointer data);
99 static void impl_show_properties (RBMediaPlayerSource *source, GtkWidget *info_box, GtkWidget *notebook);
100 static void impl_add_playlist (RBMediaPlayerSource *source, char *name, GList *entries);
101 static void impl_remove_playlists (RBMediaPlayerSource *source);
102 
103 static char *default_get_mount_path (RBGenericPlayerSource *source);
104 static void default_load_playlists (RBGenericPlayerSource *source);
105 static char * default_uri_from_playlist_uri (RBGenericPlayerSource *source,
106 					     const char *uri);
107 static char * default_uri_to_playlist_uri (RBGenericPlayerSource *source,
108 					   const char *uri,
109 					   TotemPlParserType playlist_type);
110 
111 static void new_playlist_action_cb (GSimpleAction *, GVariant *, gpointer);
112 
113 enum
114 {
115 	PROP_0,
116 	PROP_MOUNT,
117 	PROP_IGNORE_ENTRY_TYPE,
118 	PROP_ERROR_ENTRY_TYPE,
119 	PROP_DEVICE_INFO
120 };
121 
122 typedef struct
123 {
124 	RhythmDB *db;
125 
126 	gboolean loaded;
127 	RhythmDBImportJob *import_job;
128 	gint load_playlists_id;
129 	GList *playlists;
130 	RBSource *import_errors;
131 
132 	char *mount_path;
133 
134 	/* entry types */
135 	RhythmDBEntryType *ignore_type;
136 	RhythmDBEntryType *error_type;
137 
138 	/* information derived from volume */
139 	gboolean read_only;
140 
141 	MPIDDevice *device_info;
142 	GMount *mount;
143 	gboolean ejecting;
144 
145 	GSimpleAction *new_playlist_action;
146 	char *new_playlist_action_name;
147 
148 } RBGenericPlayerSourcePrivate;
149 
150 G_DEFINE_DYNAMIC_TYPE_EXTENDED (
151 	RBGenericPlayerSource,
152 	rb_generic_player_source,
153 	RB_TYPE_MEDIA_PLAYER_SOURCE,
154 	0,
155 	G_IMPLEMENT_INTERFACE_DYNAMIC (RB_TYPE_DEVICE_SOURCE, rb_generic_player_device_source_init)
156 	G_IMPLEMENT_INTERFACE_DYNAMIC (RB_TYPE_TRANSFER_TARGET, rb_generic_player_source_transfer_target_init))
157 
158 #define GET_PRIVATE(o)   (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_GENERIC_PLAYER_SOURCE, RBGenericPlayerSourcePrivate))
159 
160 
161 static void
rb_generic_player_source_class_init(RBGenericPlayerSourceClass * klass)162 rb_generic_player_source_class_init (RBGenericPlayerSourceClass *klass)
163 {
164 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
165 	RBDisplayPageClass *page_class = RB_DISPLAY_PAGE_CLASS (klass);
166 	RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
167 	RBMediaPlayerSourceClass *mps_class = RB_MEDIA_PLAYER_SOURCE_CLASS (klass);
168 
169 	object_class->set_property = impl_set_property;
170 	object_class->get_property = impl_get_property;
171 	object_class->constructed = impl_constructed;
172 	object_class->dispose = impl_dispose;
173 
174 	page_class->delete_thyself = impl_delete_thyself;
175 	page_class->selected = impl_selected;
176 
177 	source_class->can_delete = impl_can_delete;
178 	source_class->delete_selected = impl_delete_selected;
179 	source_class->can_move_to_trash = (RBSourceFeatureFunc) rb_false_function;
180 	source_class->can_paste = impl_can_paste;
181 	source_class->paste = impl_paste;
182 	source_class->want_uri = rb_device_source_want_uri;
183 	source_class->uri_is_source = rb_device_source_uri_is_source;
184 
185 	mps_class->get_entries = impl_get_entries;
186 	mps_class->get_capacity = impl_get_capacity;
187 	mps_class->get_free_space = impl_get_free_space;
188 	mps_class->delete_entries = impl_delete_entries;
189 	mps_class->show_properties = impl_show_properties;
190 	mps_class->add_playlist = impl_add_playlist;
191 	mps_class->remove_playlists = impl_remove_playlists;
192 
193 	klass->get_mount_path = default_get_mount_path;
194 	klass->load_playlists = default_load_playlists;
195 	klass->uri_from_playlist_uri = default_uri_from_playlist_uri;
196 	klass->uri_to_playlist_uri = default_uri_to_playlist_uri;
197 
198 	g_object_class_install_property (object_class,
199 					 PROP_ERROR_ENTRY_TYPE,
200 					 g_param_spec_object ("error-entry-type",
201 							      "Error entry type",
202 							      "Entry type to use for import error entries added by this source",
203 							      RHYTHMDB_TYPE_ENTRY_TYPE,
204 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
205 	g_object_class_install_property (object_class,
206 					 PROP_IGNORE_ENTRY_TYPE,
207 					 g_param_spec_object ("ignore-entry-type",
208 							      "Ignore entry type",
209 							      "Entry type to use for ignore entries added by this source",
210 							      RHYTHMDB_TYPE_ENTRY_TYPE,
211 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
212 	g_object_class_install_property (object_class,
213 					 PROP_DEVICE_INFO,
214 					 g_param_spec_object ("device-info",
215 							      "device info",
216 							      "device information object",
217 							      MPID_TYPE_DEVICE,
218 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
219 	g_object_class_install_property (object_class,
220 					 PROP_MOUNT,
221 					 g_param_spec_object ("mount",
222 							      "mount",
223 							      "GMount object",
224 							      G_TYPE_MOUNT,
225 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
226 
227 	g_type_class_add_private (klass, sizeof (RBGenericPlayerSourcePrivate));
228 }
229 
230 static void
rb_generic_player_device_source_init(RBDeviceSourceInterface * interface)231 rb_generic_player_device_source_init (RBDeviceSourceInterface *interface)
232 {
233 	interface->eject = impl_eject;
234 }
235 
236 static void
rb_generic_player_source_transfer_target_init(RBTransferTargetInterface * interface)237 rb_generic_player_source_transfer_target_init (RBTransferTargetInterface *interface)
238 {
239 	interface->build_dest_uri = impl_build_dest_uri;
240 }
241 
242 static void
rb_generic_player_source_class_finalize(RBGenericPlayerSourceClass * klass)243 rb_generic_player_source_class_finalize (RBGenericPlayerSourceClass *klass)
244 {
245 }
246 
247 static void
rb_generic_player_source_init(RBGenericPlayerSource * source)248 rb_generic_player_source_init (RBGenericPlayerSource *source)
249 {
250 
251 }
252 
253 static void
impl_constructed(GObject * object)254 impl_constructed (GObject *object)
255 {
256 	RBGenericPlayerSource *source;
257 	RBGenericPlayerSourcePrivate *priv;
258 	RhythmDBEntryType *entry_type;
259 	char **playlist_formats;
260 	char **output_formats;
261 	char *mount_name;
262 	RBShell *shell;
263 	GFile *root;
264 	GFileInfo *info;
265 	GError *error = NULL;
266 	char *label;
267 	char *fullname;
268 	char *name;
269 
270 	RB_CHAIN_GOBJECT_METHOD (rb_generic_player_source_parent_class, constructed, object);
271 	source = RB_GENERIC_PLAYER_SOURCE (object);
272 
273 	priv = GET_PRIVATE (source);
274 
275 	rb_device_source_set_display_details (RB_DEVICE_SOURCE (source));
276 
277 	g_object_get (source,
278 		      "shell", &shell,
279 		      "entry-type", &entry_type,
280 		      "name", &name,
281 		      NULL);
282 
283 	g_object_get (shell, "db", &priv->db, NULL);
284 
285 	priv->import_errors = rb_import_errors_source_new (shell,
286 							   priv->error_type,
287 							   entry_type,
288 							   priv->ignore_type);
289 
290 
291 	priv->new_playlist_action_name = g_strdup_printf ("generic-player-%p-playlist-new", source);
292 	fullname = g_strdup_printf ("app.%s", priv->new_playlist_action_name);
293 
294 	label = g_strdup_printf (_("New Playlist on %s"), name);
295 
296 	rb_application_add_plugin_menu_item (RB_APPLICATION (g_application_get_default ()),
297 					     "display-page-add-playlist",
298 					     priv->new_playlist_action_name,
299 					     g_menu_item_new (label, fullname));
300 	g_free (fullname);
301 	g_free (label);
302 	g_free (name);
303 
304 	root = g_mount_get_root (priv->mount);
305 	mount_name = g_mount_get_name (priv->mount);
306 
307 	info = g_file_query_filesystem_info (root, G_FILE_ATTRIBUTE_FILESYSTEM_READONLY, NULL, &error);
308 	if (error != NULL) {
309 		rb_debug ("error querying filesystem info for %s: %s", mount_name, error->message);
310 		g_error_free (error);
311 		priv->read_only = FALSE;
312 	} else {
313 		priv->read_only = g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_FILESYSTEM_READONLY);
314 		g_object_unref (info);
315 	}
316 
317 	g_free (mount_name);
318 	g_object_unref (root);
319 
320 	g_object_get (priv->device_info, "playlist-formats", &playlist_formats, NULL);
321 	if ((priv->read_only == FALSE) && playlist_formats != NULL && g_strv_length (playlist_formats) > 0) {
322 		RBDisplayPageModel *model;
323 		GMenu *playlist_menu;
324 		GMenuModel *playlists;
325 
326 		priv->new_playlist_action = g_simple_action_new (priv->new_playlist_action_name, NULL);
327 		g_signal_connect (priv->new_playlist_action, "activate", G_CALLBACK (new_playlist_action_cb), source);
328 		g_action_map_add_action (G_ACTION_MAP (g_application_get_default ()), G_ACTION (priv->new_playlist_action));
329 
330 		g_object_get (shell, "display-page-model", &model, NULL);
331 		playlists = rb_display_page_menu_new (model,
332 						      RB_DISPLAY_PAGE (source),
333 						      RB_TYPE_GENERIC_PLAYER_PLAYLIST_SOURCE,
334 						      "app.playlist-add-to");
335 		g_object_unref (model);
336 
337 		playlist_menu = g_menu_new ();
338 		g_menu_append (playlist_menu, _("Add to New Playlist"), priv->new_playlist_action_name);
339 		g_menu_append_section (playlist_menu, NULL, playlists);
340 
341 		g_object_set (source, "playlist-menu", playlist_menu, NULL);
342 	}
343 	g_strfreev (playlist_formats);
344 	g_object_unref (entry_type);
345 
346 	g_object_get (priv->device_info, "output-formats", &output_formats, NULL);
347 	if (output_formats != NULL) {
348 		GstEncodingTarget *target;
349 		int i;
350 
351 		target = gst_encoding_target_new ("generic-player", "device", "", NULL);
352 		for (i = 0; output_formats[i] != NULL; i++) {
353 			const char *media_type = rb_gst_mime_type_to_media_type (output_formats[i]);
354 			if (media_type != NULL) {
355 				GstEncodingProfile *profile;
356 				profile = rb_gst_get_encoding_profile (media_type);
357 				if (profile != NULL) {
358 					gst_encoding_target_add_profile (target, profile);
359 				}
360 			}
361 		}
362 		g_object_set (source, "encoding-target", target, NULL);
363 	}
364 	g_strfreev (output_formats);
365 
366 	g_object_unref (shell);
367 }
368 
369 static void
impl_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)370 impl_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
371 {
372 	RBGenericPlayerSourcePrivate *priv = GET_PRIVATE (object);
373 
374 	switch (prop_id) {
375 	case PROP_IGNORE_ENTRY_TYPE:
376 		priv->ignore_type = g_value_get_object (value);
377 		break;
378 	case PROP_ERROR_ENTRY_TYPE:
379 		priv->error_type = g_value_get_object (value);
380 		break;
381 	case PROP_DEVICE_INFO:
382 		priv->device_info = g_value_dup_object (value);
383 		break;
384 	case PROP_MOUNT:
385 		priv->mount = g_value_dup_object (value);
386 		break;
387 	default:
388 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
389 		break;
390 	}
391 }
392 
393 static void
impl_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)394 impl_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
395 {
396 	RBGenericPlayerSourcePrivate *priv = GET_PRIVATE (object);
397 
398 	switch (prop_id) {
399 	case PROP_IGNORE_ENTRY_TYPE:
400 		g_value_set_object (value, priv->ignore_type);
401 		break;
402 	case PROP_ERROR_ENTRY_TYPE:
403 		g_value_set_object (value, priv->error_type);
404 		break;
405 	case PROP_DEVICE_INFO:
406 		g_value_set_object (value, priv->device_info);
407 		break;
408 	case PROP_MOUNT:
409 		g_value_set_object (value, priv->mount);
410 		break;
411 	default:
412 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
413 		break;
414 	}
415 }
416 
417 static void
impl_dispose(GObject * object)418 impl_dispose (GObject *object)
419 {
420 	RBGenericPlayerSourcePrivate *priv = GET_PRIVATE (object);
421 
422 	if (priv->load_playlists_id != 0) {
423 		g_source_remove (priv->load_playlists_id);
424 		priv->load_playlists_id = 0;
425 	}
426 
427 	if (priv->db != NULL) {
428 		if (priv->ignore_type != NULL) {
429 			rhythmdb_entry_delete_by_type (priv->db, priv->ignore_type);
430 			g_object_unref (priv->ignore_type);
431 			priv->ignore_type = NULL;
432 		}
433 		if (priv->error_type != NULL) {
434 			rhythmdb_entry_delete_by_type (priv->db, priv->error_type);
435 			g_object_unref (priv->error_type);
436 			priv->error_type = NULL;
437 		}
438 
439 		g_object_unref (priv->db);
440 		priv->db = NULL;
441 	}
442 
443 	if (priv->import_job != NULL) {
444 		rhythmdb_import_job_cancel (priv->import_job);
445 		g_object_unref (priv->import_job);
446 		priv->import_job = NULL;
447 	}
448 
449 	if (priv->device_info != NULL) {
450 		g_object_unref (priv->device_info);
451 		priv->device_info = NULL;
452 	}
453 
454 	if (priv->mount != NULL) {
455 		g_object_unref (priv->mount);
456 		priv->mount = NULL;
457 	}
458 
459 	rb_application_remove_plugin_menu_item (RB_APPLICATION (g_application_get_default ()),
460 						"display-page-add-playlist",
461 						priv->new_playlist_action_name);
462 
463 	G_OBJECT_CLASS (rb_generic_player_source_parent_class)->dispose (object);
464 }
465 
466 static void
impl_delete_thyself(RBDisplayPage * page)467 impl_delete_thyself (RBDisplayPage *page)
468 {
469 	GList *pl;
470 	GList *p;
471 	RBGenericPlayerSourcePrivate *priv = GET_PRIVATE (page);
472 
473 	/* take a copy of the list first, as playlist_deleted_cb modifies priv->playlists */
474 	pl = g_list_copy (priv->playlists);
475 	for (p = pl; p != NULL; p = p->next) {
476 		RBDisplayPage *playlist_page = RB_DISPLAY_PAGE (p->data);
477 		rb_display_page_delete_thyself (playlist_page);
478 	}
479 	g_list_free (priv->playlists);
480 	g_list_free (pl);
481 	priv->playlists = NULL;
482 
483 	if (priv->import_errors != NULL) {
484 		rb_display_page_delete_thyself (RB_DISPLAY_PAGE (priv->import_errors));
485 		priv->import_errors = NULL;
486 	}
487 
488 	RB_DISPLAY_PAGE_CLASS (rb_generic_player_source_parent_class)->delete_thyself (page);
489 }
490 
491 static gboolean
ensure_loaded(RBGenericPlayerSource * source)492 ensure_loaded (RBGenericPlayerSource *source)
493 {
494 	RBGenericPlayerSourcePrivate *priv = GET_PRIVATE (source);
495 	RBSourceLoadStatus status;
496 
497 	if (priv->loaded) {
498 		g_object_get (source, "load-status", &status, NULL);
499 		return (status == RB_SOURCE_LOAD_STATUS_LOADED);
500 	} else {
501 		priv->loaded = TRUE;
502 		rb_media_player_source_load (RB_MEDIA_PLAYER_SOURCE (source));
503 		load_songs (source);
504 		return FALSE;
505 	}
506 }
507 
508 static void
impl_selected(RBDisplayPage * page)509 impl_selected (RBDisplayPage *page)
510 {
511 	ensure_loaded (RB_GENERIC_PLAYER_SOURCE (page));
512 }
513 
514 static void
import_complete_cb(RhythmDBImportJob * job,int total,RBGenericPlayerSource * source)515 import_complete_cb (RhythmDBImportJob *job, int total, RBGenericPlayerSource *source)
516 {
517 	RBGenericPlayerSourceClass *klass = RB_GENERIC_PLAYER_SOURCE_GET_CLASS (source);
518 	RBGenericPlayerSourcePrivate *priv = GET_PRIVATE (source);
519 	GSettings *settings;
520 	RBShell *shell;
521 
522 	if (priv->ejecting) {
523 		rb_device_source_default_eject (RB_DEVICE_SOURCE (source));
524 	} else {
525 		g_object_get (source, "shell", &shell, NULL);
526 		rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (priv->import_errors), RB_DISPLAY_PAGE (source));
527 		g_object_unref (shell);
528 
529 		if (klass->load_playlists)
530 			klass->load_playlists (source);
531 
532 		g_object_set (source, "load-status", RB_SOURCE_LOAD_STATUS_LOADED, NULL);
533 
534 		g_object_get (source, "encoding-settings", &settings, NULL);
535 		rb_transfer_target_transfer (RB_TRANSFER_TARGET (source), settings, NULL, FALSE);
536 		g_object_unref (settings);
537 
538 		rb_media_player_source_purge_metadata_cache (RB_MEDIA_PLAYER_SOURCE (source));
539 	}
540 
541 	g_object_unref (priv->import_job);
542 	priv->import_job = NULL;
543 }
544 
545 static void
load_songs(RBGenericPlayerSource * source)546 load_songs (RBGenericPlayerSource *source)
547 {
548 	RBGenericPlayerSourcePrivate *priv = GET_PRIVATE (source);
549 	RhythmDBEntryType *entry_type;
550 	char **audio_folders;
551 	char *mount_path;
552 	RBShell *shell;
553 	RBTaskList *tasklist;
554 	char *name;
555 	char *label;
556 
557 	mount_path = rb_generic_player_source_get_mount_path (source);
558 	g_object_get (source, "entry-type", &entry_type, NULL);
559 
560 	/* if we have a set of folders on the device containing audio files,
561 	 * load only those folders, otherwise add the whole volume.
562 	 */
563 	priv->import_job = rhythmdb_import_job_new (priv->db, entry_type, priv->ignore_type, priv->error_type);
564 	g_object_get (source, "name", &name, NULL);
565 	label = g_strdup_printf (_("Scanning %s"), name);
566 	g_object_set (priv->import_job, "task-label", label, NULL);
567 	g_free (label);
568 	g_free (name);
569 
570 	g_signal_connect_object (priv->import_job, "complete", G_CALLBACK (import_complete_cb), source, 0);
571 
572 	g_object_get (priv->device_info, "audio-folders", &audio_folders, NULL);
573 	if (audio_folders != NULL && g_strv_length (audio_folders) > 0) {
574 		int af;
575 		for (af=0; audio_folders[af] != NULL; af++) {
576 			char *path;
577 			path = rb_uri_append_path (mount_path, audio_folders[af]);
578 			rb_debug ("loading songs from device audio folder %s", path);
579 			rhythmdb_import_job_add_uri (priv->import_job, path);
580 			g_free (path);
581 		}
582 	} else {
583 		rb_debug ("loading songs from device mount path %s", mount_path);
584 		rhythmdb_import_job_add_uri (priv->import_job, mount_path);
585 	}
586 	g_strfreev (audio_folders);
587 
588 	rhythmdb_import_job_start (priv->import_job);
589 
590 	g_object_get (source, "shell", &shell, NULL);
591 	g_object_get (shell, "task-list", &tasklist, NULL);
592 	rb_task_list_add_task (tasklist, RB_TASK_PROGRESS (priv->import_job));
593 	g_object_unref (tasklist);
594 	g_object_unref (shell);
595 
596 	g_object_unref (entry_type);
597 	g_free (mount_path);
598 }
599 
600 char *
rb_generic_player_source_get_mount_path(RBGenericPlayerSource * source)601 rb_generic_player_source_get_mount_path (RBGenericPlayerSource *source)
602 {
603 	RBGenericPlayerSourceClass *klass = RB_GENERIC_PLAYER_SOURCE_GET_CLASS (source);
604 
605 	return klass->get_mount_path (source);
606 }
607 
608 static char *
default_get_mount_path(RBGenericPlayerSource * source)609 default_get_mount_path (RBGenericPlayerSource *source)
610 {
611 	RBGenericPlayerSourcePrivate *priv = GET_PRIVATE (source);
612 
613 	if (priv->mount_path == NULL) {
614 		GFile *root;
615 
616 		root = g_mount_get_root (priv->mount);
617 		if (root != NULL) {
618 			priv->mount_path = g_file_get_uri (root);
619 			g_object_unref (root);
620 		}
621 	}
622 
623 	return g_strdup (priv->mount_path);
624 }
625 
626 gboolean
rb_generic_player_is_mount_player(GMount * mount,MPIDDevice * device_info)627 rb_generic_player_is_mount_player (GMount *mount, MPIDDevice *device_info)
628 {
629 	char **protocols;
630 	gboolean result = FALSE;
631 	int i;
632 
633 	/* claim anything with 'storage' as an access protocol */
634 	g_object_get (device_info, "access-protocols", &protocols, NULL);
635 	if (protocols != NULL) {
636 		for (i = 0; protocols[i] != NULL; i++) {
637 			if (g_str_equal (protocols[i], "storage")) {
638 				result = TRUE;
639 				break;
640 			}
641 		}
642 		g_strfreev (protocols);
643 	}
644 
645 	return result;
646 }
647 
648 /* code for playlist loading */
649 
650 static void
playlist_deleted_cb(RBSource * playlist,RBGenericPlayerSource * source)651 playlist_deleted_cb (RBSource *playlist, RBGenericPlayerSource *source)
652 {
653 	RBGenericPlayerSourcePrivate *priv = GET_PRIVATE (source);
654 	GList *p;
655 
656 	p = g_list_find (priv->playlists, playlist);
657 	if (p != NULL) {
658 		priv->playlists = g_list_delete_link (priv->playlists, p);
659 		g_object_unref (playlist);
660 	}
661 }
662 
663 void
rb_generic_player_source_add_playlist(RBGenericPlayerSource * source,RBShell * shell,RBSource * playlist)664 rb_generic_player_source_add_playlist (RBGenericPlayerSource *source,
665 				       RBShell *shell,
666 				       RBSource *playlist)
667 {
668 	RBGenericPlayerSourcePrivate *priv = GET_PRIVATE (source);
669 	g_object_ref (playlist);
670 	priv->playlists = g_list_prepend (priv->playlists, playlist);
671 
672 	g_signal_connect_object (playlist, "deleted", G_CALLBACK (playlist_deleted_cb), source, 0);
673 
674 	rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (playlist), RB_DISPLAY_PAGE (source));
675 }
676 
677 
678 
679 static char *
default_uri_from_playlist_uri(RBGenericPlayerSource * source,const char * uri)680 default_uri_from_playlist_uri (RBGenericPlayerSource *source, const char *uri)
681 {
682 	char *mount_uri;
683 	char *full_uri;
684 
685 	mount_uri = rb_generic_player_source_get_mount_path (source);
686 	if (g_str_has_prefix (uri, mount_uri)) {
687 		return g_strdup (uri);
688 	}
689 
690 	full_uri = rb_uri_append_uri (mount_uri, uri);
691 	g_free (mount_uri);
692 
693 	rb_debug ("%s => %s", uri, full_uri);
694 	return full_uri;
695 }
696 
697 static char *
default_uri_to_playlist_uri(RBGenericPlayerSource * source,const char * uri,TotemPlParserType playlist_type)698 default_uri_to_playlist_uri (RBGenericPlayerSource *source, const char *uri, TotemPlParserType playlist_type)
699 {
700 	char *mount_uri;
701 	char *playlist_uri;
702 
703 	switch (playlist_type) {
704 	case TOTEM_PL_PARSER_IRIVER_PLA:
705 		/* we need absolute paths within the device filesystem for this format */
706 		mount_uri = rb_generic_player_source_get_mount_path (source);
707 		if (g_str_has_prefix (uri, mount_uri) == FALSE) {
708 			rb_debug ("uri %s is not under device mount uri %s", uri, mount_uri);
709 			return NULL;
710 		}
711 
712 		playlist_uri = g_strdup_printf ("file://%s", uri + strlen (mount_uri));
713 		return playlist_uri;
714 
715 	case TOTEM_PL_PARSER_M3U_DOS:
716 	case TOTEM_PL_PARSER_M3U:
717 	case TOTEM_PL_PARSER_PLS:
718 	default:
719 		/* leave the URI as-is, so we end up with relative paths in the playlist file */
720 		return g_strdup (uri);
721 	}
722 }
723 
724 char *
rb_generic_player_source_uri_from_playlist_uri(RBGenericPlayerSource * source,const char * uri)725 rb_generic_player_source_uri_from_playlist_uri (RBGenericPlayerSource *source, const char *uri)
726 {
727 	RBGenericPlayerSourceClass *klass = RB_GENERIC_PLAYER_SOURCE_GET_CLASS (source);
728 
729 	return klass->uri_from_playlist_uri (source, uri);
730 }
731 
732 char *
rb_generic_player_source_uri_to_playlist_uri(RBGenericPlayerSource * source,const char * uri,TotemPlParserType playlist_type)733 rb_generic_player_source_uri_to_playlist_uri (RBGenericPlayerSource *source, const char *uri, TotemPlParserType playlist_type)
734 {
735 	RBGenericPlayerSourceClass *klass = RB_GENERIC_PLAYER_SOURCE_GET_CLASS (source);
736 
737 	return klass->uri_to_playlist_uri (source, uri, playlist_type);
738 }
739 
740 static void
load_playlist_file(RBGenericPlayerSource * source,const char * playlist_path,const char * rel_path)741 load_playlist_file (RBGenericPlayerSource *source,
742 		    const char *playlist_path,
743 		    const char *rel_path)
744 {
745 	RhythmDBEntryType *entry_type;
746 	RBGenericPlayerPlaylistSource *playlist;
747 	RBShell *shell;
748 	GMenuModel *playlist_menu;
749 	char *mount_path;
750 
751 	g_object_get (source,
752 		      "shell", &shell,
753 		      "entry-type", &entry_type,
754 		      "playlist-menu", &playlist_menu,
755 		      NULL);
756 
757 	mount_path = rb_generic_player_source_get_mount_path (source);
758 	rb_debug ("loading playlist %s", playlist_path);
759 	playlist = RB_GENERIC_PLAYER_PLAYLIST_SOURCE (
760 			rb_generic_player_playlist_source_new (shell,
761 							       source,
762 							       playlist_path,
763 							       mount_path,
764 							       entry_type,
765 							       playlist_menu));
766 
767 	if (playlist != NULL) {
768 		rb_generic_player_source_add_playlist (source, shell, RB_SOURCE (playlist));
769 	}
770 
771 	g_object_unref (playlist_menu);
772 	g_object_unref (entry_type);
773 	g_object_unref (shell);
774 	g_free (mount_path);
775 }
776 
777 static gboolean
visit_playlist_dirs(GFile * file,gboolean dir,RBGenericPlayerSource * source)778 visit_playlist_dirs (GFile *file,
779 		     gboolean dir,
780 		     RBGenericPlayerSource *source)
781 {
782 	char *basename;
783 	char *uri;
784 	RhythmDBEntry *entry;
785 	RhythmDBEntryType *entry_type;
786 	RBGenericPlayerSourcePrivate *priv = GET_PRIVATE (source);
787 
788 	if (dir) {
789 		return TRUE;
790 	}
791 
792 	/* check if we've already got an entry
793 	 * for this file, just to save some i/o.
794 	 */
795 	uri = g_file_get_uri (file);
796 	entry = rhythmdb_entry_lookup_by_location (priv->db, uri);
797 	g_free (uri);
798 	if (entry != NULL) {
799 		gboolean is_song;
800 
801 		is_song = FALSE;
802 
803 		g_object_get (source, "entry-type", &entry_type, NULL);
804 		is_song = (rhythmdb_entry_get_entry_type (entry) == entry_type);
805 		g_object_unref (entry_type);
806 
807 		if (is_song) {
808 			rb_debug ("%s was loaded as a song",
809 				  rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION));
810 			return TRUE;
811 		}
812 	}
813 
814 	basename = g_file_get_basename (file);
815 	if (strcmp (basename, ".is_audio_player") != 0) {
816 		char *playlist_path;
817 		playlist_path = g_file_get_path (file);
818 		load_playlist_file (source, playlist_path, basename);
819 		g_free (playlist_path);
820 	}
821 
822 	g_free (basename);
823 
824 	return TRUE;
825 }
826 
827 static void
default_load_playlists(RBGenericPlayerSource * source)828 default_load_playlists (RBGenericPlayerSource *source)
829 {
830 	RBGenericPlayerSourcePrivate *priv = GET_PRIVATE (source);
831 	char *mount_path;
832 	char *playlist_path;
833 	char *full_playlist_path;
834 	char **playlist_formats;
835 
836 	mount_path = rb_generic_player_source_get_mount_path (source);
837 
838 	playlist_path = rb_generic_player_source_get_playlist_path (RB_GENERIC_PLAYER_SOURCE (source));
839 	if (playlist_path) {
840 
841 		/* If the device only supports a single playlist, just load that */
842 		if (g_str_has_suffix (playlist_path, ".m3u") ||
843 		    g_str_has_suffix (playlist_path, ".pls")) {
844 			full_playlist_path = rb_uri_append_path (mount_path, playlist_path);
845 			if (rb_uri_exists (full_playlist_path)) {
846 				load_playlist_file (source, full_playlist_path, playlist_path);
847 			}
848 
849 			g_free (full_playlist_path);
850 			g_free (playlist_path);
851 			return;
852 		}
853 
854 		/* Otherwise, limit the search to the device's playlist folder */
855 		full_playlist_path = rb_uri_append_path (mount_path, playlist_path);
856 		rb_debug ("constructed playlist search path %s", full_playlist_path);
857 	} else {
858 		g_free (playlist_path);
859 		return;
860 	}
861 
862 	/* only try to load playlists if the device has at least one playlist format */
863 	g_object_get (priv->device_info, "playlist-formats", &playlist_formats, NULL);
864 	if (playlist_formats != NULL && g_strv_length (playlist_formats) > 0) {
865 		rb_debug ("searching for playlists in %s", playlist_path);
866 		rb_uri_handle_recursively (full_playlist_path,
867 					   NULL,
868 					   (RBUriRecurseFunc) visit_playlist_dirs,
869 					   source);
870 	}
871 	g_strfreev (playlist_formats);
872 
873 	g_free (playlist_path);
874 	g_free (full_playlist_path);
875 	g_free (mount_path);
876 }
877 
878 static gboolean
impl_can_paste(RBSource * source)879 impl_can_paste (RBSource *source)
880 {
881 	RBGenericPlayerSourcePrivate *priv = GET_PRIVATE (source);
882 
883 	return (priv->read_only == FALSE);
884 }
885 
886 static RBTrackTransferBatch *
impl_paste(RBSource * source,GList * entries)887 impl_paste (RBSource *source, GList *entries)
888 {
889 	gboolean defer;
890 	GSettings *settings;
891 	RBTrackTransferBatch *batch;
892 
893 	defer = (ensure_loaded (RB_GENERIC_PLAYER_SOURCE (source)) == FALSE);
894 	g_object_get (source, "encoding-settings", &settings, NULL);
895 	batch = rb_transfer_target_transfer (RB_TRANSFER_TARGET (source), settings, entries, defer);
896 	g_object_unref (settings);
897 	return batch;
898 }
899 
900 static gboolean
impl_can_delete(RBSource * source)901 impl_can_delete (RBSource *source)
902 {
903 	RBGenericPlayerSourcePrivate *priv = GET_PRIVATE (source);
904 
905 	return (priv->read_only == FALSE);
906 }
907 
908 static gboolean
can_delete_directory(RBGenericPlayerSource * source,GFile * dir)909 can_delete_directory (RBGenericPlayerSource *source, GFile *dir)
910 {
911 	RBGenericPlayerSourcePrivate *priv = GET_PRIVATE (source);
912 	gboolean result;
913 	GMount *mount;
914 	GFile *root;
915 	char **audio_folders;
916 	int i;
917 
918 	g_object_get (source, "mount", &mount, NULL);
919 	root = g_mount_get_root (mount);
920 	g_object_unref (mount);
921 
922 	/* can't delete the root dir */
923 	if (g_file_equal (dir, root)) {
924 		rb_debug ("refusing to delete device root dir");
925 		g_object_unref (root);
926 		return FALSE;
927 	}
928 
929 	/* can't delete the device's audio folders */
930 	result = TRUE;
931 	g_object_get (priv->device_info, "audio-folders", &audio_folders, NULL);
932 	if (audio_folders != NULL && g_strv_length (audio_folders) > 0) {
933 		for (i = 0; audio_folders[i] != NULL; i++) {
934 			GFile *check;
935 
936 			check = g_file_resolve_relative_path (root, audio_folders[i]);
937 			if (g_file_equal (dir, check)) {
938 				rb_debug ("refusing to delete device audio folder %s", audio_folders[i]);
939 				result = FALSE;
940 			}
941 			g_object_unref (check);
942 		}
943 	}
944 	g_strfreev (audio_folders);
945 
946 	/* can delete anything else */
947 	g_object_unref (root);
948 	return result;
949 }
950 
951 static void
impl_delete_selected(RBSource * source)952 impl_delete_selected (RBSource *source)
953 {
954 	RBEntryView *view;
955 	GList *sel;
956 
957 	view = rb_source_get_entry_view (source);
958 	sel = rb_entry_view_get_selected_entries (view);
959 
960 	impl_delete_entries (RB_MEDIA_PLAYER_SOURCE (source), sel, NULL, NULL);
961 	g_list_free_full (sel, (GDestroyNotify) rhythmdb_entry_unref);
962 }
963 
964 
965 static void
impl_eject(RBDeviceSource * source)966 impl_eject (RBDeviceSource *source)
967 {
968 	RBGenericPlayerSourcePrivate *priv = GET_PRIVATE (source);
969 
970 	if (priv->import_job != NULL) {
971 		rhythmdb_import_job_cancel (priv->import_job);
972 		priv->ejecting = TRUE;
973 	} else {
974 		rb_device_source_default_eject (source);
975 	}
976 }
977 
978 
979 static char *
sanitize_path(const char * str)980 sanitize_path (const char *str)
981 {
982 	char *res = NULL;
983 	char *s;
984 
985 	/* Skip leading periods, otherwise files disappear... */
986 	while (*str == '.')
987 		str++;
988 
989 	s = g_strdup (str);
990 	g_strdelimit (s, "/", '-');
991 	res = g_uri_escape_string (s, G_URI_RESERVED_CHARS_ALLOWED_IN_PATH_ELEMENT, TRUE);
992 	g_free (s);
993 	return res;
994 }
995 
996 static char *
impl_build_dest_uri(RBTransferTarget * target,RhythmDBEntry * entry,const char * media_type,const char * extension)997 impl_build_dest_uri (RBTransferTarget *target,
998 		     RhythmDBEntry *entry,
999 		     const char *media_type,
1000 		     const char *extension)
1001 {
1002 	RBGenericPlayerSourcePrivate *priv = GET_PRIVATE (target);
1003 	const char *in_artist;
1004 	char *artist, *album, *title;
1005 	gulong track_number, disc_number;
1006 	const char *folders;
1007 	char **audio_folders;
1008 	char *mount_path;
1009 	char *number;
1010 	char *file = NULL;
1011 	char *path;
1012 	char *ext;
1013 
1014 	rb_debug ("building dest uri for entry at %s", rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION));
1015 
1016 	if (extension != NULL) {
1017 		ext = g_strconcat (".", extension, NULL);
1018 	} else {
1019 		ext = g_strdup ("");
1020 	}
1021 
1022 	in_artist = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ALBUM_ARTIST);
1023 	if (in_artist[0] == '\0') {
1024 		in_artist = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ARTIST);
1025 	}
1026 	artist = sanitize_path (in_artist);
1027 	album = sanitize_path (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ALBUM));
1028 	title = sanitize_path (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_TITLE));
1029 
1030 	/* we really do need to fix this so untagged entries actually have NULL rather than
1031 	 * a translated string.
1032 	 */
1033 	if (strcmp (artist, _("Unknown")) == 0 && strcmp (album, _("Unknown")) == 0 &&
1034 	    g_str_has_suffix (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION), title)) {
1035 		/* file isn't tagged, so just use the filename as-is, replacing the extension */
1036 		char *p;
1037 
1038 		p = g_utf8_strrchr (title, -1, '.');
1039 		if (p != NULL) {
1040 			*p = '\0';
1041 		}
1042 		file = g_strdup_printf ("%s%s", title, ext);
1043 	}
1044 
1045 	if (file == NULL) {
1046 		int folder_depth;
1047 
1048 		track_number = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_TRACK_NUMBER);
1049 		disc_number = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DISC_NUMBER);
1050 		if (disc_number > 0)
1051 			number = g_strdup_printf ("%.02u.%.02u", (guint)disc_number, (guint)track_number);
1052 		else
1053 			number = g_strdup_printf ("%.02u", (guint)track_number);
1054 
1055 		g_object_get (priv->device_info, "folder-depth", &folder_depth, NULL);
1056 		switch (folder_depth) {
1057 		case 0:
1058 			/* artist - album - number - title */
1059 			file = g_strdup_printf ("%s - %s - %s - %s%s",
1060 						artist, album, number, title, ext);
1061 			break;
1062 
1063 		case 1:
1064 			/* artist - album/number - title */
1065 			file = g_strdup_printf ("%s - %s" G_DIR_SEPARATOR_S "%s - %s%s",
1066 						artist, album, number, title, ext);
1067 			break;
1068 
1069 		default: /* use this for players that don't care */
1070 		case 2:
1071 			/* artist/album/number - title */
1072 			file = g_strdup_printf ("%s" G_DIR_SEPARATOR_S "%s" G_DIR_SEPARATOR_S "%s - %s%s",
1073 						artist, album, number, title, ext);
1074 			break;
1075 		}
1076 		g_free (number);
1077 	}
1078 
1079 	g_free (artist);
1080 	g_free (album);
1081 	g_free (title);
1082 	g_free (ext);
1083 
1084 	if (file == NULL)
1085 		return NULL;
1086 
1087 	g_object_get (priv->device_info, "audio-folders", &audio_folders, NULL);
1088 	if (audio_folders != NULL && g_strv_length (audio_folders) > 0) {
1089 		folders = g_strdup (audio_folders[0]);
1090 	} else {
1091 		folders = "";
1092 	}
1093 	g_strfreev (audio_folders);
1094 
1095 	mount_path = rb_generic_player_source_get_mount_path (RB_GENERIC_PLAYER_SOURCE (target));
1096 	path = g_build_filename (mount_path, folders, file, NULL);
1097 	g_free (file);
1098 	g_free (mount_path);
1099 
1100 	/* TODO: check for duplicates, or just overwrite by default? */
1101 	rb_debug ("dest file is %s", path);
1102 	return path;
1103 }
1104 
1105 static gboolean
strv_contains(char ** strv,const char * s)1106 strv_contains (char **strv, const char *s)
1107 {
1108 	int i;
1109 	for (i = 0; strv[i] != NULL; i++) {
1110 		if (g_str_equal (strv[i], s))
1111 			return TRUE;
1112 	}
1113 	return FALSE;
1114 }
1115 
1116 void
rb_generic_player_source_set_supported_formats(RBGenericPlayerSource * source,TotemPlParser * parser)1117 rb_generic_player_source_set_supported_formats (RBGenericPlayerSource *source, TotemPlParser *parser)
1118 {
1119 	RBGenericPlayerSourcePrivate *priv = GET_PRIVATE (source);
1120 	char **playlist_formats;
1121 	const char *check[] = { "audio/x-mpegurl", "audio/x-scpls", "audio/x-iriver-pla" };
1122 
1123 	g_object_get (priv->device_info, "playlist-formats", &playlist_formats, NULL);
1124 	if (playlist_formats != NULL && g_strv_length (playlist_formats) > 0) {
1125 		int i;
1126 		for (i = 0; i < G_N_ELEMENTS (check); i++) {
1127 			if (strv_contains (playlist_formats, check[i]) == FALSE) {
1128 				totem_pl_parser_add_ignored_mimetype (parser, check[i]);
1129 			}
1130 		}
1131 	}
1132 	g_strfreev (playlist_formats);
1133 
1134 	totem_pl_parser_add_ignored_mimetype (parser, "x-directory/normal");
1135 }
1136 
1137 TotemPlParserType
rb_generic_player_source_get_playlist_format(RBGenericPlayerSource * source)1138 rb_generic_player_source_get_playlist_format (RBGenericPlayerSource *source)
1139 {
1140 	RBGenericPlayerSourcePrivate *priv = GET_PRIVATE (source);
1141 	TotemPlParserType result;
1142 	char **playlist_formats;
1143 
1144 	g_object_get (priv->device_info, "playlist-formats", &playlist_formats, NULL);
1145 
1146 	if (playlist_formats == NULL || g_strv_length (playlist_formats) == 0 || strv_contains (playlist_formats, "audio/x-scpls")) {
1147 		result = TOTEM_PL_PARSER_PLS;
1148 	} else if (strv_contains (playlist_formats, "audio/x-mpegurl")) {
1149 		result = TOTEM_PL_PARSER_M3U_DOS;
1150 	} else if (strv_contains (playlist_formats, "audio/x-iriver-pla")) {
1151 		result = TOTEM_PL_PARSER_IRIVER_PLA;
1152 	} else {
1153 		/* now what? */
1154 		result = TOTEM_PL_PARSER_PLS;
1155 	}
1156 
1157 	g_strfreev (playlist_formats);
1158 	return result;
1159 }
1160 
1161 char *
rb_generic_player_source_get_playlist_path(RBGenericPlayerSource * source)1162 rb_generic_player_source_get_playlist_path (RBGenericPlayerSource *source)
1163 {
1164 	RBGenericPlayerSourcePrivate *priv = GET_PRIVATE (source);
1165 	char *path;
1166 
1167 	g_object_get (priv->device_info, "playlist-path", &path, NULL);
1168 	if (path != NULL && g_str_has_suffix (path, "%File")) {
1169 		path[strlen (path) - strlen("%File")] = '\0';
1170 	}
1171 	return path;
1172 }
1173 
1174 static guint64
get_fs_property(RBGenericPlayerSource * source,const char * attr)1175 get_fs_property (RBGenericPlayerSource *source, const char *attr)
1176 {
1177 	char *mountpoint;
1178 	GFile *root;
1179 	GFileInfo *info;
1180 	guint64 value = 0;
1181 
1182 	mountpoint = rb_generic_player_source_get_mount_path (source);
1183 	root = g_file_new_for_uri (mountpoint);
1184 	g_free (mountpoint);
1185 
1186 	info = g_file_query_filesystem_info (root, attr, NULL, NULL);
1187 	g_object_unref (root);
1188 	if (info != NULL) {
1189 		if (g_file_info_has_attribute (info, attr)) {
1190 			value = g_file_info_get_attribute_uint64 (info, attr);
1191 		}
1192 		g_object_unref (info);
1193 	}
1194 	return value;
1195 }
1196 static guint64
impl_get_capacity(RBMediaPlayerSource * source)1197 impl_get_capacity (RBMediaPlayerSource *source)
1198 {
1199 	return get_fs_property (RB_GENERIC_PLAYER_SOURCE (source), G_FILE_ATTRIBUTE_FILESYSTEM_SIZE);
1200 }
1201 
1202 static guint64
impl_get_free_space(RBMediaPlayerSource * source)1203 impl_get_free_space (RBMediaPlayerSource *source)
1204 {
1205 	return get_fs_property (RB_GENERIC_PLAYER_SOURCE (source), G_FILE_ATTRIBUTE_FILESYSTEM_FREE);
1206 }
1207 
1208 static void
impl_get_entries(RBMediaPlayerSource * source,const char * category,GHashTable * map)1209 impl_get_entries (RBMediaPlayerSource *source,
1210 		  const char *category,
1211 		  GHashTable *map)
1212 {
1213 	RhythmDBQueryModel *model;
1214 	GtkTreeIter iter;
1215 	gboolean podcast;
1216 
1217 	/* we don't have anything else to distinguish podcasts from regular
1218 	 * tracks, so just use the genre.
1219 	 */
1220 	podcast = (g_str_equal (category, SYNC_CATEGORY_PODCAST));
1221 
1222 	g_object_get (source, "base-query-model", &model, NULL);
1223 	if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (model), &iter) == FALSE) {
1224 		g_object_unref (model);
1225 		return;
1226 	}
1227 
1228 	do {
1229 		RhythmDBEntry *entry;
1230 		const char *genre;
1231 		entry = rhythmdb_query_model_iter_to_entry (model, &iter);
1232 		genre = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_GENRE);
1233 		if (g_str_equal (genre, "Podcast") == podcast) {
1234 			_rb_media_player_source_add_to_map (map, entry);
1235 		}
1236 	} while (gtk_tree_model_iter_next (GTK_TREE_MODEL (model), &iter));
1237 
1238 	g_object_unref (model);
1239 }
1240 
1241 static void
delete_data_destroy(gpointer data)1242 delete_data_destroy (gpointer data)
1243 {
1244 	g_list_free_full (data, (GDestroyNotify) rhythmdb_entry_unref);
1245 }
1246 
1247 static void
delete_entries_task(GTask * task,gpointer source_object,gpointer task_data,GCancellable * cancellable)1248 delete_entries_task (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable)
1249 {
1250 	RBGenericPlayerSource *source = RB_GENERIC_PLAYER_SOURCE (source_object);
1251 	RBGenericPlayerSourcePrivate *priv = GET_PRIVATE (source);
1252 	GList *l;
1253 
1254 	for (l = task_data; l != NULL; l = l->next) {
1255 		RhythmDBEntry *entry;
1256 		const char *uri;
1257 		GFile *file;
1258 		GFile *dir;
1259 
1260 		entry = l->data;
1261 		uri = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
1262 		file = g_file_new_for_uri (uri);
1263 		g_file_delete (file, NULL, NULL);
1264 
1265 		/* now walk up the directory structure and delete empty dirs
1266 		 * until we reach the root or one of the device's audio folders.
1267 		 */
1268 		dir = g_file_get_parent (file);
1269 		while (can_delete_directory (source, dir)) {
1270 			GFile *parent;
1271 			char *path;
1272 
1273 			path = g_file_get_path (dir);
1274 			rb_debug ("trying to delete %s", path);
1275 			g_free (path);
1276 
1277 			if (g_file_delete (dir, NULL, NULL) == FALSE) {
1278 				break;
1279 			}
1280 
1281 			parent = g_file_get_parent (dir);
1282 			if (parent == NULL) {
1283 				break;
1284 			}
1285 			g_object_unref (dir);
1286 			dir = parent;
1287 		}
1288 
1289 		g_object_unref (dir);
1290 		g_object_unref (file);
1291 
1292 		rhythmdb_entry_delete (priv->db, entry);
1293 	}
1294 
1295 	rhythmdb_commit (priv->db);
1296 
1297 	g_task_return_boolean (task, TRUE);
1298 	g_object_unref (task);
1299 }
1300 
1301 static void
impl_delete_entries(RBMediaPlayerSource * source,GList * entries,GAsyncReadyCallback callback,gpointer data)1302 impl_delete_entries (RBMediaPlayerSource *source,
1303 		     GList *entries,
1304 		     GAsyncReadyCallback callback,
1305 		     gpointer data)
1306 {
1307 	RBGenericPlayerSourcePrivate *priv = GET_PRIVATE (source);
1308 	GTask *task;
1309 	GList *task_entries;
1310 
1311 	if (priv->read_only != FALSE)
1312 		return;
1313 
1314 	task = g_task_new (source, NULL, callback, data);
1315 	task_entries = g_list_copy_deep (entries, (GCopyFunc) rhythmdb_entry_ref, NULL);
1316 	g_task_set_task_data (task, task_entries, delete_data_destroy);
1317 	g_task_run_in_thread (task, delete_entries_task);
1318 }
1319 
1320 
1321 static void
impl_show_properties(RBMediaPlayerSource * source,GtkWidget * info_box,GtkWidget * notebook)1322 impl_show_properties (RBMediaPlayerSource *source, GtkWidget *info_box, GtkWidget *notebook)
1323 {
1324 	RBGenericPlayerSourcePrivate *priv = GET_PRIVATE (source);
1325 	RhythmDBQueryModel *model;
1326 	GtkBuilder *builder;
1327 	GtkWidget *widget;
1328 	GString *str;
1329 	char *device_name;
1330 	char *vendor_name;
1331 	char *model_name;
1332 	char *serial_id;
1333 	GObject *plugin;
1334 	char *text;
1335 	GList *output_formats;
1336 	GList *t;
1337 
1338 	g_object_get (source, "plugin", &plugin, NULL);
1339 	builder = rb_builder_load_plugin_file (plugin, "generic-player-info.ui", NULL);
1340 	g_object_unref (plugin);
1341 
1342 	/* 'basic' tab stuff */
1343 
1344 	widget = GTK_WIDGET (gtk_builder_get_object (builder, "generic-player-basic-info"));
1345 	gtk_box_pack_start (GTK_BOX (info_box), widget, TRUE, TRUE, 0);
1346 
1347 	widget = GTK_WIDGET (gtk_builder_get_object (builder, "entry-device-name"));
1348 	g_object_get (source, "name", &device_name, NULL);
1349 	gtk_entry_set_text (GTK_ENTRY (widget), device_name);
1350 	g_free (device_name);
1351 	/* don't think we can support this..
1352 	g_signal_connect (widget, "focus-out-event",
1353 			  (GCallback)rb_mtp_source_name_changed_cb, source);
1354 			  */
1355 
1356 	g_object_get (source, "base-query-model", &model, NULL);
1357 	widget = GTK_WIDGET (gtk_builder_get_object (builder, "num-tracks"));
1358 	text = g_strdup_printf ("%d", gtk_tree_model_iter_n_children (GTK_TREE_MODEL (model), NULL));
1359 	gtk_label_set_text (GTK_LABEL (widget), text);
1360 	g_free (text);
1361 	g_object_unref (model);
1362 
1363 	widget = GTK_WIDGET (gtk_builder_get_object (builder, "num-playlists"));
1364 	text = g_strdup_printf ("%d", g_list_length (priv->playlists));
1365 	gtk_label_set_text (GTK_LABEL (widget), text);
1366 	g_free (text);
1367 
1368 	/* 'advanced' tab stuff */
1369 	widget = GTK_WIDGET (gtk_builder_get_object (builder, "generic-player-advanced-tab"));
1370 	gtk_notebook_append_page (GTK_NOTEBOOK (notebook), widget, gtk_label_new (_("Advanced")));
1371 
1372 	g_object_get (priv->device_info,
1373 		      "model", &model_name,
1374 		      "vendor", &vendor_name,
1375 		      "serial", &serial_id,
1376 		      NULL);
1377 
1378 	widget = GTK_WIDGET (gtk_builder_get_object (builder, "label-model-value"));
1379 	gtk_label_set_text (GTK_LABEL (widget), model_name);
1380 
1381 	widget = GTK_WIDGET (gtk_builder_get_object (builder, "label-manufacturer-value"));
1382 	gtk_label_set_text (GTK_LABEL (widget), vendor_name);
1383 
1384 	widget = GTK_WIDGET (gtk_builder_get_object (builder, "label-serial-number-value"));
1385 	gtk_label_set_text (GTK_LABEL (widget), serial_id);
1386 
1387 	g_free (model_name);
1388 	g_free (vendor_name);
1389 	g_free (serial_id);
1390 
1391 	str = g_string_new ("");
1392 	output_formats = rb_transfer_target_get_format_descriptions (RB_TRANSFER_TARGET (source));
1393 	for (t = output_formats; t != NULL; t = t->next) {
1394 		if (t != output_formats) {
1395 			g_string_append (str, "\n");
1396 		}
1397 		g_string_append (str, t->data);
1398 	}
1399 	rb_list_deep_free (output_formats);
1400 
1401 	widget = GTK_WIDGET (gtk_builder_get_object (builder, "audio-format-list"));
1402 	gtk_label_set_text (GTK_LABEL (widget), str->str);
1403 	g_string_free (str, TRUE);
1404 
1405 	g_object_unref (builder);
1406 }
1407 
1408 static void
impl_add_playlist(RBMediaPlayerSource * source,char * name,GList * entries)1409 impl_add_playlist (RBMediaPlayerSource *source, char *name, GList *entries)
1410 {
1411 	RBSource *playlist;
1412 	RhythmDBEntryType *entry_type;
1413 	RBShell *shell;
1414 	GList *i;
1415 	GMenuModel *playlist_menu;
1416 
1417 	g_object_get (source,
1418 		      "shell", &shell,
1419 		      "entry-type", &entry_type,
1420 		      "playlist-menu", &playlist_menu,
1421 		      NULL);
1422 
1423 	playlist = rb_generic_player_playlist_source_new (shell, RB_GENERIC_PLAYER_SOURCE (source), NULL, NULL, entry_type, playlist_menu);
1424 	g_object_unref (entry_type);
1425 
1426 	rb_generic_player_source_add_playlist (RB_GENERIC_PLAYER_SOURCE (source),
1427 					       shell,
1428 					       playlist);
1429 	g_object_set (playlist, "name", name, NULL);
1430 
1431 	for (i = entries; i != NULL; i = i->next) {
1432 		rb_static_playlist_source_add_entry (RB_STATIC_PLAYLIST_SOURCE (playlist),
1433 						     i->data,
1434 						     -1);
1435 	}
1436 
1437 	g_object_unref (playlist_menu);
1438 	g_object_unref (shell);
1439 }
1440 
1441 static void
impl_remove_playlists(RBMediaPlayerSource * source)1442 impl_remove_playlists (RBMediaPlayerSource *source)
1443 {
1444 	RBGenericPlayerSourcePrivate *priv = GET_PRIVATE (source);
1445 	GList *playlists;
1446 	GList *t;
1447 
1448 	playlists = g_list_copy (priv->playlists);
1449 	for (t = playlists; t != NULL; t = t->next) {
1450 		RBDisplayPage *p = RB_DISPLAY_PAGE (t->data);
1451 		rb_display_page_remove (p);
1452 	}
1453 
1454 	g_list_free (playlists);
1455 }
1456 
1457 void
_rb_generic_player_source_register_type(GTypeModule * module)1458 _rb_generic_player_source_register_type (GTypeModule *module)
1459 {
1460 	rb_generic_player_source_register_type (module);
1461 }
1462 
1463 static void
new_playlist_action_cb(GSimpleAction * action,GVariant * parameters,gpointer data)1464 new_playlist_action_cb (GSimpleAction *action, GVariant *parameters, gpointer data)
1465 {
1466 	RBGenericPlayerSource *source = RB_GENERIC_PLAYER_SOURCE (data);
1467 	RBShell *shell;
1468 	RBSource *playlist;
1469 	RBDisplayPageTree *page_tree;
1470 	RhythmDBEntryType *entry_type;
1471 	GMenuModel *playlist_menu;
1472 
1473 	g_object_get (source,
1474 		      "shell", &shell,
1475 		      "entry-type", &entry_type,
1476 		      "playlist-menu", &playlist_menu,
1477 		      NULL);
1478 
1479 	playlist = rb_generic_player_playlist_source_new (shell, source, NULL, NULL, entry_type, playlist_menu);
1480 	g_object_unref (entry_type);
1481 
1482 	rb_generic_player_source_add_playlist (source, shell, playlist);
1483 
1484 	g_object_get (shell, "display-page-tree", &page_tree, NULL);
1485 	rb_display_page_tree_edit_source_name (page_tree, playlist);
1486 	g_object_unref (page_tree);
1487 
1488 	g_object_unref (playlist_menu);
1489 	g_object_unref (shell);
1490 }
1491