No issues found
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2 *
3 * Copyright (C) 2002 Jorn Baayen <jorn@nl.linux.org>
4 * Copyright (C) 2003,2004 Colin Walters <walters@verbum.org>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * The Rhythmbox authors hereby grant permission for non-GPL compatible
12 * GStreamer plugins to be used and distributed together with GStreamer
13 * and Rhythmbox. This permission is above and beyond the permissions granted
14 * by the GPL license by which Rhythmbox is covered. If you modify this code
15 * you may extend this exception to your version of the code, but you are not
16 * obligated to do so. If you do not wish to do so, delete this exception
17 * statement from your version.
18 *
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * You should have received a copy of the GNU General Public License
25 * along with this program; if not, write to the Free Software
26 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
27 *
28 */
29
30 /**
31 * SECTION:rb-library-source
32 * @short_description: main library source, containing all local songs
33 *
34 * The library source contains all local songs that have been imported
35 * into the database.
36 *
37 * It provides a preferences page for configuring the library location,
38 * the directory structure to use when transferring new files into
39 * the library from another source, and the preferred audio encoding
40 * to use.
41 *
42 * If multiple library locations are configured, the library source
43 * creates a child source for each location, which will only show
44 * files found under that location.
45 */
46
47 #include "config.h"
48
49 #include <string.h>
50
51 #include <gtk/gtk.h>
52 #include <glib/gi18n.h>
53 #include <glib-object.h>
54 #include <gst/pbutils/install-plugins.h>
55
56 #include "rb-track-transfer-batch.h"
57 #include "rb-track-transfer-queue.h"
58
59 #include "rhythmdb.h"
60 #include "rb-debug.h"
61 #include "rb-dialog.h"
62 #include "rb-builder-helpers.h"
63 #include "rb-file-helpers.h"
64 #include "rb-util.h"
65 #include "rb-library-source.h"
66 #include "rb-auto-playlist-source.h"
67 #include "rb-encoder.h"
68 #include "rb-missing-plugins.h"
69 #include "rb-gst-media-types.h"
70 #include "rb-object-property-editor.h"
71 #include "rb-import-dialog.h"
72
73 #define SOURCE_PAGE 0
74 #define IMPORT_DIALOG_PAGE 1
75
76 static void rb_library_source_class_init (RBLibrarySourceClass *klass);
77 static void rb_library_source_init (RBLibrarySource *source);
78 static void rb_library_source_constructed (GObject *object);
79 static void rb_library_source_dispose (GObject *object);
80 static void rb_library_source_finalize (GObject *object);
81
82 static gboolean impl_show_popup (RBDisplayPage *source);
83 static GtkWidget *impl_get_config_widget (RBDisplayPage *source, RBShellPreferences *prefs);
84 static gboolean impl_receive_drag (RBDisplayPage *source, GtkSelectionData *data);
85 static void impl_get_status (RBDisplayPage *source, char **text, char **progress_text, float *progress);
86
87 static gboolean impl_can_paste (RBSource *asource);
88 static RBTrackTransferBatch *impl_paste (RBSource *source, GList *entries);
89 static guint impl_want_uri (RBSource *source, const char *uri);
90 static void impl_add_uri (RBSource *source,
91 const char *uri,
92 const char *title,
93 const char *genre,
94 RBSourceAddCallback callback,
95 gpointer data,
96 GDestroyNotify destroy_data);
97 static void impl_pack_content (RBBrowserSource *source, GtkWidget *content);
98
99 static void library_settings_changed_cb (GSettings *settings, const char *key, RBLibrarySource *source);
100 static void encoding_settings_changed_cb (GSettings *settings, const char *key, RBLibrarySource *source);
101 static void db_settings_changed_cb (GSettings *settings, const char *key, RBLibrarySource *source);
102 static gboolean rb_library_source_library_location_cb (GtkEntry *entry,
103 GdkEventFocus *event,
104 RBLibrarySource *source);
105 static void rb_library_source_sync_child_sources (RBLibrarySource *source);
106 static void rb_library_source_path_changed_cb (GtkComboBox *box,
107 RBLibrarySource *source);
108 static void rb_library_source_filename_changed_cb (GtkComboBox *box,
109 RBLibrarySource *source);
110 static void rb_library_source_format_changed_cb (GtkWidget *widget,
111 RBLibrarySource *source);
112 static void rb_library_source_preset_changed_cb (GtkWidget *widget,
113 RBLibrarySource *source);
114 static void rb_library_source_install_plugins_cb (GtkWidget *widget,
115 RBLibrarySource *source);
116 static void update_layout_example_label (RBLibrarySource *source);
117 static RhythmDBImportJob *maybe_create_import_job (RBLibrarySource *source);
118
119 typedef struct {
120 char *title;
121 char *path;
122 } LibraryPathElement;
123
124 const LibraryPathElement library_layout_paths[] = {
125 {N_("Artist/Artist - Album"), "%aa/%aa - %at"},
126 {N_("Artist/Album"), "%aa/%at"},
127 {N_("Artist - Album"), "%aa - %at"},
128 {N_("Album"), "%at"},
129 {N_("Artist"), "%aa"},
130 };
131 const int num_library_layout_paths = G_N_ELEMENTS (library_layout_paths);
132
133 const LibraryPathElement library_layout_filenames[] = {
134 {N_("Number - Title"), "%tN - %tt"},
135 {N_("Artist - Title"), "%ta - %tt"},
136 {N_("Artist - Number - Title"), "%ta - %tN - %tt"},
137 {N_("Artist (Album) - Number - Title"), "%ta (%at) - %tN - %tt"},
138 {N_("Title"), "%tt"},
139 {N_("Number. Artist - Title"), "%tN. %ta - %tt"},
140 };
141 const int num_library_layout_filenames = G_N_ELEMENTS (library_layout_filenames);
142
143 #define CUSTOM_SETTINGS_PRESET "rhythmbox-custom-settings"
144
145 struct RBLibrarySourcePrivate
146 {
147 RhythmDB *db;
148
149 RBShellPreferences *shell_prefs;
150
151 GtkWidget *notebook;
152 GtkWidget *config_widget;
153 GtkWidget *import_dialog;
154
155 GList *child_sources;
156
157 GtkWidget *library_location_entry;
158 GtkWidget *watch_library_check;
159 GtkWidget *layout_path_menu;
160 GtkWidget *layout_filename_menu;
161 GtkWidget *preferred_format_menu;
162 GtkWidget *preset_menu;
163 GtkWidget *layout_example_label;
164 GtkWidget *install_plugins_button;
165 GtkWidget *encoder_property_holder;
166 GtkWidget *encoder_property_editor;
167 GtkTreeModel *profile_model;
168 GtkTreeModel *preset_model;
169
170 GstElement *encoder_element;
171 GList *import_jobs;
172 guint start_import_job_id;
173 gulong profile_changed_id;
174 gboolean custom_settings_exists;
175 gboolean profile_init;
176 gboolean do_initial_import;
177
178 GSettings *settings;
179 GSettings *db_settings;
180 GSettings *encoding_settings;
181 };
182
183 #define RB_LIBRARY_SOURCE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_LIBRARY_SOURCE, RBLibrarySourcePrivate))
184 G_DEFINE_TYPE (RBLibrarySource, rb_library_source, RB_TYPE_BROWSER_SOURCE)
185
186 static void
187 rb_library_source_class_init (RBLibrarySourceClass *klass)
188 {
189 GObjectClass *object_class = G_OBJECT_CLASS (klass);
190 RBDisplayPageClass *page_class = RB_DISPLAY_PAGE_CLASS (klass);
191 RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
192 RBBrowserSourceClass *browser_source_class = RB_BROWSER_SOURCE_CLASS (klass);
193
194 object_class->dispose = rb_library_source_dispose;
195 object_class->finalize = rb_library_source_finalize;
196 object_class->constructed = rb_library_source_constructed;
197
198 page_class->show_popup = impl_show_popup;
199 page_class->get_config_widget = impl_get_config_widget;
200 page_class->receive_drag = impl_receive_drag;
201 page_class->get_status = impl_get_status;
202
203 source_class->impl_can_copy = (RBSourceFeatureFunc) rb_true_function;
204 source_class->impl_can_paste = (RBSourceFeatureFunc) impl_can_paste;
205 source_class->impl_paste = impl_paste;
206 source_class->impl_want_uri = impl_want_uri;
207 source_class->impl_add_uri = impl_add_uri;
208
209 browser_source_class->has_drop_support = (RBBrowserSourceFeatureFunc) rb_true_function;
210 browser_source_class->pack_content = impl_pack_content;
211
212 g_type_class_add_private (klass, sizeof (RBLibrarySourcePrivate));
213 }
214
215 static void
216 rb_library_source_init (RBLibrarySource *source)
217 {
218 source->priv = RB_LIBRARY_SOURCE_GET_PRIVATE (source);
219 }
220
221 static void
222 rb_library_source_dispose (GObject *object)
223 {
224 RBLibrarySource *source;
225 source = RB_LIBRARY_SOURCE (object);
226
227 if (source->priv->shell_prefs) {
228 g_object_unref (source->priv->shell_prefs);
229 source->priv->shell_prefs = NULL;
230 }
231
232 if (source->priv->db) {
233 g_object_unref (source->priv->db);
234 source->priv->db = NULL;
235 }
236
237 if (source->priv->settings) {
238 g_object_unref (source->priv->settings);
239 source->priv->settings = NULL;
240 }
241 if (source->priv->encoding_settings) {
242 g_object_unref (source->priv->encoding_settings);
243 source->priv->encoding_settings = NULL;
244 }
245 if (source->priv->db_settings) {
246 g_object_unref (source->priv->db_settings);
247 source->priv->db_settings = NULL;
248 }
249
250 if (source->priv->import_jobs != NULL) {
251 GList *t;
252 if (source->priv->start_import_job_id != 0) {
253 g_source_remove (source->priv->start_import_job_id);
254 source->priv->start_import_job_id = 0;
255 }
256 for (t = source->priv->import_jobs; t != NULL; t = t->next) {
257 RhythmDBImportJob *job = RHYTHMDB_IMPORT_JOB (t->data);
258 rhythmdb_import_job_cancel (job);
259 g_object_unref (job);
260 }
261 g_list_free (source->priv->import_jobs);
262 source->priv->import_jobs = NULL;
263 }
264
265 G_OBJECT_CLASS (rb_library_source_parent_class)->dispose (object);
266 }
267
268 static void
269 rb_library_source_finalize (GObject *object)
270 {
271 RBLibrarySource *source;
272
273 g_return_if_fail (object != NULL);
274 g_return_if_fail (RB_IS_LIBRARY_SOURCE (object));
275
276 source = RB_LIBRARY_SOURCE (object);
277
278 g_return_if_fail (source->priv != NULL);
279
280 rb_debug ("finalizing library source");
281
282 G_OBJECT_CLASS (rb_library_source_parent_class)->finalize (object);
283 }
284
285 static void
286 initial_import_job_complete_cb (RhythmDBImportJob *job, int total, RBLibrarySource *source)
287 {
288 if (rhythmdb_import_job_get_imported (job) == 0) {
289 rb_library_source_show_import_dialog (source);
290 }
291 }
292
293 static void
294 db_load_complete_cb (RhythmDB *db, RBLibrarySource *source)
295 {
296 RhythmDBImportJob *job;
297
298 /* once the database is loaded, we can run the query to populate the library source */
299 g_object_set (source, "populate", TRUE, NULL);
300
301 if (source->priv->do_initial_import) {
302 const char *music_dir;
303 char *music_dir_uri;
304
305 music_dir = rb_music_dir ();
306 music_dir_uri = g_filename_to_uri (music_dir, NULL, NULL);
307
308 /* create the music dir if it doesn't exist */
309 if (g_file_test (music_dir, G_FILE_TEST_EXISTS) == FALSE) {
310 g_mkdir_with_parents (music_dir, 0700);
311 }
312
313 /* import anything that's already in there */
314 job = maybe_create_import_job (source);
315 rhythmdb_import_job_add_uri (job, music_dir_uri);
316
317 /* if this doesn't import anything, show the import dialog */
318 g_signal_connect (job, "complete", G_CALLBACK (initial_import_job_complete_cb), source);
319
320 g_free (music_dir_uri);
321 }
322 }
323
324 static void
325 rb_library_source_constructed (GObject *object)
326 {
327 RBLibrarySource *source;
328 RBShell *shell;
329 RBEntryView *songs;
330 char **locations;
331
332 source = RB_LIBRARY_SOURCE (object);
333 source->priv->notebook = gtk_notebook_new ();
334 gtk_notebook_set_show_tabs (GTK_NOTEBOOK (source->priv->notebook), FALSE);
335 gtk_notebook_set_show_border (GTK_NOTEBOOK (source->priv->notebook), FALSE);
336
337 RB_CHAIN_GOBJECT_METHOD (rb_library_source_parent_class, constructed, object);
338
339 g_object_get (source, "shell", &shell, NULL);
340 g_object_get (shell, "db", &source->priv->db, NULL);
341
342 gtk_container_add (GTK_CONTAINER (source), source->priv->notebook);
343
344 gtk_notebook_set_current_page (GTK_NOTEBOOK (source->priv->notebook), 0);
345 gtk_widget_show_all (source->priv->notebook);
346
347 source->priv->settings = g_settings_new ("org.gnome.rhythmbox.library");
348 g_signal_connect_object (source->priv->settings, "changed", G_CALLBACK (library_settings_changed_cb), source, 0);
349
350 source->priv->encoding_settings = g_settings_get_child (source->priv->settings, "encoding");
351 g_signal_connect_object (source->priv->encoding_settings, "changed", G_CALLBACK (encoding_settings_changed_cb), source, 0);
352
353 source->priv->db_settings = g_settings_new ("org.gnome.rhythmbox.rhythmdb");
354 g_signal_connect_object (source->priv->db_settings, "changed", G_CALLBACK (db_settings_changed_cb), source, 0);
355
356 g_signal_connect_object (source->priv->db, "load-complete", G_CALLBACK (db_load_complete_cb), source, 0);
357
358 /* Set up the default library location if there's no library location set */
359 locations = g_settings_get_strv (source->priv->db_settings, "locations");
360 if (g_strv_length (locations) == 0) {
361 char *music_dir_uri;
362
363 music_dir_uri = g_filename_to_uri (rb_music_dir (), NULL, NULL);
364 if (music_dir_uri != NULL) {
365 const char *set_locations[2];
366
367 set_locations[0] = music_dir_uri;
368 set_locations[1] = NULL;
369 g_settings_set_strv (source->priv->db_settings, "locations", set_locations);
370
371 source->priv->do_initial_import = TRUE;
372
373 g_free (music_dir_uri);
374 }
375 }
376 g_strfreev (locations);
377
378 songs = rb_source_get_entry_view (RB_SOURCE (source));
379
380 rb_entry_view_append_column (songs, RB_ENTRY_VIEW_COL_RATING, FALSE);
381 rb_entry_view_append_column (songs, RB_ENTRY_VIEW_COL_LAST_PLAYED, FALSE);
382 rb_entry_view_append_column (songs, RB_ENTRY_VIEW_COL_FIRST_SEEN, FALSE);
383
384 rb_library_source_sync_child_sources (source);
385
386 g_object_unref (shell);
387 }
388
389 /**
390 * rb_library_source_new:
391 * @shell: the #RBShell
392 *
393 * Creates and returns the #RBLibrarySource instance
394 *
395 * Return value: the #RBLibrarySource
396 */
397 RBSource *
398 rb_library_source_new (RBShell *shell)
399 {
400 RBSource *source;
401 GdkPixbuf *icon;
402 GSettings *settings;
403 gint size;
404
405 gtk_icon_size_lookup (RB_SOURCE_ICON_SIZE, &size, NULL);
406 icon = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
407 "audio-x-generic",
408 size,
409 0, NULL);
410 settings = g_settings_new ("org.gnome.rhythmbox.library");
411 source = RB_SOURCE (g_object_new (RB_TYPE_LIBRARY_SOURCE,
412 "name", _("Music"),
413 "entry-type", RHYTHMDB_ENTRY_TYPE_SONG,
414 "shell", shell,
415 "pixbuf", icon,
416 "populate", FALSE, /* wait until the database is loaded */
417 "toolbar-path", "/LibrarySourceToolBar",
418 "settings", g_settings_get_child (settings, "source"),
419 NULL));
420 if (icon != NULL) {
421 g_object_unref (icon);
422 }
423 g_object_unref (settings);
424
425 rb_shell_register_entry_type_for_source (shell, source, RHYTHMDB_ENTRY_TYPE_SONG);
426
427 return source;
428 }
429
430 static void
431 impl_pack_content (RBBrowserSource *bsource, GtkWidget *content)
432 {
433 RBLibrarySource *source = RB_LIBRARY_SOURCE (bsource);
434 gtk_notebook_append_page (GTK_NOTEBOOK (source->priv->notebook), content, NULL);
435 gtk_widget_show_all (content);
436 }
437
438 static void
439 location_response_cb (GtkDialog *dialog, int response, RBLibrarySource *source)
440 {
441 char *uri;
442
443 uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dialog));
444 if (uri == NULL) {
445 uri = gtk_file_chooser_get_current_folder_uri (GTK_FILE_CHOOSER (dialog));
446 }
447 gtk_widget_destroy (GTK_WIDGET (dialog));
448
449 if (response == GTK_RESPONSE_ACCEPT) {
450 char *path;
451
452 path = g_uri_unescape_string (uri, NULL);
453
454 gtk_entry_set_text (GTK_ENTRY (source->priv->library_location_entry), path);
455 rb_library_source_library_location_cb (GTK_ENTRY (source->priv->library_location_entry),
456 NULL, source);
457 g_free (path);
458 }
459 g_free (uri);
460 }
461
462 static void
463 rb_library_source_location_button_clicked_cb (GtkButton *button, RBLibrarySource *source)
464 {
465 GtkWidget *dialog;
466
467 dialog = rb_file_chooser_new (_("Choose Library Location"),
468 GTK_WINDOW (source->priv->shell_prefs),
469 GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
470 FALSE);
471 g_signal_connect (dialog, "response", G_CALLBACK (location_response_cb), source);
472 gtk_widget_show_all (dialog);
473 }
474
475 static void
476 update_library_locations (RBLibrarySource *source)
477 {
478 char **locations;
479
480 if (source->priv->library_location_entry == NULL) {
481 return;
482 }
483
484 locations = g_settings_get_strv (source->priv->db_settings, "locations");
485
486 /* don't trigger the change notification */
487 g_signal_handlers_block_by_func (G_OBJECT (source->priv->library_location_entry),
488 G_CALLBACK (rb_library_source_library_location_cb),
489 source);
490
491 if (g_strv_length (locations) == 1) {
492 char *path;
493
494 gtk_widget_set_sensitive (source->priv->library_location_entry, TRUE);
495
496 path = g_uri_unescape_string (locations[0], NULL);
497 gtk_entry_set_text (GTK_ENTRY (source->priv->library_location_entry), path);
498 g_free (path);
499 } else if (g_strv_length (locations) == 0) {
500 /* no library directories */
501 gtk_widget_set_sensitive (source->priv->library_location_entry, TRUE);
502 gtk_entry_set_text (GTK_ENTRY (source->priv->library_location_entry), "");
503 } else {
504 /* multiple library directories */
505 gtk_widget_set_sensitive (source->priv->library_location_entry, FALSE);
506 gtk_entry_set_text (GTK_ENTRY (source->priv->library_location_entry), _("Multiple locations set"));
507 }
508
509 g_signal_handlers_unblock_by_func (G_OBJECT (source->priv->library_location_entry),
510 G_CALLBACK (rb_library_source_library_location_cb),
511 source);
512
513 g_strfreev (locations);
514 }
515
516 static void
517 update_layout_path (RBLibrarySource *source)
518 {
519 char *value;
520 int active;
521 int i;
522
523 value = g_settings_get_string (source->priv->settings, "layout-path");
524
525 active = -1;
526 for (i = 0; library_layout_paths[i].path != NULL; i++) {
527 if (g_strcmp0 (library_layout_paths[i].path, value) == 0) {
528 active = i;
529 break;
530 }
531 }
532
533 g_free (value);
534 if (source->priv->layout_path_menu != NULL) {
535 gtk_combo_box_set_active (GTK_COMBO_BOX (source->priv->layout_path_menu), active);
536 }
537
538 update_layout_example_label (source);
539 }
540
541 static void
542 update_layout_filename (RBLibrarySource *source)
543 {
544 char *value;
545 int active;
546 int i;
547
548 value = g_settings_get_string (source->priv->settings, "layout-filename");
549
550 active = -1;
551 for (i = 0; library_layout_filenames[i].path != NULL; i++) {
552 if (strcmp (library_layout_filenames[i].path, value) == 0) {
553 active = i;
554 break;
555 }
556 }
557 g_free (value);
558
559 gtk_combo_box_set_active (GTK_COMBO_BOX (source->priv->layout_filename_menu), active);
560
561 update_layout_example_label (source);
562 }
563
564 static void
565 insert_preset (RBLibrarySource *source, const char *display_name, const char *name, gboolean select)
566 {
567 GtkTreeIter iter;
568
569 gtk_list_store_insert_with_values (GTK_LIST_STORE (source->priv->preset_model),
570 &iter,
571 -1,
572 0, display_name,
573 1, name,
574 -1);
575 if (select) {
576 rb_debug ("preset %s is selected", display_name);
577 gtk_combo_box_set_active_iter (GTK_COMBO_BOX (source->priv->preset_menu), &iter);
578 }
579 }
580
581 static void
582 profile_changed_cb (RBObjectPropertyEditor *editor, RBLibrarySource *source)
583 {
584 if (source->priv->profile_init)
585 return;
586
587 if (source->priv->encoder_element)
588 gst_preset_save_preset (GST_PRESET (source->priv->encoder_element),
589 CUSTOM_SETTINGS_PRESET);
590 }
591
592 static void
593 update_presets (RBLibrarySource *source, const char *media_type)
594 {
595 GVariant *preset_settings;
596 char *active_preset;
597 GstEncodingProfile *profile;
598 char **profile_settings;
599 char **profile_presets;
600
601 source->priv->profile_init = TRUE;
602
603 gtk_list_store_clear (GTK_LIST_STORE (source->priv->preset_model));
604
605 if (source->priv->encoder_property_editor != NULL) {
606 g_signal_handler_disconnect (source->priv->encoder_property_editor,
607 source->priv->profile_changed_id);
608 gtk_container_remove (GTK_CONTAINER (source->priv->encoder_property_holder),
609 source->priv->encoder_property_editor);
610 source->priv->profile_changed_id = 0;
611 source->priv->encoder_property_editor = NULL;
612 }
613 if (source->priv->encoder_element != NULL) {
614 gst_object_unref (source->priv->encoder_element);
615 source->priv->encoder_element = NULL;
616 }
617
618 gtk_widget_set_sensitive (source->priv->preset_menu, FALSE);
619 if (media_type == NULL) {
620 source->priv->profile_init = FALSE;
621 return;
622 }
623
624 /* get preset for the media type from settings */
625 preset_settings = g_settings_get_value (source->priv->encoding_settings, "media-type-presets");
626 active_preset = NULL;
627 g_variant_lookup (preset_settings, media_type, "s", &active_preset);
628
629 rb_debug ("active preset for media type %s is %s", media_type, active_preset);
630
631 insert_preset (source,
632 _("Default settings"),
633 "",
634 (active_preset == NULL || active_preset[0] == '\0'));
635
636 profile = rb_gst_get_encoding_profile (media_type);
637 if (profile == NULL) {
638 g_warning ("Don't know how to encode to media type %s", media_type);
639 source->priv->profile_init = FALSE;
640 return;
641 }
642
643 profile_settings = rb_gst_encoding_profile_get_settings (profile);
644 if (profile_settings != NULL) {
645
646 rb_debug ("profile has custom settings");
647 insert_preset (source,
648 _("Custom settings"),
649 CUSTOM_SETTINGS_PRESET,
650 g_strcmp0 (active_preset, CUSTOM_SETTINGS_PRESET) == 0);
651 gtk_widget_set_sensitive (source->priv->preset_menu, TRUE);
652
653 source->priv->encoder_element = rb_gst_encoding_profile_get_encoder (profile);
654 source->priv->custom_settings_exists =
655 gst_preset_load_preset (GST_PRESET (source->priv->encoder_element),
656 CUSTOM_SETTINGS_PRESET);
657
658 source->priv->encoder_property_editor =
659 rb_object_property_editor_new (G_OBJECT (source->priv->encoder_element),
660 profile_settings);
661 source->priv->profile_changed_id =
662 g_signal_connect (source->priv->encoder_property_editor,
663 "changed",
664 G_CALLBACK (profile_changed_cb),
665 source);
666
667 gtk_grid_attach (GTK_GRID (source->priv->encoder_property_holder),
668 source->priv->encoder_property_editor,
669 0, 0, 1, 1);
670 gtk_widget_show_all (source->priv->encoder_property_editor);
671 gtk_widget_set_no_show_all (source->priv->encoder_property_editor, TRUE);
672 if (g_strcmp0 (active_preset, CUSTOM_SETTINGS_PRESET) != 0) {
673 gtk_widget_hide (source->priv->encoder_property_editor);
674 }
675 g_strfreev (profile_settings);
676 }
677
678 /* get list of actual presets for the media type */
679 profile_presets = rb_gst_encoding_profile_get_presets (profile);
680 if (profile_presets) {
681 int i;
682 for (i = 0; profile_presets[i] != NULL; i++) {
683 if (g_strcmp0 (profile_presets[i], CUSTOM_SETTINGS_PRESET) == 0)
684 continue;
685
686 rb_debug ("profile has preset %s", profile_presets[i]);
687 insert_preset (source,
688 profile_presets[i],
689 profile_presets[i],
690 g_strcmp0 (profile_presets[i], active_preset) == 0);
691 gtk_widget_set_sensitive (source->priv->preset_menu, TRUE);
692 }
693 g_strfreev (profile_presets);
694 }
695
696 gst_encoding_profile_unref (profile);
697 source->priv->profile_init = FALSE;
698 }
699
700 static void
701 update_preferred_media_type (RBLibrarySource *source)
702 {
703 GtkTreeIter iter;
704 gboolean done;
705 char *str;
706
707 done = FALSE;
708 str = g_settings_get_string (source->priv->encoding_settings, "media-type");
709 if (gtk_tree_model_get_iter_first (source->priv->profile_model, &iter)) {
710 do {
711 char *media_type;
712
713 gtk_tree_model_get (source->priv->profile_model, &iter,
714 0, &media_type,
715 -1);
716 if (g_strcmp0 (media_type, str) == 0) {
717 gtk_combo_box_set_active_iter (GTK_COMBO_BOX (source->priv->preferred_format_menu), &iter);
718 update_presets (source, media_type);
719 done = TRUE;
720 }
721 g_free (media_type);
722 } while (done == FALSE && gtk_tree_model_iter_next (source->priv->profile_model, &iter));
723 }
724
725 if (done == FALSE) {
726 gtk_combo_box_set_active_iter (GTK_COMBO_BOX (source->priv->preferred_format_menu), NULL);
727 update_presets (source, NULL);
728 }
729
730 g_free (str);
731 }
732
733 static void
734 db_settings_changed_cb (GSettings *settings, const char *key, RBLibrarySource *source)
735 {
736 if (g_strcmp0 (key, "locations") == 0) {
737 update_library_locations (source);
738 rb_library_source_sync_child_sources (source);
739 }
740 }
741
742 static void
743 library_settings_changed_cb (GSettings *settings, const char *key, RBLibrarySource *source)
744 {
745 if (g_strcmp0 (key, "layout-path") == 0) {
746 rb_debug ("layout path changed");
747 update_layout_path (source);
748 } else if (g_strcmp0 (key, "layout-filename") == 0) {
749 rb_debug ("layout filename changed");
750 update_layout_filename (source);
751 }
752 }
753
754 static void
755 encoding_settings_changed_cb (GSettings *settings, const char *key, RBLibrarySource *source)
756 {
757 if (g_strcmp0 (key, "media-type") == 0) {
758 rb_debug ("preferred media type changed");
759 update_preferred_media_type (source);
760 } else if (g_strcmp0 (key, "media-type-presets") == 0) {
761 rb_debug ("media type presets changed");
762 /* need to do anything here? update selection if the
763 * preset for the preferred media type changed?
764 */
765 }
766 }
767
768 static GtkWidget *
769 impl_get_config_widget (RBDisplayPage *asource, RBShellPreferences *prefs)
770 {
771 RBLibrarySource *source = RB_LIBRARY_SOURCE (asource);
772 GtkCellRenderer *renderer;
773 GstEncodingTarget *target;
774 GtkBuilder *builder;
775 GObject *tmp;
776 GObject *label;
777 const GList *p;
778 int i;
779
780 if (source->priv->config_widget)
781 return source->priv->config_widget;
782
783 g_object_ref (prefs);
784 source->priv->shell_prefs = prefs;
785
786 builder = rb_builder_load ("library-prefs.ui", source);
787 source->priv->config_widget =
788 GTK_WIDGET (gtk_builder_get_object (builder, "library_vbox"));
789
790 rb_builder_boldify_label (builder, "library_location_label");
791
792 source->priv->library_location_entry = GTK_WIDGET (gtk_builder_get_object (builder, "library_location_entry"));
793 tmp = gtk_builder_get_object (builder, "library_location_button");
794 g_signal_connect (tmp,
795 "clicked",
796 G_CALLBACK (rb_library_source_location_button_clicked_cb),
797 asource);
798 g_signal_connect (G_OBJECT (source->priv->library_location_entry),
799 "focus-out-event",
800 G_CALLBACK (rb_library_source_library_location_cb),
801 asource);
802
803 source->priv->watch_library_check = GTK_WIDGET (gtk_builder_get_object (builder, "watch_library_check"));
804 g_settings_bind (source->priv->db_settings, "monitor-library",
805 source->priv->watch_library_check, "active",
806 G_SETTINGS_BIND_DEFAULT);
807
808 rb_builder_boldify_label (builder, "library_structure_label");
809
810 tmp = gtk_builder_get_object (builder, "layout_path_menu_box");
811 label = gtk_builder_get_object (builder, "layout_path_menu_label");
812 source->priv->layout_path_menu = gtk_combo_box_text_new ();
813 gtk_box_pack_start (GTK_BOX (tmp), source->priv->layout_path_menu, TRUE, TRUE, 0);
814 gtk_label_set_mnemonic_widget (GTK_LABEL (label), source->priv->layout_path_menu);
815 g_signal_connect (G_OBJECT (source->priv->layout_path_menu),
816 "changed",
817 G_CALLBACK (rb_library_source_path_changed_cb),
818 asource);
819 for (i = 0; i < num_library_layout_paths; i++) {
820 gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (source->priv->layout_path_menu),
821 _(library_layout_paths[i].title));
822 }
823
824 tmp = gtk_builder_get_object (builder, "layout_filename_menu_box");
825 label = gtk_builder_get_object (builder, "layout_filename_menu_label");
826 source->priv->layout_filename_menu = gtk_combo_box_text_new ();
827 gtk_box_pack_start (GTK_BOX (tmp), source->priv->layout_filename_menu, TRUE, TRUE, 0);
828 gtk_label_set_mnemonic_widget (GTK_LABEL (label), source->priv->layout_filename_menu);
829 g_signal_connect (G_OBJECT (source->priv->layout_filename_menu),
830 "changed",
831 G_CALLBACK (rb_library_source_filename_changed_cb),
832 asource);
833 for (i = 0; i < num_library_layout_filenames; i++) {
834 gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (source->priv->layout_filename_menu),
835 _(library_layout_filenames[i].title));
836 }
837
838 target = rb_gst_get_default_encoding_target ();
839 source->priv->profile_model = GTK_TREE_MODEL (gtk_tree_store_new (3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER));
840 for (p = gst_encoding_target_get_profiles (target); p != NULL; p = p->next) {
841 GstEncodingProfile *profile = GST_ENCODING_PROFILE (p->data);
842 char *media_type;
843
844 media_type = rb_gst_encoding_profile_get_media_type (profile);
845 if (media_type == NULL) {
846 continue;
847 }
848 gtk_tree_store_insert_with_values (GTK_TREE_STORE (source->priv->profile_model),
849 NULL,
850 NULL,
851 -1,
852 0, media_type,
853 1, gst_encoding_profile_get_description (profile),
854 2, profile,
855 -1);
856 g_free (media_type);
857 }
858
859 source->priv->preset_model = GTK_TREE_MODEL (gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING));
860
861 source->priv->preferred_format_menu = GTK_WIDGET (gtk_builder_get_object (builder, "format_select_combo"));
862 gtk_combo_box_set_model (GTK_COMBO_BOX (source->priv->preferred_format_menu), source->priv->profile_model);
863 renderer = gtk_cell_renderer_text_new ();
864 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (source->priv->preferred_format_menu), renderer, TRUE);
865 gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (source->priv->preferred_format_menu), renderer, "text", 1, NULL);
866
867 g_signal_connect (G_OBJECT (source->priv->preferred_format_menu),
868 "changed",
869 G_CALLBACK (rb_library_source_format_changed_cb),
870 asource);
871
872 source->priv->preset_menu = GTK_WIDGET (gtk_builder_get_object (builder, "preset_select_combo"));
873 gtk_combo_box_set_model (GTK_COMBO_BOX (source->priv->preset_menu), source->priv->preset_model);
874 renderer = gtk_cell_renderer_text_new ();
875 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (source->priv->preset_menu), renderer, TRUE);
876 gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (source->priv->preset_menu), renderer, "text", 0, NULL);
877
878 g_signal_connect (G_OBJECT (source->priv->preset_menu),
879 "changed",
880 G_CALLBACK (rb_library_source_preset_changed_cb),
881 asource);
882
883
884 source->priv->layout_example_label = GTK_WIDGET (gtk_builder_get_object (builder, "layout_example_label"));
885
886 source->priv->install_plugins_button = GTK_WIDGET (gtk_builder_get_object (builder, "install_plugins_button"));
887 gtk_widget_set_no_show_all (source->priv->install_plugins_button, TRUE);
888 g_signal_connect (G_OBJECT (source->priv->install_plugins_button), "clicked", G_CALLBACK (rb_library_source_install_plugins_cb), source);
889
890 source->priv->encoder_property_holder = GTK_WIDGET (gtk_builder_get_object (builder, "encoder_property_holder"));
891
892 update_library_locations (source);
893 update_preferred_media_type (source);
894
895 update_layout_path (source);
896 update_layout_filename (source);
897
898 return source->priv->config_widget;
899 }
900
901 static gboolean
902 rb_library_source_library_location_cb (GtkEntry *entry,
903 GdkEventFocus *event,
904 RBLibrarySource *source)
905 {
906 const char *path;
907 const char *locations[2] = { NULL, NULL };
908 GFile *file;
909 char *uri;
910
911 path = gtk_entry_get_text (entry);
912 file = g_file_parse_name (path);
913 uri = g_file_get_uri (file);
914 g_object_unref (file);
915
916 if (uri && uri[0]) {
917 locations[0] = uri;
918 }
919
920 g_settings_set_strv (source->priv->db_settings, "locations", locations);
921
922 g_free (uri);
923 return FALSE;
924 }
925
926 static gboolean
927 impl_receive_drag (RBDisplayPage *asource, GtkSelectionData *data)
928 {
929 RBLibrarySource *source = RB_LIBRARY_SOURCE (asource);
930 GList *list, *i;
931 GList *entries = NULL;
932 gboolean is_id;
933
934 rb_debug ("parsing uri list");
935 list = rb_uri_list_parse ((const char *) gtk_selection_data_get_data (data));
936 is_id = (gtk_selection_data_get_data_type (data) == gdk_atom_intern ("application/x-rhythmbox-entry", TRUE));
937
938 for (i = list; i != NULL; i = g_list_next (i)) {
939 if (i->data != NULL) {
940 char *uri = i->data;
941 RhythmDBEntry *entry;
942
943 entry = rhythmdb_entry_lookup_from_string (source->priv->db, uri, is_id);
944
945 if (entry == NULL) {
946 RhythmDBImportJob *job;
947 /* add to the library */
948 job = maybe_create_import_job (source);
949 rhythmdb_import_job_add_uri (job, uri);
950 } else {
951 /* add to list of entries to copy */
952 entries = g_list_prepend (entries, entry);
953 }
954
955 g_free (uri);
956 }
957 }
958
959 if (entries) {
960 entries = g_list_reverse (entries);
961 if (rb_source_can_paste (RB_SOURCE (asource)))
962 rb_source_paste (RB_SOURCE (asource), entries);
963 g_list_free (entries);
964 }
965
966 g_list_free (list);
967 return TRUE;
968 }
969
970 static gboolean
971 impl_show_popup (RBDisplayPage *source)
972 {
973 _rb_display_page_show_popup (source, "/LibrarySourcePopup");
974 return TRUE;
975 }
976
977 static void
978 rb_library_source_path_changed_cb (GtkComboBox *box, RBLibrarySource *source)
979 {
980 const char *path;
981 gint index;
982
983 index = gtk_combo_box_get_active (box);
984 if (index >= 0) {
985 path = library_layout_paths[index].path;
986
987 g_settings_set_string (source->priv->settings, "layout-path", path);
988 }
989 }
990
991 static void
992 rb_library_source_filename_changed_cb (GtkComboBox *box, RBLibrarySource *source)
993 {
994 const char *filename;
995 gint index;
996
997 index = gtk_combo_box_get_active (box);
998 if (index >= 0) {
999 filename = library_layout_filenames[index].path;
1000 g_settings_set_string (source->priv->settings, "layout-filename", filename);
1001 }
1002 }
1003
1004 static void
1005 rb_library_source_format_changed_cb (GtkWidget *widget, RBLibrarySource *source)
1006 {
1007 GtkTreeIter iter;
1008 char *media_type = NULL;
1009 GstEncodingProfile *profile;
1010 RBEncoder *encoder;
1011
1012 if (source->priv->profile_init)
1013 return;
1014
1015 /* get selected media type */
1016 if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (widget), &iter) == FALSE)
1017 return;
1018 gtk_tree_model_get (GTK_TREE_MODEL (source->priv->profile_model),
1019 &iter,
1020 0, &media_type,
1021 2, &profile,
1022 -1);
1023
1024 g_settings_set_string (source->priv->encoding_settings, "media-type", media_type);
1025
1026 update_layout_example_label (source);
1027
1028 /* indicate whether additional plugins are required to encode in this format */
1029 encoder = rb_encoder_new ();
1030 if (rb_encoder_get_missing_plugins (encoder, profile, NULL, NULL)) {
1031 rb_debug ("additional plugins are required to encode %s", media_type);
1032 gtk_widget_set_visible (source->priv->install_plugins_button, TRUE);
1033 /* not a great way to handle this situation; probably should describe
1034 * the plugins that are missing when automatic install isn't available.
1035 */
1036 gtk_widget_set_sensitive (source->priv->install_plugins_button,
1037 gst_install_plugins_supported ());
1038 } else {
1039 rb_debug ("can encode %s", media_type);
1040 gtk_widget_set_visible (source->priv->install_plugins_button, FALSE);
1041 }
1042 g_free (media_type);
1043 }
1044
1045 static void
1046 rb_library_source_preset_changed_cb (GtkWidget *widget, RBLibrarySource *source)
1047 {
1048 GtkTreeIter iter;
1049 char *media_type = NULL;
1050 char *preset = NULL;
1051 char *stored;
1052 gboolean have_preset;
1053 GVariant *settings;
1054
1055 if (source->priv->profile_init)
1056 return;
1057
1058 /* get selected media type */
1059 if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (source->priv->preferred_format_menu), &iter) == FALSE) {
1060 rb_debug ("no media type selected?");
1061 return;
1062 }
1063 gtk_tree_model_get (GTK_TREE_MODEL (source->priv->profile_model),
1064 &iter,
1065 0, &media_type,
1066 -1);
1067
1068 /* get selected preset */
1069 if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (source->priv->preset_menu), &iter)) {
1070 gtk_tree_model_get (GTK_TREE_MODEL (source->priv->preset_model),
1071 &iter,
1072 1, &preset,
1073 -1);
1074 rb_debug ("preset %s now selected for media type %s", preset, media_type);
1075 } else {
1076 rb_debug ("no preset selected for media type %s?", media_type);
1077 }
1078
1079 /* update custom settings widgets */
1080 if (g_strcmp0 (preset, CUSTOM_SETTINGS_PRESET) == 0) {
1081 /* make sure the preset exists so encoder batches can use it */
1082 if (source->priv->custom_settings_exists == FALSE) {
1083 gst_preset_save_preset (GST_PRESET (source->priv->encoder_element),
1084 CUSTOM_SETTINGS_PRESET);
1085 }
1086
1087 if (source->priv->encoder_property_editor != NULL) {
1088 gtk_widget_show (source->priv->encoder_property_editor);
1089 }
1090 } else {
1091
1092 if (source->priv->encoder_property_editor != NULL) {
1093 gtk_widget_hide (source->priv->encoder_property_editor);
1094 }
1095 }
1096
1097 /* store selected preset */
1098 settings = g_settings_get_value (source->priv->encoding_settings, "media-type-presets");
1099 stored = NULL;
1100 have_preset = (preset != NULL && preset[0] != '\0');
1101 g_variant_lookup (settings, media_type, "s", &stored);
1102 if (have_preset == FALSE && (stored == NULL || stored[0] == '\0')) {
1103 /* don't bother */
1104 } else if (g_strcmp0 (stored, preset) != 0) {
1105 GVariantBuilder b;
1106 GVariantIter i;
1107 char *mt;
1108 char *p;
1109 gboolean stored;
1110
1111 g_variant_builder_init (&b, G_VARIANT_TYPE ("a{ss}"));
1112 g_variant_iter_init (&i, settings);
1113 stored = FALSE;
1114 while (g_variant_iter_loop (&i, "{ss}", &mt, &p)) {
1115 if (g_strcmp0 (mt, media_type) == 0) {
1116 if (have_preset) {
1117 g_variant_builder_add (&b, "{ss}", mt, preset);
1118 }
1119 stored = TRUE;
1120 } else {
1121 g_variant_builder_add (&b, "{ss}", mt, p);
1122 rb_debug ("keeping %s => %s", mt, p);
1123 }
1124 }
1125
1126 if (have_preset && stored == FALSE) {
1127 g_variant_builder_add (&b, "{ss}", media_type, preset);
1128 }
1129
1130 g_settings_set_value (source->priv->encoding_settings, "media-type-presets", g_variant_builder_end (&b));
1131 }
1132 g_variant_unref (settings);
1133
1134 g_free (stored);
1135 g_free (preset);
1136 g_free (media_type);
1137 }
1138
1139 static void
1140 plugin_install_done_cb (gpointer inst, gboolean retry, RBLibrarySource *source)
1141 {
1142 rb_library_source_format_changed_cb (source->priv->preferred_format_menu, source);
1143 }
1144
1145 static void
1146 rb_library_source_install_plugins_cb (GtkWidget *widget, RBLibrarySource *source)
1147 {
1148 char *media_type;
1149 GstEncodingProfile *profile;
1150 RBEncoder *encoder;
1151 char **details;
1152 GClosure *closure;
1153
1154 /* get profile */
1155 media_type = g_settings_get_string (source->priv->encoding_settings, "media-type");
1156 profile = rb_gst_get_encoding_profile (media_type);
1157 if (profile == NULL) {
1158 g_warning ("no encoding profile available for %s, so how can we install plugins?",
1159 media_type);
1160 g_free (media_type);
1161 return;
1162 }
1163 g_free (media_type);
1164
1165 /* get plugin details */
1166 encoder = rb_encoder_new ();
1167 if (rb_encoder_get_missing_plugins (encoder, profile, &details, NULL) == FALSE) {
1168 /* what? */
1169 g_object_unref (encoder);
1170 return;
1171 }
1172
1173 /* attempt installation */
1174 closure = g_cclosure_new ((GCallback) plugin_install_done_cb,
1175 g_object_ref (source),
1176 (GClosureNotify) g_object_unref);
1177 g_closure_set_marshal (closure, g_cclosure_marshal_VOID__BOOLEAN);
1178
1179 rb_missing_plugins_install ((const char **)details, TRUE, closure);
1180
1181 g_closure_sink (closure);
1182 g_strfreev (details);
1183 }
1184
1185 /*
1186 * Perform magic on a path to make it safe.
1187 *
1188 * This will always replace '/' with '-', and optionally make the file name
1189 * shell-friendly. This involves removing replacing shell metacharacters and all
1190 * whitespace with '_'. Also any leading periods are removed so that the files
1191 * don't end up being hidden.
1192 */
1193 static char *
1194 sanitize_path (gboolean strip_chars, const char *str)
1195 {
1196 gchar *s;
1197
1198 /* Skip leading periods, otherwise files disappear... */
1199 while (*str == '.')
1200 str++;
1201
1202 s = g_strdup(str);
1203 /* Replace path seperators with a hyphen */
1204 g_strdelimit (s, "/", '-');
1205 if (strip_chars) {
1206 /* Replace separators with a hyphen */
1207 g_strdelimit (s, "\\:|", '-');
1208 /* Replace all other weird characters to whitespace */
1209 g_strdelimit (s, "*?&!\'\"$()`>{}", ' ');
1210 /* Replace all whitespace with underscores */
1211 /* TODO: I'd like this to compress whitespace aswell */
1212 g_strdelimit (s, "\t ", '_');
1213 }
1214
1215 return s;
1216 }
1217
1218 static char *
1219 sanitize_pattern (gboolean strip_chars, const char *pat)
1220 {
1221 if (strip_chars) {
1222 gchar *s;
1223
1224 s = g_strdup (pat);
1225 g_strdelimit (s, "\t ", '_');
1226 return s;
1227 } else {
1228 return g_strdup (pat);
1229 }
1230 }
1231
1232 /*
1233 * Parse a filename pattern and replace markers with values from a RhythmDBEntry
1234 *
1235 * Valid markers so far are:
1236 * %at -- album title
1237 * %aa -- album artist
1238 * %aA -- album artist (lowercase)
1239 * %as -- album artist sortname
1240 * %aS -- album artist sortname (lowercase)
1241 * %ay -- album release year
1242 * %an -- album disc number
1243 * %aN -- album disc number, zero padded
1244 * %ag -- album genre
1245 * %aG -- album genre (lowercase)
1246 * %tn -- track number (i.e 8)
1247 * %tN -- track number, zero padded (i.e 08)
1248 * %tt -- track title
1249 * %ta -- track artist
1250 * %tA -- track artist (lowercase)
1251 * %ts -- track artist sortname
1252 * %tS -- track artist sortname (lowercase)
1253 */
1254 static char *
1255 filepath_parse_pattern (RBLibrarySource *source,
1256 const char *pattern,
1257 RhythmDBEntry *entry)
1258 {
1259 /* p is the pattern iterator, i is a general purpose iterator */
1260 const char *p;
1261 char *temp;
1262 GString *s;
1263 RBRefString *albumartist;
1264 RBRefString *albumartist_sort;
1265 gboolean strip_chars;
1266
1267 if (pattern == NULL || pattern[0] == 0)
1268 return g_strdup (" ");
1269
1270 strip_chars = g_settings_get_boolean (source->priv->settings, "strip-chars");
1271
1272 /* figure out album artist - use the plain artist field if not specified */
1273 albumartist = rhythmdb_entry_get_refstring (entry, RHYTHMDB_PROP_ALBUM_ARTIST);
1274 if (albumartist == NULL || g_strcmp0 (rb_refstring_get (albumartist), "") == 0) {
1275 albumartist = rhythmdb_entry_get_refstring (entry, RHYTHMDB_PROP_ARTIST);
1276 }
1277 albumartist_sort = rhythmdb_entry_get_refstring (entry, RHYTHMDB_PROP_ALBUM_ARTIST_SORTNAME);
1278 if (albumartist_sort == NULL || g_strcmp0 (rb_refstring_get (albumartist_sort), "") == 0) {
1279 albumartist_sort = rhythmdb_entry_get_refstring (entry, RHYTHMDB_PROP_ARTIST_SORTNAME);
1280 }
1281
1282 s = g_string_new (NULL);
1283
1284 p = pattern;
1285 while (*p) {
1286 char *string = NULL;
1287
1288 /* If not a % marker, copy and continue */
1289 if (*p != '%') {
1290 g_string_append_c (s, *p++);
1291 /* Explicit increment as we continue past the increment */
1292 continue;
1293 }
1294
1295 /* Is a % marker, go to next and see what to do */
1296 switch (*++p) {
1297 case '%':
1298 /*
1299 * Literal %
1300 */
1301 g_string_append_c (s, '%');
1302 break;
1303 case 'a':
1304 /*
1305 * Album tag
1306 */
1307 switch (*++p) {
1308 case 't':
1309 string = sanitize_path (strip_chars, rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ALBUM));
1310 break;
1311 case 'T':
1312 string = sanitize_path (strip_chars, rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ALBUM_FOLDED));
1313 break;
1314 case 'a':
1315 string = sanitize_path (strip_chars, rb_refstring_get (albumartist));
1316 break;
1317 case 'A':
1318 string = sanitize_path (strip_chars, rb_refstring_get_folded (albumartist));
1319 break;
1320 case 's':
1321 string = sanitize_path (strip_chars, rb_refstring_get (albumartist_sort));
1322 break;
1323 case 'S':
1324 string = sanitize_path (strip_chars, rb_refstring_get_folded (albumartist_sort));
1325 break;
1326 case 'y':
1327 string = g_strdup_printf ("%u", (guint)rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_YEAR));
1328 break;
1329 case 'n':
1330 string = g_strdup_printf ("%u", (guint)rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DISC_NUMBER));
1331 break;
1332 case 'N':
1333 string = g_strdup_printf ("%02u", (guint)rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DISC_NUMBER));
1334 break;
1335 case 'g':
1336 string = sanitize_path (strip_chars, rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_GENRE));
1337 break;
1338 case 'G':
1339 string = sanitize_path (strip_chars, rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_GENRE_FOLDED));
1340 break;
1341 default:
1342 string = g_strdup_printf ("%%a%c", *p);
1343 }
1344
1345 break;
1346
1347 case 't':
1348 /*
1349 * Track tag
1350 */
1351 switch (*++p) {
1352 case 't':
1353 string = sanitize_path (strip_chars, rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_TITLE));
1354 break;
1355 case 'T':
1356 string = sanitize_path (strip_chars, rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_TITLE_FOLDED));
1357 break;
1358 case 'a':
1359 string = sanitize_path (strip_chars, rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ARTIST));
1360 break;
1361 case 'A':
1362 string = sanitize_path (strip_chars, rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ARTIST_FOLDED));
1363 break;
1364 case 's':
1365 string = sanitize_path (strip_chars, rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ARTIST_SORTNAME));
1366 break;
1367 case 'S':
1368 string = sanitize_path (strip_chars, rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ARTIST_SORTNAME_FOLDED));
1369 break;
1370 case 'n':
1371 /* Track number */
1372 string = g_strdup_printf ("%u", (guint)rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_TRACK_NUMBER));
1373 break;
1374 case 'N':
1375 /* Track number, zero-padded */
1376 string = g_strdup_printf ("%02u", (guint)rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_TRACK_NUMBER));
1377 break;
1378 default:
1379 string = g_strdup_printf ("%%t%c", *p);
1380 }
1381
1382 break;
1383
1384 default:
1385 string = g_strdup_printf ("%%%c", *p);
1386 }
1387
1388 if (string)
1389 g_string_append (s, string);
1390 g_free (string);
1391
1392 ++p;
1393 }
1394
1395 temp = s->str;
1396 g_string_free (s, FALSE);
1397 rb_refstring_unref (albumartist);
1398 return temp;
1399 }
1400
1401 static void
1402 update_layout_example_label (RBLibrarySource *source)
1403 {
1404 char *file_pattern;
1405 char *path_pattern;
1406 char *file_value;
1407 char *path_value;
1408 char *example;
1409 char *format;
1410 char *tmp;
1411 gboolean strip_chars;
1412 char *media_type;
1413 RhythmDBEntryType *entry_type;
1414 RhythmDBEntry *sample_entry;
1415
1416 media_type = g_settings_get_string (source->priv->encoding_settings, "media-type");
1417
1418 file_pattern = g_settings_get_string (source->priv->settings, "layout-filename");
1419 if (file_pattern == NULL) {
1420 file_pattern = g_strdup (library_layout_filenames[0].path);
1421 }
1422 strip_chars = g_settings_get_boolean (source->priv->settings, "strip-chars");
1423 tmp = sanitize_pattern (strip_chars, file_pattern);
1424 g_free (file_pattern);
1425 file_pattern = tmp;
1426
1427 path_pattern = g_settings_get_string (source->priv->settings, "layout-path");
1428 if (path_pattern == NULL) {
1429 path_pattern = g_strdup (library_layout_paths[0].path);
1430 }
1431
1432 g_object_get (source, "entry-type", &entry_type, NULL);
1433 sample_entry = rhythmdb_entry_example_new (source->priv->db, entry_type, NULL);
1434 g_object_unref (entry_type);
1435
1436 file_value = filepath_parse_pattern (source, file_pattern, sample_entry);
1437 path_value = filepath_parse_pattern (source, path_pattern, sample_entry);
1438 rhythmdb_entry_unref (sample_entry);
1439
1440 example = g_build_filename (G_DIR_SEPARATOR_S, path_value, file_value, NULL);
1441 g_free (file_value);
1442 g_free (file_pattern);
1443 g_free (path_value);
1444 g_free (path_pattern);
1445
1446 format = g_strconcat ("<small><i><b>",
1447 _("Example Path:"),
1448 "</b> ",
1449 example,
1450 ".",
1451 media_type ? rb_gst_media_type_to_extension (media_type) : "ogg",
1452 "</i></small>", NULL);
1453 g_free (example);
1454 g_free (media_type);
1455
1456 gtk_label_set_markup (GTK_LABEL (source->priv->layout_example_label), format);
1457 g_free (format);
1458 }
1459
1460 /*
1461 * Build the absolute filename for the specified track.
1462 *
1463 * The base path is the extern variable 'base_path', the format to use
1464 * is the extern variable 'file_pattern'. Free the result when you
1465 * have finished with it.
1466 *
1467 * Stolen from Sound-Juicer
1468 */
1469 static char*
1470 build_filename (RBLibrarySource *source, RhythmDBEntry *entry, const char *extension)
1471 {
1472 GFile *library_location;
1473 GFile *dir;
1474 GFile *dest;
1475 char *realfile;
1476 char *realpath;
1477 char *filename;
1478 char *string = NULL;
1479 char *tmp;
1480 char **locations;
1481 char *layout_path;
1482 char *layout_filename;
1483 gboolean strip_chars;
1484
1485 locations = g_settings_get_strv (source->priv->db_settings, "locations");
1486 layout_path = g_settings_get_string (source->priv->settings, "layout-path");
1487 layout_filename = g_settings_get_string (source->priv->settings, "layout-filename");
1488 strip_chars = g_settings_get_boolean (source->priv->settings, "strip-chars");
1489
1490 if (locations == NULL || layout_path == NULL || layout_filename == NULL) {
1491 /* emit warning */
1492 rb_debug ("Could not retrieve library layout settings");
1493 goto out;
1494 }
1495
1496 tmp = sanitize_pattern (strip_chars, layout_filename);
1497 g_free (layout_filename);
1498 layout_filename = tmp;
1499
1500 realpath = filepath_parse_pattern (source, layout_path, entry);
1501
1502 library_location = g_file_new_for_uri ((const char *)locations[0]);
1503 dir = g_file_resolve_relative_path (library_location, realpath);
1504 g_object_unref (library_location);
1505 g_free (realpath);
1506
1507 realfile = filepath_parse_pattern (source, layout_filename, entry);
1508 if (extension) {
1509 filename = g_strdup_printf ("%s.%s", realfile, extension);
1510 g_free (realfile);
1511 } else {
1512 filename = realfile;
1513 }
1514
1515 dest = g_file_resolve_relative_path (dir, filename);
1516 g_object_unref (dir);
1517 g_free (filename);
1518
1519 string = g_file_get_uri (dest);
1520 g_object_unref (dest);
1521 out:
1522 g_strfreev (locations);
1523 g_free (layout_path);
1524 g_free (layout_filename);
1525
1526 return string;
1527 }
1528
1529 static gboolean
1530 impl_can_paste (RBSource *asource)
1531 {
1532 RBLibrarySource *source = RB_LIBRARY_SOURCE (asource);
1533 char **locations;
1534 gboolean can_paste = TRUE;
1535 char *str;
1536
1537 locations = g_settings_get_strv (source->priv->db_settings, "locations");
1538 can_paste = (g_strv_length (locations) > 0);
1539 g_strfreev (locations);
1540
1541 str = g_settings_get_string (source->priv->settings, "layout-path");
1542 can_paste &= (str != NULL);
1543 g_free (str);
1544
1545 str = g_settings_get_string (source->priv->settings, "layout-filename");
1546 can_paste &= (str != NULL);
1547 g_free (str);
1548
1549 str = g_settings_get_string (source->priv->encoding_settings, "media-type");
1550 can_paste &= (str != NULL);
1551 g_free (str);
1552
1553 return can_paste;
1554 }
1555
1556 static char *
1557 get_dest_uri_cb (RBTrackTransferBatch *batch,
1558 RhythmDBEntry *entry,
1559 const char *mediatype,
1560 const char *extension,
1561 RBLibrarySource *source)
1562 {
1563 char *dest;
1564 char *sane_dest;
1565
1566 dest = build_filename (source, entry, extension);
1567 if (dest == NULL) {
1568 rb_debug ("could not create destination path for entry");
1569 return NULL;
1570 }
1571
1572 sane_dest = rb_sanitize_uri_for_filesystem (dest);
1573 g_free (dest);
1574 rb_debug ("destination URI for %s is %s",
1575 rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION),
1576 sane_dest);
1577 return sane_dest;
1578 }
1579
1580 static void
1581 configure_profile_cb (RBTrackTransferBatch *batch,
1582 const char *media_type,
1583 GstEncodingProfile *profile,
1584 RBLibrarySource *source)
1585 {
1586 GVariant *preset_settings;
1587 char *active_preset;
1588
1589 preset_settings = g_settings_get_value (source->priv->encoding_settings, "media-type-presets");
1590 active_preset = NULL;
1591 g_variant_lookup (preset_settings, media_type, "s", &active_preset);
1592
1593 rb_debug ("setting preset %s for media type %s", active_preset, media_type);
1594 rb_gst_encoding_profile_set_preset (profile, active_preset);
1595
1596 g_free (active_preset);
1597 }
1598
1599 static void
1600 track_done_cb (RBTrackTransferBatch *batch,
1601 RhythmDBEntry *entry,
1602 const char *dest,
1603 guint64 dest_size,
1604 const char *dest_mediatype,
1605 GError *error,
1606 RBLibrarySource *source)
1607 {
1608 if (error != NULL) {
1609 /* probably want to cancel the batch on some errors:
1610 * - out of disk space / read only
1611 * - source has vanished (hmm, how would we know?)
1612 *
1613 * and we probably want to do something intelligent about some other errors:
1614 * - encoder pipeline errors? hmm.
1615 */
1616 if (g_error_matches (error, RB_ENCODER_ERROR, RB_ENCODER_ERROR_OUT_OF_SPACE) ||
1617 g_error_matches (error, RB_ENCODER_ERROR, RB_ENCODER_ERROR_DEST_READ_ONLY)) {
1618 rb_debug ("fatal transfer error: %s", error->message);
1619 rb_track_transfer_batch_cancel (batch);
1620 rb_error_dialog (NULL, _("Error transferring track"), "%s", error->message);
1621 } else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS)) {
1622 rb_debug ("not displaying 'file exists' error for %s", dest);
1623 } else {
1624 rb_error_dialog (NULL, _("Error transferring track"), "%s", error->message);
1625 }
1626 } else if (dest != NULL) {
1627 /* could probably do something smarter here to avoid
1628 * re-reading tags etc.
1629 */
1630 rhythmdb_add_uri (source->priv->db, dest);
1631 }
1632 }
1633
1634 static RBTrackTransferBatch *
1635 impl_paste (RBSource *asource, GList *entries)
1636 {
1637 RBLibrarySource *source = RB_LIBRARY_SOURCE (asource);
1638 RBTrackTransferQueue *xferq;
1639 GList *l;
1640 RBShell *shell;
1641 RhythmDBEntryType *source_entry_type;
1642 RBTrackTransferBatch *batch;
1643 gboolean start_batch = FALSE;
1644 GstEncodingTarget *target;
1645 GstEncodingProfile *profile;
1646 char *preferred_media_type;
1647
1648 if (impl_can_paste (asource) == FALSE) {
1649 g_warning ("RBLibrarySource impl_paste called when layout settings unset");
1650 return NULL;
1651 }
1652
1653 g_object_get (source,
1654 "shell", &shell,
1655 "entry-type", &source_entry_type,
1656 NULL);
1657 g_object_get (shell, "track-transfer-queue", &xferq, NULL);
1658 g_object_unref (shell);
1659
1660 target = gst_encoding_target_new ("rhythmbox-library", "device", "", NULL);
1661
1662 /* set up profile for user's preferred format */
1663 preferred_media_type = g_settings_get_string (source->priv->encoding_settings, "media-type");
1664 profile = rb_gst_get_encoding_profile (preferred_media_type);
1665 g_free (preferred_media_type);
1666 /* have a preset as part of the user settings too? would that work for containerful streams,
1667 * where the interesting settings are on the stream inside the container?
1668 */
1669 if (profile != NULL) {
1670 gst_encoding_target_add_profile (target, profile);
1671 }
1672
1673 /* set up profile for copying, which accepts any format */
1674 profile = GST_ENCODING_PROFILE (gst_encoding_audio_profile_new (gst_caps_new_any (), NULL, NULL, 1));
1675 gst_encoding_profile_set_name (profile, "copy");
1676 gst_encoding_target_add_profile (target, profile);
1677
1678 batch = rb_track_transfer_batch_new (target, NULL, G_OBJECT (source));
1679 g_signal_connect_object (batch, "get-dest-uri", G_CALLBACK (get_dest_uri_cb), source, 0);
1680 g_signal_connect_object (batch, "track-done", G_CALLBACK (track_done_cb), source, 0);
1681 g_signal_connect_object (batch, "configure-profile", G_CALLBACK (configure_profile_cb), source, 0);
1682
1683 for (l = entries; l != NULL; l = g_list_next (l)) {
1684 RhythmDBEntry *entry = (RhythmDBEntry *)l->data;
1685 RhythmDBEntryType *entry_type;
1686 RBSource *source_source;
1687
1688 rb_debug ("pasting entry %s", rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION));
1689
1690 entry_type = rhythmdb_entry_get_entry_type (entry);
1691 if (entry_type == source_entry_type) {
1692 rb_debug ("can't copy an entry from the library to itself");
1693 continue;
1694 }
1695
1696 /* see if the responsible source lets us copy */
1697 source_source = rb_shell_get_source_by_entry_type (shell, entry_type);
1698 if ((source_source != NULL) && !rb_source_can_copy (source_source)) {
1699 rb_debug ("source for the entry doesn't want us to copy it");
1700 continue;
1701 }
1702
1703 rb_track_transfer_batch_add (batch, entry);
1704 start_batch = TRUE;
1705 }
1706 g_object_unref (source_entry_type);
1707
1708 if (start_batch) {
1709 rb_track_transfer_queue_start_batch (xferq, batch);
1710 } else {
1711 g_object_unref (batch);
1712 batch = NULL;
1713 }
1714
1715 g_object_unref (xferq);
1716 return batch;
1717 }
1718
1719 static guint
1720 impl_want_uri (RBSource *source, const char *uri)
1721 {
1722 /* assume anything local, on smb, or on sftp is a song */
1723 if (rb_uri_is_local (uri) ||
1724 g_str_has_prefix (uri, "smb://") ||
1725 g_str_has_prefix (uri, "sftp://") ||
1726 g_str_has_prefix (uri, "ssh://"))
1727 return 50;
1728
1729 return 0;
1730 }
1731
1732 static void
1733 import_job_status_changed_cb (RhythmDBImportJob *job, int total, int imported, RBLibrarySource *source)
1734 {
1735 RhythmDBImportJob *head = RHYTHMDB_IMPORT_JOB (source->priv->import_jobs->data);
1736 if (job == head) { /* it was inevitable */
1737 rb_display_page_notify_status_changed (RB_DISPLAY_PAGE (source));
1738 }
1739 }
1740
1741 static void
1742 import_job_complete_cb (RhythmDBImportJob *job, int total, RBLibrarySource *source)
1743 {
1744 rb_debug ("import job complete");
1745
1746 /* maybe show a notification here? */
1747
1748 source->priv->import_jobs = g_list_remove (source->priv->import_jobs, job);
1749 g_object_unref (job);
1750 }
1751
1752 static gboolean
1753 start_import_job (RBLibrarySource *source)
1754 {
1755 RhythmDBImportJob *job;
1756 source->priv->start_import_job_id = 0;
1757
1758 rb_debug ("starting import job");
1759 job = RHYTHMDB_IMPORT_JOB (source->priv->import_jobs->data);
1760
1761 rhythmdb_import_job_start (job);
1762
1763 return FALSE;
1764 }
1765
1766 static RhythmDBImportJob *
1767 maybe_create_import_job (RBLibrarySource *source)
1768 {
1769 RhythmDBImportJob *job;
1770 if (source->priv->import_jobs == NULL || source->priv->start_import_job_id == 0) {
1771
1772 rb_debug ("creating new import job");
1773 job = rhythmdb_import_job_new (source->priv->db,
1774 RHYTHMDB_ENTRY_TYPE_SONG,
1775 RHYTHMDB_ENTRY_TYPE_IGNORE,
1776 RHYTHMDB_ENTRY_TYPE_IMPORT_ERROR);
1777
1778 g_signal_connect_object (job,
1779 "status-changed",
1780 G_CALLBACK (import_job_status_changed_cb),
1781 source, 0);
1782 g_signal_connect_object (job,
1783 "complete",
1784 G_CALLBACK (import_job_complete_cb),
1785 source, 0);
1786 source->priv->import_jobs = g_list_prepend (source->priv->import_jobs, job);
1787 } else {
1788 rb_debug ("using existing unstarted import job");
1789 job = RHYTHMDB_IMPORT_JOB (source->priv->import_jobs->data);
1790 }
1791
1792 /* allow some time for more URIs to be added if we're importing a bunch of things */
1793 if (source->priv->start_import_job_id != 0) {
1794 g_source_remove (source->priv->start_import_job_id);
1795 }
1796 source->priv->start_import_job_id = g_timeout_add (250, (GSourceFunc) start_import_job, source);
1797
1798 return job;
1799 }
1800
1801 struct ImportJobCallbackData {
1802 char *uri;
1803 RBSource *source;
1804 RBSourceAddCallback callback;
1805 gpointer data;
1806 GDestroyNotify destroy_data;
1807 };
1808
1809 static void
1810 import_job_callback_destroy (struct ImportJobCallbackData *data)
1811 {
1812 if (data->destroy_data != NULL) {
1813 data->destroy_data (data->data);
1814 }
1815 g_object_unref (data->source);
1816 g_free (data->uri);
1817 g_free (data);
1818 }
1819
1820 static void
1821 import_job_callback_cb (RhythmDBImportJob *job, int total, struct ImportJobCallbackData *data)
1822 {
1823 data->callback (data->source, data->uri, data->data);
1824 }
1825
1826 static void
1827 impl_add_uri (RBSource *asource,
1828 const char *uri,
1829 const char *title,
1830 const char *genre,
1831 RBSourceAddCallback callback,
1832 gpointer data,
1833 GDestroyNotify destroy_data)
1834 {
1835 RBLibrarySource *source = RB_LIBRARY_SOURCE (asource);
1836 RhythmDBImportJob *job;
1837
1838 job = maybe_create_import_job (source);
1839
1840 rb_debug ("adding uri %s to library", uri);
1841 rhythmdb_import_job_add_uri (job, uri);
1842
1843 if (callback != NULL) {
1844 struct ImportJobCallbackData *cbdata;
1845
1846 cbdata = g_new0 (struct ImportJobCallbackData, 1);
1847 cbdata->uri = g_strdup (uri);
1848 cbdata->source = g_object_ref (source);
1849 cbdata->callback = callback;
1850 cbdata->data = data;
1851 cbdata->destroy_data = destroy_data;
1852 g_signal_connect_data (job, "complete", G_CALLBACK (import_job_callback_cb), cbdata, (GClosureNotify) import_job_callback_destroy, 0);
1853 }
1854 }
1855
1856 static void
1857 rb_library_source_add_child_source (const char *path, RBLibrarySource *library_source)
1858 {
1859 RBSource *source;
1860 GPtrArray *query;
1861 RBShell *shell;
1862 char *name;
1863 GdkPixbuf *icon;
1864 RhythmDBEntryType *entry_type;
1865 char *sort_column;
1866 int sort_order;
1867 GFile *file;
1868
1869 g_object_get (library_source,
1870 "shell", &shell,
1871 "entry-type", &entry_type,
1872 NULL);
1873
1874 file = g_file_new_for_uri (path);
1875 name = g_file_get_basename (file);
1876 g_object_unref (file);
1877
1878 rb_entry_view_get_sorting_order (rb_source_get_entry_view (RB_SOURCE (library_source)),
1879 &sort_column, &sort_order);
1880
1881 source = rb_auto_playlist_source_new (shell, name, FALSE);
1882 query = rhythmdb_query_parse (library_source->priv->db,
1883 RHYTHMDB_QUERY_PROP_EQUALS, RHYTHMDB_PROP_TYPE, entry_type,
1884 RHYTHMDB_QUERY_PROP_PREFIX, RHYTHMDB_PROP_LOCATION, path,
1885 RHYTHMDB_QUERY_END);
1886 rb_auto_playlist_source_set_query (RB_AUTO_PLAYLIST_SOURCE (source), query,
1887 RHYTHMDB_QUERY_MODEL_LIMIT_NONE, NULL,
1888 sort_column, sort_order);
1889 rhythmdb_query_free (query);
1890 g_free (sort_column);
1891
1892 g_object_get (library_source, "pixbuf", &icon, NULL);
1893 g_object_set (source, "pixbuf", icon, NULL);
1894 if (icon != NULL) {
1895 g_object_unref (icon);
1896 }
1897
1898 rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (source), RB_DISPLAY_PAGE (library_source));
1899 library_source->priv->child_sources = g_list_prepend (library_source->priv->child_sources, source);
1900
1901 g_object_unref (entry_type);
1902 g_object_unref (shell);
1903 g_free (name);
1904 }
1905
1906 static void
1907 rb_library_source_sync_child_sources (RBLibrarySource *source)
1908 {
1909 char **locations;
1910 int num_locations;
1911
1912 locations = g_settings_get_strv (source->priv->db_settings, "locations");
1913
1914 /* FIXME: don't delete and re-create sources that are still there */
1915 g_list_foreach (source->priv->child_sources, (GFunc)rb_display_page_delete_thyself, NULL);
1916 g_list_free (source->priv->child_sources);
1917 source->priv->child_sources = NULL;
1918
1919 num_locations = g_strv_length (locations);
1920 if (num_locations > 1) {
1921 int i;
1922 for (i = 0; i < num_locations; i++) {
1923 rb_library_source_add_child_source (locations[i], source);
1924 }
1925 }
1926 g_strfreev (locations);
1927 }
1928
1929 static void
1930 impl_get_status (RBDisplayPage *source, char **text, char **progress_text, float *progress)
1931 {
1932 RB_DISPLAY_PAGE_CLASS (rb_library_source_parent_class)->get_status (source, text, progress_text, progress);
1933 RBLibrarySource *lsource = RB_LIBRARY_SOURCE (source);
1934
1935 if (lsource->priv->import_jobs != NULL) {
1936 RhythmDBImportJob *job = RHYTHMDB_IMPORT_JOB (lsource->priv->import_jobs->data);
1937 _rb_source_set_import_status (RB_SOURCE (source), job, progress_text, progress);
1938 } else if (gtk_notebook_get_current_page (GTK_NOTEBOOK (lsource->priv->notebook)) == IMPORT_DIALOG_PAGE) {
1939 g_free (*text);
1940 g_object_get (lsource->priv->import_dialog, "status", text, NULL);
1941 }
1942 }
1943
1944 static void
1945 import_dialog_closed_cb (RBImportDialog *dialog, RBLibrarySource *source)
1946 {
1947 gtk_notebook_set_current_page (GTK_NOTEBOOK (source->priv->notebook), 0);
1948 rb_display_page_notify_status_changed (RB_DISPLAY_PAGE (source));
1949 }
1950
1951 static void
1952 import_dialog_status_notify_cb (GObject *dialog, GParamSpec *pspec, RBLibrarySource *source)
1953 {
1954 rb_display_page_notify_status_changed (RB_DISPLAY_PAGE (source));
1955 }
1956
1957 void
1958 rb_library_source_show_import_dialog (RBLibrarySource *source)
1959 {
1960 if (source->priv->import_dialog == NULL) {
1961 RBShell *shell;
1962
1963 g_object_get (source, "shell", &shell, NULL);
1964 source->priv->import_dialog = rb_import_dialog_new (shell);
1965 g_object_unref (shell);
1966
1967 g_signal_connect (source->priv->import_dialog,
1968 "closed",
1969 G_CALLBACK (import_dialog_closed_cb),
1970 source);
1971 g_signal_connect (source->priv->import_dialog,
1972 "notify::status",
1973 G_CALLBACK (import_dialog_status_notify_cb),
1974 source);
1975
1976 gtk_widget_show_all (GTK_WIDGET (source->priv->import_dialog));
1977 gtk_notebook_append_page (GTK_NOTEBOOK (source->priv->notebook),
1978 source->priv->import_dialog,
1979 NULL);
1980 }
1981
1982 if (gtk_notebook_get_current_page (GTK_NOTEBOOK (source->priv->notebook)) != IMPORT_DIALOG_PAGE) {
1983 rb_import_dialog_reset (RB_IMPORT_DIALOG (source->priv->import_dialog));
1984 gtk_notebook_set_current_page (GTK_NOTEBOOK (source->priv->notebook), IMPORT_DIALOG_PAGE);
1985 rb_display_page_notify_status_changed (RB_DISPLAY_PAGE (source));
1986 }
1987 }