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 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
);
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
,
337 rb_audioscrobbler_set_property (GObject
*object
,
342 RBAudioscrobbler
*audioscrobbler
= RB_AUDIOSCROBBLER (object
);
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
),
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
),
358 G_CALLBACK (rb_audioscrobbler_proxy_config_changed_cb
),
362 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
368 rb_audioscrobbler_get_property (GObject
*object
,
373 RBAudioscrobbler
*audioscrobbler
= RB_AUDIOSCROBBLER (object
);
376 case PROP_SHELL_PLAYER
:
377 g_value_set_object (value
, audioscrobbler
->priv
->shell_player
);
380 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
385 /* Add the audioscrobbler thread timer */
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
,
397 /* updates the queue and submits entries as required */
399 rb_audioscrobbler_timeout_cb (RBAudioscrobbler
*audioscrobbler
)
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);
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
);
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
,
436 audioscrobbler
->priv
->queue_changed
= TRUE
;
437 audioscrobbler
->priv
->queue_count
++;
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
);
470 rb_audioscrobbler_save_queue (audioscrobbler
);
476 /* Audioscrobbler functions: */
480 md5_state_t md5state
;
482 gchar md5_response
[33];
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
++) {
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
));
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
) {
511 body
= g_malloc0 ((msg
->response
).length
+ 1);
512 memcpy (body
, (msg
->response
).body
, (msg
->response
).length
);
515 breaks
= g_strsplit (body
, "\n", 4);
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
);
540 } else if (g_str_has_prefix (breaks
[i
], "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
);
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);
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")) {
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
;
590 audioscrobbler
->priv
->status
= REQUEST_FAILED
;
591 audioscrobbler
->priv
->status_msg
= g_strdup (soup_status_get_phrase (msg
->status_code
));
596 rb_audioscrobbler_perform (RBAudioscrobbler
*audioscrobbler
,
599 SoupMessageCallbackFn response_handler
)
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
,
614 /* create soup session, if we haven't got one yet */
615 if (!audioscrobbler
->priv
->soup_session
) {
618 uri
= rb_proxy_config_get_libsoup_uri (audioscrobbler
->priv
->proxy_config
);
619 audioscrobbler
->priv
->soup_session
= soup_session_async_new_with_options (
626 soup_session_queue_message (audioscrobbler
->priv
->soup_session
,
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
639 * - we have a username
642 if (! audioscrobbler
->priv
->handshake
&&
643 time (NULL
) >= audioscrobbler
->priv
->handshake_next
&&
644 strcmp (audioscrobbler
->priv
->username
, "") != 0) {
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",
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
);
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",
675 audioscrobbler
->priv
->handshake_next
);
676 if (strcmp (audioscrobbler
->priv
->username
, "") == 0)
677 rb_debug ("Username not set");
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
) {
693 case CLIENT_UPDATE_REQUIRED
:
694 audioscrobbler
->priv
->handshake
= TRUE
;
695 audioscrobbler
->priv
->failures
= 0;
698 rb_debug ("Handshake failed");
699 ++audioscrobbler
->priv
->failures
;
705 rb_audioscrobbler_submit_queue (RBAudioscrobbler
*audioscrobbler
)
708 * - Must have username and password
709 * - Must have md5_challenge
710 * - Queue must not be empty
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
) {
725 gchar
*md5_password
= mkmd5 (audioscrobbler
->priv
->password
);
726 gchar
*md5_temp
= g_strconcat (md5_password
,
727 audioscrobbler
->priv
->md5_challenge
,
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
);
736 g_free (md5_response
);
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&",
754 i
, entry
->timestamp
);
759 /* add to submission list */
760 audioscrobbler
->priv
->submission
= g_slist_concat (audioscrobbler
->priv
->submission
, l
);
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
,
770 rb_audioscrobbler_submit_queue_cb
);
772 /* libsoup will free post_data when the request is finished */
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");
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
));
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
;
826 rb_debug ("Queue submission failed %d times", audioscrobbler
->priv
->failures
);
830 rb_audioscrobbler_preferences_sync (audioscrobbler
);
833 /* Configuration functions: */
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"));
850 rb_audioscrobbler_preferences_sync (RBAudioscrobbler
*audioscrobbler
)
853 char *free_this
= NULL
;
855 if (!audioscrobbler
->priv
->config_widget
)
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
) {
869 status
= _("Logging in");
872 status
= _("Request failed");
875 status
= _("Incorrect username");
878 status
= _("Incorrect password");
880 case HANDSHAKE_FAILED
:
881 status
= _("Handshake failed");
883 case CLIENT_UPDATE_REQUIRED
:
884 status
= _("Client update required");
887 status
= _("Track submission failed");
890 status
= _("Queue is too long");
893 status
= _("Track submission failed too many times");
896 g_assert_not_reached ();
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
);
905 gtk_label_set_text (GTK_LABEL (audioscrobbler
->priv
->status_label
),
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
);
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
);
917 gtk_label_set_text (GTK_LABEL (audioscrobbler
->priv
->submit_time_label
),
918 audioscrobbler
->priv
->submit_time
);
922 rb_audioscrobbler_get_config_widget (RBAudioscrobbler
*audioscrobbler
)
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: */
954 rb_audioscrobbler_proxy_config_changed_cb (RBProxyConfig
*config
,
955 RBAudioscrobbler
*audioscrobbler
)
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
),
970 rb_audioscrobbler_gconf_changed_cb (GConfClient
*client
,
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
));
993 rb_debug ("Unhandled GConf key updated: \"%s\"", entry
->key
);
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
;
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
;
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
);
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
;
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
));
1049 rb_audioscrobbler_username_entry_activate_cb (GtkEntry
*entry
,
1050 RBAudioscrobbler
*audioscrobbler
)
1052 gtk_widget_grab_focus (audioscrobbler
->priv
->password_entry
);
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
));
1064 rb_audioscrobbler_password_entry_activate_cb (GtkEntry
*entry
,
1065 RBAudioscrobbler
*audioscrobbler
)
1070 /* AudioscrobblerEntry functions: */
1072 audioscrobbler_entry_init (AudioscrobblerEntry
*entry
)
1074 entry
->artist
= g_strdup ("");
1075 entry
->album
= g_strdup ("");
1076 entry
->title
= g_strdup ("");
1078 entry
->mbid
= g_strdup ("");
1079 entry
->timestamp
= g_strdup ("");
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
);
1095 /* Queue functions: */
1097 static AudioscrobblerEntry
*
1098 rb_audioscrobbler_load_entry_from_string (const char *string
)
1100 AudioscrobblerEntry
*entry
;
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
);
1152 rb_audioscrobbler_load_queue (RBAudioscrobbler
*audioscrobbler
)
1154 char *pathname
, *uri
;
1155 GnomeVFSResult result
;
1159 pathname
= g_build_filename (rb_dot_dir (), "audioscrobbler.queue", NULL
);
1160 uri
= g_filename_to_uri (pathname
, NULL
, NULL
);
1162 rb_debug ("Loading Audioscrobbler queue from \"%s\"", uri
);
1164 result
= gnome_vfs_read_entire_file (uri
, &size
, &data
);
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');
1181 entry
= rb_audioscrobbler_load_entry_from_string (start
);
1183 audioscrobbler
->priv
->queue
= g_slist_append (audioscrobbler
->priv
->queue
, entry
);
1184 audioscrobbler
->priv
->queue_count
++;
1191 if (result
!= GNOME_VFS_OK
) {
1192 rb_debug ("Unable to load Audioscrobbler queue from disk: %s",
1193 gnome_vfs_result_to_string (result
));
1197 return (result
== GNOME_VFS_OK
);
1201 rb_audioscrobbler_save_queue (RBAudioscrobbler
*audioscrobbler
)
1204 GnomeVFSHandle
*handle
= NULL
;
1205 GnomeVFSResult result
;
1207 if (!audioscrobbler
->priv
->queue_changed
) {
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);
1217 if (result
== GNOME_VFS_OK
) {
1218 GString
*s
= g_string_new (NULL
);
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",
1232 result
= gnome_vfs_write (handle
, s
->str
, s
->len
, NULL
);
1233 if (result
!= GNOME_VFS_OK
)
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
));
1243 audioscrobbler
->priv
->queue_changed
= FALSE
;
1247 gnome_vfs_close (handle
);
1248 return (result
== GNOME_VFS_OK
);
1252 rb_audioscrobbler_print_queue (RBAudioscrobbler
*audioscrobbler
, gboolean submission
)
1255 AudioscrobblerEntry
*entry
;
1259 l
= audioscrobbler
->priv
->submission
;
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
);
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
);
1282 audioscrobbler
->priv
->queue_changed
= TRUE
;