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__
32 #include <glib/gi18n.h>
33 #include <glib/gprintf.h>
35 #include <gconf/gconf-value.h>
37 #include <libsoup/soup.h>
38 #include <libsoup/soup-uri.h>
41 #include "eel-gconf-extensions.h"
42 #include "rb-audioscrobbler.h"
44 #include "rb-file-helpers.h"
45 #include "rb-glade-helpers.h"
46 #include "rb-preferences.h"
48 #include "rb-shell-player.h"
49 #include "rb-source.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 "&+"
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 */
100 CLIENT_UPDATE_REQUIRED
,
107 /* Submission queue */
109 /* Entries currently being submitted */
113 /* Handshake has been done? */
115 time_t handshake_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
;
132 /* Currently playing song info, will be queued if priv->should_queue == TRUE */
140 /* Preference notifications */
141 guint notification_username_id
;
142 guint notification_password_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
,
168 static void rb_audioscrobbler_set_property (GObject
*object
,
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
,
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
,
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
);
207 G_DEFINE_TYPE (RBAudioscrobbler
, rb_audioscrobbler
, G_TYPE_OBJECT
)
211 /* Class-related functions: */
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
,
224 g_param_spec_object ("shell-player",
226 "RBShellPlayer object",
227 RB_TYPE_SHELL_PLAYER
,
228 G_PARAM_READWRITE
| G_PARAM_CONSTRUCT_ONLY
));
229 g_object_class_install_property (object_class
,
231 g_param_spec_object ("proxy-config",
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
));
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
,
277 audioscrobbler
->priv
->notification_password_id
=
278 eel_gconf_notification_add (CONF_AUDIOSCROBBLER_PASSWORD
,
279 (GConfClientNotifyFunc
) rb_audioscrobbler_gconf_changed_cb
,
282 rb_audioscrobbler_preferences_sync (audioscrobbler
);
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
);
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
,
339 rb_audioscrobbler_set_property (GObject
*object
,
344 RBAudioscrobbler
*audioscrobbler
= RB_AUDIOSCROBBLER (object
);
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
),
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
),
360 G_CALLBACK (rb_audioscrobbler_proxy_config_changed_cb
),
364 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
370 rb_audioscrobbler_get_property (GObject
*object
,
375 RBAudioscrobbler
*audioscrobbler
= RB_AUDIOSCROBBLER (object
);
378 case PROP_SHELL_PLAYER
:
379 g_value_set_object (value
, audioscrobbler
->priv
->shell_player
);
382 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
387 /* Add the audioscrobbler thread timer */
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
,
399 /* updates the queue and submits entries as required */
401 rb_audioscrobbler_timeout_cb (RBAudioscrobbler
*audioscrobbler
)
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);
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
);
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
,
438 audioscrobbler
->priv
->queue_changed
= TRUE
;
439 audioscrobbler
->priv
->queue_count
++;
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
);
472 rb_audioscrobbler_save_queue (audioscrobbler
);
478 /* Audioscrobbler functions: */
482 md5_state_t md5state
;
484 gchar md5_response
[33];
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
++) {
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
));
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
) {
513 body
= g_malloc0 ((msg
->response
).length
+ 1);
514 memcpy (body
, (msg
->response
).body
, (msg
->response
).length
);
517 breaks
= g_strsplit (body
, "\n", 4);
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
);
542 } else if (g_str_has_prefix (breaks
[i
], "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
);
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);
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")) {
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
;
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
601 rb_audioscrobbler_perform (RBAudioscrobbler
*audioscrobbler
,
604 SoupMessageCallbackFn response_handler
)
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
,
619 /* create soup session, if we haven't got one yet */
620 if (!audioscrobbler
->priv
->soup_session
) {
623 uri
= rb_proxy_config_get_libsoup_uri (audioscrobbler
->priv
->proxy_config
);
624 audioscrobbler
->priv
->soup_session
= soup_session_async_new_with_options (
631 soup_session_queue_message (audioscrobbler
->priv
->soup_session
,
634 g_object_ref (audioscrobbler
));
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
644 * - we have a username
647 if (! audioscrobbler
->priv
->handshake
&&
648 time (NULL
) >= audioscrobbler
->priv
->handshake_next
&&
649 strcmp (audioscrobbler
->priv
->username
, "") != 0) {
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",
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
);
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",
680 audioscrobbler
->priv
->handshake_next
);
681 if (strcmp (audioscrobbler
->priv
->username
, "") == 0)
682 rb_debug ("Username not set");
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
) {
698 case CLIENT_UPDATE_REQUIRED
:
699 audioscrobbler
->priv
->handshake
= TRUE
;
700 audioscrobbler
->priv
->failures
= 0;
703 rb_debug ("Handshake failed");
704 ++audioscrobbler
->priv
->failures
;
708 g_object_unref (audioscrobbler
);
712 rb_audioscrobbler_submit_queue (RBAudioscrobbler
*audioscrobbler
)
715 * - Must have username and password
716 * - Must have md5_challenge
717 * - Queue must not be empty
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
) {
732 gchar
*md5_password
= mkmd5 (audioscrobbler
->priv
->password
);
733 gchar
*md5_temp
= g_strconcat (md5_password
,
734 audioscrobbler
->priv
->md5_challenge
,
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
);
743 g_free (md5_response
);
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&",
761 i
, entry
->timestamp
);
766 /* add to submission list */
767 audioscrobbler
->priv
->submission
= g_slist_concat (audioscrobbler
->priv
->submission
, l
);
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
,
777 rb_audioscrobbler_submit_queue_cb
);
779 /* libsoup will free post_data when the request is finished */
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");
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
));
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
;
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: */
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"));
858 rb_audioscrobbler_preferences_sync (RBAudioscrobbler
*audioscrobbler
)
861 char *free_this
= NULL
;
863 if (!audioscrobbler
->priv
->config_widget
)
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
) {
877 status
= _("Logging in");
880 status
= _("Request failed");
883 status
= _("Incorrect username");
886 status
= _("Incorrect password");
888 case HANDSHAKE_FAILED
:
889 status
= _("Handshake failed");
891 case CLIENT_UPDATE_REQUIRED
:
892 status
= _("Client update required");
895 status
= _("Track submission failed");
898 status
= _("Queue is too long");
901 status
= _("Track submission failed too many times");
904 g_assert_not_reached ();
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
);
913 gtk_label_set_text (GTK_LABEL (audioscrobbler
->priv
->status_label
),
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
);
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
);
925 gtk_label_set_text (GTK_LABEL (audioscrobbler
->priv
->submit_time_label
),
926 audioscrobbler
->priv
->submit_time
);
930 rb_audioscrobbler_get_config_widget (RBAudioscrobbler
*audioscrobbler
)
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: */
962 rb_audioscrobbler_proxy_config_changed_cb (RBProxyConfig
*config
,
963 RBAudioscrobbler
*audioscrobbler
)
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
),
978 rb_audioscrobbler_gconf_changed_cb (GConfClient
*client
,
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
));
1001 rb_debug ("Unhandled GConf key updated: \"%s\"", entry
->key
);
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
;
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
;
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
);
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
;
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
));
1057 rb_audioscrobbler_username_entry_activate_cb (GtkEntry
*entry
,
1058 RBAudioscrobbler
*audioscrobbler
)
1060 gtk_widget_grab_focus (audioscrobbler
->priv
->password_entry
);
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
));
1072 rb_audioscrobbler_password_entry_activate_cb (GtkEntry
*entry
,
1073 RBAudioscrobbler
*audioscrobbler
)
1078 /* AudioscrobblerEntry functions: */
1080 audioscrobbler_entry_init (AudioscrobblerEntry
*entry
)
1082 entry
->artist
= g_strdup ("");
1083 entry
->album
= g_strdup ("");
1084 entry
->title
= g_strdup ("");
1086 entry
->mbid
= g_strdup ("");
1087 entry
->timestamp
= g_strdup ("");
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
);
1103 /* Queue functions: */
1105 static AudioscrobblerEntry
*
1106 rb_audioscrobbler_load_entry_from_string (const char *string
)
1108 AudioscrobblerEntry
*entry
;
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
);
1160 rb_audioscrobbler_load_queue (RBAudioscrobbler
*audioscrobbler
)
1162 char *pathname
, *uri
;
1163 GnomeVFSResult result
;
1167 pathname
= g_build_filename (rb_dot_dir (), "audioscrobbler.queue", NULL
);
1168 uri
= g_filename_to_uri (pathname
, NULL
, NULL
);
1170 rb_debug ("Loading Audioscrobbler queue from \"%s\"", uri
);
1172 result
= gnome_vfs_read_entire_file (uri
, &size
, &data
);
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');
1189 entry
= rb_audioscrobbler_load_entry_from_string (start
);
1191 audioscrobbler
->priv
->queue
= g_slist_append (audioscrobbler
->priv
->queue
, entry
);
1192 audioscrobbler
->priv
->queue_count
++;
1199 if (result
!= GNOME_VFS_OK
) {
1200 rb_debug ("Unable to load Audioscrobbler queue from disk: %s",
1201 gnome_vfs_result_to_string (result
));
1205 return (result
== GNOME_VFS_OK
);
1209 rb_audioscrobbler_save_queue (RBAudioscrobbler
*audioscrobbler
)
1212 GnomeVFSHandle
*handle
= NULL
;
1213 GnomeVFSResult result
;
1215 if (!audioscrobbler
->priv
->queue_changed
) {
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);
1225 if (result
== GNOME_VFS_OK
) {
1226 GString
*s
= g_string_new (NULL
);
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",
1240 result
= gnome_vfs_write (handle
, s
->str
, s
->len
, NULL
);
1241 if (result
!= GNOME_VFS_OK
)
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
));
1251 audioscrobbler
->priv
->queue_changed
= FALSE
;
1255 gnome_vfs_close (handle
);
1256 return (result
== GNOME_VFS_OK
);
1260 rb_audioscrobbler_print_queue (RBAudioscrobbler
*audioscrobbler
, gboolean submission
)
1263 AudioscrobblerEntry
*entry
;
1267 l
= audioscrobbler
->priv
->submission
;
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
);
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
);
1290 audioscrobbler
->priv
->queue_changed
= TRUE
;