hythmbox-2.98/plugins/audioscrobbler/rb-audioscrobbler-radio-source.c

No issues found

   1 /*
   2  * rb-audioscrobbler-radio-source.c
   3  *
   4  * Copyright (C) 2010 Jamie Nicol <jamie@thenicols.net>
   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, or (at your option)
   9  * 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 #include "config.h"
  30 #include <string.h>
  31 #include <unistd.h>
  32 #include <libsoup/soup.h>
  33 #include <libsoup/soup-gnome.h>
  34 #include <json-glib/json-glib.h>
  35 #include <glib/gi18n.h>
  36 #include <glib/gstdio.h>
  37 
  38 #ifdef WITH_GNOME_KEYRING
  39 #include <gnome-keyring.h>
  40 #endif
  41 
  42 #include <totem-pl-parser.h>
  43 
  44 #include "rb-audioscrobbler-radio-source.h"
  45 #include "rb-audioscrobbler-radio-track-entry-type.h"
  46 #include "rb-audioscrobbler-play-order.h"
  47 #include "rb-debug.h"
  48 #include "rb-display-page-tree.h"
  49 #include "rb-util.h"
  50 #include "rb-file-helpers.h"
  51 #include "rb-source-toolbar.h"
  52 #include "rb-ext-db.h"
  53 
  54 
  55 /* radio type stuff */
  56 static const char* radio_types[] = {
  57 	/* Translators: describes a radio stream playing tracks similar to those by an artist.
  58 	 * Followed by a text entry box for the artist name.
  59 	 */
  60 	N_("Similar to Artist:"),
  61 	/* Translators: describes a radio stream playing tracks listened to by the top fans of
  62 	 * a particular artist.  Followed by a text entry box for the artist name.
  63 	 */
  64 	N_("Top Fans of Artist:"),
  65 	/* Translators: describes a radio stream playing tracks from the library of a particular
  66 	 * user.  Followed by a text entry box for the user name.
  67 	 */
  68 	N_("Library of User:"),
  69 	/* Translators: describes a radio stream playing tracks played by users similar to a
  70 	 * particular user.  Followed by a text entry box for the user name.
  71 	 */
  72 	N_("Neighbourhood of User:"),
  73 	/* Translators: describes a radio stream playing tracks that a particular user has marked
  74 	 * as loved.  Followed by a text entry box for the user name.
  75 	 */
  76 	N_("Tracks Loved by User:"),
  77 	/* Translators: describes a radio stream playing tracks recommended to a particular user.
  78 	 * Followed by a text entry box for the user name.
  79 	 */
  80 	N_("Recommendations for User:"),
  81 	/* Translators: a type of station named "Mix Radio" by Last.fm.
  82 	 * See http://blog.last.fm/2010/10/29/mix-radio-a-new-radio-station for a description of it.
  83 	 * Followed by a text entry box for the user name.
  84 	 */
  85 	N_("Mix Radio for User:"),
  86 	/* Translators: describes a radio stream playing tracks tagged with a particular tag.
  87 	 * Followed by a text entry box for the tag.
  88 	 */
  89 	N_("Tracks Tagged with:"),
  90 	/* Translators: describes a radio stream playing tracks often listened to by members of
  91 	 * a particular group. Followed by a text entry box for the group name.
  92 	 */
  93 	N_("Listened by Group:"),
  94 	NULL
  95 };
  96 
  97 const char *
  98 rb_audioscrobbler_radio_type_get_text (RBAudioscrobblerRadioType type)
  99 {
 100 	return _(radio_types[type]);
 101 }
 102 
 103 static const char* radio_urls[] = {
 104 	"lastfm://artist/%s/similarartists",
 105 	"lastfm://artist/%s/fans",
 106 	"lastfm://user/%s/library",
 107 	"lastfm://user/%s/neighbours",
 108 	"lastfm://user/%s/loved",
 109 	"lastfm://user/%s/recommended",
 110 	"lastfm://user/%s/mix",
 111 	"lastfm://globaltags/%s",
 112 	"lastfm://group/%s",
 113 	NULL
 114 };
 115 
 116 const char *
 117 rb_audioscrobbler_radio_type_get_url (RBAudioscrobblerRadioType type)
 118 {
 119 	return radio_urls[type];
 120 }
 121 
 122 /* Translators: I have chosen these names for the radio stations based upon
 123  * what last.fm's website uses or what I thought to be sensible.
 124  */
 125 static const char* radio_names[] = {
 126 
 127 	/* Translators: station is built from artists similar to the artist %s */
 128 	N_("%s Radio"),
 129 	/* Translators: station is built from the artist %s's top fans */
 130 	N_("%s Fan Radio"),
 131 	/* Translators: station is built from the library of the user %s */
 132 	N_("%s's Library"),
 133 	/* Translators: station is built from the "neighbourhood" of the user %s.
 134 	 * Last.fm uses "neighbourhood" to mean other users with similar music tastes */
 135 	N_("%s's Neighbourhood"),
 136 	/* Translators: station is built from the tracks which have been "loved" by the user %s */
 137 	N_("%s's Loved Tracks"),
 138 	/* Translators: station is built from the tracks which are recommended to the user %s */
 139 	N_("%s's Recommended Radio"),
 140 	/* Translators: station is the "Mix Radio" for the user %s.
 141 	 * See http://blog.last.fm/2010/10/29/mix-radio-a-new-radio-station for description. */
 142 	N_("%s's Mix Radio"),
 143 	/* Translators: station is built from the tracks which have been "tagged" with %s.
 144 	 * Last.fm lets users "tag" songs with any string they wish. Tags are usually genres,
 145 	 * but nationalities, record labels, decades and very random words are also commmon */
 146 	N_("%s Tag Radio"),
 147 	/* Translators: station is built from the library of the group %s */
 148 	N_("%s Group Radio"),
 149 	NULL
 150 };
 151 
 152 const char *
 153 rb_audioscrobbler_radio_type_get_default_name (RBAudioscrobblerRadioType type)
 154 {
 155 	return _(radio_names[type]);
 156 }
 157 
 158 /* source declarations */
 159 struct _RBAudioscrobblerRadioSourcePrivate
 160 {
 161 	RBAudioscrobblerProfilePage *parent;
 162 
 163 	RBAudioscrobblerService *service;
 164 	char *username;
 165 	char *session_key;
 166 	char *station_url;
 167 
 168 	SoupSession *soup_session;
 169 
 170 	GtkWidget *error_info_bar;
 171 	GtkWidget *error_info_bar_label;
 172 
 173 	GtkWidget *password_info_bar;
 174 	GtkWidget *password_info_bar_entry;
 175 
 176 	RBEntryView *track_view;
 177 	RhythmDBQueryModel *track_model;
 178 
 179 	gboolean is_busy;
 180 
 181 	RBPlayOrder *play_order;
 182 
 183 	/* the currently playing entry from this source, if there is one */
 184 	RhythmDBEntry *playing_entry;
 185 
 186 	RBExtDB *art_store;
 187 
 188 	guint ui_merge_id;
 189 	GtkActionGroup *action_group;
 190 
 191 	/* used when streaming radio using old api */
 192 	char *old_api_password;
 193 	char *old_api_session_id;
 194 	char *old_api_base_url;
 195 	char *old_api_base_path;
 196 	gboolean old_api_is_banned;
 197 };
 198 
 199 #define RB_AUDIOSCROBBLER_RADIO_SOURCE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_AUDIOSCROBBLER_RADIO_SOURCE, RBAudioscrobblerRadioSourcePrivate))
 200 
 201 static void rb_audioscrobbler_radio_source_constructed (GObject *object);
 202 static void rb_audioscrobbler_radio_source_dispose (GObject *object);
 203 static void rb_audioscrobbler_radio_source_finalize (GObject *object);
 204 static void rb_audioscrobbler_radio_source_get_property (GObject *object,
 205                                                          guint prop_id,
 206                                                          GValue *value,
 207                                                          GParamSpec *pspec);
 208 static void rb_audioscrobbler_radio_source_set_property (GObject *object,
 209                                                          guint prop_id,
 210                                                          const GValue *value,
 211                                                          GParamSpec *pspec);
 212 
 213 static void playing_song_changed_cb (RBShellPlayer *player,
 214                                      RhythmDBEntry *entry,
 215                                      RBAudioscrobblerRadioSource *source);
 216 
 217 /* last.fm api requests */
 218 static void tune (RBAudioscrobblerRadioSource *source);
 219 static void tune_response_cb (SoupSession *session,
 220                               SoupMessage *msg,
 221                               gpointer user_data);
 222 static void fetch_playlist (RBAudioscrobblerRadioSource *source);
 223 static void fetch_playlist_response_cb (SoupSession *session,
 224                                         SoupMessage *msg,
 225                                         gpointer user_data);
 226 static void xspf_entry_parsed (TotemPlParser *parser,
 227                                const char *uri,
 228                                GHashTable *metadata,
 229                                RBAudioscrobblerRadioSource *source);
 230 
 231 /* old api */
 232 static void old_api_shake_hands (RBAudioscrobblerRadioSource *source);
 233 static void old_api_handshake_response_cb (SoupSession *session,
 234                                            SoupMessage *msg,
 235                                            gpointer user_data);
 236 static void old_api_tune (RBAudioscrobblerRadioSource *source);
 237 static void old_api_tune_response_cb (SoupSession *session,
 238                                       SoupMessage *msg,
 239                                       gpointer user_data);
 240 static void old_api_fetch_playlist (RBAudioscrobblerRadioSource *source);
 241 
 242 /* info bar related things */
 243 static void display_error_info_bar (RBAudioscrobblerRadioSource *source,
 244                                     const char *message);
 245 static void display_password_info_bar (RBAudioscrobblerRadioSource *source);
 246 static void password_info_bar_response_cb (GtkInfoBar *info_bar,
 247                                            int response_id,
 248                                            RBAudioscrobblerRadioSource *source);
 249 
 250 /* action callbacks */
 251 static void rename_station_action_cb (GtkAction *action,
 252                                       RBAudioscrobblerRadioSource *source);
 253 static void delete_station_action_cb (GtkAction *action,
 254                                       RBAudioscrobblerRadioSource *source);
 255 
 256 
 257 /* RBDisplayPage implementations */
 258 static void impl_selected (RBDisplayPage *page);
 259 static void impl_get_status (RBDisplayPage *page, char **text, char **progress_text, float *progress);
 260 static gboolean impl_show_popup (RBDisplayPage *page);
 261 static void impl_delete_thyself (RBDisplayPage *page);
 262 
 263 /* RBSource implementations */
 264 static RBEntryView *impl_get_entry_view (RBSource *asource);
 265 static RBSourceEOFType impl_handle_eos (RBSource *asource);
 266 
 267 enum {
 268 	PROP_0,
 269 	PROP_PARENT,
 270 	PROP_SERVICE,
 271 	PROP_USERNAME,
 272 	PROP_SESSION_KEY,
 273 	PROP_STATION_URL,
 274 	PROP_PLAY_ORDER
 275 };
 276 
 277 #define AUDIOSCROBBLER_RADIO_SOURCE_POPUP_PATH "/AudioscrobblerRadioSourcePopup"
 278 
 279 static GtkActionEntry rb_audioscrobbler_radio_source_actions [] =
 280 {
 281 	{ "AudioscrobblerRadioRenameStation", NULL, N_("_Rename Station"), NULL,
 282 	  N_("Rename station"),
 283 	  G_CALLBACK (rename_station_action_cb) },
 284 	{ "AudioscrobblerRadioDeleteStation", GTK_STOCK_DELETE, N_("_Delete Station"), NULL,
 285 	  N_("Delete station"),
 286 	  G_CALLBACK (delete_station_action_cb) }
 287 };
 288 
 289 G_DEFINE_DYNAMIC_TYPE (RBAudioscrobblerRadioSource, rb_audioscrobbler_radio_source, RB_TYPE_STREAMING_SOURCE)
 290 
 291 RBSource *
 292 rb_audioscrobbler_radio_source_new (RBAudioscrobblerProfilePage *parent,
 293                                     RBAudioscrobblerService *service,
 294                                     const char *username,
 295                                     const char *session_key,
 296                                     const char *station_name,
 297                                     const char *station_url)
 298 {
 299 	RBSource *source;
 300 	RBShell *shell;
 301 	GObject *plugin;
 302 	RhythmDB *db;
 303 	char *toolbar_path;
 304 
 305 	g_object_get (parent, "shell", &shell, "plugin", &plugin, NULL);
 306 	g_object_get (shell, "db", &db, NULL);
 307 
 308 	if (RHYTHMDB_ENTRY_TYPE_AUDIOSCROBBLER_RADIO_TRACK == NULL) {
 309 		rb_audioscrobbler_radio_track_register_entry_type (db);
 310 	}
 311 
 312 	g_object_get (parent, "toolbar-path", &toolbar_path, NULL);
 313 
 314 	source = g_object_new (RB_TYPE_AUDIOSCROBBLER_RADIO_SOURCE,
 315 	                       "shell", shell,
 316 	                       "plugin", plugin,
 317 	                       "name", station_name,
 318 	                       "entry-type", RHYTHMDB_ENTRY_TYPE_AUDIOSCROBBLER_RADIO_TRACK,
 319 	                       "parent", parent,
 320 	                       "service", service,
 321                                "username", username,
 322 	                       "session-key", session_key,
 323 	                       "station-url", station_url,
 324 			       "toolbar-path", toolbar_path,
 325 	                       NULL);
 326 
 327 	g_object_unref (shell);
 328 	g_object_unref (plugin);
 329 	g_object_unref (db);
 330 	g_free (toolbar_path);
 331 
 332 	return source;
 333 }
 334 
 335 static void
 336 rb_audioscrobbler_radio_source_class_init (RBAudioscrobblerRadioSourceClass *klass)
 337 {
 338 	GObjectClass *object_class;
 339 	RBDisplayPageClass *page_class;
 340 	RBSourceClass *source_class;
 341 
 342 	object_class = G_OBJECT_CLASS (klass);
 343 	object_class->constructed = rb_audioscrobbler_radio_source_constructed;
 344 	object_class->dispose = rb_audioscrobbler_radio_source_dispose;
 345 	object_class->finalize = rb_audioscrobbler_radio_source_finalize;
 346 	object_class->get_property = rb_audioscrobbler_radio_source_get_property;
 347 	object_class->set_property = rb_audioscrobbler_radio_source_set_property;
 348 
 349 	page_class = RB_DISPLAY_PAGE_CLASS (klass);
 350 	page_class->selected = impl_selected;
 351 	page_class->get_status = impl_get_status;
 352 	page_class->show_popup = impl_show_popup;
 353 	page_class->delete_thyself = impl_delete_thyself;
 354 
 355 	source_class = RB_SOURCE_CLASS (klass);
 356 	source_class->impl_can_rename = (RBSourceFeatureFunc) rb_true_function;
 357 	source_class->impl_can_copy = (RBSourceFeatureFunc) rb_false_function;
 358 	source_class->impl_can_delete = (RBSourceFeatureFunc) rb_false_function;
 359 	source_class->impl_can_pause = (RBSourceFeatureFunc) rb_false_function;
 360 	source_class->impl_try_playlist = (RBSourceFeatureFunc) rb_false_function;
 361 	source_class->impl_get_entry_view = impl_get_entry_view;
 362 	source_class->impl_handle_eos = impl_handle_eos;
 363 
 364 	g_object_class_install_property (object_class,
 365 	                                 PROP_PARENT,
 366 	                                 g_param_spec_object ("parent",
 367 	                                                      "Parent",
 368 	                                                      "Profile page that created this radio source",
 369 	                                                      RB_TYPE_AUDIOSCROBBLER_PROFILE_PAGE,
 370                                                               G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
 371 
 372 	g_object_class_install_property (object_class,
 373 	                                 PROP_SERVICE,
 374 	                                 g_param_spec_object ("service",
 375 	                                                      "Service",
 376 	                                                      "Service to stream radio from",
 377 	                                                      RB_TYPE_AUDIOSCROBBLER_SERVICE,
 378                                                               G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
 379 
 380 	g_object_class_install_property (object_class,
 381 	                                 PROP_USERNAME,
 382 	                                 g_param_spec_string ("username",
 383 	                                                      "Username",
 384 	                                                      "Username of the user who is streaming radio",
 385 	                                                      NULL,
 386                                                               G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
 387 
 388 	g_object_class_install_property (object_class,
 389 	                                 PROP_SESSION_KEY,
 390 	                                 g_param_spec_string ("session-key",
 391 	                                                      "Session Key",
 392 	                                                      "Session key used to authenticate the user",
 393 	                                                      NULL,
 394                                                               G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
 395 
 396 	g_object_class_install_property (object_class,
 397 	                                 PROP_STATION_URL,
 398 	                                 g_param_spec_string ("station-url",
 399 	                                                      "Station URL",
 400 	                                                      "Last.fm radio URL of the station this source will stream",
 401 	                                                      NULL,
 402                                                               G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 403 
 404 	g_object_class_override_property (object_class,
 405 					  PROP_PLAY_ORDER,
 406 					  "play-order");
 407 
 408 	g_type_class_add_private (klass, sizeof (RBAudioscrobblerRadioSourcePrivate));
 409 }
 410 
 411 static void
 412 rb_audioscrobbler_radio_source_class_finalize (RBAudioscrobblerRadioSourceClass *klass)
 413 {
 414 }
 415 
 416 static void
 417 rb_audioscrobbler_radio_source_init (RBAudioscrobblerRadioSource *source)
 418 {
 419 	source->priv = RB_AUDIOSCROBBLER_RADIO_SOURCE_GET_PRIVATE (source);
 420 
 421 	source->priv->soup_session =
 422 		soup_session_async_new_with_options (SOUP_SESSION_ADD_FEATURE_BY_TYPE,
 423 		                                     SOUP_TYPE_GNOME_FEATURES_2_26,
 424 		                                     NULL);
 425 }
 426 
 427 static void
 428 rb_audioscrobbler_radio_source_constructed (GObject *object)
 429 {
 430 	RBAudioscrobblerRadioSource *source;
 431 	RBShell *shell;
 432 	RBShellPlayer *shell_player;
 433 	RhythmDB *db;
 434 	GtkWidget *main_vbox;
 435 	GtkWidget *error_info_bar_content_area;
 436 	GtkWidget *password_info_bar_label;
 437 	GtkWidget *password_info_bar_content_area;
 438 	GObject *plugin;
 439 	GtkUIManager *ui_manager;
 440 	RBSourceToolbar *toolbar;
 441 	char *ui_file;
 442 
 443 	RB_CHAIN_GOBJECT_METHOD (rb_audioscrobbler_radio_source_parent_class, constructed, object);
 444 
 445 	source = RB_AUDIOSCROBBLER_RADIO_SOURCE (object);
 446 	g_object_get (source, "shell", &shell, NULL);
 447 	g_object_get (shell,
 448 		      "db", &db,
 449 		      "shell-player", &shell_player,
 450 		      "ui-manager", &ui_manager,
 451 		      NULL);
 452 
 453 	source->priv->art_store = rb_ext_db_new ("album-art");
 454 
 455 	main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 4);
 456 	gtk_widget_show (main_vbox);
 457 	gtk_container_add (GTK_CONTAINER (source), main_vbox);
 458 
 459 	/* toolbar */
 460 	toolbar = rb_source_toolbar_new (RB_DISPLAY_PAGE (source), ui_manager);
 461 	gtk_box_pack_start (GTK_BOX (main_vbox), GTK_WIDGET (toolbar), FALSE, FALSE, 0);
 462 	gtk_widget_show_all (GTK_WIDGET (toolbar));
 463 
 464 	/* error info bar */
 465 	source->priv->error_info_bar = gtk_info_bar_new ();
 466 	source->priv->error_info_bar_label = gtk_label_new ("");
 467 	error_info_bar_content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (source->priv->error_info_bar));
 468 	gtk_container_add (GTK_CONTAINER (error_info_bar_content_area), source->priv->error_info_bar_label);
 469 	gtk_box_pack_start (GTK_BOX (main_vbox), source->priv->error_info_bar, FALSE, FALSE, 0);
 470 
 471 	/* password info bar */
 472 	source->priv->password_info_bar = gtk_info_bar_new ();
 473 	password_info_bar_label = gtk_label_new (_("You must enter your password to listen to this station"));
 474 	password_info_bar_content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (source->priv->password_info_bar));
 475 	gtk_container_add (GTK_CONTAINER (password_info_bar_content_area), password_info_bar_label);
 476 	source->priv->password_info_bar_entry = gtk_entry_new ();
 477 	gtk_entry_set_visibility (GTK_ENTRY (source->priv->password_info_bar_entry), FALSE);
 478 	gtk_info_bar_add_action_widget (GTK_INFO_BAR (source->priv->password_info_bar),
 479 	                                source->priv->password_info_bar_entry,
 480 	                                GTK_RESPONSE_NONE);
 481 	gtk_info_bar_add_button (GTK_INFO_BAR (source->priv->password_info_bar), GTK_STOCK_OK, GTK_RESPONSE_OK);
 482 	g_signal_connect (source->priv->password_info_bar,
 483 	                  "response",
 484 	                  G_CALLBACK (password_info_bar_response_cb),
 485 	                  source);
 486 	gtk_box_pack_start (GTK_BOX (main_vbox), source->priv->password_info_bar, FALSE, FALSE, 0);
 487 
 488 	/* entry view */
 489 	source->priv->track_view = rb_entry_view_new (db, G_OBJECT (shell_player), FALSE, FALSE);
 490 	rb_entry_view_append_column (source->priv->track_view, RB_ENTRY_VIEW_COL_TITLE, TRUE);
 491 	rb_entry_view_append_column (source->priv->track_view, RB_ENTRY_VIEW_COL_ARTIST, FALSE);
 492 	rb_entry_view_append_column (source->priv->track_view, RB_ENTRY_VIEW_COL_ALBUM, FALSE);
 493 	rb_entry_view_append_column (source->priv->track_view, RB_ENTRY_VIEW_COL_DURATION, FALSE);
 494 	rb_entry_view_set_columns_clickable (source->priv->track_view, FALSE);
 495 	gtk_widget_show_all (GTK_WIDGET (source->priv->track_view));
 496 
 497 	gtk_box_pack_start (GTK_BOX (main_vbox), GTK_WIDGET (source->priv->track_view), TRUE, TRUE, 0);
 498 
 499 	rb_source_bind_settings (RB_SOURCE (source), GTK_WIDGET (source->priv->track_view), NULL, NULL);
 500 
 501 	/* query model */
 502 	source->priv->track_model = rhythmdb_query_model_new_empty (db);
 503 	rb_entry_view_set_model (source->priv->track_view, source->priv->track_model);
 504 	g_object_set (source, "query-model", source->priv->track_model, NULL);
 505 
 506 	/* play order */
 507 	source->priv->play_order = rb_audioscrobbler_play_order_new (shell_player);
 508 
 509 	/* signals */
 510 	g_signal_connect_object (shell_player,
 511 				 "playing-song-changed",
 512 				 G_CALLBACK (playing_song_changed_cb),
 513 				 source, 0);
 514 
 515 	/* merge ui */
 516 	g_object_get (source, "plugin", &plugin, NULL);
 517 	ui_file = rb_find_plugin_data_file (plugin, "audioscrobbler-radio-ui.xml");
 518 	source->priv->ui_merge_id = gtk_ui_manager_add_ui_from_file (ui_manager, ui_file, NULL);
 519 
 520 	/* actions */
 521 	source->priv->action_group = _rb_display_page_register_action_group (RB_DISPLAY_PAGE (source),
 522 									     "AudioscrobblerRadioActions",
 523 									     NULL, 0,
 524 									     source);
 525 	_rb_action_group_add_display_page_actions (source->priv->action_group,
 526 						   G_OBJECT (shell),
 527 						   rb_audioscrobbler_radio_source_actions,
 528 						   G_N_ELEMENTS (rb_audioscrobbler_radio_source_actions));
 529 
 530 	rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (source), RB_DISPLAY_PAGE (source->priv->parent));
 531 
 532 	g_object_unref (shell);
 533 	g_object_unref (shell_player);
 534 	g_object_unref (db);
 535 	g_object_unref (plugin);
 536 	g_object_unref (ui_manager);
 537 	g_free (ui_file);
 538 }
 539 
 540 static void
 541 rb_audioscrobbler_radio_source_dispose (GObject *object)
 542 {
 543 	RBAudioscrobblerRadioSource *source = RB_AUDIOSCROBBLER_RADIO_SOURCE (object);
 544 
 545 	if (source->priv->soup_session != NULL) {
 546 		soup_session_abort (source->priv->soup_session);
 547 		g_object_unref (source->priv->soup_session);
 548 		source->priv->soup_session = NULL;
 549 	}
 550 
 551 	if (source->priv->service != NULL) {
 552 		g_object_unref (source->priv->service);
 553 		source->priv->service = NULL;
 554 	}
 555 
 556 	if (source->priv->track_model != NULL) {
 557 		g_object_unref (source->priv->track_model);
 558 		source->priv->track_model = NULL;
 559 	}
 560 
 561 	if (source->priv->play_order != NULL) {
 562 		g_object_unref (source->priv->play_order);
 563 		source->priv->play_order = NULL;
 564 	}
 565 
 566 	if (source->priv->art_store != NULL) {
 567 		g_object_unref (source->priv->art_store);
 568 		source->priv->art_store = NULL;
 569 	}
 570 
 571 	G_OBJECT_CLASS (rb_audioscrobbler_radio_source_parent_class)->dispose (object);
 572 }
 573 
 574 static void
 575 rb_audioscrobbler_radio_source_finalize (GObject *object)
 576 {
 577 	RBAudioscrobblerRadioSource *source = RB_AUDIOSCROBBLER_RADIO_SOURCE (object);
 578 
 579 	g_free (source->priv->username);
 580 	g_free (source->priv->session_key);
 581 	g_free (source->priv->station_url);
 582 
 583 	g_free (source->priv->old_api_password);
 584 	g_free (source->priv->old_api_session_id);
 585 	g_free (source->priv->old_api_base_url);
 586 	g_free (source->priv->old_api_base_path);
 587 
 588 	G_OBJECT_CLASS (rb_audioscrobbler_radio_source_parent_class)->finalize (object);
 589 }
 590 
 591 static void
 592 rb_audioscrobbler_radio_source_get_property (GObject *object,
 593                                              guint prop_id,
 594                                              GValue *value,
 595                                              GParamSpec *pspec)
 596 {
 597 	RBAudioscrobblerRadioSource *source = RB_AUDIOSCROBBLER_RADIO_SOURCE (object);
 598 	switch (prop_id) {
 599 	case PROP_STATION_URL:
 600 		g_value_set_string (value, source->priv->station_url);
 601 		break;
 602 	case PROP_PLAY_ORDER:
 603 		g_value_set_object (value, source->priv->play_order);
 604 		break;
 605 	default:
 606 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 607 		break;
 608 	}
 609 }
 610 
 611 static void
 612 rb_audioscrobbler_radio_source_set_property (GObject *object,
 613                                              guint prop_id,
 614                                              const GValue *value,
 615                                              GParamSpec *pspec)
 616 {
 617 	RBAudioscrobblerRadioSource *source = RB_AUDIOSCROBBLER_RADIO_SOURCE (object);
 618 	switch (prop_id) {
 619 	case PROP_PARENT:
 620 		source->priv->parent = g_value_get_object (value);
 621 		break;
 622 	case PROP_SERVICE:
 623 		source->priv->service = g_value_dup_object (value);
 624 		break;
 625 	case PROP_USERNAME:
 626 		source->priv->username = g_value_dup_string (value);
 627 		break;
 628 	case PROP_SESSION_KEY:
 629 		source->priv->session_key = g_value_dup_string (value);
 630 		break;
 631 	case PROP_STATION_URL:
 632 		source->priv->station_url = g_value_dup_string (value);
 633 		break;
 634 	default:
 635 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 636 		break;
 637 	}
 638 }
 639 
 640 static void
 641 playing_song_changed_cb (RBShellPlayer *player,
 642                          RhythmDBEntry *entry,
 643                          RBAudioscrobblerRadioSource *source)
 644 {
 645 	RhythmDB *db;
 646 	GtkTreeIter playing_iter;
 647 
 648 	g_object_get (player, "db", &db, NULL);
 649 
 650 	/* delete old entry */
 651 	if (source->priv->playing_entry != NULL) {
 652 		rhythmdb_query_model_remove_entry (source->priv->track_model, source->priv->playing_entry);
 653 		rhythmdb_entry_delete (db, source->priv->playing_entry);
 654 		source->priv->playing_entry = NULL;
 655 	}
 656 
 657 	/* check if the new playing entry is from this source */
 658 	if (rhythmdb_query_model_entry_to_iter (source->priv->track_model, entry, &playing_iter) == TRUE) {
 659 		RBAudioscrobblerRadioTrackData *track_data;
 660 		RBExtDBKey *key;
 661 		GtkTreeIter iter;
 662 		gboolean reached_playing = FALSE;
 663 		int entries_after_playing = 0;
 664 		GList *remove = NULL;
 665 		GList *i;
 666 
 667 		/* update our playing entry */
 668 		source->priv->playing_entry = entry;
 669 
 670 		/* mark invalidated entries for removal and count remaining */
 671 		gtk_tree_model_get_iter_first (GTK_TREE_MODEL (source->priv->track_model), &iter);
 672 		do {
 673 			RhythmDBEntry *iter_entry;
 674 			iter_entry = rhythmdb_query_model_iter_to_entry (source->priv->track_model, &iter);
 675 
 676 			if (reached_playing == TRUE) {
 677 				entries_after_playing++;
 678 			} else if (iter_entry == entry) {
 679 				reached_playing = TRUE;
 680 			} else {
 681 				/* add to list of entries marked for removal */
 682 				remove = g_list_append (remove, iter_entry);
 683 			}
 684 
 685 			rhythmdb_entry_unref (iter_entry);
 686 
 687 		} while (gtk_tree_model_iter_next (GTK_TREE_MODEL (source->priv->track_model), &iter));
 688 
 689 		/* remove invalidated entries */
 690 		for (i = remove; i != NULL; i = i->next) {
 691 			rhythmdb_query_model_remove_entry (source->priv->track_model, i->data);
 692 			rhythmdb_entry_delete (db, i->data);
 693 		}
 694 
 695 		/* request more if needed */
 696 		if (entries_after_playing <= 2) {
 697 			tune (source);
 698 		}
 699 
 700 		/* provide cover art */
 701 		key = rb_ext_db_key_create_storage ("album", rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ALBUM));
 702 		rb_ext_db_key_add_field (key, "artist", rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ARTIST));
 703 		track_data = RHYTHMDB_ENTRY_GET_TYPE_DATA(entry, RBAudioscrobblerRadioTrackData);
 704 		rb_ext_db_store_uri (source->priv->art_store,
 705 				     key,
 706 				     RB_EXT_DB_SOURCE_SEARCH,
 707 				     track_data->image_url);
 708 		rb_ext_db_key_free (key);
 709 	}
 710 
 711 	rhythmdb_commit (db);
 712 
 713 	g_object_unref (db);
 714 }
 715 
 716 static void
 717 tune (RBAudioscrobblerRadioSource *source)
 718 {
 719 	char *sig_arg;
 720 	char *sig;
 721 	char *escaped_station_url;
 722 	char *request;
 723 	char *msg_url;
 724 	SoupMessage *msg;
 725 
 726 	/* only go through the tune + get playlist process once at a time */
 727 	if (source->priv->is_busy == TRUE) {
 728 		return;
 729 	}
 730 
 731 	source->priv->is_busy = TRUE;
 732 	gtk_widget_hide (source->priv->error_info_bar);
 733 	gtk_widget_hide (source->priv->password_info_bar);
 734 
 735 	sig_arg = g_strdup_printf ("api_key%smethodradio.tunesk%sstation%s%s",
 736 	                           rb_audioscrobbler_service_get_api_key (source->priv->service),
 737 	                           source->priv->session_key,
 738 	                           source->priv->station_url,
 739 	                           rb_audioscrobbler_service_get_api_secret (source->priv->service));
 740 
 741 	sig = g_compute_checksum_for_string (G_CHECKSUM_MD5, sig_arg, -1);
 742 
 743 	escaped_station_url = g_uri_escape_string (source->priv->station_url, NULL, FALSE);
 744 
 745 	request = g_strdup_printf ("method=radio.tune&station=%s&api_key=%s&api_sig=%s&sk=%s",
 746 	                           escaped_station_url,
 747 	                           rb_audioscrobbler_service_get_api_key (source->priv->service),
 748 	                           sig,
 749 	                           source->priv->session_key);
 750 
 751 	/* The format parameter needs to go here instead of in the request body */
 752 	msg_url = g_strdup_printf ("%s?format=json",
 753 	                           rb_audioscrobbler_service_get_api_url (source->priv->service));
 754 
 755 	rb_debug ("sending tune request: %s", request);
 756 	msg = soup_message_new ("POST", msg_url);
 757 	soup_message_set_request (msg,
 758 	                          "application/x-www-form-urlencoded",
 759 	                          SOUP_MEMORY_COPY,
 760 	                          request,
 761 	                          strlen (request));
 762 	soup_session_queue_message (source->priv->soup_session,
 763 	                            msg,
 764 	                            tune_response_cb,
 765 	                            source);
 766 
 767 	g_free (escaped_station_url);
 768 	g_free (sig_arg);
 769 	g_free (sig);
 770 	g_free (request);
 771 	g_free (msg_url);
 772 }
 773 
 774 static void
 775 tune_response_cb (SoupSession *session,
 776                   SoupMessage *msg,
 777                   gpointer user_data)
 778 {
 779 	RBAudioscrobblerRadioSource *source;
 780 	JsonParser *parser;
 781 
 782 	source = RB_AUDIOSCROBBLER_RADIO_SOURCE (user_data);
 783 	parser = json_parser_new ();
 784 
 785 	if (msg->response_body->data == NULL) {
 786 		rb_debug ("no response from tune request");
 787 		display_error_info_bar (source, _("Error tuning station: no response"));
 788 		source->priv->is_busy = FALSE;
 789 
 790 	} else if (json_parser_load_from_data (parser, msg->response_body->data, msg->response_body->length, NULL)) {
 791 		JsonObject *root_object;
 792 		root_object = json_node_get_object (json_parser_get_root (parser));
 793 
 794 		/* Noticed on 2010-08-12 that Last.fm now responds with a "{ status:ok }"
 795 		 * instead of providing a "station" object with various properties.
 796 		 * Checking for a "station" or "status" member ensures compatibility with
 797 		 * both Last.fm and Libre.fm.
 798 		 */
 799 		if (json_object_has_member (root_object, "station") ||
 800 		    json_object_has_member (root_object, "status")) {
 801 			rb_debug ("tune request was successful");
 802 
 803 			/* get the playlist */
 804 			fetch_playlist (source);
 805 		} else if (json_object_has_member (root_object, "error")) {
 806 			int code;
 807 			const char *message;
 808 
 809 			code = json_object_get_int_member (root_object, "error");
 810 			message = json_object_get_string_member (root_object, "message");
 811 
 812 			rb_debug ("tune request responded with error: %s", message);
 813 
 814 			if (code == 4) {
 815 				/* Our API key only allows streaming of radio to subscribers */
 816 				rb_debug ("attempting to use old API to tune radio");
 817 				old_api_tune (source);
 818 			} else {
 819 				/* show appropriate error message */
 820 				char *error_message = NULL;
 821 
 822 				if (code == 6) {
 823 					/* Invalid station url */
 824 					error_message = g_strdup (_("Invalid station URL"));
 825 				} else if (code == 12) {
 826 					/* Subscriber only station */
 827 					/* Translators: %s is the name of the audioscrobbler service, for example "Last.fm".
 828 					 * This message indicates that to listen to this radio station the user needs to be
 829 					 * a paying subscriber to the service. */
 830 					error_message = g_strdup_printf (_("This station is only available to %s subscribers"),
 831 							                 rb_audioscrobbler_service_get_name (source->priv->service));
 832 				} else if (code == 20) {
 833 					/* Not enough content */
 834 					error_message = g_strdup (_("Not enough content to play station"));
 835 				} else if (code == 27) {
 836 					/* Deprecated station */
 837 					/* Translators: %s is the name of the audioscrobbler service, for example "Last.fm".
 838 					 * This message indicates that the service has deprecated this type of station. */
 839 					error_message = g_strdup_printf (_("%s no longer supports this type of station"),
 840 							                 rb_audioscrobbler_service_get_name (source->priv->service));
 841 				} else {
 842 					/* Other error */
 843 					error_message = g_strdup_printf (_("Error tuning station: %i - %s"), code, message);
 844 				}
 845 
 846 				display_error_info_bar (source, error_message);
 847 
 848 				g_free (error_message);
 849 
 850 				source->priv->is_busy = FALSE;
 851 			}
 852 		} else {
 853 			rb_debug ("unexpected response from tune request: %s", msg->response_body->data);
 854 			display_error_info_bar(source, _("Error tuning station: unexpected response"));
 855 			source->priv->is_busy = FALSE;
 856 		}
 857 	} else {
 858 		rb_debug ("invalid response from tune request: %s", msg->response_body->data);
 859 		display_error_info_bar(source, _("Error tuning station: invalid response"));
 860 		source->priv->is_busy = FALSE;
 861 	}
 862 }
 863 
 864 static void
 865 fetch_playlist (RBAudioscrobblerRadioSource *source)
 866 {
 867 	char *sig_arg;
 868 	char *sig;
 869 	char *request;
 870 	SoupMessage *msg;
 871 
 872 	sig_arg = g_strdup_printf ("api_key%smethodradio.getPlaylistrawtruesk%s%s",
 873 	                           rb_audioscrobbler_service_get_api_key (source->priv->service),
 874 	                           source->priv->session_key,
 875 	                           rb_audioscrobbler_service_get_api_secret (source->priv->service));
 876 
 877 	sig = g_compute_checksum_for_string (G_CHECKSUM_MD5, sig_arg, -1);
 878 
 879 	request = g_strdup_printf ("method=radio.getPlaylist&api_key=%s&api_sig=%s&sk=%s&raw=true",
 880 	                           rb_audioscrobbler_service_get_api_key (source->priv->service),
 881 	                           sig,
 882 	                           source->priv->session_key);
 883 
 884 	rb_debug ("sending playlist request: %s", request);
 885 	msg = soup_message_new ("POST", rb_audioscrobbler_service_get_api_url (source->priv->service));
 886 	soup_message_set_request (msg,
 887 	                          "application/x-www-form-urlencoded",
 888 	                          SOUP_MEMORY_COPY,
 889 	                          request,
 890 	                          strlen (request));
 891 	soup_session_queue_message (source->priv->soup_session,
 892 	                            msg,
 893 	                            fetch_playlist_response_cb,
 894 	                            source);
 895 
 896 	g_free (sig_arg);
 897 	g_free (sig);
 898 	g_free (request);
 899 }
 900 
 901 static void
 902 fetch_playlist_response_cb (SoupSession *session,
 903                             SoupMessage *msg,
 904                             gpointer user_data)
 905 {
 906 	RBAudioscrobblerRadioSource *source;
 907 	int tmp_fd;
 908 	char *tmp_name;
 909 	char *tmp_uri = NULL;
 910 	GIOChannel *channel = NULL;
 911 	TotemPlParser *parser = NULL;
 912 	TotemPlParserResult result;
 913 	GError *error = NULL;
 914 
 915 	source = RB_AUDIOSCROBBLER_RADIO_SOURCE (user_data);
 916 
 917 	source->priv->is_busy = FALSE;
 918 
 919 	if (msg->response_body->data == NULL) {
 920 		rb_debug ("no response from get playlist request");
 921 		return;
 922 	}
 923 
 924 	/* until totem-pl-parser can parse playlists from in-memory data, we save it to a
 925 	 * temporary file.
 926 	 */
 927 
 928 	tmp_fd = g_file_open_tmp ("rb-audioscrobbler-playlist-XXXXXX.xspf", &tmp_name, &error);
 929 	if (error != NULL) {
 930 		rb_debug ("unable to save playlist: %s", error->message);
 931 		goto cleanup;
 932 	}
 933 
 934 	channel = g_io_channel_unix_new (tmp_fd);
 935 	g_io_channel_write_chars (channel, msg->response_body->data, msg->response_body->length, NULL, &error);
 936 	if (error != NULL) {
 937 		rb_debug ("unable to save playlist: %s", error->message);
 938 		goto cleanup;
 939 	}
 940 	g_io_channel_flush (channel, NULL);		/* ignore errors.. */
 941 
 942 	tmp_uri = g_filename_to_uri (tmp_name, NULL, &error);
 943 	if (error != NULL) {
 944 		rb_debug ("unable to parse playlist: %s", error->message);
 945 		goto cleanup;
 946 	}
 947 
 948 	rb_debug ("parsing playlist %s", tmp_uri);
 949 
 950 	parser = totem_pl_parser_new ();
 951 	g_signal_connect_data (parser, "entry-parsed",
 952 	                       G_CALLBACK (xspf_entry_parsed),
 953 	                       source, NULL, 0);
 954 	result = totem_pl_parser_parse (parser, tmp_uri, FALSE);
 955 
 956 	switch (result) {
 957 	default:
 958 	case TOTEM_PL_PARSER_RESULT_UNHANDLED:
 959 	case TOTEM_PL_PARSER_RESULT_IGNORED:
 960 	case TOTEM_PL_PARSER_RESULT_ERROR:
 961 		rb_debug ("playlist didn't parse");
 962 		break;
 963 
 964 	case TOTEM_PL_PARSER_RESULT_SUCCESS:
 965 		rb_debug ("playlist parsed successfully");
 966 		break;
 967 	}
 968 
 969  cleanup:
 970 	if (channel != NULL) {
 971 		g_io_channel_unref (channel);
 972 	}
 973 	if (parser != NULL) {
 974 		g_object_unref (parser);
 975 	}
 976 	if (error != NULL) {
 977 		g_error_free (error);
 978 	}
 979 	close (tmp_fd);
 980 	g_unlink (tmp_name);
 981 	g_free (tmp_name);
 982 	g_free (tmp_uri);
 983 }
 984 
 985 static void
 986 xspf_entry_parsed (TotemPlParser *parser,
 987                    const char *uri,
 988                    GHashTable *metadata,
 989                    RBAudioscrobblerRadioSource *source)
 990 {
 991 	RBShell *shell;
 992 	RhythmDBEntryType *entry_type;
 993 	RhythmDB *db;
 994 
 995 	RhythmDBEntry *entry;
 996 	RBAudioscrobblerRadioTrackData *track_data;
 997 	const char *value;
 998 	GValue v = {0,};
 999 	int i;
1000 	struct {
1001 		const char *field;
1002 		RhythmDBPropType prop;
1003 	} field_mapping[] = {
1004 		{ TOTEM_PL_PARSER_FIELD_TITLE, RHYTHMDB_PROP_TITLE },
1005 		{ TOTEM_PL_PARSER_FIELD_AUTHOR, RHYTHMDB_PROP_ARTIST },
1006 		{ TOTEM_PL_PARSER_FIELD_ALBUM, RHYTHMDB_PROP_ALBUM },
1007 	};
1008 
1009 	g_object_get (source, "shell", &shell, "entry-type", &entry_type, NULL);
1010 	g_object_get (shell, "db", &db, NULL);
1011 
1012 	/* create db entry if it doesn't already exist */
1013 	entry = rhythmdb_entry_lookup_by_location (db, uri);
1014 	if (entry == NULL) {
1015 		rb_debug ("creating new track entry for %s", uri);
1016 		entry = rhythmdb_entry_new (db, entry_type, uri);
1017 	} else {
1018 		rb_debug ("track entry %s already exists", uri);
1019 	}
1020 	track_data = RHYTHMDB_ENTRY_GET_TYPE_DATA (entry, RBAudioscrobblerRadioTrackData);
1021 	track_data->service = source->priv->service;
1022 
1023 	/* straightforward string copying */
1024 	for (i = 0; i < G_N_ELEMENTS (field_mapping); i++) {
1025 		value = g_hash_table_lookup (metadata, field_mapping[i].field);
1026 		if (value != NULL) {
1027 			g_value_init (&v, G_TYPE_STRING);
1028 			g_value_set_string (&v, value);
1029 			rhythmdb_entry_set (db, entry, field_mapping[i].prop, &v);
1030 			g_value_unset (&v);
1031 		}
1032 	}
1033 
1034 	/* duration needs some conversion */
1035 	value = g_hash_table_lookup (metadata, TOTEM_PL_PARSER_FIELD_DURATION_MS);
1036 	if (value != NULL) {
1037 		gint64 duration;
1038 
1039 		duration = totem_pl_parser_parse_duration (value, FALSE);
1040 		if (duration > 0) {
1041 			g_value_init (&v, G_TYPE_ULONG);
1042 			g_value_set_ulong (&v, (gulong) duration / 1000);		/* ms -> s */
1043 			rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_DURATION, &v);
1044 			g_value_unset (&v);
1045 		}
1046 	}
1047 
1048 	/* image URL and track auth ID are stored in entry type specific data */
1049 	value = g_hash_table_lookup (metadata, TOTEM_PL_PARSER_FIELD_IMAGE_URI);
1050 	if (value != NULL) {
1051 		track_data->image_url = g_strdup (value);
1052 	}
1053 
1054 	value = g_hash_table_lookup (metadata, TOTEM_PL_PARSER_FIELD_ID);
1055 	if (value != NULL) {
1056 		track_data->track_auth = g_strdup (value);
1057 	}
1058 
1059 	value = g_hash_table_lookup (metadata, TOTEM_PL_PARSER_FIELD_DOWNLOAD_URI);
1060 	if (value != NULL) {
1061 		track_data->download_url = g_strdup (value);
1062 		rb_debug ("track %s has a download url: %s", uri, track_data->download_url);
1063 	}
1064 
1065 	rhythmdb_query_model_add_entry (source->priv->track_model, entry, -1);
1066 
1067 	g_object_unref (shell);
1068 	g_object_unref (db);
1069 }
1070 
1071 static void
1072 old_api_shake_hands (RBAudioscrobblerRadioSource *source)
1073 {
1074 	if (source->priv->old_api_password != NULL) {
1075 		char *password_hash;
1076 		char *msg_url;
1077 		SoupMessage *msg;
1078 
1079 		password_hash = g_compute_checksum_for_string (G_CHECKSUM_MD5, source->priv->old_api_password, -1);
1080 
1081 		msg_url = g_strdup_printf ("%sradio/handshake.php?username=%s&passwordmd5=%s",
1082 			                   rb_audioscrobbler_service_get_old_radio_api_url (source->priv->service),
1083 			                   source->priv->username,
1084 			                   password_hash);
1085 
1086 		rb_debug ("sending old api handshake request: %s", msg_url);
1087 		msg = soup_message_new ("GET", msg_url);
1088 		soup_session_queue_message (source->priv->soup_session,
1089 			                    msg,
1090 			                    old_api_handshake_response_cb,
1091 			                    source);
1092 
1093 		g_free (password_hash);
1094 		g_free (msg_url);
1095 	} else {
1096 #ifdef WITH_GNOME_KEYRING
1097 		GnomeKeyringResult result;
1098 		char *password;
1099 
1100 		rb_debug ("attempting to retrieve password from keyring");
1101 		result = gnome_keyring_find_password_sync (GNOME_KEYRING_NETWORK_PASSWORD,
1102 		                                           &password,
1103 		                                           "user", source->priv->username,
1104 		                                           "server", rb_audioscrobbler_service_get_name (source->priv->service),
1105 		                                           NULL);
1106 
1107 		if (result == GNOME_KEYRING_RESULT_OK) {
1108 			source->priv->old_api_password = g_strdup (password);
1109 			rb_debug ("password found. shaking hands");
1110 			old_api_shake_hands (source);
1111 		} else {
1112 			rb_debug ("no password found");
1113 #endif
1114 			rb_debug ("cannot shake hands. asking user for password");
1115 			display_password_info_bar (source);
1116 			source->priv->is_busy = FALSE;
1117 #ifdef WITH_GNOME_KEYRING
1118 		}
1119 #endif
1120 	}
1121 }
1122 
1123 static void
1124 old_api_handshake_response_cb (SoupSession *session,
1125                                SoupMessage *msg,
1126                                gpointer user_data)
1127 {
1128 	RBAudioscrobblerRadioSource *source;
1129 
1130 	source = RB_AUDIOSCROBBLER_RADIO_SOURCE (user_data);
1131 
1132 	if (msg->response_body->data == NULL) {
1133 		g_free (source->priv->old_api_session_id);
1134 		source->priv->old_api_session_id = NULL;
1135 		rb_debug ("handshake failed: no response");
1136 		display_error_info_bar (source, _("Error tuning station: no response"));
1137 	} else {
1138 		char **pieces;
1139 		int i;
1140 
1141 		pieces = g_strsplit (msg->response_body->data, "\n", 0);
1142 		for (i = 0; pieces[i] != NULL; i++) {
1143 			gchar **values = g_strsplit (pieces[i], "=", 2);
1144 
1145 			if (values[0] == NULL) {
1146 				rb_debug ("unexpected response content: %s", pieces[i]);
1147 			} else if (strcmp (values[0], "session") == 0) {
1148 				if (strcmp (values[1], "FAILED") == 0) {
1149 					g_free (source->priv->old_api_session_id);
1150 					source->priv->old_api_session_id = NULL;
1151 
1152 					rb_debug ("handshake failed: probably bad authentication. asking user for new password");
1153 					g_free (source->priv->old_api_password);
1154 					source->priv->old_api_password = NULL;
1155 					display_password_info_bar (source);
1156 				} else {
1157 					g_free (source->priv->old_api_session_id);
1158 					source->priv->old_api_session_id = g_strdup (values[1]);
1159 					rb_debug ("session ID: %s", source->priv->old_api_session_id);
1160 				}
1161 			} else if (strcmp (values[0], "base_url") == 0) {
1162 				g_free (source->priv->old_api_base_url);
1163 				source->priv->old_api_base_url = g_strdup (values[1]);
1164 				rb_debug ("base url: %s", source->priv->old_api_base_url);
1165 			} else if (strcmp (values[0], "base_path") == 0) {
1166 				g_free (source->priv->old_api_base_path);
1167 				source->priv->old_api_base_path = g_strdup (values[1]);
1168 				rb_debug ("base path: %s", source->priv->old_api_base_path);
1169 			} else if (strcmp (values[0], "banned") == 0) {
1170 				if (strcmp (values[1], "0") != 0) {
1171 					source->priv->old_api_is_banned = TRUE;
1172 				} else {
1173 					source->priv->old_api_is_banned = FALSE;
1174 				}
1175 				rb_debug ("banned: %i", source->priv->old_api_is_banned);
1176 			}
1177 
1178 			g_strfreev (values);
1179 		}
1180 		g_strfreev (pieces);
1181 	}
1182 
1183 	/* if handshake was successful then tune */
1184 	if (source->priv->old_api_session_id != NULL) {
1185 		old_api_tune (source);
1186 	} else {
1187 		source->priv->is_busy = FALSE;
1188 	}
1189 }
1190 
1191 static void
1192 old_api_tune (RBAudioscrobblerRadioSource *source)
1193 {
1194 	/* get a handshake first if we don't have one */
1195 	if (source->priv->old_api_session_id == NULL) {
1196 		old_api_shake_hands (source);
1197 	} else {
1198 		char *escaped_station_url;
1199 		char *msg_url;
1200 		SoupMessage *msg;
1201 
1202 		escaped_station_url = g_uri_escape_string (source->priv->station_url, NULL, FALSE);
1203 
1204 		msg_url = g_strdup_printf("http://%s%s/adjust.php?session=%s&url=%s",
1205 			                  source->priv->old_api_base_url,
1206 			                  source->priv->old_api_base_path,
1207 			                  source->priv->old_api_session_id,
1208 			                  escaped_station_url);
1209 
1210 		rb_debug ("sending old api tune request: %s", msg_url);
1211 		msg = soup_message_new ("GET", msg_url);
1212 		soup_session_queue_message (source->priv->soup_session,
1213 			                    msg,
1214 			                    old_api_tune_response_cb,
1215 			                    source);
1216 
1217 		g_free (escaped_station_url);
1218 		g_free (msg_url);
1219 	}
1220 }
1221 
1222 static void
1223 old_api_tune_response_cb (SoupSession *session,
1224                           SoupMessage *msg,
1225                           gpointer user_data)
1226 {
1227 	RBAudioscrobblerRadioSource *source;
1228 
1229 	source = RB_AUDIOSCROBBLER_RADIO_SOURCE (user_data);
1230 
1231 	if (msg->response_body->data != NULL) {
1232 		char **pieces;
1233 		int i;
1234 
1235 		pieces = g_strsplit (msg->response_body->data, "\n", 0);
1236 		for (i = 0; pieces[i] != NULL; i++) {
1237 			gchar **values = g_strsplit (pieces[i], "=", 2);
1238 
1239 			if (values[0] == NULL) {
1240 				rb_debug ("unexpected response from old api tune request: %s", pieces[i]);
1241 			} else if (strcmp (values[0], "response") == 0) {
1242 				if (strcmp (values[1], "OK") == 0) {
1243 					rb_debug ("old api tune request was successful");
1244 					/* no problems tuning, get the playlist */
1245 					old_api_fetch_playlist (source);
1246 				}
1247 			} else if (strcmp (values[0], "error") == 0) {
1248 				char *error_message;
1249 				rb_debug ("old api tune request responded with error: %s", pieces[i]);
1250 
1251 				error_message = g_strdup_printf (_("Error tuning station: %s"), values[1]);
1252 
1253 
1254 				g_free (error_message);
1255 
1256 				source->priv->is_busy = FALSE;
1257 			}
1258 			/* TODO: do something with other information given here */
1259 
1260 			g_strfreev (values);
1261 		}
1262 
1263 		g_strfreev (pieces);
1264 	} else {
1265 		rb_debug ("no response from old api tune request");
1266 		display_error_info_bar (source, _("Error tuning station: no response"));
1267 		source->priv->is_busy = FALSE;
1268 	}
1269 }
1270 
1271 static void
1272 old_api_fetch_playlist (RBAudioscrobblerRadioSource *source)
1273 {
1274 	char *msg_url;
1275 	SoupMessage *msg;
1276 
1277 	msg_url = g_strdup_printf("http://%s%s/xspf.php?sk=%s&discovery=%i&desktop=%s",
1278 		                  source->priv->old_api_base_url,
1279 		                  source->priv->old_api_base_path,
1280 		                  source->priv->old_api_session_id,
1281 		                  0,
1282 		                  "1.5");
1283 
1284 	rb_debug ("sending old api playlist request: %s", msg_url);
1285 	msg = soup_message_new ("GET", msg_url);
1286 	soup_session_queue_message (source->priv->soup_session,
1287 		                    msg,
1288 		                    fetch_playlist_response_cb,
1289 		                    source);
1290 
1291 	g_free (msg_url);
1292 }
1293 
1294 static void
1295 display_error_info_bar (RBAudioscrobblerRadioSource *source,
1296                         const char *message)
1297 {
1298 	gtk_label_set_label (GTK_LABEL (source->priv->error_info_bar_label), message);
1299 	gtk_info_bar_set_message_type (GTK_INFO_BAR (source->priv->error_info_bar), GTK_MESSAGE_WARNING);
1300 	gtk_widget_show_all (source->priv->error_info_bar);
1301 }
1302 
1303 static void
1304 display_password_info_bar (RBAudioscrobblerRadioSource *source)
1305 {
1306 	gtk_widget_show_all (source->priv->password_info_bar);
1307 }
1308 
1309 static void
1310 password_info_bar_response_cb (GtkInfoBar *info_bar,
1311                                int response_id,
1312                                RBAudioscrobblerRadioSource *source)
1313 {
1314 	gtk_widget_hide (source->priv->password_info_bar);
1315 
1316 	g_free (source->priv->old_api_password);
1317 	source->priv->old_api_password = g_strdup (gtk_entry_get_text (GTK_ENTRY (source->priv->password_info_bar_entry)));
1318 
1319 #ifdef WITH_GNOME_KEYRING
1320 	/* save the new password */
1321 	char *password_desc;
1322 
1323 	password_desc = g_strdup_printf (_("Password for streaming %s radio using the deprecated API"),
1324 	                                 rb_audioscrobbler_service_get_name (source->priv->service));
1325 
1326 	rb_debug ("saving password to keyring");
1327 	gnome_keyring_store_password_sync (GNOME_KEYRING_NETWORK_PASSWORD,
1328 	                                   GNOME_KEYRING_DEFAULT,
1329 	                                   password_desc,
1330 	                                   source->priv->old_api_password,
1331 	                                   "user", source->priv->username,
1332 	                                   "server", rb_audioscrobbler_service_get_name (source->priv->service),
1333 	                                   NULL);
1334 
1335 	g_free (password_desc);
1336 #endif
1337 
1338 	gtk_entry_set_text (GTK_ENTRY (source->priv->password_info_bar_entry), "");
1339 
1340 	old_api_shake_hands (source);
1341 }
1342 
1343 static void
1344 rename_station_action_cb (GtkAction *action, RBAudioscrobblerRadioSource *source)
1345 {
1346 	RBShell *shell;
1347 	RBDisplayPageTree *page_tree;
1348 
1349 	g_object_get (source, "shell", &shell, NULL);
1350 	g_object_get (shell, "display-page-tree", &page_tree, NULL);
1351 
1352 	rb_display_page_tree_edit_source_name (page_tree, RB_SOURCE (source));
1353 
1354 	g_object_unref (shell);
1355 	g_object_unref (page_tree);
1356 }
1357 
1358 static void
1359 delete_station_action_cb (GtkAction *action, RBAudioscrobblerRadioSource *source)
1360 {
1361 	rb_audioscrobbler_profile_page_remove_radio_station (source->priv->parent, RB_SOURCE (source));
1362 }
1363 
1364 static void
1365 impl_selected (RBDisplayPage *page)
1366 {
1367 	RBAudioscrobblerRadioSource *source = RB_AUDIOSCROBBLER_RADIO_SOURCE (page);
1368 
1369 	RB_DISPLAY_PAGE_CLASS (rb_audioscrobbler_radio_source_parent_class)->selected (page);
1370 
1371 	/* if the query model is empty then attempt to add some tracks to it */
1372 	if (rhythmdb_query_model_get_duration (source->priv->track_model) == 0) {
1373 		tune (source);
1374 	}
1375 }
1376 
1377 static RBEntryView *
1378 impl_get_entry_view (RBSource *asource)
1379 {
1380 	RBAudioscrobblerRadioSource *source = RB_AUDIOSCROBBLER_RADIO_SOURCE (asource);
1381 
1382 	return source->priv->track_view;
1383 }
1384 
1385 static void
1386 impl_get_status (RBDisplayPage *page, char **text, char **progress_text, float *progress)
1387 {
1388 	RBAudioscrobblerRadioSource *source = RB_AUDIOSCROBBLER_RADIO_SOURCE (page);
1389 
1390 	/* pulse progressbar if we're busy, otherwise see what the streaming source part of us has to say */
1391 	if (source->priv->is_busy) {
1392 		/* We could be calling either radio.tune or radio.getPlaylist methods.
1393 		 * "Tuning station" seems like a user friendly message to display for both cases.
1394 		 */
1395 		*progress_text = g_strdup (_("Tuning station"));
1396 		*progress = -1.0f;
1397 	} else {
1398 		rb_streaming_source_get_progress (RB_STREAMING_SOURCE (source), progress_text, progress);
1399 	}
1400 }
1401 
1402 static RBSourceEOFType
1403 impl_handle_eos (RBSource *asource)
1404 {
1405 	return RB_SOURCE_EOF_NEXT;
1406 }
1407 
1408 static gboolean
1409 impl_show_popup (RBDisplayPage *page)
1410 {
1411 	_rb_display_page_show_popup (page, AUDIOSCROBBLER_RADIO_SOURCE_POPUP_PATH);
1412 	return TRUE;
1413 }
1414 
1415 static void
1416 impl_delete_thyself (RBDisplayPage *page)
1417 {
1418 	RBAudioscrobblerRadioSource *source;
1419 	RBShell *shell;
1420 	GtkUIManager *ui_manager;
1421 	RhythmDB *db;
1422 	GtkTreeIter iter;
1423 	gboolean loop;
1424 
1425 	rb_debug ("deleting radio source");
1426 
1427 	source = RB_AUDIOSCROBBLER_RADIO_SOURCE (page);
1428 
1429 	g_object_get (source, "shell", &shell, "ui-manager", &ui_manager, NULL);
1430 	g_object_get (shell, "db", &db, NULL);
1431 
1432 	/* unmerge ui */
1433 	gtk_ui_manager_remove_ui (ui_manager, source->priv->ui_merge_id);
1434 
1435 	/* Ensure playing entry isn't deleted twice */
1436 	source->priv->playing_entry = NULL;
1437 
1438 	/* delete all entries */
1439 	loop = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (source->priv->track_model), &iter);
1440 	while (loop) {
1441 		RhythmDBEntry *entry;
1442 
1443 		entry = rhythmdb_query_model_iter_to_entry (source->priv->track_model, &iter);
1444 		rhythmdb_entry_delete (db, entry);
1445 		rhythmdb_entry_unref (entry);
1446 
1447 		loop = gtk_tree_model_iter_next (GTK_TREE_MODEL (source->priv->track_model), &iter);
1448 	}
1449 
1450 	rhythmdb_commit (db);
1451 
1452 	g_object_unref (shell);
1453 	g_object_unref (ui_manager);
1454 	g_object_unref (db);
1455 }
1456 
1457 void
1458 _rb_audioscrobbler_radio_source_register_type (GTypeModule *module)
1459 {
1460 	rb_audioscrobbler_radio_source_register_type (module);
1461 }