hythmbox-2.98/plugins/iradio/rb-iradio-source.c

No issues found

   1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
   2  *
   3  *  Copyright (C) 2002,2003 Colin Walters <walters@debian.org>
   4  *
   5  *  This program is free software; you can redistribute it and/or modify
   6  *  it under the terms of the GNU General Public License as published by
   7  *  the Free Software Foundation; either version 2 of the License, or
   8  *  (at your option) any later version.
   9  *
  10  *  The Rhythmbox authors hereby grant permission for non-GPL compatible
  11  *  GStreamer plugins to be used and distributed together with GStreamer
  12  *  and Rhythmbox. This permission is above and beyond the permissions granted
  13  *  by the GPL license by which Rhythmbox is covered. If you modify this code
  14  *  you may extend this exception to your version of the code, but you are not
  15  *  obligated to do so. If you do not wish to do so, delete this exception
  16  *  statement from your version.
  17  *
  18  *  This program is distributed in the hope that it will be useful,
  19  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  20  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  21  *  GNU General Public License for more details.
  22  *
  23  *  You should have received a copy of the GNU General Public License
  24  *  along with this program; if not, write to the Free Software
  25  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
  26  *
  27  */
  28 
  29 #include "config.h"
  30 
  31 #include <string.h>
  32 
  33 #include <glib/gi18n.h>
  34 #include <gtk/gtk.h>
  35 #include <libxml/tree.h>
  36 
  37 #include "rb-iradio-source.h"
  38 #include "rb-iradio-source-search.h"
  39 
  40 #include "rhythmdb-query-model.h"
  41 #include "rb-stock-icons.h"
  42 #include "rb-entry-view.h"
  43 #include "rb-property-view.h"
  44 #include "rb-util.h"
  45 #include "rb-file-helpers.h"
  46 #include "totem-pl-parser.h"
  47 #include "rb-dialog.h"
  48 #include "rb-station-properties-dialog.h"
  49 #include "rb-uri-dialog.h"
  50 #include "rb-debug.h"
  51 #include "rb-shell-player.h"
  52 #include "rb-player.h"
  53 #include "rb-metadata.h"
  54 #include "rb-cut-and-paste-code.h"
  55 #include "rb-source-search-basic.h"
  56 #include "rb-source-toolbar.h"
  57 
  58 /* icon names */
  59 #define IRADIO_SOURCE_ICON  "library-internet-radio"
  60 #define IRADIO_NEW_STATION_ICON "internet-radio-new"
  61 
  62 typedef struct _RhythmDBEntryType RBIRadioEntryType;
  63 typedef struct _RhythmDBEntryTypeClass RBIRadioEntryTypeClass;
  64 
  65 static void rb_iradio_source_class_init (RBIRadioSourceClass *klass);
  66 static void rb_iradio_source_init (RBIRadioSource *source);
  67 static void rb_iradio_source_constructed (GObject *object);
  68 static void rb_iradio_source_dispose (GObject *object);
  69 static void rb_iradio_source_set_property (GObject *object,
  70 			                  guint prop_id,
  71 			                  const GValue *value,
  72 			                  GParamSpec *pspec);
  73 static void rb_iradio_source_get_property (GObject *object,
  74 			                  guint prop_id,
  75 			                  GValue *value,
  76 			                  GParamSpec *pspec);
  77 static void rb_iradio_source_songs_show_popup_cb (RBEntryView *view,
  78 						  gboolean over_entry,
  79 						  RBIRadioSource *source);
  80 static void genre_selected_cb (RBPropertyView *propview, const char *name,
  81 			       RBIRadioSource *iradio_source);
  82 static void genre_selection_reset_cb (RBPropertyView *propview, RBIRadioSource *iradio_source);
  83 static void rb_iradio_source_songs_view_sort_order_changed_cb (GObject *object, GParamSpec *pspec, RBIRadioSource *source);
  84 static char *guess_uri_scheme (const char *uri);
  85 
  86 /* entry type */
  87 static void rb_iradio_entry_type_class_init (RBIRadioEntryTypeClass *klass);
  88 static void rb_iradio_entry_type_init (RBIRadioEntryType *etype);
  89 GType rb_iradio_entry_type_get_type (void);
  90 
  91 /* page methods */
  92 static gboolean impl_show_popup (RBDisplayPage *page);
  93 static void impl_get_status (RBDisplayPage *page, char **text, char **progress_text, float *progress);
  94 
  95 /* source methods */
  96 static RBEntryView *impl_get_entry_view (RBSource *source);
  97 static void impl_search (RBSource *source, RBSourceSearch *search, const char *cur_text, const char *new_text);
  98 static void impl_delete (RBSource *source);
  99 static void impl_song_properties (RBSource *source);
 100 static guint impl_want_uri (RBSource *source, const char *uri);
 101 static void impl_add_uri (RBSource *source,
 102 			  const char *uri,
 103 			  const char *title,
 104 			  const char *genre,
 105 			  RBSourceAddCallback callback,
 106 			  gpointer data,
 107 			  GDestroyNotify destroy_data);
 108 
 109 static void rb_iradio_source_do_query (RBIRadioSource *source);
 110 static void impl_reset_filters (RBSource *source);
 111 
 112 void rb_iradio_source_show_columns_changed_cb (GtkToggleButton *button,
 113 					     RBIRadioSource *source);
 114 static void stations_view_drag_data_received_cb (GtkWidget *widget,
 115 						 GdkDragContext *dc,
 116 						 gint x, gint y,
 117 						 GtkSelectionData *data,
 118 						 guint info, guint time,
 119 						 RBIRadioSource *source);
 120 static void rb_iradio_source_cmd_new_station (GtkAction *action,
 121 					      RBIRadioSource *source);
 122 
 123 static void playing_source_changed_cb (RBShellPlayer *player,
 124 				       RBSource *source,
 125 				       RBIRadioSource *iradio_source);
 126 
 127 enum
 128 {
 129 	PROP_0,
 130 	PROP_SHOW_BROWSER
 131 };
 132 
 133 struct RBIRadioSourcePrivate
 134 {
 135 	RhythmDB *db;
 136 
 137 	GtkActionGroup *action_group;
 138 
 139 	RBSourceToolbar *toolbar;
 140 	RBPropertyView *genres;
 141 	RBEntryView *stations;
 142 	gboolean setting_new_query;
 143 
 144 	char *selected_genre;
 145 	RhythmDBQuery *search_query;
 146 	RBSourceSearch *default_search;
 147 
 148 	RBShellPlayer *player;
 149 
 150 	gint info_available_id;
 151 
 152 	gboolean dispose_has_run;
 153 };
 154 
 155 #define RB_IRADIO_SOURCE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_IRADIO_SOURCE, RBIRadioSourcePrivate))
 156 
 157 static GtkActionEntry rb_iradio_source_actions [] =
 158 {
 159 	{ "MusicNewInternetRadioStation", IRADIO_NEW_STATION_ICON, N_("New Internet _Radio Station..."), "<control>I",
 160 	  N_("Create a new Internet Radio station"),
 161 	  G_CALLBACK (rb_iradio_source_cmd_new_station) }
 162 };
 163 
 164 static const GtkTargetEntry stations_view_drag_types[] = {
 165 	{  "text/uri-list", 0, 0 },
 166 	{  "_NETSCAPE_URL", 0, 1 },
 167 };
 168 
 169 G_DEFINE_DYNAMIC_TYPE (RBIRadioSource, rb_iradio_source, RB_TYPE_STREAMING_SOURCE);
 170 
 171 G_DEFINE_DYNAMIC_TYPE (RBIRadioEntryType, rb_iradio_entry_type, RHYTHMDB_TYPE_ENTRY_TYPE);
 172 
 173 static void
 174 rb_iradio_entry_type_class_init (RBIRadioEntryTypeClass *klass)
 175 {
 176 	RhythmDBEntryTypeClass *etype_class = RHYTHMDB_ENTRY_TYPE_CLASS (klass);
 177 	etype_class->can_sync_metadata = (RhythmDBEntryTypeBooleanFunc) rb_true_function;
 178 	etype_class->sync_metadata = (RhythmDBEntryTypeSyncFunc) rb_null_function;
 179 }
 180 
 181 static void
 182 rb_iradio_entry_type_class_finalize (RBIRadioEntryTypeClass *klass)
 183 {
 184 }
 185 
 186 static void
 187 rb_iradio_entry_type_init (RBIRadioEntryType *etype)
 188 {
 189 }
 190 
 191 static void
 192 rb_iradio_source_class_init (RBIRadioSourceClass *klass)
 193 {
 194 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 195 	RBDisplayPageClass *page_class = RB_DISPLAY_PAGE_CLASS (klass);
 196 	RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
 197 
 198 	object_class->dispose = rb_iradio_source_dispose;
 199 	object_class->constructed = rb_iradio_source_constructed;
 200 
 201 	object_class->set_property = rb_iradio_source_set_property;
 202 	object_class->get_property = rb_iradio_source_get_property;
 203 
 204 	page_class->show_popup = impl_show_popup;
 205 	page_class->get_status  = impl_get_status;
 206 
 207 	source_class->impl_can_copy = (RBSourceFeatureFunc) rb_false_function;
 208 	source_class->impl_can_delete = (RBSourceFeatureFunc) rb_true_function;
 209 	source_class->impl_can_pause = (RBSourceFeatureFunc) rb_false_function;
 210 	source_class->impl_delete = impl_delete;
 211 	source_class->impl_get_entry_view = impl_get_entry_view;
 212 	source_class->impl_search = impl_search;
 213 	source_class->impl_song_properties = impl_song_properties;
 214 	source_class->impl_want_uri = impl_want_uri;
 215 	source_class->impl_add_uri = impl_add_uri;
 216 	source_class->impl_reset_filters = impl_reset_filters;
 217 
 218 	g_object_class_override_property (object_class,
 219 					  PROP_SHOW_BROWSER,
 220 					  "show-browser");
 221 
 222 	g_type_class_add_private (klass, sizeof (RBIRadioSourcePrivate));
 223 }
 224 
 225 static void
 226 rb_iradio_source_class_finalize (RBIRadioSourceClass *klass)
 227 {
 228 }
 229 
 230 static void
 231 rb_iradio_source_init (RBIRadioSource *source)
 232 {
 233 	source->priv = RB_IRADIO_SOURCE_GET_PRIVATE (source);
 234 }
 235 
 236 static void
 237 rb_iradio_source_dispose (GObject *object)
 238 {
 239 	RBIRadioSource *source;
 240 
 241 	source = RB_IRADIO_SOURCE (object);
 242 
 243 	if (source->priv->dispose_has_run) {
 244 		/* If dispose did already run, return. */
 245 		return;
 246 	}
 247 	/* Make sure dispose does not run twice. */
 248 	source->priv->dispose_has_run = TRUE;
 249 
 250 	if (source->priv->player) {
 251 		g_object_unref (source->priv->player);
 252 		source->priv->player = NULL;
 253 	}
 254 
 255 	if (source->priv->db) {
 256 		g_object_unref (source->priv->db);
 257 		source->priv->db = NULL;
 258 	}
 259 
 260 	if (source->priv->action_group != NULL) {
 261 		g_object_unref (source->priv->action_group);
 262 		source->priv->action_group = NULL;
 263 	}
 264 
 265 	if (source->priv->default_search != NULL) {
 266 		g_object_unref (source->priv->default_search);
 267 		source->priv->default_search = NULL;
 268 	}
 269 
 270 	if (source->priv->search_query != NULL) {
 271 		rhythmdb_query_free (source->priv->search_query);
 272 		source->priv->search_query = NULL;
 273 	}
 274 
 275 	G_OBJECT_CLASS (rb_iradio_source_parent_class)->dispose (object);
 276 }
 277 
 278 static void
 279 rb_iradio_source_constructed (GObject *object)
 280 {
 281 	RBIRadioSource *source;
 282 	RBShell *shell;
 283 	GtkAction *action;
 284 	GSettings *settings;
 285 	GtkUIManager *ui_manager;
 286 	GtkWidget *grid;
 287 	GtkWidget *paned;
 288 	gint size;
 289 	GdkPixbuf *pixbuf;
 290 
 291 	RB_CHAIN_GOBJECT_METHOD (rb_iradio_source_parent_class, constructed, object);
 292 	source = RB_IRADIO_SOURCE (object);
 293 
 294 	paned = gtk_paned_new (GTK_ORIENTATION_HORIZONTAL);
 295 
 296 	g_object_get (source, "shell", &shell, NULL);
 297 	g_object_get (shell,
 298 		      "db", &source->priv->db,
 299 		      "shell-player", &source->priv->player,
 300 		      "ui-manager", &ui_manager,
 301 		      NULL);
 302 	g_object_unref (shell);
 303 
 304 	gtk_icon_size_lookup (RB_SOURCE_ICON_SIZE, &size, NULL);
 305 	pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
 306 					   IRADIO_SOURCE_ICON,
 307 					   size,
 308 					   0, NULL);
 309 	g_object_set (source, "pixbuf", pixbuf, NULL);
 310 	if (pixbuf != NULL) {
 311 		g_object_unref (pixbuf);
 312 	}
 313 
 314 	settings = g_settings_new ("org.gnome.rhythmbox.plugins.iradio");
 315 	if (g_settings_get_boolean (settings, "initial-stations-loaded") == FALSE) {
 316 		GObject *plugin;
 317 		char *file;
 318 
 319 		g_object_get (source, "plugin", &plugin, NULL);
 320 		file = rb_find_plugin_data_file (plugin, "iradio-initial.xspf");
 321 		if (file != NULL) {
 322 			char *uri = g_filename_to_uri (file, NULL, NULL);
 323 			if (uri != NULL) {
 324 				rb_iradio_source_add_from_playlist (source, uri);
 325 				g_free (uri);
 326 
 327 				g_settings_set_boolean (settings, "initial-stations-loaded", TRUE);
 328 			}
 329 		}
 330 		g_free (file);
 331 		g_object_unref (plugin);
 332 	}
 333 
 334 	source->priv->action_group = _rb_display_page_register_action_group (RB_DISPLAY_PAGE (source),
 335 									     "IRadioActions",
 336 									     rb_iradio_source_actions,
 337 									     G_N_ELEMENTS (rb_iradio_source_actions),
 338 									     source);
 339 
 340 	action = gtk_action_group_get_action (source->priv->action_group,
 341                                               "MusicNewInternetRadioStation");
 342         /* Translators: this is the toolbar button label for 
 343            New Internet Radio Station action. */
 344         g_object_set (action, "short-label", C_("Radio", "Add"), NULL);
 345 
 346 
 347 	/* set up stations view */
 348 	source->priv->stations = rb_entry_view_new (source->priv->db, G_OBJECT (source->priv->player),
 349 						    FALSE, FALSE);
 350 
 351 	rb_entry_view_append_column (source->priv->stations, RB_ENTRY_VIEW_COL_TITLE, TRUE);
 352 	rb_entry_view_append_column (source->priv->stations, RB_ENTRY_VIEW_COL_GENRE, FALSE);
 353 /* 	rb_entry_view_append_column (source->priv->stations, RB_ENTRY_VIEW_COL_QUALITY, FALSE); */
 354 	rb_entry_view_append_column (source->priv->stations, RB_ENTRY_VIEW_COL_RATING, FALSE);
 355 /*	rb_entry_view_append_column (source->priv->stations, RB_ENTRY_VIEW_COL_PLAY_COUNT, FALSE);*/
 356 	rb_entry_view_append_column (source->priv->stations, RB_ENTRY_VIEW_COL_LAST_PLAYED, FALSE);
 357 	g_signal_connect_object (source->priv->stations,
 358 				 "notify::sort-order",
 359 				 G_CALLBACK (rb_iradio_source_songs_view_sort_order_changed_cb),
 360 				 source, 0);
 361 
 362 	/* set up drag and drop for the song tree view.
 363 	 * we don't use RBEntryView's DnD support because it does too much.
 364 	 * we just want to be able to drop stations in to add them.
 365 	 */
 366 	g_signal_connect_object (source->priv->stations,
 367 				 "drag_data_received",
 368 				 G_CALLBACK (stations_view_drag_data_received_cb),
 369 				 source, 0);
 370 	gtk_drag_dest_set (GTK_WIDGET (source->priv->stations),
 371 			   GTK_DEST_DEFAULT_ALL,
 372 			   stations_view_drag_types, 2,
 373 			   GDK_ACTION_COPY | GDK_ACTION_MOVE);
 374 
 375 	g_signal_connect_object (source->priv->stations, "show_popup",
 376 				 G_CALLBACK (rb_iradio_source_songs_show_popup_cb), source, 0);
 377 
 378 	/* set up genre entry view */
 379 	source->priv->genres = rb_property_view_new (source->priv->db,
 380 						     RHYTHMDB_PROP_GENRE,
 381 						     _("Genre"));
 382 	gtk_widget_show_all (GTK_WIDGET (source->priv->genres));
 383 	gtk_widget_set_no_show_all (GTK_WIDGET (source->priv->genres), TRUE);
 384 	g_signal_connect_object (source->priv->genres,
 385 				 "property-selected",
 386 				 G_CALLBACK (genre_selected_cb),
 387 				 source, 0);
 388 	g_signal_connect_object (source->priv->genres,
 389 				 "property-selection-reset",
 390 				 G_CALLBACK (genre_selection_reset_cb),
 391 				 source, 0);
 392 
 393 	g_object_set (source->priv->genres, "vscrollbar_policy",
 394 		      GTK_POLICY_AUTOMATIC, NULL);
 395 
 396 	gtk_paned_pack1 (GTK_PANED (paned), GTK_WIDGET (source->priv->genres), FALSE, FALSE);
 397 	gtk_paned_pack2 (GTK_PANED (paned), GTK_WIDGET (source->priv->stations), TRUE, FALSE);
 398 
 399 	/* set up toolbar */
 400 	source->priv->toolbar = rb_source_toolbar_new (RB_DISPLAY_PAGE (source), ui_manager);
 401 	rb_source_toolbar_add_search_entry (source->priv->toolbar, NULL, _("Search your internet radio stations"));
 402 
 403 	grid = gtk_grid_new ();
 404 	gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
 405 	gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
 406 	gtk_widget_set_margin_top (GTK_WIDGET (grid), 6);
 407 	gtk_grid_attach (GTK_GRID (grid), GTK_WIDGET (source->priv->toolbar), 0, 0, 1, 1);
 408 	gtk_grid_attach (GTK_GRID (grid), paned, 0, 1, 1, 1);
 409 
 410 	gtk_container_add (GTK_CONTAINER (source), grid);
 411 
 412 	rb_source_bind_settings (RB_SOURCE (source),
 413 				 GTK_WIDGET (source->priv->stations),
 414 				 paned,
 415 				 GTK_WIDGET (source->priv->genres));
 416 
 417 	gtk_widget_show_all (GTK_WIDGET (source));
 418 
 419 	g_signal_connect_object (source->priv->player, "playing-source-changed",
 420 				 G_CALLBACK (playing_source_changed_cb),
 421 				 source, 0);
 422 
 423 	source->priv->default_search = rb_iradio_source_search_new ();
 424 
 425 	rb_iradio_source_do_query (source);
 426 }
 427 
 428 static void
 429 rb_iradio_source_set_property (GObject *object,
 430 			       guint prop_id,
 431 			       const GValue *value,
 432 			       GParamSpec *pspec)
 433 {
 434 	RBIRadioSource *source = RB_IRADIO_SOURCE (object);
 435 
 436 	switch (prop_id) {
 437 	case PROP_SHOW_BROWSER:
 438 		gtk_widget_set_visible (GTK_WIDGET (source->priv->genres), g_value_get_boolean (value));
 439 		break;
 440 	default:
 441 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 442 		break;
 443 	}
 444 }
 445 
 446 static void
 447 rb_iradio_source_get_property (GObject *object,
 448 			       guint prop_id,
 449 			       GValue *value,
 450 			       GParamSpec *pspec)
 451 {
 452 	RBIRadioSource *source = RB_IRADIO_SOURCE (object);
 453 
 454 	switch (prop_id) {
 455 	case PROP_SHOW_BROWSER:
 456 		g_value_set_boolean (value, gtk_widget_get_visible (GTK_WIDGET (source->priv->genres)));
 457 		break;
 458 	default:
 459 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 460 		break;
 461 	}
 462 }
 463 
 464 RBSource *
 465 rb_iradio_source_new (RBShell *shell, GObject *plugin)
 466 {
 467 	RBSource *source;
 468 	RhythmDBEntryType *entry_type;
 469 	RhythmDB *db;
 470 	GSettings *settings;
 471 
 472 	g_object_get (shell, "db", &db, NULL);
 473 
 474 	entry_type = rhythmdb_entry_type_get_by_name (db, "iradio");
 475 	if (entry_type == NULL) {
 476 		entry_type = g_object_new (RHYTHMDB_TYPE_ENTRY_TYPE,
 477 					   "db", db,
 478 					   "name", "iradio",
 479 					   "save-to-disk", TRUE,
 480 					   "category", RHYTHMDB_ENTRY_STREAM,
 481 					   NULL);
 482 		rhythmdb_register_entry_type (db, entry_type);
 483 	}
 484 	g_object_unref (db);
 485 
 486 	settings = g_settings_new ("org.gnome.rhythmbox.plugins.iradio");
 487 	source = RB_SOURCE (g_object_new (RB_TYPE_IRADIO_SOURCE,
 488 					  "name", _("Radio"),
 489 					  "shell", shell,
 490 					  "entry-type", entry_type,
 491 					  "plugin", plugin,
 492 					  "settings", g_settings_get_child (settings, "source"),
 493 					  "toolbar-path", "/IRadioSourceToolBar",
 494 					  NULL));
 495 	g_object_unref (settings);
 496 	rb_shell_register_entry_type_for_source (shell, source, entry_type);
 497 	return source;
 498 }
 499 
 500 static char *
 501 guess_uri_scheme (const char *uri)
 502 {
 503 	const char *scheme;
 504 
 505 	/* if the URI has no scheme, it might be an absolute path, or it might be
 506 	 * host:port for HTTP.
 507 	 */
 508 	scheme = strstr (uri, "://");
 509 	if (scheme == NULL) {
 510 		if (uri[0] == '/') {
 511 			return g_strdup_printf ("file://%s", uri);
 512 		} else {
 513 			return g_strdup_printf ("http://%s", uri);
 514 		}
 515 	}
 516 
 517 	return NULL;
 518 }
 519 
 520 void
 521 rb_iradio_source_add_station (RBIRadioSource *source,
 522 			      const char *uri,
 523 			      const char *title,
 524 			      const char *genre)
 525 {
 526 	RhythmDBEntry *entry;
 527 	GValue val = { 0, };
 528 	char *real_uri = NULL;
 529 	char *fixed_title;
 530 	char *fixed_genre = NULL;
 531 	RhythmDBEntryType *entry_type;
 532 
 533 	real_uri = guess_uri_scheme (uri);
 534 	if (real_uri)
 535 		uri = real_uri;
 536 
 537 	entry = rhythmdb_entry_lookup_by_location (source->priv->db, uri);
 538 	if (entry) {
 539 		rb_debug ("uri %s already in db", uri);
 540 		g_free (real_uri);
 541 		return;
 542 	}
 543 
 544 	g_object_get (source, "entry-type", &entry_type, NULL);
 545 	entry = rhythmdb_entry_new (source->priv->db, entry_type, uri);
 546 	g_object_unref (entry_type);
 547 	if (entry == NULL) {
 548 		g_free (real_uri);
 549 		return;
 550 	}
 551 
 552 	g_value_init (&val, G_TYPE_STRING);
 553 	if (title) {
 554 		fixed_title = rb_make_valid_utf8 (title, '?');
 555 	} else {
 556 		fixed_title = g_uri_unescape_string (uri, NULL);
 557 	}
 558 	g_value_take_string (&val, fixed_title);
 559 
 560 	rhythmdb_entry_set (source->priv->db, entry, RHYTHMDB_PROP_TITLE, &val);
 561 	g_value_reset (&val);
 562 
 563 	if ((!genre) || (strcmp (genre, "") == 0)) {
 564 		genre = _("Unknown");
 565 	} else {
 566 		fixed_genre = rb_make_valid_utf8 (genre, '?');
 567 		genre = fixed_genre;
 568 	}
 569 
 570 	g_value_set_string (&val, genre);
 571 	rhythmdb_entry_set (source->priv->db, entry, RHYTHMDB_PROP_GENRE, &val);
 572 	g_value_unset (&val);
 573 	g_free (fixed_genre);
 574 
 575 	g_value_init (&val, G_TYPE_DOUBLE);
 576 	g_value_set_double (&val, 0.0);
 577 	rhythmdb_entry_set (source->priv->db, entry, RHYTHMDB_PROP_RATING, &val);
 578 	g_value_unset (&val);
 579 
 580 	rhythmdb_commit (source->priv->db);
 581 
 582 	g_free (real_uri);
 583 }
 584 static void
 585 impl_search (RBSource *asource,
 586 	     RBSourceSearch *search,
 587 	     const char *cur_text,
 588 	     const char *new_text)
 589 {
 590 	RBIRadioSource *source = RB_IRADIO_SOURCE (asource);
 591 
 592 	if (source->priv->search_query != NULL) {
 593 		rhythmdb_query_free (source->priv->search_query);
 594 	}
 595 
 596 	if (search == NULL) {
 597 		search = source->priv->default_search;
 598 	}
 599 	source->priv->search_query = rb_source_search_create_query (search, source->priv->db, new_text);
 600 
 601 	rb_iradio_source_do_query (source);
 602 
 603 	rb_source_notify_filter_changed (RB_SOURCE (source));
 604 }
 605 
 606 static RBEntryView *
 607 impl_get_entry_view (RBSource *asource)
 608 {
 609 	RBIRadioSource *source = RB_IRADIO_SOURCE (asource);
 610 
 611 	return source->priv->stations;
 612 }
 613 
 614 static void
 615 impl_get_status (RBDisplayPage *page,
 616 		 char **text,
 617 		 char **progress_text,
 618 		 float *progress)
 619 {
 620 	RhythmDBQueryModel *model;
 621 	guint num_entries;
 622 	RBIRadioSource *source = RB_IRADIO_SOURCE (page);
 623 
 624 	g_object_get (source, "query-model", &model, NULL);
 625 	num_entries = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (model), NULL);
 626 	g_object_unref (model);
 627 
 628 	*text = g_strdup_printf (ngettext ("%d station", "%d stations", num_entries),
 629 				 num_entries);
 630 
 631 	rb_streaming_source_get_progress (RB_STREAMING_SOURCE (source), progress_text, progress);
 632 }
 633 
 634 static void
 635 impl_delete (RBSource *asource)
 636 {
 637 	RBIRadioSource *source = RB_IRADIO_SOURCE (asource);
 638 	GList *sel;
 639 	GList *l;
 640 
 641 	sel = rb_entry_view_get_selected_entries (source->priv->stations);
 642 	for (l = sel; l != NULL; l = g_list_next (l)) {
 643 		rhythmdb_entry_delete (source->priv->db, l->data);
 644 		rhythmdb_commit (source->priv->db);
 645 	}
 646 
 647 	g_list_foreach (sel, (GFunc)rhythmdb_entry_unref, NULL);
 648 	g_list_free (sel);
 649 }
 650 
 651 static void
 652 impl_song_properties (RBSource *asource)
 653 {
 654 	RBIRadioSource *source = RB_IRADIO_SOURCE (asource);
 655 	GObject *plugin;
 656 	GtkWidget *dialog;
 657 
 658 	g_object_get (source, "plugin", &plugin, NULL);
 659 	dialog = rb_station_properties_dialog_new (plugin, source->priv->stations);
 660 	g_object_unref (plugin);
 661 
 662 	rb_debug ("in song properties");
 663 	if (dialog)
 664 		gtk_widget_show_all (dialog);
 665 	else
 666 		rb_debug ("no selection!");
 667 }
 668 
 669 static guint
 670 impl_want_uri (RBSource *source, const char *uri)
 671 {
 672 	if (g_str_has_prefix (uri, "http://")) {
 673 		/* other entry types might have
 674 		 * more specific guesses for HTTP
 675 		 */
 676 		return 50;
 677 	} else if (g_str_has_prefix (uri, "pnm://") ||
 678 		   g_str_has_prefix (uri, "rtsp://") ||
 679 		   g_str_has_prefix (uri, "mms://") ||
 680 		   g_str_has_prefix (uri, "mmsh://")) {
 681 		return 100;
 682 	}
 683 
 684 	return 0;
 685 }
 686 
 687 static void
 688 impl_add_uri (RBSource *source,
 689 	      const char *uri,
 690 	      const char *title,
 691 	      const char *genre,
 692 	      RBSourceAddCallback callback,
 693 	      gpointer data,
 694 	      GDestroyNotify destroy_data)
 695 {
 696 	if (rb_uri_is_local (uri)) {
 697 		rb_iradio_source_add_from_playlist (RB_IRADIO_SOURCE (source), uri);
 698 	} else {
 699 		rb_iradio_source_add_station (RB_IRADIO_SOURCE (source),
 700 					      uri, title, genre);
 701 	}
 702 	if (callback != NULL) {
 703 		callback (source, uri, data);
 704 		if (destroy_data != NULL) {
 705 			destroy_data (data);
 706 		}
 707 	}
 708 }
 709 
 710 static void
 711 rb_iradio_source_songs_view_sort_order_changed_cb (GObject *object,
 712 						   GParamSpec *pspec,
 713 						   RBIRadioSource *source)
 714 {
 715 	rb_debug ("sort order changed");
 716 	rb_entry_view_resort_model (RB_ENTRY_VIEW (object));
 717 }
 718 
 719 static void
 720 rb_iradio_source_songs_show_popup_cb (RBEntryView *view,
 721 				      gboolean over_entry,
 722 				      RBIRadioSource *source)
 723 {
 724 	if (source == NULL) {
 725 		return;
 726 	}
 727 
 728 	if (over_entry)
 729 		_rb_display_page_show_popup (RB_DISPLAY_PAGE (source), "/IRadioViewPopup");
 730 	else
 731 		_rb_display_page_show_popup (RB_DISPLAY_PAGE (source), "/IRadioSourcePopup");
 732 }
 733 
 734 static void
 735 genre_selected_cb (RBPropertyView *propview, const char *name,
 736 		   RBIRadioSource *iradio_source)
 737 {
 738 	if (iradio_source->priv->setting_new_query)
 739 		return;
 740 
 741 	g_free (iradio_source->priv->selected_genre);
 742 	iradio_source->priv->selected_genre = g_strdup (name);
 743 	rb_iradio_source_do_query (iradio_source);
 744 
 745 	rb_source_notify_filter_changed (RB_SOURCE (iradio_source));
 746 }
 747 
 748 static void
 749 genre_selection_reset_cb (RBPropertyView *propview,
 750 			  RBIRadioSource *iradio_source)
 751 {
 752 	if (iradio_source->priv->setting_new_query)
 753 		return;
 754 
 755 	g_free (iradio_source->priv->selected_genre);
 756 	iradio_source->priv->selected_genre = NULL;
 757 
 758 	rb_iradio_source_do_query (iradio_source);
 759 
 760 	rb_source_notify_filter_changed (RB_SOURCE (iradio_source));
 761 }
 762 
 763 static void
 764 rb_iradio_source_do_query (RBIRadioSource *source)
 765 {
 766 	RhythmDBQueryModel *genre_query_model = NULL;
 767 	RhythmDBQueryModel *station_query_model = NULL;
 768 	RhythmDBPropertyModel *genre_model;
 769 	GPtrArray *query;
 770 	RhythmDBEntryType *entry_type;
 771 
 772 	/* don't update the selection while we're rebuilding the query */
 773 	source->priv->setting_new_query = TRUE;
 774 
 775 	/* construct and run the query for the search box.
 776 	 * this is used as the model for the genre view.
 777 	 */
 778 
 779 	g_object_get (source, "entry-type", &entry_type, NULL);
 780 	query = rhythmdb_query_parse (source->priv->db,
 781 				      RHYTHMDB_QUERY_PROP_EQUALS,
 782 				      RHYTHMDB_PROP_TYPE,
 783 				      entry_type,
 784 				      RHYTHMDB_QUERY_END);
 785 	g_object_unref (entry_type);
 786 
 787 	if (source->priv->search_query != NULL) {
 788 		rhythmdb_query_append (source->priv->db,
 789 				       query,
 790 				       RHYTHMDB_QUERY_SUBQUERY,
 791 				       source->priv->search_query,
 792 				       RHYTHMDB_QUERY_END);
 793 	}
 794 
 795 	genre_model = rb_property_view_get_model (source->priv->genres);
 796 
 797 	genre_query_model = rhythmdb_query_model_new_empty (source->priv->db);
 798 	g_object_set (genre_model, "query-model", genre_query_model, NULL);
 799 
 800 	rhythmdb_do_full_query_parsed (source->priv->db,
 801 				       RHYTHMDB_QUERY_RESULTS (genre_query_model),
 802 				       query);
 803 
 804 	rhythmdb_query_free (query);
 805 	query = NULL;
 806 
 807 	/* check the selected genre is still available, and if not, select 'all' */
 808 	if (source->priv->selected_genre != NULL) {
 809 		GList *sel = NULL;
 810 
 811 		if (!rhythmdb_property_model_iter_from_string (genre_model,
 812 							       source->priv->selected_genre,
 813 							       NULL)) {
 814 			g_free (source->priv->selected_genre);
 815 			source->priv->selected_genre = NULL;
 816 		}
 817 
 818 		sel = g_list_prepend (sel, source->priv->selected_genre);
 819 		rb_property_view_set_selection (source->priv->genres, sel);
 820 		g_list_free (sel);
 821 	}
 822 
 823 	/* if a genre is selected, construct a new query for it, and create
 824 	 * a new model based on the search box query model.  otherwise, just
 825 	 * reuse the search box query model.
 826 	 */
 827 
 828 	if (source->priv->selected_genre != NULL) {
 829 		rb_debug ("matching on genre \"%s\"", source->priv->selected_genre);
 830 
 831 		station_query_model = rhythmdb_query_model_new_empty (source->priv->db);
 832 		query = rhythmdb_query_parse (source->priv->db,
 833 					      RHYTHMDB_QUERY_PROP_EQUALS,
 834 					      RHYTHMDB_PROP_GENRE,
 835 					      source->priv->selected_genre,
 836 					      RHYTHMDB_QUERY_END);
 837 
 838 		g_object_set (station_query_model,
 839 			      "query", query,
 840 			      "base-model", genre_query_model,
 841 			      NULL);
 842 
 843 		rhythmdb_query_free (query);
 844 		query = NULL;
 845 	} else {
 846 		station_query_model = g_object_ref (genre_query_model);
 847 	}
 848 
 849 	rb_entry_view_set_model (source->priv->stations, station_query_model);
 850 	g_object_set (source, "query-model", station_query_model, NULL);
 851 
 852 	g_object_unref (genre_query_model);
 853 	g_object_unref (station_query_model);
 854 
 855 	source->priv->setting_new_query = FALSE;
 856 }
 857 
 858 static void
 859 impl_reset_filters (RBSource *asource)
 860 {
 861 	RBIRadioSource *source = RB_IRADIO_SOURCE (asource);
 862 
 863 	if (source->priv->search_query != NULL) {
 864 		rhythmdb_query_free (source->priv->search_query);
 865 		source->priv->search_query = NULL;
 866 	}
 867 	rb_source_toolbar_clear_search_entry (source->priv->toolbar);
 868 
 869 	rb_property_view_set_selection (source->priv->genres, NULL);
 870 }
 871 
 872 static void
 873 handle_playlist_entry_cb (TotemPlParser *playlist,
 874 			  const char *uri,
 875 			  GHashTable *metadata,
 876 			  RBIRadioSource *source)
 877 {
 878 	const char *title, *genre;
 879 
 880 	title = g_hash_table_lookup (metadata, TOTEM_PL_PARSER_FIELD_TITLE);
 881 	genre = g_hash_table_lookup (metadata, TOTEM_PL_PARSER_FIELD_GENRE);
 882 	rb_iradio_source_add_station (source, uri, title, genre);
 883 }
 884 
 885 void
 886 rb_iradio_source_add_from_playlist (RBIRadioSource *source,
 887 				    const char     *uri)
 888 {
 889 	TotemPlParser *parser = totem_pl_parser_new ();
 890 	char *real_uri;
 891 
 892 	real_uri = guess_uri_scheme (uri);
 893 	if (real_uri)
 894 		uri = real_uri;
 895 
 896 	g_signal_connect_object (parser, "entry-parsed",
 897 				 G_CALLBACK (handle_playlist_entry_cb),
 898 				 source, 0);
 899 	g_object_set (parser, "recurse", FALSE, NULL);
 900 
 901 	switch (totem_pl_parser_parse (parser, uri, FALSE)) {
 902 	case TOTEM_PL_PARSER_RESULT_UNHANDLED:
 903 	case TOTEM_PL_PARSER_RESULT_IGNORED:
 904 		/* maybe it's the actual stream URL, then */
 905 		rb_iradio_source_add_station (source, uri, NULL, NULL);
 906 		break;
 907 
 908 	default:
 909 	case TOTEM_PL_PARSER_RESULT_SUCCESS:
 910 	case TOTEM_PL_PARSER_RESULT_ERROR:
 911 		break;
 912 	}
 913 	g_object_unref (parser);
 914 	g_free (real_uri);
 915 }
 916 
 917 static void
 918 stations_view_drag_data_received_cb (GtkWidget *widget,
 919 				     GdkDragContext *dc,
 920 				     gint x,
 921 				     gint y,
 922 				     GtkSelectionData *selection_data,
 923 				     guint info,
 924 				     guint time,
 925 				     RBIRadioSource *source)
 926 {
 927 	GList *uri_list, *i;
 928 
 929 	rb_debug ("parsing uri list");
 930 	uri_list = rb_uri_list_parse ((char *) gtk_selection_data_get_data (selection_data));
 931 	if (uri_list == NULL)
 932 		return;
 933 
 934 	for (i = uri_list; i != NULL; i = i->next) {
 935 		char *uri = NULL;
 936 
 937 		uri = i->data;
 938 		if (uri != NULL) {
 939 			rb_iradio_source_add_station (source, uri, NULL, NULL);
 940 		}
 941 
 942 		if (info == 1) {
 943 			/* for _NETSCAPE_URL drags, this item is the link text */
 944 			i = i->next;
 945 		}
 946 	}
 947 
 948 	rb_list_deep_free (uri_list);
 949 	return;
 950 }
 951 
 952 static gboolean
 953 impl_show_popup (RBDisplayPage *page)
 954 {
 955 	_rb_display_page_show_popup (page, "/IRadioSourcePopup");
 956 	return TRUE;
 957 }
 958 
 959 static void
 960 new_station_location_added (RBURIDialog    *dialog,
 961 			    const char     *uri,
 962 			    RBIRadioSource *source)
 963 {
 964 	rb_iradio_source_add_station (source, uri, NULL, NULL);
 965 }
 966 
 967 static void
 968 new_station_response_cb (GtkDialog *dialog, int response, gpointer meh)
 969 {
 970 	gtk_widget_destroy (GTK_WIDGET (dialog));
 971 }
 972 
 973 static void
 974 rb_iradio_source_cmd_new_station (GtkAction *action,
 975 				  RBIRadioSource *source)
 976 {
 977 	GtkWidget *dialog;
 978 
 979 	rb_debug ("Got new station command");
 980 
 981 	/* should prevent multiple dialogs?  going to kill this nonsense anyway soon.. */
 982 
 983 	dialog = rb_uri_dialog_new (_("New Internet Radio Station"), _("URL of internet radio station:"));
 984 	g_signal_connect_object (dialog, "location-added",
 985 				 G_CALLBACK (new_station_location_added),
 986 				 source, 0);
 987 	g_signal_connect (dialog, "response", G_CALLBACK (new_station_response_cb), NULL);
 988 
 989 	gtk_widget_show_all (dialog);
 990 }
 991 
 992 static gboolean
 993 check_entry_type (RBIRadioSource *source, RhythmDBEntry *entry)
 994 {
 995 	RhythmDBEntryType *entry_type;
 996 	gboolean matches = FALSE;
 997 
 998 	g_object_get (source, "entry-type", &entry_type, NULL);
 999 	if (entry != NULL && rhythmdb_entry_get_entry_type (entry) == entry_type)
1000 		matches = TRUE;
1001 	g_object_unref (entry_type);
1002 
1003 	return matches;
1004 }
1005 
1006 static void
1007 info_available_cb (RBPlayer *backend,
1008 		   const char *uri,
1009 		   RBMetaDataField field,
1010 		   GValue *value,
1011 		   RBIRadioSource *source)
1012 {
1013 	RhythmDBEntry *entry;
1014         RhythmDBPropType entry_field = 0;
1015         gboolean set_field = FALSE;
1016 	char *str = NULL;
1017 
1018 	/* sanity check */
1019 	if (!rb_player_opened (backend)) {
1020 		rb_debug ("Got info_available but not playing");
1021 		return;
1022 	}
1023 
1024 	GDK_THREADS_ENTER ();
1025 
1026 	entry = rb_shell_player_get_playing_entry (source->priv->player);
1027 	if (check_entry_type (source, entry) == FALSE)
1028 		goto out_unlock;
1029 
1030 	/* validate the value */
1031 	switch (field) {
1032 	case RB_METADATA_FIELD_TITLE:
1033 	case RB_METADATA_FIELD_ARTIST:
1034 	case RB_METADATA_FIELD_GENRE:
1035 	case RB_METADATA_FIELD_COMMENT:
1036 		str = g_value_dup_string (value);
1037 		if (!g_utf8_validate (str, -1, NULL)) {
1038 			g_warning ("Invalid UTF-8 from internet radio: %s", str);
1039 			g_free (str);
1040 			goto out_unlock;
1041 		}
1042 		break;
1043 	default:
1044 		break;
1045 	}
1046 
1047 
1048 	switch (field) {
1049 		/* streaming song information */
1050 	case RB_METADATA_FIELD_TITLE:
1051 	{
1052 		rb_streaming_source_set_streaming_title (RB_STREAMING_SOURCE (source), str);
1053 		break;
1054 	}
1055 	case RB_METADATA_FIELD_ARTIST:
1056 	{
1057 		rb_streaming_source_set_streaming_artist (RB_STREAMING_SOURCE (source), str);
1058 		break;
1059 	}
1060 
1061 		/* station information */
1062 	case RB_METADATA_FIELD_GENRE:
1063 	{
1064 		const char *existing;
1065 
1066 		/* check if the db entry already has a genre; if so, don't change it */
1067 		existing = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_GENRE);
1068 		if ((existing == NULL) ||
1069 		    (strcmp (existing, "") == 0) ||
1070 		    (strcmp (existing, _("Unknown")) == 0)) {
1071 			entry_field = RHYTHMDB_PROP_GENRE;
1072 			rb_debug ("setting genre of iradio station to %s", str);
1073 			set_field = TRUE;
1074 		} else {
1075 			rb_debug ("iradio station already has genre: %s; ignoring %s", existing, str);
1076 		}
1077 		break;
1078 	}
1079 	case RB_METADATA_FIELD_COMMENT:
1080 	{
1081 		const char *existing;
1082 		const char *location;
1083 
1084 		/* check if the db entry already has a title; if so, don't change it.
1085 		 * consider title==URI to be the same as no title, since that's what
1086 		 * happens for stations imported by DnD or commandline args.
1087 		 * if the station title really is the same as the URI, then surely
1088 		 * the station title in the stream metadata will say that too..
1089 		 */
1090 		existing = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_TITLE);
1091 		location = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
1092 		if ((existing == NULL) ||
1093 		    (strcmp (existing, "") == 0) ||
1094 		    (strcmp (existing, location) == 0)) {
1095 			entry_field = RHYTHMDB_PROP_TITLE;
1096 			rb_debug ("setting title of iradio station to %s", str);
1097 			set_field = TRUE;
1098 		} else {
1099 			rb_debug ("iradio station already has title: %s; ignoring %s", existing, str);
1100 		}
1101 		break;
1102 	}
1103 	case RB_METADATA_FIELD_BITRATE:
1104 		if (!rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_BITRATE)) {
1105 			gulong bitrate;
1106 
1107 			/* GStreamer sends us bitrate in bps, but we need it in kbps*/
1108 			bitrate = g_value_get_ulong (value);
1109 			g_value_set_ulong (value, bitrate/1000);
1110 
1111 			rb_debug ("setting bitrate of iradio station to %lu",
1112 				  g_value_get_ulong (value));
1113 			entry_field = RHYTHMDB_PROP_BITRATE;
1114 			set_field = TRUE;
1115 		}
1116 		break;
1117 	default:
1118 		break;
1119 	}
1120 
1121 	if (set_field && entry_field != 0) {
1122 		rhythmdb_entry_set (source->priv->db, entry, entry_field, value);
1123 		rhythmdb_commit (source->priv->db);
1124 	}
1125 
1126 	g_free (str);
1127  out_unlock:
1128 	GDK_THREADS_LEAVE ();
1129 }
1130 
1131 static void
1132 playing_source_changed_cb (RBShellPlayer *player,
1133 			   RBSource *source,
1134 			   RBIRadioSource *iradio_source)
1135 {
1136 	GObject *backend;
1137 
1138 	g_object_get (player, "player", &backend, NULL);
1139 
1140 	if (source == RB_SOURCE (iradio_source) && (iradio_source->priv->info_available_id == 0)) {
1141 		rb_debug ("connecting info-available signal handler");
1142 		iradio_source->priv->info_available_id =
1143 			g_signal_connect_object (backend, "info",
1144 						 G_CALLBACK (info_available_cb),
1145 						 iradio_source, 0);
1146 	} else if (iradio_source->priv->info_available_id) {
1147 		rb_debug ("disconnecting info-available signal handler");
1148 		g_signal_handler_disconnect (backend,
1149 					     iradio_source->priv->info_available_id);
1150 		iradio_source->priv->info_available_id = 0;
1151 	}
1152 
1153 	g_object_unref (backend);
1154 }
1155 
1156 void
1157 _rb_iradio_source_register_type (GTypeModule *module)
1158 {
1159 	rb_iradio_entry_type_register_type (module);
1160 	rb_iradio_source_register_type (module);
1161 }