Updated Finnish translation
[rhythmbox.git] / plugins / audioscrobbler / rb-audioscrobbler.c
blob79bbc4889ec0b39b9bd96a44f509d5864987722d
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 g_object_unref (G_OBJECT (audioscrobbler->priv->soup_session));
317 g_object_unref (G_OBJECT (audioscrobbler->priv->proxy_config));
318 g_object_unref (G_OBJECT (audioscrobbler->priv->shell_player));
320 rb_audioscrobbler_free_queue_entries (audioscrobbler, &audioscrobbler->priv->queue);
321 rb_audioscrobbler_free_queue_entries (audioscrobbler, &audioscrobbler->priv->submission);
323 G_OBJECT_CLASS (rb_audioscrobbler_parent_class)->finalize (object);
326 RBAudioscrobbler*
327 rb_audioscrobbler_new (RBShellPlayer *shell_player,
328 RBProxyConfig *proxy_config)
330 return g_object_new (RB_TYPE_AUDIOSCROBBLER,
331 "shell-player", shell_player,
332 "proxy-config", proxy_config,
333 NULL);
336 static void
337 rb_audioscrobbler_set_property (GObject *object,
338 guint prop_id,
339 const GValue *value,
340 GParamSpec *pspec)
342 RBAudioscrobbler *audioscrobbler = RB_AUDIOSCROBBLER (object);
344 switch (prop_id) {
345 case PROP_SHELL_PLAYER:
346 audioscrobbler->priv->shell_player = g_value_get_object (value);
347 g_object_ref (G_OBJECT (audioscrobbler->priv->shell_player));
348 g_signal_connect_object (G_OBJECT (audioscrobbler->priv->shell_player),
349 "playing-song-changed",
350 G_CALLBACK (rb_audioscrobbler_song_changed_cb),
351 audioscrobbler, 0);
352 break;
353 case PROP_PROXY_CONFIG:
354 audioscrobbler->priv->proxy_config = g_value_get_object (value);
355 g_object_ref (G_OBJECT (audioscrobbler->priv->proxy_config));
356 g_signal_connect_object (G_OBJECT (audioscrobbler->priv->proxy_config),
357 "config-changed",
358 G_CALLBACK (rb_audioscrobbler_proxy_config_changed_cb),
359 audioscrobbler, 0);
360 break;
361 default:
362 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
363 break;
367 static void
368 rb_audioscrobbler_get_property (GObject *object,
369 guint prop_id,
370 GValue *value,
371 GParamSpec *pspec)
373 RBAudioscrobbler *audioscrobbler = RB_AUDIOSCROBBLER (object);
375 switch (prop_id) {
376 case PROP_SHELL_PLAYER:
377 g_value_set_object (value, audioscrobbler->priv->shell_player);
378 break;
379 default:
380 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
381 break;
385 /* Add the audioscrobbler thread timer */
386 static void
387 rb_audioscrobbler_add_timeout (RBAudioscrobbler *audioscrobbler)
389 if (!audioscrobbler->priv->timeout_id) {
390 rb_debug ("Adding Audioscrobbler timer (15 seconds)");
391 audioscrobbler->priv->timeout_id =
392 g_timeout_add (15000, (GSourceFunc) rb_audioscrobbler_timeout_cb,
393 audioscrobbler);
397 /* updates the queue and submits entries as required */
398 static gboolean
399 rb_audioscrobbler_timeout_cb (RBAudioscrobbler *audioscrobbler)
401 guint elapsed;
402 int elapsed_delta;
404 /* should we add this song to the queue? */
405 if (audioscrobbler->priv->should_queue) {
407 rb_shell_player_get_playing_time (audioscrobbler->priv->shell_player, &elapsed, NULL);
408 elapsed_delta = elapsed - audioscrobbler->priv->elapsed;
409 audioscrobbler->priv->elapsed = elapsed;
411 if ((elapsed >= audioscrobbler->priv->duration / 2 || elapsed >= 240) && elapsed_delta < 20) {
413 /* Add song to queue, if the queue isn't too long already */
414 if (g_slist_length (audioscrobbler->priv->queue) < MAX_QUEUE_SIZE) {
416 AudioscrobblerEntry *entry = g_new0 (AudioscrobblerEntry, 1);
417 time_t tt;
419 time (&tt);
421 rb_debug ("Adding song to queue");
423 entry->artist = soup_uri_encode (audioscrobbler->priv->artist, EXTRA_URI_ENCODE_CHARS);
424 if (strcmp (audioscrobbler->priv->album, _("Unknown")) != 0)
425 entry->album = soup_uri_encode (audioscrobbler->priv->album, EXTRA_URI_ENCODE_CHARS);
426 else
427 entry->album = g_strdup ("");
428 entry->title = soup_uri_encode (audioscrobbler->priv->title, EXTRA_URI_ENCODE_CHARS);
429 entry->mbid = soup_uri_encode (audioscrobbler->priv->mbid, EXTRA_URI_ENCODE_CHARS);
430 entry->length = audioscrobbler->priv->duration;
431 entry->timestamp = g_new0 (gchar, 30);
432 strftime (entry->timestamp, 30, "%Y%%2D%m%%2D%d%%20%H%%3A%M%%3A%S", gmtime (&tt));
434 audioscrobbler->priv->queue = g_slist_append (audioscrobbler->priv->queue,
435 entry);
436 audioscrobbler->priv->queue_changed = TRUE;
437 audioscrobbler->priv->queue_count++;
438 } else {
439 rb_debug ("Queue is too long. Not adding song to queue");
440 g_free (audioscrobbler->priv->status_msg);
441 audioscrobbler->priv->status = QUEUE_TOO_LONG;
442 audioscrobbler->priv->status_msg = NULL;
445 /* Mark current track as having been queued. */
446 audioscrobbler->priv->should_queue = FALSE;
448 rb_audioscrobbler_preferences_sync (audioscrobbler);
449 } else if (elapsed_delta > 20) {
450 rb_debug ("Skipping detected; not submitting current song");
451 /* not sure about this - what if I skip to somewhere towards
452 * the end, but then go back and listen to the whole song?
454 audioscrobbler->priv->should_queue = FALSE;
458 /* do handshake if we need to */
459 if (! audioscrobbler->priv->handshake &&
460 time (NULL) > audioscrobbler->priv->handshake_next &&
461 strcmp (audioscrobbler->priv->username, "") != 0) {
462 rb_audioscrobbler_do_handshake (audioscrobbler);
465 /* if there's something in the queue, submit it if we can, save it otherwise */
466 if (audioscrobbler->priv->queue != NULL) {
467 if (audioscrobbler->priv->handshake)
468 rb_audioscrobbler_submit_queue (audioscrobbler);
469 else
470 rb_audioscrobbler_save_queue (audioscrobbler);
472 return TRUE;
476 /* Audioscrobbler functions: */
477 static gchar *
478 mkmd5 (char *string)
480 md5_state_t md5state;
481 guchar md5pword[16];
482 gchar md5_response[33];
484 int j = 0;
486 memset (md5_response, 0, sizeof (md5_response));
488 md5_init (&md5state);
489 md5_append (&md5state, (unsigned char*)string, strlen (string));
490 md5_finish (&md5state, md5pword);
492 for (j = 0; j < 16; j++) {
493 char a[3];
494 sprintf (a, "%02x", md5pword[j]);
495 md5_response[2*j] = a[0];
496 md5_response[2*j+1] = a[1];
499 return (g_strdup (md5_response));
502 static void
503 rb_audioscrobbler_parse_response (RBAudioscrobbler *audioscrobbler, SoupMessage *msg)
505 rb_debug ("Parsing response, status=%d", msg->status_code);
507 if (SOUP_STATUS_IS_SUCCESSFUL (msg->status_code) && (msg->response).body != NULL) {
508 gchar *body;
509 gchar **breaks;
511 body = g_malloc0 ((msg->response).length + 1);
512 memcpy (body, (msg->response).body, (msg->response).length);
514 g_strstrip (body);
515 breaks = g_strsplit (body, "\n", 4);
516 int i;
518 g_free (audioscrobbler->priv->status_msg);
519 audioscrobbler->priv->status = STATUS_OK;
520 audioscrobbler->priv->status_msg = NULL;
521 for (i = 0; breaks[i] != NULL; i++) {
522 rb_debug ("RESPONSE: %s", breaks[i]);
523 if (g_str_has_prefix (breaks[i], "UPTODATE")) {
524 rb_debug ("UPTODATE");
526 if (breaks[i+1] != NULL) {
527 g_free (audioscrobbler->priv->md5_challenge);
528 audioscrobbler->priv->md5_challenge = g_strdup (breaks[i+1]);
529 rb_debug ("MD5 challenge: \"%s\"", audioscrobbler->priv->md5_challenge);
531 if (breaks[i+2] != NULL) {
532 g_free (audioscrobbler->priv->submit_url);
533 audioscrobbler->priv->submit_url = g_strdup (breaks[i+2]);
534 rb_debug ("Submit URL: \"%s\"", audioscrobbler->priv->submit_url);
535 i++;
537 i++;
540 } else if (g_str_has_prefix (breaks[i], "UPDATE")) {
541 rb_debug ("UPDATE");
542 audioscrobbler->priv->status = CLIENT_UPDATE_REQUIRED;
544 if (breaks[i+1] != NULL) {
545 g_free (audioscrobbler->priv->md5_challenge);
546 audioscrobbler->priv->md5_challenge = g_strdup (breaks[i+1]);
547 rb_debug ("MD5 challenge: \"%s\"", audioscrobbler->priv->md5_challenge);
549 if (breaks[i+2] != NULL) {
550 g_free (audioscrobbler->priv->submit_url);
551 audioscrobbler->priv->submit_url = g_strdup (breaks[i+2]);
552 rb_debug ("Submit URL: \"%s\"", audioscrobbler->priv->submit_url);
553 i++;
555 i++;
558 } else if (g_str_has_prefix (breaks[i], "FAILED")) {
559 audioscrobbler->priv->status = HANDSHAKE_FAILED;
561 if (strlen (breaks[i]) > 7) {
562 rb_debug ("FAILED: \"%s\"", breaks[i] + 7);
563 audioscrobbler->priv->status_msg = g_strdup (breaks[i] + 7);
564 } else {
565 rb_debug ("FAILED");
569 } else if (g_str_has_prefix (breaks[i], "BADUSER")) {
570 rb_debug ("BADUSER");
571 audioscrobbler->priv->status = BAD_USERNAME;
572 } else if (g_str_has_prefix (breaks[i], "BADAUTH")) {
573 rb_debug ("BADAUTH");
574 audioscrobbler->priv->status = BAD_PASSWORD;
575 } else if (g_str_has_prefix (breaks[i], "OK")) {
576 rb_debug ("OK");
577 } else if (g_str_has_prefix (breaks[i], "INTERVAL ")) {
578 audioscrobbler->priv->submit_interval = g_ascii_strtod(breaks[i] + 9, NULL);
579 rb_debug ("INTERVAL: %s", breaks[i] + 9);
583 /* respect the last submit interval we were given */
584 if (audioscrobbler->priv->submit_interval > 0)
585 audioscrobbler->priv->submit_next = time(NULL) + audioscrobbler->priv->submit_interval;
587 g_strfreev (breaks);
588 g_free (body);
589 } else {
590 audioscrobbler->priv->status = REQUEST_FAILED;
591 audioscrobbler->priv->status_msg = g_strdup (soup_status_get_phrase (msg->status_code));
595 static void
596 rb_audioscrobbler_perform (RBAudioscrobbler *audioscrobbler,
597 char *url,
598 char *post_data,
599 SoupMessageCallbackFn response_handler)
601 SoupMessage *msg;
603 msg = soup_message_new (post_data == NULL ? "GET" : "POST", url);
605 if (post_data != NULL) {
606 rb_debug ("Submitting to Audioscrobbler: %s", post_data);
607 soup_message_set_request (msg,
608 "application/x-www-form-urlencoded",
609 SOUP_BUFFER_SYSTEM_OWNED,
610 post_data,
611 strlen (post_data));
614 /* create soup session, if we haven't got one yet */
615 if (!audioscrobbler->priv->soup_session) {
616 SoupUri *uri;
618 uri = rb_proxy_config_get_libsoup_uri (audioscrobbler->priv->proxy_config);
619 audioscrobbler->priv->soup_session = soup_session_async_new_with_options (
620 "proxy-uri", uri,
621 NULL);
622 if (uri)
623 soup_uri_free (uri);
626 soup_session_queue_message (audioscrobbler->priv->soup_session,
627 msg,
628 response_handler,
629 audioscrobbler);
632 static void
633 rb_audioscrobbler_do_handshake (RBAudioscrobbler *audioscrobbler)
635 /* Perform handshake if necessary. Only perform handshake if
636 * - we have no current handshake; AND
637 * - we have waited the appropriate amount of time between
638 * handshakes; AND
639 * - we have a username
642 if (! audioscrobbler->priv->handshake &&
643 time (NULL) >= audioscrobbler->priv->handshake_next &&
644 strcmp (audioscrobbler->priv->username, "") != 0) {
645 gchar *username;
646 gchar *url;
648 username = soup_uri_encode (audioscrobbler->priv->username, EXTRA_URI_ENCODE_CHARS);
649 url = g_strdup_printf ("%s?hs=true&p=%s&c=%s&v=%s&u=%s",
650 SCROBBLER_URL,
651 SCROBBLER_VERSION,
652 CLIENT_ID,
653 CLIENT_VERSION,
654 username);
655 g_free (username);
657 /* Make sure we wait at least 30 minutes between handshakes. */
658 audioscrobbler->priv->handshake_next = time (NULL) + 1800;
660 rb_debug ("Performing handshake with Audioscrobbler server: %s", url);
662 audioscrobbler->priv->status = HANDSHAKING;
663 rb_audioscrobbler_preferences_sync (audioscrobbler);
665 rb_audioscrobbler_perform (audioscrobbler, url, NULL, rb_audioscrobbler_do_handshake_cb);
667 g_free (url);
668 } else {
669 rb_debug ("Will not attempt handshake:");
670 if (audioscrobbler->priv->handshake)
671 rb_debug ("We already have a valid handshake");
672 if (time (NULL) < audioscrobbler->priv->handshake_next)
673 rb_debug ("time=%lu; handshake_next=%lu",
674 time (NULL),
675 audioscrobbler->priv->handshake_next);
676 if (strcmp (audioscrobbler->priv->username, "") == 0)
677 rb_debug ("Username not set");
682 static void
683 rb_audioscrobbler_do_handshake_cb (SoupMessage *msg, gpointer user_data)
685 RBAudioscrobbler *audioscrobbler = RB_AUDIOSCROBBLER(user_data);
687 rb_debug ("Handshake response");
688 rb_audioscrobbler_parse_response (audioscrobbler, msg);
689 rb_audioscrobbler_preferences_sync (audioscrobbler);
691 switch (audioscrobbler->priv->status) {
692 case STATUS_OK:
693 case CLIENT_UPDATE_REQUIRED:
694 audioscrobbler->priv->handshake = TRUE;
695 audioscrobbler->priv->failures = 0;
696 break;
697 default:
698 rb_debug ("Handshake failed");
699 ++audioscrobbler->priv->failures;
700 break;
704 static void
705 rb_audioscrobbler_submit_queue (RBAudioscrobbler *audioscrobbler)
707 /* Conditions:
708 * - Must have username and password
709 * - Must have md5_challenge
710 * - Queue must not be empty
713 time_t now;
714 time(&now);
716 if (strcmp (audioscrobbler->priv->username, "") != 0 &&
717 strcmp (audioscrobbler->priv->password, "") != 0 &&
718 strcmp (audioscrobbler->priv->md5_challenge, "") != 0 &&
719 now > audioscrobbler->priv->submit_next &&
720 audioscrobbler->priv->queue != NULL) {
721 GSList *l;
723 int i = 0;
725 gchar *md5_password = mkmd5 (audioscrobbler->priv->password);
726 gchar *md5_temp = g_strconcat (md5_password,
727 audioscrobbler->priv->md5_challenge,
728 NULL);
729 gchar *md5_response = mkmd5 (md5_temp);
731 gchar *username = soup_uri_encode (audioscrobbler->priv->username, EXTRA_URI_ENCODE_CHARS);
732 gchar *post_data = g_strdup_printf ("u=%s&s=%s&", username, md5_response);
734 g_free (md5_password);
735 g_free (md5_temp);
736 g_free (md5_response);
737 g_free (username);
739 do {
740 AudioscrobblerEntry *entry;
742 /* remove first queue entry */
743 l = audioscrobbler->priv->queue;
744 audioscrobbler->priv->queue = g_slist_remove_link (l, l);
745 entry = (AudioscrobblerEntry *)l->data;
747 gchar *new = g_strdup_printf ("%sa[%d]=%s&t[%d]=%s&b[%d]=%s&m[%d]=%s&l[%d]=%d&i[%d]=%s&",
748 post_data,
749 i, entry->artist,
750 i, entry->title,
751 i, entry->album,
752 i, entry->mbid,
753 i, entry->length,
754 i, entry->timestamp);
756 g_free (post_data);
757 post_data = new;
759 /* add to submission list */
760 audioscrobbler->priv->submission = g_slist_concat (audioscrobbler->priv->submission, l);
761 i++;
762 } while (audioscrobbler->priv->queue && (i < MAX_SUBMIT_SIZE));
764 rb_debug ("Submitting queue to Audioscrobbler");
765 rb_audioscrobbler_print_queue (audioscrobbler, TRUE);
767 rb_audioscrobbler_perform (audioscrobbler,
768 audioscrobbler->priv->submit_url,
769 post_data,
770 rb_audioscrobbler_submit_queue_cb);
772 /* libsoup will free post_data when the request is finished */
773 } else {
774 rb_debug ("Not submitting queue because:");
775 if (strcmp (audioscrobbler->priv->username, "") == 0)
776 rb_debug ("Blank username");
777 if (strcmp (audioscrobbler->priv->password, "") == 0)
778 rb_debug ("Blank password");
779 if (strcmp (audioscrobbler->priv->md5_challenge, "") == 0)
780 rb_debug ("Blank md5_challenge");
781 if (now <= audioscrobbler->priv->submit_next)
782 rb_debug ("Too soon (next submission in %ld seconds)", audioscrobbler->priv->submit_next - now);
783 if (!audioscrobbler->priv->queue)
784 rb_debug ("Queue is empty");
788 static void
789 rb_audioscrobbler_submit_queue_cb (SoupMessage *msg, gpointer user_data)
791 RBAudioscrobbler *audioscrobbler = RB_AUDIOSCROBBLER (user_data);
793 rb_debug ("Submission response");
794 rb_audioscrobbler_parse_response (audioscrobbler, msg);
796 if (audioscrobbler->priv->status == STATUS_OK) {
797 rb_debug ("Queue submitted successfully");
798 rb_audioscrobbler_free_queue_entries (audioscrobbler, &audioscrobbler->priv->submission);
799 rb_audioscrobbler_save_queue (audioscrobbler);
801 audioscrobbler->priv->submit_count += audioscrobbler->priv->queue_count;
802 audioscrobbler->priv->queue_count = 0;
804 g_free (audioscrobbler->priv->submit_time);
805 audioscrobbler->priv->submit_time = rb_utf_friendly_time (time (NULL));
806 } else {
807 ++audioscrobbler->priv->failures;
809 /* add failed submission entries back to queue */
810 audioscrobbler->priv->queue =
811 g_slist_concat (audioscrobbler->priv->submission, audioscrobbler->priv->queue);
812 audioscrobbler->priv->submission = NULL;
813 rb_audioscrobbler_save_queue (audioscrobbler);
815 rb_audioscrobbler_print_queue (audioscrobbler, FALSE);
817 if (audioscrobbler->priv->failures >= 3) {
818 rb_debug ("Queue submission has failed %d times; caching tracks locally",
819 audioscrobbler->priv->failures);
820 g_free (audioscrobbler->priv->status_msg);
822 audioscrobbler->priv->handshake = FALSE;
823 audioscrobbler->priv->status = GIVEN_UP;
824 audioscrobbler->priv->status_msg = NULL;
825 } else {
826 rb_debug ("Queue submission failed %d times", audioscrobbler->priv->failures);
830 rb_audioscrobbler_preferences_sync (audioscrobbler);
833 /* Configuration functions: */
834 static void
835 rb_audioscrobbler_import_settings (RBAudioscrobbler *audioscrobbler)
837 /* import gconf settings. */
838 g_free (audioscrobbler->priv->username);
839 g_free (audioscrobbler->priv->password);
840 audioscrobbler->priv->username = eel_gconf_get_string (CONF_AUDIOSCROBBLER_USERNAME);
841 audioscrobbler->priv->password = eel_gconf_get_string (CONF_AUDIOSCROBBLER_PASSWORD);
843 rb_audioscrobbler_add_timeout (audioscrobbler);
844 audioscrobbler->priv->status = HANDSHAKING;
846 audioscrobbler->priv->submit_time = g_strdup (_("Never"));
849 static void
850 rb_audioscrobbler_preferences_sync (RBAudioscrobbler *audioscrobbler)
852 const char *status;
853 char *free_this = NULL;
855 if (!audioscrobbler->priv->config_widget)
856 return;
858 rb_debug ("Syncing data with preferences window");
859 gtk_entry_set_text (GTK_ENTRY (audioscrobbler->priv->username_entry),
860 audioscrobbler->priv->username);
861 gtk_entry_set_text (GTK_ENTRY (audioscrobbler->priv->password_entry),
862 audioscrobbler->priv->password);
864 switch (audioscrobbler->priv->status) {
865 case STATUS_OK:
866 status = _("OK");
867 break;
868 case HANDSHAKING:
869 status = _("Logging in");
870 break;
871 case REQUEST_FAILED:
872 status = _("Request failed");
873 break;
874 case BAD_USERNAME:
875 status = _("Incorrect username");
876 break;
877 case BAD_PASSWORD:
878 status = _("Incorrect password");
879 break;
880 case HANDSHAKE_FAILED:
881 status = _("Handshake failed");
882 break;
883 case CLIENT_UPDATE_REQUIRED:
884 status = _("Client update required");
885 break;
886 case SUBMIT_FAILED:
887 status = _("Track submission failed");
888 break;
889 case QUEUE_TOO_LONG:
890 status = _("Queue is too long");
891 break;
892 case GIVEN_UP:
893 status = _("Track submission failed too many times");
894 break;
895 default:
896 g_assert_not_reached ();
897 break;
900 if (audioscrobbler->priv->status_msg && audioscrobbler->priv->status_msg[0] != '\0') {
901 free_this = g_strdup_printf ("%s: %s", status, audioscrobbler->priv->status_msg);
902 status = free_this;
905 gtk_label_set_text (GTK_LABEL (audioscrobbler->priv->status_label),
906 status);
907 g_free (free_this);
909 free_this = g_strdup_printf ("%u", audioscrobbler->priv->submit_count);
910 gtk_label_set_text (GTK_LABEL (audioscrobbler->priv->submit_count_label), free_this);
911 g_free (free_this);
913 free_this = g_strdup_printf ("%u", audioscrobbler->priv->queue_count);
914 gtk_label_set_text (GTK_LABEL (audioscrobbler->priv->queue_count_label), free_this);
915 g_free (free_this);
917 gtk_label_set_text (GTK_LABEL (audioscrobbler->priv->submit_time_label),
918 audioscrobbler->priv->submit_time);
921 GtkWidget *
922 rb_audioscrobbler_get_config_widget (RBAudioscrobbler *audioscrobbler)
924 GladeXML *xml;
926 if (audioscrobbler->priv->config_widget) {
927 return audioscrobbler->priv->config_widget;
930 xml = rb_glade_xml_new ("audioscrobbler-prefs.glade", "audioscrobbler_vbox", audioscrobbler);
931 audioscrobbler->priv->config_widget = glade_xml_get_widget (xml, "audioscrobbler_vbox");
932 audioscrobbler->priv->username_entry = glade_xml_get_widget (xml, "username_entry");
933 audioscrobbler->priv->username_label = glade_xml_get_widget (xml, "username_label");
934 audioscrobbler->priv->password_entry = glade_xml_get_widget (xml, "password_entry");
935 audioscrobbler->priv->password_label = glade_xml_get_widget (xml, "password_label");
936 audioscrobbler->priv->status_label = glade_xml_get_widget (xml, "status_label");
937 audioscrobbler->priv->queue_count_label = glade_xml_get_widget (xml, "queue_count_label");
938 audioscrobbler->priv->submit_count_label = glade_xml_get_widget (xml, "submit_count_label");
939 audioscrobbler->priv->submit_time_label = glade_xml_get_widget (xml, "submit_time_label");
941 rb_glade_boldify_label (xml, "audioscrobbler_label");
943 g_object_unref (G_OBJECT (xml));
945 rb_audioscrobbler_preferences_sync (audioscrobbler);
947 return audioscrobbler->priv->config_widget;
951 /* Callback functions: */
953 static void
954 rb_audioscrobbler_proxy_config_changed_cb (RBProxyConfig *config,
955 RBAudioscrobbler *audioscrobbler)
957 SoupUri *uri;
959 if (audioscrobbler->priv->soup_session) {
960 uri = rb_proxy_config_get_libsoup_uri (config);
961 g_object_set (G_OBJECT (audioscrobbler->priv->soup_session),
962 "proxy-uri", uri,
963 NULL);
964 if (uri)
965 soup_uri_free (uri);
969 static void
970 rb_audioscrobbler_gconf_changed_cb (GConfClient *client,
971 guint cnxn_id,
972 GConfEntry *entry,
973 RBAudioscrobbler *audioscrobbler)
975 rb_debug ("GConf key updated: \"%s\"", entry->key);
976 if (strcmp (entry->key, CONF_AUDIOSCROBBLER_USERNAME) == 0) {
977 g_free (audioscrobbler->priv->username);
978 audioscrobbler->priv->username = g_strdup (gconf_value_get_string (entry->value));
980 if (audioscrobbler->priv->username_entry)
981 gtk_entry_set_text (GTK_ENTRY (audioscrobbler->priv->username_entry),
982 gconf_value_get_string (entry->value));
984 audioscrobbler->priv->handshake = FALSE;
985 } else if (strcmp (entry->key, CONF_AUDIOSCROBBLER_PASSWORD) == 0) {
986 g_free (audioscrobbler->priv->password);
987 audioscrobbler->priv->password = g_strdup (gconf_value_get_string (entry->value));
989 if (audioscrobbler->priv->password_entry)
990 gtk_entry_set_text (GTK_ENTRY (audioscrobbler->priv->password_entry),
991 gconf_value_get_string (entry->value));
992 } else {
993 rb_debug ("Unhandled GConf key updated: \"%s\"", entry->key);
997 static void
998 rb_audioscrobbler_song_changed_cb (RBShellPlayer *player,
999 RhythmDBEntry *entry,
1000 RBAudioscrobbler *audioscrobbler)
1002 RhythmDBEntryType type;
1004 if (entry == NULL) {
1005 audioscrobbler->priv->should_queue = FALSE;
1006 return;
1009 type = rhythmdb_entry_get_entry_type (entry);
1010 if (type->category != RHYTHMDB_ENTRY_NORMAL ||
1011 type == RHYTHMDB_ENTRY_TYPE_PODCAST_POST ||
1012 rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_PLAYBACK_ERROR) != NULL) {
1013 audioscrobbler->priv->should_queue = FALSE;
1014 return;
1017 audioscrobbler->priv->title = rhythmdb_entry_dup_string (entry, RHYTHMDB_PROP_TITLE);
1018 audioscrobbler->priv->artist = rhythmdb_entry_dup_string (entry, RHYTHMDB_PROP_ARTIST);
1019 audioscrobbler->priv->album = rhythmdb_entry_dup_string (entry, RHYTHMDB_PROP_ALBUM);
1020 audioscrobbler->priv->duration = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DURATION);
1021 audioscrobbler->priv->mbid = rhythmdb_entry_dup_string (entry, RHYTHMDB_PROP_MUSICBRAINZ_TRACKID);
1022 guint time;
1023 rb_shell_player_get_playing_time (audioscrobbler->priv->shell_player, &time, NULL);
1024 audioscrobbler->priv->elapsed = (int) time;
1026 /* Ignore tracks that violate the specification (v1.1) */
1027 if (audioscrobbler->priv->duration < 30 ||
1028 ! strcmp (audioscrobbler->priv->artist, _("Unknown")) ||
1029 ! strcmp (audioscrobbler->priv->title, _("Unknown"))) {
1030 audioscrobbler->priv->should_queue = FALSE;
1031 } else if (time < 15) {
1032 /* even if it's the same song, it's being played again from
1033 * the start so we can queue it again.
1035 audioscrobbler->priv->should_queue = TRUE;
1040 void
1041 rb_audioscrobbler_username_entry_changed_cb (GtkEntry *entry,
1042 RBAudioscrobbler *audioscrobbler)
1044 eel_gconf_set_string (CONF_AUDIOSCROBBLER_USERNAME,
1045 gtk_entry_get_text (entry));
1048 void
1049 rb_audioscrobbler_username_entry_activate_cb (GtkEntry *entry,
1050 RBAudioscrobbler *audioscrobbler)
1052 gtk_widget_grab_focus (audioscrobbler->priv->password_entry);
1055 void
1056 rb_audioscrobbler_password_entry_changed_cb (GtkEntry *entry,
1057 RBAudioscrobbler *audioscrobbler)
1059 eel_gconf_set_string (CONF_AUDIOSCROBBLER_PASSWORD,
1060 gtk_entry_get_text (entry));
1063 void
1064 rb_audioscrobbler_password_entry_activate_cb (GtkEntry *entry,
1065 RBAudioscrobbler *audioscrobbler)
1067 /* ? */
1070 /* AudioscrobblerEntry functions: */
1071 static void
1072 audioscrobbler_entry_init (AudioscrobblerEntry *entry)
1074 entry->artist = g_strdup ("");
1075 entry->album = g_strdup ("");
1076 entry->title = g_strdup ("");
1077 entry->length = 0;
1078 entry->mbid = g_strdup ("");
1079 entry->timestamp = g_strdup ("");
1082 static void
1083 audioscrobbler_entry_free (AudioscrobblerEntry *entry)
1085 g_free (entry->artist);
1086 g_free (entry->album);
1087 g_free (entry->title);
1088 g_free (entry->mbid);
1089 g_free (entry->timestamp);
1091 g_free (entry);
1095 /* Queue functions: */
1097 static AudioscrobblerEntry*
1098 rb_audioscrobbler_load_entry_from_string (const char *string)
1100 AudioscrobblerEntry *entry;
1101 int i = 0;
1102 char **breaks;
1104 entry = g_new0 (AudioscrobblerEntry, 1);
1105 audioscrobbler_entry_init (entry);
1107 breaks = g_strsplit (string, "&", 6);
1109 for (i = 0; breaks[i] != NULL; i++) {
1110 char **breaks2 = g_strsplit (breaks[i], "=", 2);
1112 if (breaks2[0] != NULL && breaks2[1] != NULL) {
1113 if (g_str_has_prefix (breaks2[0], "a")) {
1114 g_free (entry->artist);
1115 entry->artist = g_strdup (breaks2[1]);
1117 if (g_str_has_prefix (breaks2[0], "t")) {
1118 g_free (entry->title);
1119 entry->title = g_strdup (breaks2[1]);
1121 if (g_str_has_prefix (breaks2[0], "b")) {
1122 g_free (entry->album);
1123 entry->album = g_strdup (breaks2[1]);
1125 if (g_str_has_prefix (breaks2[0], "m")) {
1126 g_free (entry->mbid);
1127 entry->mbid = g_strdup (breaks2[1]);
1129 if (g_str_has_prefix (breaks2[0], "l")) {
1130 entry->length = atoi (breaks2[1]);
1132 if (g_str_has_prefix (breaks2[0], "i")) {
1133 g_free (entry->timestamp);
1134 entry->timestamp = g_strdup (breaks2[1]);
1138 g_strfreev (breaks2);
1141 g_strfreev (breaks);
1143 if (strcmp (entry->artist, "") == 0 || strcmp (entry->title, "") == 0) {
1144 audioscrobbler_entry_free (entry);
1145 entry = NULL;
1148 return entry;
1151 static gboolean
1152 rb_audioscrobbler_load_queue (RBAudioscrobbler *audioscrobbler)
1154 char *pathname, *uri;
1155 GnomeVFSResult result;
1156 char *data;
1157 int size;
1159 pathname = g_build_filename (rb_dot_dir (), "audioscrobbler.queue", NULL);
1160 uri = g_filename_to_uri (pathname, NULL, NULL);
1161 g_free (pathname);
1162 rb_debug ("Loading Audioscrobbler queue from \"%s\"", uri);
1164 result = gnome_vfs_read_entire_file (uri, &size, &data);
1165 g_free (uri);
1167 /* do stuff */
1168 if (result == GNOME_VFS_OK) {
1169 char *start = data, *end;
1171 /* scan along the file's data, turning each line into a string */
1172 while (start < (data + size)) {
1173 AudioscrobblerEntry *entry;
1175 /* find the end of the line, to terminate the string */
1176 end = g_utf8_strchr (start, -1, '\n');
1177 if (end == NULL)
1178 break;
1179 *end = 0;
1181 entry = rb_audioscrobbler_load_entry_from_string (start);
1182 if (entry) {
1183 audioscrobbler->priv->queue = g_slist_append (audioscrobbler->priv->queue, entry);
1184 audioscrobbler->priv->queue_count++;
1187 start = end + 1;
1191 if (result != GNOME_VFS_OK) {
1192 rb_debug ("Unable to load Audioscrobbler queue from disk: %s",
1193 gnome_vfs_result_to_string (result));
1196 g_free (data);
1197 return (result == GNOME_VFS_OK);
1200 static gboolean
1201 rb_audioscrobbler_save_queue (RBAudioscrobbler *audioscrobbler)
1203 char *pathname;
1204 GnomeVFSHandle *handle = NULL;
1205 GnomeVFSResult result;
1207 if (!audioscrobbler->priv->queue_changed) {
1208 return TRUE;
1211 pathname = g_build_filename (rb_dot_dir (), "audioscrobbler.queue", NULL);
1212 rb_debug ("Saving Audioscrobbler queue to \"%s\"", pathname);
1214 result = gnome_vfs_create (&handle, pathname, GNOME_VFS_OPEN_WRITE, FALSE, 0600);
1215 g_free (pathname);
1217 if (result == GNOME_VFS_OK) {
1218 GString *s = g_string_new (NULL);
1219 GSList *l;
1221 for (l = audioscrobbler->priv->queue; l; l = g_slist_next (l)) {
1222 AudioscrobblerEntry *entry;
1224 entry = (AudioscrobblerEntry *) l->data;
1225 g_string_printf (s, "a=%s&t=%s&b=%s&m=%s&l=%d&i=%s\n",
1226 entry->artist,
1227 entry->title,
1228 entry->album,
1229 entry->mbid,
1230 entry->length,
1231 entry->timestamp);
1232 result = gnome_vfs_write (handle, s->str, s->len, NULL);
1233 if (result != GNOME_VFS_OK)
1234 break;
1236 g_string_free (s, TRUE);
1239 if (result != GNOME_VFS_OK) {
1240 rb_debug ("Unable to save Audioscrobbler queue to disk: %s",
1241 gnome_vfs_result_to_string (result));
1242 } else {
1243 audioscrobbler->priv->queue_changed = FALSE;
1246 if (handle)
1247 gnome_vfs_close (handle);
1248 return (result == GNOME_VFS_OK);
1251 static void
1252 rb_audioscrobbler_print_queue (RBAudioscrobbler *audioscrobbler, gboolean submission)
1254 GSList *l;
1255 AudioscrobblerEntry *entry;
1256 int i = 0;
1258 if (submission)
1259 l = audioscrobbler->priv->submission;
1260 else
1261 l = audioscrobbler->priv->queue;
1262 rb_debug ("Audioscrobbler %s (%d entries):", submission ? "submission" : "queue", g_slist_length (l));
1264 for (; l; l = g_slist_next (l)) {
1265 entry = (AudioscrobblerEntry *) l->data;
1267 rb_debug ("%-3d artist: %s", ++i, entry->artist);
1268 rb_debug (" album: %s", entry->album);
1269 rb_debug (" title: %s", entry->title);
1270 rb_debug (" length: %d", entry->length);
1271 rb_debug (" timestamp: %s", entry->timestamp);
1275 static void
1276 rb_audioscrobbler_free_queue_entries (RBAudioscrobbler *audioscrobbler, GSList **queue)
1278 g_slist_foreach (*queue, (GFunc) audioscrobbler_entry_free, NULL);
1279 g_slist_free (*queue);
1280 *queue = NULL;
1282 audioscrobbler->priv->queue_changed = TRUE;