udapted vi.po
[rhythmbox.git] / plugins / audioscrobbler / rb-audioscrobbler.c
blobc89a06db664c83471b3b009472ffcb279b78f013
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
3 * arch-tag: Implementation of Rhythmbox Audioscrobbler support
5 * Copyright (C) 2005 Alex Revo <xiphoidappendix@gmail.com>,
6 * Ruben Vermeersch <ruben@Lambda1.be>
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24 #define __EXTENSIONS__
26 #include <errno.h>
28 #include <string.h>
29 #include <time.h>
31 #include <glib.h>
32 #include <glib/gi18n.h>
33 #include <glib/gprintf.h>
34 #include <gtk/gtk.h>
35 #include <gconf/gconf-value.h>
37 #include <libsoup/soup.h>
38 #include <libsoup/soup-uri.h>
40 #include "config.h"
41 #include "eel-gconf-extensions.h"
42 #include "rb-audioscrobbler.h"
43 #include "rb-debug.h"
44 #include "rb-file-helpers.h"
45 #include "rb-glade-helpers.h"
46 #include "rb-preferences.h"
47 #include "rb-shell.h"
48 #include "rb-shell-player.h"
49 #include "rb-source.h"
50 #include "md5.h"
51 #include "rb-proxy-config.h"
52 #include "rb-cut-and-paste-code.h"
55 #define CLIENT_ID "rbx"
56 #define CLIENT_VERSION VERSION
57 #define MAX_QUEUE_SIZE 1000
58 #define MAX_SUBMIT_SIZE 10
59 #define SCROBBLER_URL "http://post.audioscrobbler.com/"
60 #define SCROBBLER_VERSION "1.1"
62 #define EXTRA_URI_ENCODE_CHARS "&+"
64 typedef struct
66 gchar *artist;
67 gchar *album;
68 gchar *title;
69 guint length;
70 gchar *mbid;
71 gchar *timestamp;
72 } AudioscrobblerEntry;
74 struct _RBAudioscrobblerPrivate
76 RBShellPlayer *shell_player;
78 /* Widgets for the prefs pane */
79 GtkWidget *config_widget;
80 GtkWidget *username_entry;
81 GtkWidget *username_label;
82 GtkWidget *password_entry;
83 GtkWidget *password_label;
84 GtkWidget *status_label;
85 GtkWidget *submit_count_label;
86 GtkWidget *submit_time_label;
87 GtkWidget *queue_count_label;
89 /* Data for the prefs pane */
90 guint submit_count;
91 char *submit_time;
92 guint queue_count;
93 enum {
94 STATUS_OK = 0,
95 HANDSHAKING,
96 REQUEST_FAILED,
97 BAD_USERNAME,
98 BAD_PASSWORD,
99 HANDSHAKE_FAILED,
100 CLIENT_UPDATE_REQUIRED,
101 SUBMIT_FAILED,
102 QUEUE_TOO_LONG,
103 GIVEN_UP,
104 } status;
105 char *status_msg;
107 /* Submission queue */
108 GSList *queue;
109 /* Entries currently being submitted */
110 GSList *submission;
112 guint failures;
113 /* Handshake has been done? */
114 gboolean handshake;
115 time_t handshake_next;
116 time_t submit_next;
117 time_t submit_interval;
119 /* Whether this song should be queued once enough of it has been played;
120 * will be set to false when queued, or if the song shouldn't be submitted
122 gboolean should_queue;
123 /* Only write the queue to a file if it has been changed */
124 gboolean queue_changed;
126 /* Authentication cookie + authentication info */
127 gchar *md5_challenge;
128 gchar *username;
129 gchar *password;
130 gchar *submit_url;
132 /* Currently playing song info, will be queued if priv->should_queue == TRUE */
133 gchar *artist;
134 gchar *album;
135 gchar *title;
136 gchar *mbid;
137 guint duration;
138 guint elapsed;
140 /* Preference notifications */
141 guint notification_username_id;
142 guint notification_password_id;
144 guint timeout_id;
146 /* HTTP requests session */
147 SoupSession *soup_session;
148 RBProxyConfig *proxy_config;
151 #define RB_AUDIOSCROBBLER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_AUDIOSCROBBLER, RBAudioscrobblerPrivate))
154 static void audioscrobbler_entry_init (AudioscrobblerEntry *entry);
155 static void audioscrobbler_entry_free (AudioscrobblerEntry *entry);
157 static gboolean rb_audioscrobbler_load_queue (RBAudioscrobbler *audioscrobbler);
158 static int rb_audioscrobbler_save_queue (RBAudioscrobbler *audioscrobbler);
159 static void rb_audioscrobbler_print_queue (RBAudioscrobbler *audioscrobbler, gboolean submission);
160 static void rb_audioscrobbler_free_queue_entries (RBAudioscrobbler *audioscrobbler, GSList **queue);
162 static void rb_audioscrobbler_class_init (RBAudioscrobblerClass *klass);
163 static void rb_audioscrobbler_init (RBAudioscrobbler *audioscrobbler);
164 static void rb_audioscrobbler_get_property (GObject *object,
165 guint prop_id,
166 GValue *value,
167 GParamSpec *pspec);
168 static void rb_audioscrobbler_set_property (GObject *object,
169 guint prop_id,
170 const GValue *value,
171 GParamSpec *pspec);
172 static void rb_audioscrobbler_finalize (GObject *object);
174 static void rb_audioscrobbler_add_timeout (RBAudioscrobbler *audioscrobbler);
175 static gboolean rb_audioscrobbler_timeout_cb (RBAudioscrobbler *audioscrobbler);
177 static gchar * mkmd5 (char *string);
178 static void rb_audioscrobbler_parse_response (RBAudioscrobbler *audioscrobbler, SoupMessage *msg);
179 static void rb_audioscrobbler_perform (RBAudioscrobbler *audioscrobbler,
180 char *url,
181 char *post_data,
182 SoupMessageCallbackFn response_handler);
183 static void rb_audioscrobbler_do_handshake (RBAudioscrobbler *audioscrobbler);
184 static void rb_audioscrobbler_do_handshake_cb (SoupMessage *msg, gpointer user_data);
185 static void rb_audioscrobbler_submit_queue (RBAudioscrobbler *audioscrobbler);
186 static void rb_audioscrobbler_submit_queue_cb (SoupMessage *msg, gpointer user_data);
188 static void rb_audioscrobbler_import_settings (RBAudioscrobbler *audioscrobbler);
189 static void rb_audioscrobbler_preferences_sync (RBAudioscrobbler *audioscrobbler);
191 static void rb_audioscrobbler_gconf_changed_cb (GConfClient *client,
192 guint cnxn_id,
193 GConfEntry *entry,
194 RBAudioscrobbler *audioscrobbler);
195 static void rb_audioscrobbler_song_changed_cb (RBShellPlayer *player,
196 RhythmDBEntry *entry,
197 RBAudioscrobbler *audioscrobbler);
198 static void rb_audioscrobbler_proxy_config_changed_cb (RBProxyConfig *config,
199 RBAudioscrobbler *audioscrobbler);
200 enum
202 PROP_0,
203 PROP_SHELL_PLAYER,
204 PROP_PROXY_CONFIG
207 G_DEFINE_TYPE (RBAudioscrobbler, rb_audioscrobbler, G_TYPE_OBJECT)
211 /* Class-related functions: */
212 static void
213 rb_audioscrobbler_class_init (RBAudioscrobblerClass *klass)
215 GObjectClass *object_class = G_OBJECT_CLASS (klass);
217 object_class->finalize = rb_audioscrobbler_finalize;
219 object_class->set_property = rb_audioscrobbler_set_property;
220 object_class->get_property = rb_audioscrobbler_get_property;
222 g_object_class_install_property (object_class,
223 PROP_SHELL_PLAYER,
224 g_param_spec_object ("shell-player",
225 "RBShellPlayer",
226 "RBShellPlayer object",
227 RB_TYPE_SHELL_PLAYER,
228 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
229 g_object_class_install_property (object_class,
230 PROP_PROXY_CONFIG,
231 g_param_spec_object ("proxy-config",
232 "RBProxyConfig",
233 "RBProxyConfig object",
234 RB_TYPE_PROXY_CONFIG,
235 G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
237 g_type_class_add_private (klass, sizeof (RBAudioscrobblerPrivate));
240 static void
241 rb_audioscrobbler_init (RBAudioscrobbler *audioscrobbler)
243 rb_debug ("Initialising Audioscrobbler");
244 rb_debug ("Plugin ID: %s, Version %s (Protocol %s)",
245 CLIENT_ID, CLIENT_VERSION, SCROBBLER_VERSION);
247 audioscrobbler->priv = RB_AUDIOSCROBBLER_GET_PRIVATE (audioscrobbler);
249 audioscrobbler->priv->queue = NULL;
250 audioscrobbler->priv->submission= NULL;
251 audioscrobbler->priv->failures = 0;
252 audioscrobbler->priv->handshake = FALSE;
253 audioscrobbler->priv->handshake_next = 0;
254 audioscrobbler->priv->submit_next = 0;
255 audioscrobbler->priv->should_queue = FALSE;
256 audioscrobbler->priv->md5_challenge = g_strdup ("");
257 audioscrobbler->priv->username = g_strdup ("");
258 audioscrobbler->priv->password = g_strdup ("");
259 audioscrobbler->priv->submit_url = g_strdup ("");
260 audioscrobbler->priv->artist = g_strdup ("");
261 audioscrobbler->priv->album = g_strdup ("");
262 audioscrobbler->priv->title = g_strdup ("");
263 audioscrobbler->priv->mbid = g_strdup ("");
264 audioscrobbler->priv->duration = 0;
265 audioscrobbler->priv->elapsed = 0;
266 audioscrobbler->priv->timeout_id = 0;
268 rb_audioscrobbler_load_queue (audioscrobbler);
270 rb_audioscrobbler_import_settings (audioscrobbler);
272 /* gconf notifications: */
273 audioscrobbler->priv->notification_username_id =
274 eel_gconf_notification_add (CONF_AUDIOSCROBBLER_USERNAME,
275 (GConfClientNotifyFunc) rb_audioscrobbler_gconf_changed_cb,
276 audioscrobbler);
277 audioscrobbler->priv->notification_password_id =
278 eel_gconf_notification_add (CONF_AUDIOSCROBBLER_PASSWORD,
279 (GConfClientNotifyFunc) rb_audioscrobbler_gconf_changed_cb,
280 audioscrobbler);
282 rb_audioscrobbler_preferences_sync (audioscrobbler);
285 static void
286 rb_audioscrobbler_finalize (GObject *object)
288 RBAudioscrobbler *audioscrobbler;
290 rb_debug ("Finalizing Audioscrobbler");
292 g_return_if_fail (object != NULL);
293 g_return_if_fail (RB_IS_AUDIOSCROBBLER (object));
295 audioscrobbler = RB_AUDIOSCROBBLER (object);
297 g_return_if_fail (audioscrobbler->priv != NULL);
299 /* Save any remaining entries */
300 rb_audioscrobbler_save_queue (audioscrobbler);
302 eel_gconf_notification_remove (audioscrobbler->priv->notification_username_id);
303 eel_gconf_notification_remove (audioscrobbler->priv->notification_password_id);
305 g_source_remove (audioscrobbler->priv->timeout_id);
307 g_free (audioscrobbler->priv->md5_challenge);
308 g_free (audioscrobbler->priv->username);
309 g_free (audioscrobbler->priv->password);
310 g_free (audioscrobbler->priv->submit_url);
311 g_free (audioscrobbler->priv->artist);
312 g_free (audioscrobbler->priv->album);
313 g_free (audioscrobbler->priv->title);
314 g_free (audioscrobbler->priv->mbid);
315 if (audioscrobbler->priv->soup_session) {
316 soup_session_abort (audioscrobbler->priv->soup_session);
317 g_object_unref (G_OBJECT (audioscrobbler->priv->soup_session));
319 g_object_unref (G_OBJECT (audioscrobbler->priv->proxy_config));
320 g_object_unref (G_OBJECT (audioscrobbler->priv->shell_player));
322 rb_audioscrobbler_free_queue_entries (audioscrobbler, &audioscrobbler->priv->queue);
323 rb_audioscrobbler_free_queue_entries (audioscrobbler, &audioscrobbler->priv->submission);
325 G_OBJECT_CLASS (rb_audioscrobbler_parent_class)->finalize (object);
328 RBAudioscrobbler*
329 rb_audioscrobbler_new (RBShellPlayer *shell_player,
330 RBProxyConfig *proxy_config)
332 return g_object_new (RB_TYPE_AUDIOSCROBBLER,
333 "shell-player", shell_player,
334 "proxy-config", proxy_config,
335 NULL);
338 static void
339 rb_audioscrobbler_set_property (GObject *object,
340 guint prop_id,
341 const GValue *value,
342 GParamSpec *pspec)
344 RBAudioscrobbler *audioscrobbler = RB_AUDIOSCROBBLER (object);
346 switch (prop_id) {
347 case PROP_SHELL_PLAYER:
348 audioscrobbler->priv->shell_player = g_value_get_object (value);
349 g_object_ref (G_OBJECT (audioscrobbler->priv->shell_player));
350 g_signal_connect_object (G_OBJECT (audioscrobbler->priv->shell_player),
351 "playing-song-changed",
352 G_CALLBACK (rb_audioscrobbler_song_changed_cb),
353 audioscrobbler, 0);
354 break;
355 case PROP_PROXY_CONFIG:
356 audioscrobbler->priv->proxy_config = g_value_get_object (value);
357 g_object_ref (G_OBJECT (audioscrobbler->priv->proxy_config));
358 g_signal_connect_object (G_OBJECT (audioscrobbler->priv->proxy_config),
359 "config-changed",
360 G_CALLBACK (rb_audioscrobbler_proxy_config_changed_cb),
361 audioscrobbler, 0);
362 break;
363 default:
364 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
365 break;
369 static void
370 rb_audioscrobbler_get_property (GObject *object,
371 guint prop_id,
372 GValue *value,
373 GParamSpec *pspec)
375 RBAudioscrobbler *audioscrobbler = RB_AUDIOSCROBBLER (object);
377 switch (prop_id) {
378 case PROP_SHELL_PLAYER:
379 g_value_set_object (value, audioscrobbler->priv->shell_player);
380 break;
381 default:
382 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
383 break;
387 /* Add the audioscrobbler thread timer */
388 static void
389 rb_audioscrobbler_add_timeout (RBAudioscrobbler *audioscrobbler)
391 if (!audioscrobbler->priv->timeout_id) {
392 rb_debug ("Adding Audioscrobbler timer (15 seconds)");
393 audioscrobbler->priv->timeout_id =
394 g_timeout_add (15000, (GSourceFunc) rb_audioscrobbler_timeout_cb,
395 audioscrobbler);
399 /* updates the queue and submits entries as required */
400 static gboolean
401 rb_audioscrobbler_timeout_cb (RBAudioscrobbler *audioscrobbler)
403 guint elapsed;
404 int elapsed_delta;
406 /* should we add this song to the queue? */
407 if (audioscrobbler->priv->should_queue) {
409 rb_shell_player_get_playing_time (audioscrobbler->priv->shell_player, &elapsed, NULL);
410 elapsed_delta = elapsed - audioscrobbler->priv->elapsed;
411 audioscrobbler->priv->elapsed = elapsed;
413 if ((elapsed >= audioscrobbler->priv->duration / 2 || elapsed >= 240) && elapsed_delta < 20) {
415 /* Add song to queue, if the queue isn't too long already */
416 if (g_slist_length (audioscrobbler->priv->queue) < MAX_QUEUE_SIZE) {
418 AudioscrobblerEntry *entry = g_new0 (AudioscrobblerEntry, 1);
419 time_t tt;
421 time (&tt);
423 rb_debug ("Adding song to queue");
425 entry->artist = soup_uri_encode (audioscrobbler->priv->artist, EXTRA_URI_ENCODE_CHARS);
426 if (strcmp (audioscrobbler->priv->album, _("Unknown")) != 0)
427 entry->album = soup_uri_encode (audioscrobbler->priv->album, EXTRA_URI_ENCODE_CHARS);
428 else
429 entry->album = g_strdup ("");
430 entry->title = soup_uri_encode (audioscrobbler->priv->title, EXTRA_URI_ENCODE_CHARS);
431 entry->mbid = soup_uri_encode (audioscrobbler->priv->mbid, EXTRA_URI_ENCODE_CHARS);
432 entry->length = audioscrobbler->priv->duration;
433 entry->timestamp = g_new0 (gchar, 30);
434 strftime (entry->timestamp, 30, "%Y%%2D%m%%2D%d%%20%H%%3A%M%%3A%S", gmtime (&tt));
436 audioscrobbler->priv->queue = g_slist_append (audioscrobbler->priv->queue,
437 entry);
438 audioscrobbler->priv->queue_changed = TRUE;
439 audioscrobbler->priv->queue_count++;
440 } else {
441 rb_debug ("Queue is too long. Not adding song to queue");
442 g_free (audioscrobbler->priv->status_msg);
443 audioscrobbler->priv->status = QUEUE_TOO_LONG;
444 audioscrobbler->priv->status_msg = NULL;
447 /* Mark current track as having been queued. */
448 audioscrobbler->priv->should_queue = FALSE;
450 rb_audioscrobbler_preferences_sync (audioscrobbler);
451 } else if (elapsed_delta > 20) {
452 rb_debug ("Skipping detected; not submitting current song");
453 /* not sure about this - what if I skip to somewhere towards
454 * the end, but then go back and listen to the whole song?
456 audioscrobbler->priv->should_queue = FALSE;
460 /* do handshake if we need to */
461 if (! audioscrobbler->priv->handshake &&
462 time (NULL) > audioscrobbler->priv->handshake_next &&
463 strcmp (audioscrobbler->priv->username, "") != 0) {
464 rb_audioscrobbler_do_handshake (audioscrobbler);
467 /* if there's something in the queue, submit it if we can, save it otherwise */
468 if (audioscrobbler->priv->queue != NULL) {
469 if (audioscrobbler->priv->handshake)
470 rb_audioscrobbler_submit_queue (audioscrobbler);
471 else
472 rb_audioscrobbler_save_queue (audioscrobbler);
474 return TRUE;
478 /* Audioscrobbler functions: */
479 static gchar *
480 mkmd5 (char *string)
482 md5_state_t md5state;
483 guchar md5pword[16];
484 gchar md5_response[33];
486 int j = 0;
488 memset (md5_response, 0, sizeof (md5_response));
490 md5_init (&md5state);
491 md5_append (&md5state, (unsigned char*)string, strlen (string));
492 md5_finish (&md5state, md5pword);
494 for (j = 0; j < 16; j++) {
495 char a[3];
496 sprintf (a, "%02x", md5pword[j]);
497 md5_response[2*j] = a[0];
498 md5_response[2*j+1] = a[1];
501 return (g_strdup (md5_response));
504 static void
505 rb_audioscrobbler_parse_response (RBAudioscrobbler *audioscrobbler, SoupMessage *msg)
507 rb_debug ("Parsing response, status=%d", msg->status_code);
509 if (SOUP_STATUS_IS_SUCCESSFUL (msg->status_code) && (msg->response).body != NULL) {
510 gchar *body;
511 gchar **breaks;
513 body = g_malloc0 ((msg->response).length + 1);
514 memcpy (body, (msg->response).body, (msg->response).length);
516 g_strstrip (body);
517 breaks = g_strsplit (body, "\n", 4);
518 int i;
520 g_free (audioscrobbler->priv->status_msg);
521 audioscrobbler->priv->status = STATUS_OK;
522 audioscrobbler->priv->status_msg = NULL;
523 for (i = 0; breaks[i] != NULL; i++) {
524 rb_debug ("RESPONSE: %s", breaks[i]);
525 if (g_str_has_prefix (breaks[i], "UPTODATE")) {
526 rb_debug ("UPTODATE");
528 if (breaks[i+1] != NULL) {
529 g_free (audioscrobbler->priv->md5_challenge);
530 audioscrobbler->priv->md5_challenge = g_strdup (breaks[i+1]);
531 rb_debug ("MD5 challenge: \"%s\"", audioscrobbler->priv->md5_challenge);
533 if (breaks[i+2] != NULL) {
534 g_free (audioscrobbler->priv->submit_url);
535 audioscrobbler->priv->submit_url = g_strdup (breaks[i+2]);
536 rb_debug ("Submit URL: \"%s\"", audioscrobbler->priv->submit_url);
537 i++;
539 i++;
542 } else if (g_str_has_prefix (breaks[i], "UPDATE")) {
543 rb_debug ("UPDATE");
544 audioscrobbler->priv->status = CLIENT_UPDATE_REQUIRED;
546 if (breaks[i+1] != NULL) {
547 g_free (audioscrobbler->priv->md5_challenge);
548 audioscrobbler->priv->md5_challenge = g_strdup (breaks[i+1]);
549 rb_debug ("MD5 challenge: \"%s\"", audioscrobbler->priv->md5_challenge);
551 if (breaks[i+2] != NULL) {
552 g_free (audioscrobbler->priv->submit_url);
553 audioscrobbler->priv->submit_url = g_strdup (breaks[i+2]);
554 rb_debug ("Submit URL: \"%s\"", audioscrobbler->priv->submit_url);
555 i++;
557 i++;
560 } else if (g_str_has_prefix (breaks[i], "FAILED")) {
561 audioscrobbler->priv->status = HANDSHAKE_FAILED;
563 if (strlen (breaks[i]) > 7) {
564 rb_debug ("FAILED: \"%s\"", breaks[i] + 7);
565 audioscrobbler->priv->status_msg = g_strdup (breaks[i] + 7);
566 } else {
567 rb_debug ("FAILED");
571 } else if (g_str_has_prefix (breaks[i], "BADUSER")) {
572 rb_debug ("BADUSER");
573 audioscrobbler->priv->status = BAD_USERNAME;
574 } else if (g_str_has_prefix (breaks[i], "BADAUTH")) {
575 rb_debug ("BADAUTH");
576 audioscrobbler->priv->status = BAD_PASSWORD;
577 } else if (g_str_has_prefix (breaks[i], "OK")) {
578 rb_debug ("OK");
579 } else if (g_str_has_prefix (breaks[i], "INTERVAL ")) {
580 audioscrobbler->priv->submit_interval = g_ascii_strtod(breaks[i] + 9, NULL);
581 rb_debug ("INTERVAL: %s", breaks[i] + 9);
585 /* respect the last submit interval we were given */
586 if (audioscrobbler->priv->submit_interval > 0)
587 audioscrobbler->priv->submit_next = time(NULL) + audioscrobbler->priv->submit_interval;
589 g_strfreev (breaks);
590 g_free (body);
591 } else {
592 audioscrobbler->priv->status = REQUEST_FAILED;
593 audioscrobbler->priv->status_msg = g_strdup (soup_status_get_phrase (msg->status_code));
598 * NOTE: the caller *must* unref the audioscrobbler object in the callback
600 static void
601 rb_audioscrobbler_perform (RBAudioscrobbler *audioscrobbler,
602 char *url,
603 char *post_data,
604 SoupMessageCallbackFn response_handler)
606 SoupMessage *msg;
608 msg = soup_message_new (post_data == NULL ? "GET" : "POST", url);
610 if (post_data != NULL) {
611 rb_debug ("Submitting to Audioscrobbler: %s", post_data);
612 soup_message_set_request (msg,
613 "application/x-www-form-urlencoded",
614 SOUP_BUFFER_SYSTEM_OWNED,
615 post_data,
616 strlen (post_data));
619 /* create soup session, if we haven't got one yet */
620 if (!audioscrobbler->priv->soup_session) {
621 SoupUri *uri;
623 uri = rb_proxy_config_get_libsoup_uri (audioscrobbler->priv->proxy_config);
624 audioscrobbler->priv->soup_session = soup_session_async_new_with_options (
625 "proxy-uri", uri,
626 NULL);
627 if (uri)
628 soup_uri_free (uri);
631 soup_session_queue_message (audioscrobbler->priv->soup_session,
632 msg,
633 response_handler,
634 g_object_ref (audioscrobbler));
637 static void
638 rb_audioscrobbler_do_handshake (RBAudioscrobbler *audioscrobbler)
640 /* Perform handshake if necessary. Only perform handshake if
641 * - we have no current handshake; AND
642 * - we have waited the appropriate amount of time between
643 * handshakes; AND
644 * - we have a username
647 if (! audioscrobbler->priv->handshake &&
648 time (NULL) >= audioscrobbler->priv->handshake_next &&
649 strcmp (audioscrobbler->priv->username, "") != 0) {
650 gchar *username;
651 gchar *url;
653 username = soup_uri_encode (audioscrobbler->priv->username, EXTRA_URI_ENCODE_CHARS);
654 url = g_strdup_printf ("%s?hs=true&p=%s&c=%s&v=%s&u=%s",
655 SCROBBLER_URL,
656 SCROBBLER_VERSION,
657 CLIENT_ID,
658 CLIENT_VERSION,
659 username);
660 g_free (username);
662 /* Make sure we wait at least 30 minutes between handshakes. */
663 audioscrobbler->priv->handshake_next = time (NULL) + 1800;
665 rb_debug ("Performing handshake with Audioscrobbler server: %s", url);
667 audioscrobbler->priv->status = HANDSHAKING;
668 rb_audioscrobbler_preferences_sync (audioscrobbler);
670 rb_audioscrobbler_perform (audioscrobbler, url, NULL, rb_audioscrobbler_do_handshake_cb);
672 g_free (url);
673 } else {
674 rb_debug ("Will not attempt handshake:");
675 if (audioscrobbler->priv->handshake)
676 rb_debug ("We already have a valid handshake");
677 if (time (NULL) < audioscrobbler->priv->handshake_next)
678 rb_debug ("time=%lu; handshake_next=%lu",
679 time (NULL),
680 audioscrobbler->priv->handshake_next);
681 if (strcmp (audioscrobbler->priv->username, "") == 0)
682 rb_debug ("Username not set");
687 static void
688 rb_audioscrobbler_do_handshake_cb (SoupMessage *msg, gpointer user_data)
690 RBAudioscrobbler *audioscrobbler = RB_AUDIOSCROBBLER(user_data);
692 rb_debug ("Handshake response");
693 rb_audioscrobbler_parse_response (audioscrobbler, msg);
694 rb_audioscrobbler_preferences_sync (audioscrobbler);
696 switch (audioscrobbler->priv->status) {
697 case STATUS_OK:
698 case CLIENT_UPDATE_REQUIRED:
699 audioscrobbler->priv->handshake = TRUE;
700 audioscrobbler->priv->failures = 0;
701 break;
702 default:
703 rb_debug ("Handshake failed");
704 ++audioscrobbler->priv->failures;
705 break;
708 g_object_unref (audioscrobbler);
711 static void
712 rb_audioscrobbler_submit_queue (RBAudioscrobbler *audioscrobbler)
714 /* Conditions:
715 * - Must have username and password
716 * - Must have md5_challenge
717 * - Queue must not be empty
720 time_t now;
721 time(&now);
723 if (strcmp (audioscrobbler->priv->username, "") != 0 &&
724 strcmp (audioscrobbler->priv->password, "") != 0 &&
725 strcmp (audioscrobbler->priv->md5_challenge, "") != 0 &&
726 now > audioscrobbler->priv->submit_next &&
727 audioscrobbler->priv->queue != NULL) {
728 GSList *l;
730 int i = 0;
732 gchar *md5_password = mkmd5 (audioscrobbler->priv->password);
733 gchar *md5_temp = g_strconcat (md5_password,
734 audioscrobbler->priv->md5_challenge,
735 NULL);
736 gchar *md5_response = mkmd5 (md5_temp);
738 gchar *username = soup_uri_encode (audioscrobbler->priv->username, EXTRA_URI_ENCODE_CHARS);
739 gchar *post_data = g_strdup_printf ("u=%s&s=%s&", username, md5_response);
741 g_free (md5_password);
742 g_free (md5_temp);
743 g_free (md5_response);
744 g_free (username);
746 do {
747 AudioscrobblerEntry *entry;
749 /* remove first queue entry */
750 l = audioscrobbler->priv->queue;
751 audioscrobbler->priv->queue = g_slist_remove_link (l, l);
752 entry = (AudioscrobblerEntry *)l->data;
754 gchar *new = g_strdup_printf ("%sa[%d]=%s&t[%d]=%s&b[%d]=%s&m[%d]=%s&l[%d]=%d&i[%d]=%s&",
755 post_data,
756 i, entry->artist,
757 i, entry->title,
758 i, entry->album,
759 i, entry->mbid,
760 i, entry->length,
761 i, entry->timestamp);
763 g_free (post_data);
764 post_data = new;
766 /* add to submission list */
767 audioscrobbler->priv->submission = g_slist_concat (audioscrobbler->priv->submission, l);
768 i++;
769 } while (audioscrobbler->priv->queue && (i < MAX_SUBMIT_SIZE));
771 rb_debug ("Submitting queue to Audioscrobbler");
772 rb_audioscrobbler_print_queue (audioscrobbler, TRUE);
774 rb_audioscrobbler_perform (audioscrobbler,
775 audioscrobbler->priv->submit_url,
776 post_data,
777 rb_audioscrobbler_submit_queue_cb);
779 /* libsoup will free post_data when the request is finished */
780 } else {
781 rb_debug ("Not submitting queue because:");
782 if (strcmp (audioscrobbler->priv->username, "") == 0)
783 rb_debug ("Blank username");
784 if (strcmp (audioscrobbler->priv->password, "") == 0)
785 rb_debug ("Blank password");
786 if (strcmp (audioscrobbler->priv->md5_challenge, "") == 0)
787 rb_debug ("Blank md5_challenge");
788 if (now <= audioscrobbler->priv->submit_next)
789 rb_debug ("Too soon (next submission in %ld seconds)", audioscrobbler->priv->submit_next - now);
790 if (!audioscrobbler->priv->queue)
791 rb_debug ("Queue is empty");
795 static void
796 rb_audioscrobbler_submit_queue_cb (SoupMessage *msg, gpointer user_data)
798 RBAudioscrobbler *audioscrobbler = RB_AUDIOSCROBBLER (user_data);
800 rb_debug ("Submission response");
801 rb_audioscrobbler_parse_response (audioscrobbler, msg);
803 if (audioscrobbler->priv->status == STATUS_OK) {
804 rb_debug ("Queue submitted successfully");
805 rb_audioscrobbler_free_queue_entries (audioscrobbler, &audioscrobbler->priv->submission);
806 rb_audioscrobbler_save_queue (audioscrobbler);
808 audioscrobbler->priv->submit_count += audioscrobbler->priv->queue_count;
809 audioscrobbler->priv->queue_count = 0;
811 g_free (audioscrobbler->priv->submit_time);
812 audioscrobbler->priv->submit_time = rb_utf_friendly_time (time (NULL));
813 } else {
814 ++audioscrobbler->priv->failures;
816 /* add failed submission entries back to queue */
817 audioscrobbler->priv->queue =
818 g_slist_concat (audioscrobbler->priv->submission, audioscrobbler->priv->queue);
819 audioscrobbler->priv->submission = NULL;
820 rb_audioscrobbler_save_queue (audioscrobbler);
822 rb_audioscrobbler_print_queue (audioscrobbler, FALSE);
824 if (audioscrobbler->priv->failures >= 3) {
825 rb_debug ("Queue submission has failed %d times; caching tracks locally",
826 audioscrobbler->priv->failures);
827 g_free (audioscrobbler->priv->status_msg);
829 audioscrobbler->priv->handshake = FALSE;
830 audioscrobbler->priv->status = GIVEN_UP;
831 audioscrobbler->priv->status_msg = NULL;
832 } else {
833 rb_debug ("Queue submission failed %d times", audioscrobbler->priv->failures);
837 rb_audioscrobbler_preferences_sync (audioscrobbler);
838 g_object_unref (audioscrobbler);
841 /* Configuration functions: */
842 static void
843 rb_audioscrobbler_import_settings (RBAudioscrobbler *audioscrobbler)
845 /* import gconf settings. */
846 g_free (audioscrobbler->priv->username);
847 g_free (audioscrobbler->priv->password);
848 audioscrobbler->priv->username = eel_gconf_get_string (CONF_AUDIOSCROBBLER_USERNAME);
849 audioscrobbler->priv->password = eel_gconf_get_string (CONF_AUDIOSCROBBLER_PASSWORD);
851 rb_audioscrobbler_add_timeout (audioscrobbler);
852 audioscrobbler->priv->status = HANDSHAKING;
854 audioscrobbler->priv->submit_time = g_strdup (_("Never"));
857 static void
858 rb_audioscrobbler_preferences_sync (RBAudioscrobbler *audioscrobbler)
860 const char *status;
861 char *free_this = NULL;
863 if (!audioscrobbler->priv->config_widget)
864 return;
866 rb_debug ("Syncing data with preferences window");
867 gtk_entry_set_text (GTK_ENTRY (audioscrobbler->priv->username_entry),
868 audioscrobbler->priv->username);
869 gtk_entry_set_text (GTK_ENTRY (audioscrobbler->priv->password_entry),
870 audioscrobbler->priv->password);
872 switch (audioscrobbler->priv->status) {
873 case STATUS_OK:
874 status = _("OK");
875 break;
876 case HANDSHAKING:
877 status = _("Logging in");
878 break;
879 case REQUEST_FAILED:
880 status = _("Request failed");
881 break;
882 case BAD_USERNAME:
883 status = _("Incorrect username");
884 break;
885 case BAD_PASSWORD:
886 status = _("Incorrect password");
887 break;
888 case HANDSHAKE_FAILED:
889 status = _("Handshake failed");
890 break;
891 case CLIENT_UPDATE_REQUIRED:
892 status = _("Client update required");
893 break;
894 case SUBMIT_FAILED:
895 status = _("Track submission failed");
896 break;
897 case QUEUE_TOO_LONG:
898 status = _("Queue is too long");
899 break;
900 case GIVEN_UP:
901 status = _("Track submission failed too many times");
902 break;
903 default:
904 g_assert_not_reached ();
905 break;
908 if (audioscrobbler->priv->status_msg && audioscrobbler->priv->status_msg[0] != '\0') {
909 free_this = g_strdup_printf ("%s: %s", status, audioscrobbler->priv->status_msg);
910 status = free_this;
913 gtk_label_set_text (GTK_LABEL (audioscrobbler->priv->status_label),
914 status);
915 g_free (free_this);
917 free_this = g_strdup_printf ("%u", audioscrobbler->priv->submit_count);
918 gtk_label_set_text (GTK_LABEL (audioscrobbler->priv->submit_count_label), free_this);
919 g_free (free_this);
921 free_this = g_strdup_printf ("%u", audioscrobbler->priv->queue_count);
922 gtk_label_set_text (GTK_LABEL (audioscrobbler->priv->queue_count_label), free_this);
923 g_free (free_this);
925 gtk_label_set_text (GTK_LABEL (audioscrobbler->priv->submit_time_label),
926 audioscrobbler->priv->submit_time);
929 GtkWidget *
930 rb_audioscrobbler_get_config_widget (RBAudioscrobbler *audioscrobbler)
932 GladeXML *xml;
934 if (audioscrobbler->priv->config_widget) {
935 return audioscrobbler->priv->config_widget;
938 xml = rb_glade_xml_new ("audioscrobbler-prefs.glade", "audioscrobbler_vbox", audioscrobbler);
939 audioscrobbler->priv->config_widget = glade_xml_get_widget (xml, "audioscrobbler_vbox");
940 audioscrobbler->priv->username_entry = glade_xml_get_widget (xml, "username_entry");
941 audioscrobbler->priv->username_label = glade_xml_get_widget (xml, "username_label");
942 audioscrobbler->priv->password_entry = glade_xml_get_widget (xml, "password_entry");
943 audioscrobbler->priv->password_label = glade_xml_get_widget (xml, "password_label");
944 audioscrobbler->priv->status_label = glade_xml_get_widget (xml, "status_label");
945 audioscrobbler->priv->queue_count_label = glade_xml_get_widget (xml, "queue_count_label");
946 audioscrobbler->priv->submit_count_label = glade_xml_get_widget (xml, "submit_count_label");
947 audioscrobbler->priv->submit_time_label = glade_xml_get_widget (xml, "submit_time_label");
949 rb_glade_boldify_label (xml, "audioscrobbler_label");
951 g_object_unref (G_OBJECT (xml));
953 rb_audioscrobbler_preferences_sync (audioscrobbler);
955 return audioscrobbler->priv->config_widget;
959 /* Callback functions: */
961 static void
962 rb_audioscrobbler_proxy_config_changed_cb (RBProxyConfig *config,
963 RBAudioscrobbler *audioscrobbler)
965 SoupUri *uri;
967 if (audioscrobbler->priv->soup_session) {
968 uri = rb_proxy_config_get_libsoup_uri (config);
969 g_object_set (G_OBJECT (audioscrobbler->priv->soup_session),
970 "proxy-uri", uri,
971 NULL);
972 if (uri)
973 soup_uri_free (uri);
977 static void
978 rb_audioscrobbler_gconf_changed_cb (GConfClient *client,
979 guint cnxn_id,
980 GConfEntry *entry,
981 RBAudioscrobbler *audioscrobbler)
983 rb_debug ("GConf key updated: \"%s\"", entry->key);
984 if (strcmp (entry->key, CONF_AUDIOSCROBBLER_USERNAME) == 0) {
985 g_free (audioscrobbler->priv->username);
986 audioscrobbler->priv->username = g_strdup (gconf_value_get_string (entry->value));
988 if (audioscrobbler->priv->username_entry)
989 gtk_entry_set_text (GTK_ENTRY (audioscrobbler->priv->username_entry),
990 gconf_value_get_string (entry->value));
992 audioscrobbler->priv->handshake = FALSE;
993 } else if (strcmp (entry->key, CONF_AUDIOSCROBBLER_PASSWORD) == 0) {
994 g_free (audioscrobbler->priv->password);
995 audioscrobbler->priv->password = g_strdup (gconf_value_get_string (entry->value));
997 if (audioscrobbler->priv->password_entry)
998 gtk_entry_set_text (GTK_ENTRY (audioscrobbler->priv->password_entry),
999 gconf_value_get_string (entry->value));
1000 } else {
1001 rb_debug ("Unhandled GConf key updated: \"%s\"", entry->key);
1005 static void
1006 rb_audioscrobbler_song_changed_cb (RBShellPlayer *player,
1007 RhythmDBEntry *entry,
1008 RBAudioscrobbler *audioscrobbler)
1010 RhythmDBEntryType type;
1012 if (entry == NULL) {
1013 audioscrobbler->priv->should_queue = FALSE;
1014 return;
1017 type = rhythmdb_entry_get_entry_type (entry);
1018 if (type->category != RHYTHMDB_ENTRY_NORMAL ||
1019 type == RHYTHMDB_ENTRY_TYPE_PODCAST_POST ||
1020 rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_PLAYBACK_ERROR) != NULL) {
1021 audioscrobbler->priv->should_queue = FALSE;
1022 return;
1025 audioscrobbler->priv->title = rhythmdb_entry_dup_string (entry, RHYTHMDB_PROP_TITLE);
1026 audioscrobbler->priv->artist = rhythmdb_entry_dup_string (entry, RHYTHMDB_PROP_ARTIST);
1027 audioscrobbler->priv->album = rhythmdb_entry_dup_string (entry, RHYTHMDB_PROP_ALBUM);
1028 audioscrobbler->priv->duration = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DURATION);
1029 audioscrobbler->priv->mbid = rhythmdb_entry_dup_string (entry, RHYTHMDB_PROP_MUSICBRAINZ_TRACKID);
1030 guint time;
1031 rb_shell_player_get_playing_time (audioscrobbler->priv->shell_player, &time, NULL);
1032 audioscrobbler->priv->elapsed = (int) time;
1034 /* Ignore tracks that violate the specification (v1.1) */
1035 if (audioscrobbler->priv->duration < 30 ||
1036 ! strcmp (audioscrobbler->priv->artist, _("Unknown")) ||
1037 ! strcmp (audioscrobbler->priv->title, _("Unknown"))) {
1038 audioscrobbler->priv->should_queue = FALSE;
1039 } else if (time < 15) {
1040 /* even if it's the same song, it's being played again from
1041 * the start so we can queue it again.
1043 audioscrobbler->priv->should_queue = TRUE;
1048 void
1049 rb_audioscrobbler_username_entry_changed_cb (GtkEntry *entry,
1050 RBAudioscrobbler *audioscrobbler)
1052 eel_gconf_set_string (CONF_AUDIOSCROBBLER_USERNAME,
1053 gtk_entry_get_text (entry));
1056 void
1057 rb_audioscrobbler_username_entry_activate_cb (GtkEntry *entry,
1058 RBAudioscrobbler *audioscrobbler)
1060 gtk_widget_grab_focus (audioscrobbler->priv->password_entry);
1063 void
1064 rb_audioscrobbler_password_entry_changed_cb (GtkEntry *entry,
1065 RBAudioscrobbler *audioscrobbler)
1067 eel_gconf_set_string (CONF_AUDIOSCROBBLER_PASSWORD,
1068 gtk_entry_get_text (entry));
1071 void
1072 rb_audioscrobbler_password_entry_activate_cb (GtkEntry *entry,
1073 RBAudioscrobbler *audioscrobbler)
1075 /* ? */
1078 /* AudioscrobblerEntry functions: */
1079 static void
1080 audioscrobbler_entry_init (AudioscrobblerEntry *entry)
1082 entry->artist = g_strdup ("");
1083 entry->album = g_strdup ("");
1084 entry->title = g_strdup ("");
1085 entry->length = 0;
1086 entry->mbid = g_strdup ("");
1087 entry->timestamp = g_strdup ("");
1090 static void
1091 audioscrobbler_entry_free (AudioscrobblerEntry *entry)
1093 g_free (entry->artist);
1094 g_free (entry->album);
1095 g_free (entry->title);
1096 g_free (entry->mbid);
1097 g_free (entry->timestamp);
1099 g_free (entry);
1103 /* Queue functions: */
1105 static AudioscrobblerEntry*
1106 rb_audioscrobbler_load_entry_from_string (const char *string)
1108 AudioscrobblerEntry *entry;
1109 int i = 0;
1110 char **breaks;
1112 entry = g_new0 (AudioscrobblerEntry, 1);
1113 audioscrobbler_entry_init (entry);
1115 breaks = g_strsplit (string, "&", 6);
1117 for (i = 0; breaks[i] != NULL; i++) {
1118 char **breaks2 = g_strsplit (breaks[i], "=", 2);
1120 if (breaks2[0] != NULL && breaks2[1] != NULL) {
1121 if (g_str_has_prefix (breaks2[0], "a")) {
1122 g_free (entry->artist);
1123 entry->artist = g_strdup (breaks2[1]);
1125 if (g_str_has_prefix (breaks2[0], "t")) {
1126 g_free (entry->title);
1127 entry->title = g_strdup (breaks2[1]);
1129 if (g_str_has_prefix (breaks2[0], "b")) {
1130 g_free (entry->album);
1131 entry->album = g_strdup (breaks2[1]);
1133 if (g_str_has_prefix (breaks2[0], "m")) {
1134 g_free (entry->mbid);
1135 entry->mbid = g_strdup (breaks2[1]);
1137 if (g_str_has_prefix (breaks2[0], "l")) {
1138 entry->length = atoi (breaks2[1]);
1140 if (g_str_has_prefix (breaks2[0], "i")) {
1141 g_free (entry->timestamp);
1142 entry->timestamp = g_strdup (breaks2[1]);
1146 g_strfreev (breaks2);
1149 g_strfreev (breaks);
1151 if (strcmp (entry->artist, "") == 0 || strcmp (entry->title, "") == 0) {
1152 audioscrobbler_entry_free (entry);
1153 entry = NULL;
1156 return entry;
1159 static gboolean
1160 rb_audioscrobbler_load_queue (RBAudioscrobbler *audioscrobbler)
1162 char *pathname, *uri;
1163 GnomeVFSResult result;
1164 char *data;
1165 int size;
1167 pathname = g_build_filename (rb_dot_dir (), "audioscrobbler.queue", NULL);
1168 uri = g_filename_to_uri (pathname, NULL, NULL);
1169 g_free (pathname);
1170 rb_debug ("Loading Audioscrobbler queue from \"%s\"", uri);
1172 result = gnome_vfs_read_entire_file (uri, &size, &data);
1173 g_free (uri);
1175 /* do stuff */
1176 if (result == GNOME_VFS_OK) {
1177 char *start = data, *end;
1179 /* scan along the file's data, turning each line into a string */
1180 while (start < (data + size)) {
1181 AudioscrobblerEntry *entry;
1183 /* find the end of the line, to terminate the string */
1184 end = g_utf8_strchr (start, -1, '\n');
1185 if (end == NULL)
1186 break;
1187 *end = 0;
1189 entry = rb_audioscrobbler_load_entry_from_string (start);
1190 if (entry) {
1191 audioscrobbler->priv->queue = g_slist_append (audioscrobbler->priv->queue, entry);
1192 audioscrobbler->priv->queue_count++;
1195 start = end + 1;
1199 if (result != GNOME_VFS_OK) {
1200 rb_debug ("Unable to load Audioscrobbler queue from disk: %s",
1201 gnome_vfs_result_to_string (result));
1204 g_free (data);
1205 return (result == GNOME_VFS_OK);
1208 static gboolean
1209 rb_audioscrobbler_save_queue (RBAudioscrobbler *audioscrobbler)
1211 char *pathname;
1212 GnomeVFSHandle *handle = NULL;
1213 GnomeVFSResult result;
1215 if (!audioscrobbler->priv->queue_changed) {
1216 return TRUE;
1219 pathname = g_build_filename (rb_dot_dir (), "audioscrobbler.queue", NULL);
1220 rb_debug ("Saving Audioscrobbler queue to \"%s\"", pathname);
1222 result = gnome_vfs_create (&handle, pathname, GNOME_VFS_OPEN_WRITE, FALSE, 0600);
1223 g_free (pathname);
1225 if (result == GNOME_VFS_OK) {
1226 GString *s = g_string_new (NULL);
1227 GSList *l;
1229 for (l = audioscrobbler->priv->queue; l; l = g_slist_next (l)) {
1230 AudioscrobblerEntry *entry;
1232 entry = (AudioscrobblerEntry *) l->data;
1233 g_string_printf (s, "a=%s&t=%s&b=%s&m=%s&l=%d&i=%s\n",
1234 entry->artist,
1235 entry->title,
1236 entry->album,
1237 entry->mbid,
1238 entry->length,
1239 entry->timestamp);
1240 result = gnome_vfs_write (handle, s->str, s->len, NULL);
1241 if (result != GNOME_VFS_OK)
1242 break;
1244 g_string_free (s, TRUE);
1247 if (result != GNOME_VFS_OK) {
1248 rb_debug ("Unable to save Audioscrobbler queue to disk: %s",
1249 gnome_vfs_result_to_string (result));
1250 } else {
1251 audioscrobbler->priv->queue_changed = FALSE;
1254 if (handle)
1255 gnome_vfs_close (handle);
1256 return (result == GNOME_VFS_OK);
1259 static void
1260 rb_audioscrobbler_print_queue (RBAudioscrobbler *audioscrobbler, gboolean submission)
1262 GSList *l;
1263 AudioscrobblerEntry *entry;
1264 int i = 0;
1266 if (submission)
1267 l = audioscrobbler->priv->submission;
1268 else
1269 l = audioscrobbler->priv->queue;
1270 rb_debug ("Audioscrobbler %s (%d entries):", submission ? "submission" : "queue", g_slist_length (l));
1272 for (; l; l = g_slist_next (l)) {
1273 entry = (AudioscrobblerEntry *) l->data;
1275 rb_debug ("%-3d artist: %s", ++i, entry->artist);
1276 rb_debug (" album: %s", entry->album);
1277 rb_debug (" title: %s", entry->title);
1278 rb_debug (" length: %d", entry->length);
1279 rb_debug (" timestamp: %s", entry->timestamp);
1283 static void
1284 rb_audioscrobbler_free_queue_entries (RBAudioscrobbler *audioscrobbler, GSList **queue)
1286 g_slist_foreach (*queue, (GFunc) audioscrobbler_entry_free, NULL);
1287 g_slist_free (*queue);
1288 *queue = NULL;
1290 audioscrobbler->priv->queue_changed = TRUE;