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