udapted vi.po
[rhythmbox.git] / plugins / audioscrobbler / rb-lastfm-source.c
blobfae1637d6c8af6e2c3e818b3ddc30cb32fba5247
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
3 * arch-tag: Implementation of last.fm station source object
5 * Copyright (C) 2006 Matt Novenstern <fisxoj@gmail.com>
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
23 /* The author would like to extend thanks to Ian Holmes, author of Last Exit,
24 * an alternative last.fm player written in C#, the code of which was
25 * extraordinarily useful in the creation of this code
28 /* TODO List
29 * - if subscriber, make user radio (low priority)
30 * - "recommendation radio" with percentage setting (0=obscure, 100=popular)
31 * - watch username gconf entries, create/update neighbour station
35 #include <config.h>
37 #include <string.h>
39 #include <glib/gi18n.h>
40 #include <gtk/gtk.h>
41 #include <glade/glade.h>
43 #include <gconf/gconf-value.h>
45 #include <libsoup/soup.h>
46 #include <libsoup/soup-uri.h>
48 #include "md5.h"
50 #include "eel-gconf-extensions.h"
52 #include "rb-proxy-config.h"
53 #include "rb-preferences.h"
55 #include "rb-audioscrobbler.h"
56 #include "rb-lastfm-source.h"
58 #include "rhythmdb-query-model.h"
59 #include "rb-glade-helpers.h"
60 #include "rb-stock-icons.h"
61 #include "rb-entry-view.h"
62 #include "rb-property-view.h"
63 #include "rb-util.h"
64 #include "rb-file-helpers.h"
65 #include "rb-preferences.h"
66 #include "rb-dialog.h"
67 #include "rb-station-properties-dialog.h"
68 #include "rb-new-station-dialog.h"
69 #include "rb-debug.h"
70 #include "eel-gconf-extensions.h"
71 #include "rb-shell-player.h"
73 #define LASTFM_URL "http://ws.audioscrobbler.com"
74 #define PLATFORM_STRING "linux"
75 #define RB_LASTFM_VERSION "1.1.1"
76 #define EXTRA_URI_ENCODE_CHARS "&+"
79 static void rb_lastfm_source_class_init (RBLastfmSourceClass *klass);
80 static void rb_lastfm_source_init (RBLastfmSource *source);
81 static GObject *rb_lastfm_source_constructor (GType type, guint n_construct_properties,
82 GObjectConstructParam *construct_properties);
83 static void rb_lastfm_source_finalize (GObject *object);
84 static void rb_lastfm_source_set_property (GObject *object,
85 guint prop_id,
86 const GValue *value,
87 GParamSpec *pspec);
88 static void rb_lastfm_source_get_property (GObject *object,
89 guint prop_id,
90 GValue *value,
91 GParamSpec *pspec);
93 static void rb_lastfm_source_songs_view_sort_order_changed_cb (RBEntryView *view,
94 RBLastfmSource *source);
95 static void rb_lastfm_source_do_query (RBLastfmSource *source);
97 /* source-specific methods */
98 static void rb_lastfm_source_do_handshake (RBLastfmSource *source);
99 static char* rb_lastfm_source_get_playback_uri (RhythmDBEntry *entry, gpointer data);
100 static void rb_lastfm_perform (RBLastfmSource *lastfm,
101 const char *url,
102 char *post_data, /* this takes ownership */
103 SoupMessageCallbackFn response_handler);
104 static void rb_lastfm_message_cb (SoupMessage *req, gpointer user_data);
105 static void rb_lastfm_change_station (RBLastfmSource *source, const char *station);
107 static void rb_lastfm_proxy_config_changed_cb (RBProxyConfig *config,
108 RBLastfmSource *source);
109 static void rb_lastfm_source_drag_cb (GtkWidget *widget,
110 GdkDragContext *dc,
111 gint x, gint y,
112 GtkSelectionData *selection_data,
113 guint info, guint time,
114 RBLastfmSource *source);
116 static void rb_lastfm_source_dispose (GObject *object);
118 /* RBSource implementation methods */
119 static void impl_delete (RBSource *asource);
120 static GList *impl_get_ui_actions (RBSource *source);
121 static RBEntryView *impl_get_entry_view (RBSource *asource);
122 static void impl_get_status (RBSource *asource, char **text, char **progress_text, float *progress);
123 static gboolean impl_receive_drag (RBSource *source, GtkSelectionData *data);
124 static void impl_activate (RBSource *source);
125 static gboolean impl_show_popup (RBSource *source);
127 static void rb_lastfm_source_new_station (char *uri, char *title, RBLastfmSource *source);
128 static void rb_lastfm_source_skip_track (GtkAction *action, RBLastfmSource *source);
129 static void rb_lastfm_source_love_track (GtkAction *action, RBLastfmSource *source);
130 static void rb_lastfm_source_ban_track (GtkAction *action, RBLastfmSource *source);
131 static char *rb_lastfm_source_title_from_uri (char *uri);
132 static void rb_lastfm_source_add_station_cb (GtkButton *button, gpointer *data);
133 static void rb_lastfm_source_entry_added_cb (RhythmDB *db, RhythmDBEntry *entry, RBLastfmSource *source);
135 static void rb_lastfm_source_new_song_cb (GObject *player_backend, gpointer data, RBLastfmSource *source);
136 static void rb_lastfm_song_changed_cb (RBShellPlayer *player, RhythmDBEntry *entry, RBLastfmSource *source);
138 static void show_entry_popup (RBEntryView *view,
139 gboolean over_entry,
140 RBSource *source);
142 #ifdef HAVE_GSTREAMER_0_10
143 /* can't be bothered creating a whole header file just for this: */
144 GType rb_lastfm_src_get_type (void);
145 #endif
148 static const char* const radio_options[][3] = {
149 {N_("Similar Artists radio"), "lastfm://artist/%s/similarartists", N_("Artists similar to %s")},
150 {N_("Tag radio"), "lastfm://globaltags/%s", N_("Tracks tagged with %s")},
151 {N_("Artist Fan radio"), "lastfm://artist/%s/fans", N_("Artists like by fans of %s")},
152 {N_("Group radio"), "lastfm://group/%s", N_("Tracks liked by the %s group")},
153 {NULL, NULL, NULL}
156 struct RBLastfmSourcePrivate
158 GtkWidget *vbox;
159 GtkWidget *paned;
160 GtkWidget *vbox2;
161 GtkWidget *hbox;
162 /*GtkWidget *tuner;*/
163 GtkWidget *txtbox;
164 GtkWidget *gobutton;
165 GtkWidget *typecombo;
166 GtkWidget *label;
167 RhythmDB *db;
169 GtkActionGroup *action_group;
171 RBEntryView *stations;
173 RBShellPlayer *shell_player;
174 RhythmDBEntryType entry_type;
175 char *session;
177 gboolean subscriber;
178 char *base_url;
179 char *base_path;
180 char *stream_url;
181 gboolean framehack;
182 char *update_url;
183 gboolean banned;
184 gboolean connected;
186 /*RhythmDBEntry *pending_entry;*/
188 enum {
189 OK = 0,
190 COMMUNICATING,
191 FAILED,
192 NO_ARTIST,
193 BANNED
194 } status;
196 SoupSession *soup_session;
197 RBProxyConfig *proxy_config;
200 G_DEFINE_TYPE (RBLastfmSource, rb_lastfm_source, RB_TYPE_STREAMING_SOURCE);
201 #define RB_LASTFM_SOURCE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_LASTFM_SOURCE, RBLastfmSourcePrivate))
203 enum
205 PROP_0,
206 PROP_ENTRY_TYPE,
207 PROP_PROXY_CONFIG
210 static GtkActionEntry rb_lastfm_source_actions [] =
212 { "LastfmSkipSong", GTK_STOCK_MEDIA_FORWARD, N_("Next Song"), NULL,
213 N_("Skip the current track"),
214 G_CALLBACK (rb_lastfm_source_skip_track) },
215 { "LastfmLoveSong", GTK_STOCK_ADD, N_("Love Song"), NULL,
216 N_("Mark this song as loved"),
217 G_CALLBACK (rb_lastfm_source_love_track) },
218 { "LastfmBanSong", GTK_STOCK_CANCEL, N_("Ban Song"), NULL,
219 N_("Ban the current track from being played again"),
220 G_CALLBACK (rb_lastfm_source_ban_track) }
223 static const GtkTargetEntry lastfm_drag_types[] = {
224 { "text/plain", 0, 0 },
225 { "_NETSCAPE_URL", 0, 1 }
228 static void
229 rb_lastfm_source_class_init (RBLastfmSourceClass *klass)
231 GObjectClass *object_class = G_OBJECT_CLASS (klass);
232 RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
234 object_class->finalize = rb_lastfm_source_finalize;
235 object_class->dispose = rb_lastfm_source_dispose;
236 object_class->constructor = rb_lastfm_source_constructor;
238 object_class->set_property = rb_lastfm_source_set_property;
239 object_class->get_property = rb_lastfm_source_get_property;
241 source_class->impl_can_copy = (RBSourceFeatureFunc) rb_false_function;
242 source_class->impl_can_delete = (RBSourceFeatureFunc) rb_true_function;
243 source_class->impl_can_pause = (RBSourceFeatureFunc) rb_false_function;
244 source_class->impl_delete = impl_delete;
245 source_class->impl_get_entry_view = impl_get_entry_view;
246 source_class->impl_get_status = impl_get_status;
247 source_class->impl_get_ui_actions = impl_get_ui_actions;
248 source_class->impl_receive_drag = impl_receive_drag;
249 source_class->impl_activate = impl_activate;
250 source_class->impl_show_popup = impl_show_popup;
252 g_object_class_install_property (object_class,
253 PROP_ENTRY_TYPE,
254 g_param_spec_boxed ("entry-type",
255 "Entry type",
256 "Type of the entries which should be displayed by this source",
257 RHYTHMDB_TYPE_ENTRY_TYPE,
258 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
259 g_object_class_install_property (object_class,
260 PROP_PROXY_CONFIG,
261 g_param_spec_object ("proxy-config",
262 "RBProxyConfig",
263 "RBProxyConfig object",
264 RB_TYPE_PROXY_CONFIG,
265 G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
266 g_type_class_add_private (klass, sizeof (RBLastfmSourcePrivate));
268 #ifdef HAVE_GSTREAMER_0_10
269 rb_lastfm_src_get_type ();
270 #endif
273 static void
274 rb_lastfm_source_init (RBLastfmSource *source)
276 gint size;
277 GdkPixbuf *pixbuf;
279 source->priv = RB_LASTFM_SOURCE_GET_PRIVATE (source);
281 source->priv->vbox = gtk_vbox_new (FALSE, 5);
283 gtk_container_add (GTK_CONTAINER (source), source->priv->vbox);
285 gtk_icon_size_lookup (GTK_ICON_SIZE_LARGE_TOOLBAR, &size, NULL);
286 pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
287 "stock_channel",
288 size,
289 0, NULL);
290 rb_source_set_pixbuf (RB_SOURCE (source), pixbuf);
291 if (pixbuf != NULL) {
292 g_object_unref (pixbuf);
296 static void
297 rb_lastfm_source_finalize (GObject *object)
299 RBLastfmSource *source;
301 g_return_if_fail (object != NULL);
302 g_return_if_fail (RB_IS_LASTFM_SOURCE (object));
304 source = RB_LASTFM_SOURCE (object);
306 g_return_if_fail (source->priv != NULL);
308 rb_debug ("finalizing lastfm source");
310 if (source->priv->db) {
311 g_object_unref (source->priv->db);
312 source->priv->db = NULL;
315 g_object_unref (G_OBJECT (source->priv->proxy_config));
317 G_OBJECT_CLASS (rb_lastfm_source_parent_class)->finalize (object);
320 static GObject *
321 rb_lastfm_source_constructor (GType type, guint n_construct_properties,
322 GObjectConstructParam *construct_properties)
324 RBLastfmSource *source;
325 RBLastfmSourceClass *klass;
326 RBShell *shell;
327 GObject *player_backend;
328 int i;
330 klass = RB_LASTFM_SOURCE_CLASS (g_type_class_peek (RB_TYPE_LASTFM_SOURCE));
332 source = RB_LASTFM_SOURCE (G_OBJECT_CLASS (rb_lastfm_source_parent_class)
333 ->constructor (type, n_construct_properties, construct_properties));
335 g_object_get (G_OBJECT (source), "shell", &shell, NULL);
336 g_object_get (G_OBJECT (shell),
337 "db", &source->priv->db,
338 "shell-player", &source->priv->shell_player,
339 NULL);
340 g_object_unref (G_OBJECT (shell));
342 g_signal_connect_object (source->priv->db,
343 "entry-added",
344 G_CALLBACK (rb_lastfm_source_entry_added_cb),
345 source, 0);
347 /* Set up station tuner */
348 /*source->priv->tuner = gtk_vbox_new (FALSE, 5); */
349 source->priv->vbox2 = gtk_vbox_new (FALSE, 5);
350 source->priv->hbox = gtk_hbox_new (FALSE, 5);
352 source->priv->label = gtk_label_new (_("Enter the item to build a Last.fm station out of:"));
353 g_object_set (source->priv->label, "xalign", 0.0, NULL);
355 source->priv->gobutton = gtk_button_new_with_label (_("Add"));
356 g_signal_connect_object (G_OBJECT (source->priv->gobutton),
357 "clicked",
358 G_CALLBACK (rb_lastfm_source_add_station_cb),
359 source, 0);
361 source->priv->typecombo = gtk_combo_box_new_text ();
362 for (i = 0; radio_options[i][0] != NULL; i++) {
363 gtk_combo_box_append_text (GTK_COMBO_BOX (source->priv->typecombo), _(radio_options[i][0]));
365 gtk_combo_box_set_active (GTK_COMBO_BOX (source->priv->typecombo), 0);
367 source->priv->txtbox = gtk_entry_new ();
369 gtk_box_pack_end_defaults (GTK_BOX (source->priv->hbox), GTK_WIDGET (source->priv->gobutton));
370 gtk_box_pack_end_defaults (GTK_BOX (source->priv->hbox), GTK_WIDGET (source->priv->txtbox));
371 gtk_box_pack_start_defaults (GTK_BOX (source->priv->hbox), GTK_WIDGET (source->priv->typecombo));
372 gtk_box_pack_end_defaults (GTK_BOX (source->priv->vbox2), GTK_WIDGET (source->priv->hbox));
373 gtk_box_pack_end_defaults (GTK_BOX (source->priv->vbox2), GTK_WIDGET (source->priv->label));
375 /* set up stations view */
376 source->priv->stations = rb_entry_view_new (source->priv->db,
377 G_OBJECT (source->priv->shell_player),
378 NULL,
379 FALSE, FALSE);
380 rb_entry_view_append_column (source->priv->stations, RB_ENTRY_VIEW_COL_TITLE, TRUE);
381 rb_entry_view_append_column (source->priv->stations, RB_ENTRY_VIEW_COL_RATING, TRUE);
382 rb_entry_view_append_column (source->priv->stations, RB_ENTRY_VIEW_COL_LAST_PLAYED, TRUE);
383 g_signal_connect_object (G_OBJECT (source->priv->stations),
384 "sort-order-changed",
385 G_CALLBACK (rb_lastfm_source_songs_view_sort_order_changed_cb),
386 source, 0);
387 g_signal_connect_object (G_OBJECT (source->priv->stations), "show_popup",
388 G_CALLBACK (show_entry_popup), source, 0);
390 /* Drag and drop URIs */
391 g_signal_connect_object (G_OBJECT (source->priv->stations),
392 "drag_data_received",
393 G_CALLBACK (rb_lastfm_source_drag_cb),
394 source, 0);
395 g_signal_connect_object (G_OBJECT (source->priv->shell_player),
396 "playing-song-changed",
397 G_CALLBACK (rb_lastfm_song_changed_cb),
398 source, 0);
400 gtk_drag_dest_set (GTK_WIDGET (source->priv->stations),
401 GTK_DEST_DEFAULT_ALL,
402 lastfm_drag_types, 2,
403 GDK_ACTION_COPY | GDK_ACTION_MOVE);
405 /* Pack the vbox */
406 /*gtk_paned_pack1 (GTK_PANED (source->priv->paned),
407 GTK_WIDGET (source->priv->tuner), FALSE, FALSE); */
409 /*source->priv->paned = gtk_vpaned_new ();
410 gtk_paned_pack2 (GTK_PANED (source->priv->paned),
411 GTK_WIDGET (source->priv->stations), TRUE, FALSE); */
413 gtk_box_pack_start (GTK_BOX (source->priv->vbox), GTK_WIDGET (source->priv->vbox2), FALSE, FALSE, 5);
414 gtk_box_pack_start_defaults (GTK_BOX (source->priv->vbox), GTK_WIDGET (source->priv->stations));
417 gtk_widget_show_all (GTK_WIDGET (source));
420 source->priv->action_group = _rb_source_register_action_group (RB_SOURCE (source),
421 "LastfmActions",
422 rb_lastfm_source_actions,
423 G_N_ELEMENTS (rb_lastfm_source_actions),
424 source);
426 rb_lastfm_source_do_query (source);
428 g_object_get (source->priv->shell_player, "player", &player_backend, NULL);
429 g_signal_connect_object (player_backend,
430 "event::rb-lastfm-new-song",
431 G_CALLBACK (rb_lastfm_source_new_song_cb),
432 source,
435 return G_OBJECT (source);
438 static void
439 rb_lastfm_source_set_property (GObject *object,
440 guint prop_id,
441 const GValue *value,
442 GParamSpec *pspec)
444 RBLastfmSource *source = RB_LASTFM_SOURCE (object);
446 switch (prop_id) {
447 case PROP_ENTRY_TYPE:
448 source->priv->entry_type = g_value_get_boxed (value);
449 break;
450 case PROP_PROXY_CONFIG:
451 source->priv->proxy_config = g_value_get_object (value);
452 g_object_ref (G_OBJECT (source->priv->proxy_config));
453 g_signal_connect_object (G_OBJECT (source->priv->proxy_config),
454 "config-changed",
455 G_CALLBACK (rb_lastfm_proxy_config_changed_cb),
456 source, 0);
457 break;
458 default:
459 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
460 break;
464 static void
465 rb_lastfm_source_get_property (GObject *object,
466 guint prop_id,
467 GValue *value,
468 GParamSpec *pspec)
470 RBLastfmSource *source = RB_LASTFM_SOURCE (object);
472 switch (prop_id) {
473 case PROP_ENTRY_TYPE:
474 g_value_set_boxed (value, source->priv->entry_type);
475 break;
476 default:
477 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
478 break;
482 static gchar *
483 mkmd5 (char *string)
485 md5_state_t md5state;
486 guchar md5pword[16];
487 gchar md5_response[33];
489 int j = 0;
491 memset (md5_response, 0, sizeof (md5_response));
493 md5_init (&md5state);
494 md5_append (&md5state, (unsigned char*)string, strlen (string));
495 md5_finish (&md5state, md5pword);
497 for (j = 0; j < 16; j++) {
498 char a[3];
499 sprintf (a, "%02x", md5pword[j]);
500 md5_response[2*j] = a[0];
501 md5_response[2*j+1] = a[1];
504 return (g_strdup (md5_response));
507 RBSource *
508 rb_lastfm_source_new (RBShell *shell)
510 RBSource *source;
511 RBProxyConfig *proxy_config;
512 RhythmDBEntryType entry_type;
513 char *uri;
514 RhythmDB *db;
515 char *username;
517 g_object_get (G_OBJECT (shell), "db", &db, NULL);
519 /* register entry type if it's not already registered */
520 entry_type = rhythmdb_entry_type_get_by_name (db, "lastfm-station");
521 if (entry_type == RHYTHMDB_ENTRY_TYPE_INVALID) {
522 entry_type = rhythmdb_entry_register_type (db, "lastfm-station");
523 entry_type->save_to_disk = TRUE;
524 entry_type->can_sync_metadata = (RhythmDBEntryCanSyncFunc) rb_true_function;
525 entry_type->sync_metadata = (RhythmDBEntrySyncFunc) rb_null_function;
526 entry_type->get_playback_uri = (RhythmDBEntryStringFunc) rb_lastfm_source_get_playback_uri;
529 g_object_get (G_OBJECT (shell), "proxy-config", &proxy_config, NULL);
531 source = RB_SOURCE (g_object_new (RB_TYPE_LASTFM_SOURCE,
532 "name", _("Last.fm"),
533 "shell", shell,
534 "entry-type", entry_type,
535 "proxy-config", proxy_config,
536 NULL));
537 rb_shell_register_entry_type_for_source (shell, source, entry_type);
539 entry_type->get_playback_uri_data = source;
541 /* create default neighbour radio station */
542 username = eel_gconf_get_string (CONF_AUDIOSCROBBLER_USERNAME);
543 if (username != NULL) {
544 RhythmDBEntry *entry;
546 uri = g_strdup_printf ("lastfm://user/%s/neighbours", username);
547 entry = rhythmdb_entry_lookup_by_location (db, uri);
548 if (entry == NULL) {
549 rb_lastfm_source_new_station (uri, _("Neighbour Radio"), RB_LASTFM_SOURCE (source));
550 } else {
551 rhythmdb_entry_unref (entry);
553 g_free (uri);
554 g_free (username);
557 g_object_unref (db);
558 g_object_unref (proxy_config);
559 return source;
562 static GList*
563 impl_get_ui_actions (RBSource *source)
565 GList *actions = NULL;
567 actions = g_list_prepend (actions, g_strdup ("LastfmLoveSong"));
568 actions = g_list_prepend (actions, g_strdup ("LastfmBanSong"));
569 actions = g_list_prepend (actions, g_strdup ("LastfmSkipSong"));
571 return actions;
574 static RBEntryView *
575 impl_get_entry_view (RBSource *asource)
577 RBLastfmSource *source = RB_LASTFM_SOURCE (asource);
579 return source->priv->stations;
582 static void
583 impl_get_status (RBSource *asource, char **text, char **progress_text, float *progress)
585 RBLastfmSource *source = RB_LASTFM_SOURCE (asource);
587 switch (source->priv->status) {
588 case NO_ARTIST:
589 *text = g_strdup (_("No such artist. Check your spelling"));
590 break;
592 case FAILED:
593 *text = g_strdup (_("Handshake failed"));
594 break;
596 case BANNED:
597 *text = g_strdup (_("The server marked you as banned"));
598 break;
600 case COMMUNICATING:
601 case OK:
603 RhythmDBQueryModel *model;
604 guint num_entries;
606 g_object_get (asource, "query-model", &model, NULL);
607 num_entries = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (model), NULL);
608 g_object_unref (model);
610 *text = g_strdup_printf (ngettext ("%d station", "%d stations", num_entries), num_entries);
611 break;
614 rb_streaming_source_get_progress (RB_STREAMING_SOURCE (source), progress_text, progress);
617 static void
618 impl_delete (RBSource *asource)
620 RBLastfmSource *source = RB_LASTFM_SOURCE (asource);
621 GList *l;
623 for (l = rb_entry_view_get_selected_entries (source->priv->stations); l != NULL; l = g_list_next (l)) {
624 rhythmdb_entry_delete (source->priv->db, l->data);
627 rhythmdb_commit (source->priv->db);
630 static void
631 rb_lastfm_source_songs_view_sort_order_changed_cb (RBEntryView *view,
632 RBLastfmSource *source)
634 rb_debug ("sort order changed");
636 rb_entry_view_resort_model (view);
639 static void
640 rb_lastfm_source_do_query (RBLastfmSource *source)
642 RhythmDBQueryModel *station_query_model;
643 GPtrArray *query;
645 query = rhythmdb_query_parse (source->priv->db,
646 RHYTHMDB_QUERY_PROP_EQUALS,
647 RHYTHMDB_PROP_TYPE,
648 source->priv->entry_type,
649 RHYTHMDB_QUERY_END);
650 station_query_model = rhythmdb_query_model_new_empty (source->priv->db);
651 rhythmdb_do_full_query_parsed (source->priv->db,
652 RHYTHMDB_QUERY_RESULTS (station_query_model),
653 query);
655 rhythmdb_query_free (query);
656 query = NULL;
658 rb_entry_view_set_model (source->priv->stations, station_query_model);
659 g_object_set (G_OBJECT (source), "query-model", station_query_model, NULL);
661 g_object_unref (G_OBJECT (station_query_model));
664 static void
665 rb_lastfm_source_do_handshake (RBLastfmSource *source)
667 char *password;
668 char *username;
669 char *md5password;
670 char *handshake_url;
672 if (source->priv->connected) {
673 return;
676 username = eel_gconf_get_string (CONF_AUDIOSCROBBLER_USERNAME);
677 if (username == NULL) {
678 rb_debug ("no last.fm username");
679 return;
682 password = eel_gconf_get_string (CONF_AUDIOSCROBBLER_PASSWORD);
683 if (password == NULL) {
684 rb_debug ("no last.fm password");
685 return;
688 md5password = mkmd5 (password);
689 g_free (password);
691 handshake_url = g_strdup_printf ("%s/radio/handshake.php?version=1.1.1&platform=linux&"
692 "username=%s&passwordmd5=%s&debug=0&partner=",
693 LASTFM_URL,
694 username,
695 md5password);
696 rb_debug ("Last.fm sending handshake");
697 g_object_ref (source);
698 rb_lastfm_perform (source, handshake_url, NULL, rb_lastfm_message_cb);
699 g_free (handshake_url);
700 g_free (username);
701 g_free (md5password);
704 static char *
705 rb_lastfm_source_get_playback_uri (RhythmDBEntry *entry, gpointer data)
707 char *location;
708 RBLastfmSource *source;
710 if (entry == NULL) {
711 rb_debug ("NULL entry");
712 return NULL;
715 source = RB_LASTFM_SOURCE (data);
716 if (source == NULL) {
717 rb_debug ("NULL source pointer");
718 return NULL;
722 if (!source->priv->connected) {
723 rb_debug ("not connected");
724 return NULL;
726 source = RB_LASTFM_SOURCE (data);
728 location = g_strdup_printf ("xrblastfm://%s", source->priv->stream_url + strlen("http://"));
729 rb_debug ("playback uri: %s", location);
730 return location;
733 static void
734 rb_lastfm_perform (RBLastfmSource *source,
735 const char *url,
736 char *post_data,
737 SoupMessageCallbackFn response_handler)
739 SoupMessage *msg;
740 msg = soup_message_new ("GET", url);
742 if (msg == NULL)
743 return;
745 soup_message_set_http_version (msg, SOUP_HTTP_1_1);
747 rb_debug ("Last.fm communicating with %s", url);
749 if (post_data != NULL) {
750 rb_debug ("POST data: %s", post_data);
751 soup_message_set_request (msg,
752 "application/x-www-form-urlencoded",
753 SOUP_BUFFER_SYSTEM_OWNED,
754 post_data,
755 strlen (post_data));
758 /* create soup session, if we haven't got one yet */
759 if (!source->priv->soup_session) {
760 SoupUri *uri;
762 uri = rb_proxy_config_get_libsoup_uri (source->priv->proxy_config);
763 source->priv->soup_session = soup_session_async_new_with_options (
764 "proxy-uri", uri,
765 NULL);
766 if (uri)
767 soup_uri_free (uri);
770 soup_session_queue_message (source->priv->soup_session,
771 msg,
772 (SoupMessageCallbackFn) response_handler,
773 source);
774 source->priv->status = COMMUNICATING;
775 rb_source_notify_status_changed (RB_SOURCE(source));
778 static void
779 rb_lastfm_message_cb (SoupMessage *req, gpointer user_data)
781 RBLastfmSource *source = RB_LASTFM_SOURCE (user_data);
782 char *body;
783 char **pieces;
784 int i;
786 if ((req->response).body == NULL) {
787 rb_debug ("Lastfm: Server failed to respond");
788 return;
791 body = g_malloc0 ((req->response).length + 1);
792 memcpy (body, (req->response).body, (req->response).length);
794 rb_debug ("response body: %s", body);
796 if (strstr (body, "ERROR - no such artist") != NULL) {
797 source->priv->status = NO_ARTIST;
800 g_strstrip (body);
801 pieces = g_strsplit (body, "\n", 6);
802 for (i = 0; pieces[i] != NULL; i++) {
803 gchar **values = g_strsplit (pieces[i], "=", 2);
804 if (strcmp (values[0], "session") == 0) {
805 if (strcmp (values[1], "FAILED") == 0) {
806 source->priv->status = FAILED;
807 rb_debug ("Lastfm failed to connect to the server");
808 break;
810 source->priv->status = OK;
811 source->priv->session = g_strdup (values[1]);
812 rb_debug ("session ID: %s", source->priv->session);
813 source->priv->connected = TRUE;
814 } else if (strcmp (values[0], "stream_url") == 0) {
815 source->priv->stream_url = g_strdup (values[1]);
816 rb_debug ("stream url: %s", source->priv->stream_url);
817 } else if (strcmp (values[0], "subscriber") == 0) {
818 if (strcmp (values[1], "0") == 0) {
819 source->priv->subscriber = FALSE;
820 } else {
821 source->priv->subscriber = TRUE;
823 } else if (strcmp (values[0], "framehack") ==0 ) {
824 if (strcmp (values[1], "0") == 0) {
825 source->priv->framehack = FALSE;
826 } else {
827 source->priv->framehack = TRUE;
829 } else if (strcmp (values[0], "base_url") ==0) {
830 source->priv->base_url = g_strdup (values[1]);
831 } else if (strcmp (values[0], "base_path") ==0) {
832 source->priv->base_path = g_strdup (values[1]);
833 } else if (strcmp (values[0], "update_url") ==0) {
834 source->priv->update_url = g_strdup (values[1]);
835 } else if (strcmp (values[0], "banned") ==0) {
836 if (strcmp (values[1], "0") ==0) {
837 source->priv->banned = FALSE;
838 } else {
839 source->priv->status = BANNED;
840 source->priv->banned = TRUE;
841 source->priv->connected = FALSE;
843 } else if (strcmp (values[0], "response") == 0) {
844 if (strcmp (values[1], "OK") == 0) {
845 source->priv->status = OK;
846 rb_debug ("Successfully communicated");
847 source->priv->connected = TRUE;
848 } else {
849 source->priv->connected = FALSE;
851 } else if (strcmp (values[0], "stationname") == 0) {
852 gchar **data = g_strsplit (g_strdown(pieces[i - 1]), "=",2);
853 RhythmDBEntry *entry;
854 GValue titlestring = {0,};
856 rb_debug ("Received station name from server: %s", values[1]);
857 entry = rhythmdb_entry_lookup_by_location (source->priv->db, data[1]);
858 g_value_init (&titlestring, G_TYPE_STRING);
859 g_value_set_string (&titlestring, values[1]);
861 if (entry == NULL) {
862 entry = rhythmdb_entry_new (source->priv->db, source->priv->entry_type, data[1]);
864 rhythmdb_entry_set (source->priv->db, entry, RHYTHMDB_PROP_TITLE, &titlestring);
865 g_value_unset (&titlestring);
866 rhythmdb_commit (source->priv->db);
871 g_strfreev (pieces);
872 g_free (body);
874 /* doesn't work yet
875 if (source->priv->pending_entry) {
876 rb_shell_player_play_entry (source->priv->shell_player,
877 source->priv->pending_entry,
878 NULL);
879 rhythmdb_entry_unref (source->priv->pending_entry);
880 source->priv->pending_entry = NULL;
884 rb_source_notify_status_changed (RB_SOURCE (source));
885 g_object_unref (source);
888 static void
889 rb_lastfm_change_station (RBLastfmSource *source, const char *station)
891 char *url;
892 if (!source->priv->connected) {
893 rb_lastfm_source_do_handshake (source);
894 return;
897 url = g_strdup_printf("%s/radio/adjust.php?session=%s&url=%s&debug=0",
898 LASTFM_URL,
899 source->priv->session,
900 station);
902 g_object_ref (source);
903 rb_lastfm_perform (source, url, NULL, rb_lastfm_message_cb);
904 g_free (url);
907 static void
908 rb_lastfm_proxy_config_changed_cb (RBProxyConfig *config,
909 RBLastfmSource *source)
911 SoupUri *uri;
913 if (source->priv->soup_session) {
914 uri = rb_proxy_config_get_libsoup_uri (config);
915 g_object_set (G_OBJECT (source->priv->soup_session),
916 "proxy-uri", uri,
917 NULL);
918 if (uri)
919 soup_uri_free (uri);
923 static void
924 rb_lastfm_source_new_station (char *uri, char *title, RBLastfmSource *source)
926 RhythmDBEntry *entry;
927 GValue v = {0,};
929 rb_debug ("adding lastfm: %s, %s",uri, title);
931 entry = rhythmdb_entry_lookup_by_location (source->priv->db, uri);
932 if (entry) {
933 rb_debug ("uri %s already in db", uri);
934 return;
937 entry = rhythmdb_entry_new (source->priv->db, source->priv->entry_type, uri);
938 g_value_init (&v, G_TYPE_STRING);
939 g_value_set_string (&v, title);
940 rhythmdb_entry_set (source->priv->db, entry, RHYTHMDB_PROP_TITLE, &v);
941 g_value_unset (&v);
943 g_value_init (&v, G_TYPE_DOUBLE);
944 g_value_set_double (&v, 0.0);
945 rhythmdb_entry_set (source->priv->db, entry, RHYTHMDB_PROP_RATING, &v);
947 rhythmdb_commit (source->priv->db);
950 static void
951 rb_lastfm_source_dispose (GObject *object)
953 RBLastfmSource *source;
955 source = RB_LASTFM_SOURCE (object);
957 if (source->priv->db) {
958 g_object_unref (source->priv->db);
959 source->priv->db = NULL;
962 G_OBJECT_CLASS (rb_lastfm_source_parent_class)->dispose (object);
965 static void
966 rb_lastfm_source_command (RBLastfmSource *source, const char *query_string, const char *status)
968 char *url;
969 if (!source->priv->connected) {
970 rb_lastfm_source_do_handshake (source);
971 return;
974 url = g_strdup_printf ("%s/radio/control.php?session=%s&debug=0&%s",
975 LASTFM_URL,
976 source->priv->session,
977 query_string);
978 g_object_ref (source);
979 rb_lastfm_perform (source, url, NULL, rb_lastfm_message_cb);
980 g_free (url);
982 rb_source_notify_status_changed (RB_SOURCE (source));
985 static void
986 rb_lastfm_source_love_track (GtkAction *action, RBLastfmSource *source)
988 rb_lastfm_source_command (source, "command=love", _("Marking song loved..."));
991 static void
992 rb_lastfm_source_skip_track (GtkAction *action, RBLastfmSource *source)
994 rb_lastfm_source_command (source, "command=skip", _("Skipping song..."));
997 static void
998 rb_lastfm_source_ban_track (GtkAction *action, RBLastfmSource *source)
1000 rb_lastfm_source_command (source, "command=ban", _("Banning song..."));
1004 static void
1005 rb_lastfm_source_drag_cb (GtkWidget *widget,
1006 GdkDragContext *dc,
1007 gint x, gint y,
1008 GtkSelectionData *selection_data,
1009 guint info, guint time,
1010 RBLastfmSource *source)
1012 impl_receive_drag (RB_SOURCE (source) , selection_data);
1015 static gboolean
1016 impl_receive_drag (RBSource *asource, GtkSelectionData *selection_data)
1018 char *uri;
1019 char *title = NULL;
1020 RBLastfmSource *source = RB_LASTFM_SOURCE (asource);
1022 uri = (char *)selection_data->data;
1023 rb_debug ("parsing uri %s", uri);
1025 if (strstr (uri, "lastfm://") == NULL)
1026 return FALSE;
1028 title = rb_lastfm_source_title_from_uri (uri);
1030 rb_lastfm_source_new_station (uri, title, source);
1031 return TRUE;
1035 static char *
1036 rb_lastfm_source_title_from_uri (char *uri)
1038 char *title = NULL;
1039 char *unesc_title;
1040 gchar **data = g_strsplit (uri, "/", 0);
1042 if (strstr (uri, "globaltags") != NULL)
1043 title = g_strdup_printf (_("Global Tag %s"), data[3]);
1045 if (title == NULL && strcmp (data[2], "artist") == 0) {
1046 /* Check if the station is from an artist page, if not, it is a similar
1047 * artist station, and the server should return a name that change_station
1048 * will handle for us.
1050 if (data[4] != NULL) {
1051 if (strcmp (data[4], "similarartists") == 0)
1052 title = g_strdup_printf (_("Artists similar to %s"), data[3]);
1053 if (strcmp (data[4], "fans") == 0)
1054 title = g_strdup_printf (_("Artists liked by fans of %s"), data[3]);
1059 if (title == NULL && strcmp (data[2], "user") == 0) {
1060 if (strcmp(data[4], "neighbours") == 0)
1061 title = g_strdup_printf (_("%s's Neighbour Radio"), data[3]);
1062 if (strcmp(data[4], "recommended") == 0)
1063 title = g_strdup_printf (_("%s's Recommended Radio: %s percent"), data[3], data[5]);
1064 /* subscriber? */
1067 if (title == NULL) {
1068 title = g_strstrip (uri);
1071 g_strfreev (data);
1072 unesc_title = gnome_vfs_unescape_string (title, NULL);
1073 g_free (title);
1074 return unesc_title;
1077 static void
1078 rb_lastfm_source_entry_added_cb (RhythmDB *db,
1079 RhythmDBEntry *entry,
1080 RBLastfmSource *source)
1082 const char *title;
1083 const char *genre;
1084 GValue v = {0,};
1086 if (rhythmdb_entry_get_entry_type (entry) != source->priv->entry_type)
1087 return;
1089 /* move station name from genre to title */
1091 title = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_TITLE);
1092 if (title != NULL && title[0] != '\0')
1093 return;
1095 genre = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_GENRE);
1096 if (genre == NULL || genre[0] == '\0')
1097 return;
1099 g_value_init (&v, G_TYPE_STRING);
1100 g_value_set_string (&v, genre);
1101 rhythmdb_entry_set (source->priv->db, entry, RHYTHMDB_PROP_TITLE, &v);
1102 g_value_unset (&v);
1104 g_value_init (&v, G_TYPE_STRING);
1105 g_value_set_string (&v, "");
1106 rhythmdb_entry_set (source->priv->db, entry, RHYTHMDB_PROP_GENRE, &v);
1107 g_value_unset (&v);
1109 /* recursive commit? really? */
1110 rhythmdb_commit (source->priv->db);
1113 static void
1114 rb_lastfm_source_add_station_cb (GtkButton *button, gpointer *data)
1116 RBLastfmSource *source = RB_LASTFM_SOURCE (data);
1117 const gchar *add;
1118 char *title;
1119 char *uri;
1120 int selection;
1122 add = gtk_entry_get_text (GTK_ENTRY (source->priv->txtbox));
1123 if (add == NULL || *add == '\0')
1124 return;
1126 selection = gtk_combo_box_get_active (GTK_COMBO_BOX (source->priv->typecombo));
1128 uri = g_strdup_printf(radio_options[selection][1], add);
1129 title = g_strdup_printf(radio_options[selection][2], add);
1130 rb_lastfm_source_new_station (uri, title, source);
1132 gtk_entry_set_text (GTK_ENTRY (source->priv->txtbox), "");
1134 g_free(uri);
1135 g_free(title);
1138 static void
1139 rb_lastfm_source_metadata_cb (SoupMessage *req, RBLastfmSource *source)
1141 char *body;
1142 char **pieces;
1143 int p;
1144 RhythmDBEntry *entry;
1146 entry = rb_shell_player_get_playing_entry (source->priv->shell_player);
1147 if (entry == NULL || rhythmdb_entry_get_entry_type (entry) != source->priv->entry_type) {
1148 rb_debug ("got response to metadata request, but not playing from this source");
1149 return;
1152 rb_debug ("got response to metadata request");
1153 body = g_malloc0 ((req->response).length + 1);
1154 memcpy (body, (req->response).body, (req->response).length);
1156 g_strstrip (body);
1157 pieces = g_strsplit (body, "\n", 0);
1159 for (p = 0; pieces[p] != NULL; p++) {
1160 gchar **values;
1162 values = g_strsplit (pieces[p], "=", 2);
1163 if (strcmp (values[0], "station") == 0) {
1164 } else if (strcmp (values[0], "station_url") == 0) {
1165 } else if (strcmp (values[0], "stationfeed") == 0) {
1166 } else if (strcmp (values[0], "stationfeed_url") == 0) {
1167 } else if (strcmp (values[0], "artist") == 0) {
1168 rb_debug ("artist -> %s", values[1]);
1169 rb_streaming_source_set_streaming_artist (RB_STREAMING_SOURCE (source), values[1]);
1170 } else if (strcmp (values[0], "album") == 0) {
1171 rb_debug ("album -> %s", values[1]);
1172 rb_streaming_source_set_streaming_album (RB_STREAMING_SOURCE (source), values[1]);
1173 } else if (strcmp (values[0], "track") == 0) {
1174 rb_debug ("track -> %s", values[1]);
1175 rb_streaming_source_set_streaming_title (RB_STREAMING_SOURCE (source), values[1]);
1176 } else if (strcmp (values[0], "albumcover_small") == 0) {
1177 } else if (strcmp (values[0], "albumcover_medium") == 0) {
1178 } else if (strcmp (values[0], "albumcover_large") == 0) {
1179 } else if (strcmp (values[0], "trackprogress") == 0) {
1180 } else if (strcmp (values[0], "trackduration") == 0) {
1181 } else if (strcmp (values[0], "artist_url") == 0) {
1182 } else if (strcmp (values[0], "album_url") == 0) {
1183 } else if (strcmp (values[0], "track_url") == 0) {
1184 } else if (strcmp (values[0], "discovery") == 0) {
1185 } else {
1186 rb_debug ("got unknown value: %s", values[0]);
1189 g_strfreev (values);
1192 g_strfreev (pieces);
1193 g_free (body);
1195 source->priv->status = OK;
1196 rb_source_notify_status_changed (RB_SOURCE (source));
1199 static void
1200 rb_lastfm_source_new_song_cb (GObject *player_backend,
1201 gpointer data,
1202 RBLastfmSource *source)
1204 char *uri;
1205 rb_debug ("got new song");
1207 uri = g_strdup_printf ("http://%s%s/np.php?session=%s&debug=0",
1208 source->priv->base_url,
1209 source->priv->base_path,
1210 source->priv->session);
1211 rb_lastfm_perform (source, uri, NULL, (SoupMessageCallbackFn) rb_lastfm_source_metadata_cb);
1212 g_free (uri);
1215 static gboolean
1216 check_entry_type (RBLastfmSource *source, RhythmDBEntry *entry)
1218 RhythmDBEntryType entry_type;
1219 gboolean matches = FALSE;
1221 g_object_get (source, "entry-type", &entry_type, NULL);
1222 if (entry != NULL && rhythmdb_entry_get_entry_type (entry) == entry_type)
1223 matches = TRUE;
1224 g_boxed_free (RHYTHMDB_TYPE_ENTRY_TYPE, entry_type);
1226 return matches;
1229 static void
1230 rb_lastfm_song_changed_cb (RBShellPlayer *player,
1231 RhythmDBEntry *entry,
1232 RBLastfmSource *source)
1234 const char *location;
1236 if (check_entry_type (source, entry)) {
1237 location = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
1238 /* this bit doesn't work */
1240 if (!source->priv->connected) {
1241 rb_lastfm_source_do_handshake (source);
1242 source->priv->pending_entry = rhythmdb_entry_ref (entry);
1243 rb_debug ("will play station %s once connected", location);
1244 } else {
1245 rb_debug ("switching to station %s", location);
1246 rb_lastfm_change_station (source, location);
1249 rb_lastfm_change_station (source, location);
1250 } else {
1251 rb_debug ("non-lastfm entry being played");
1255 static void
1256 impl_activate (RBSource *source)
1258 rb_lastfm_source_do_handshake (RB_LASTFM_SOURCE (source));
1261 static gboolean
1262 impl_show_popup (RBSource *source)
1264 _rb_source_show_popup (source, "/LastfmSourcePopup");
1265 return TRUE;
1268 static void
1269 show_entry_popup (RBEntryView *view,
1270 gboolean over_entry,
1271 RBSource *source)
1273 if (over_entry) {
1274 _rb_source_show_popup (source, "/LastfmSourceViewPopup");
1275 } else {
1276 rb_source_show_popup (source);