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 }