hythmbox-2.98/sources/rb-library-source.c

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 }