hythmbox-2.98/plugins/audioscrobbler/rb-audioscrobbler-user.c

No issues found

   1 /*
   2  * rb-audioscrobbler-user.c
   3  *
   4  * Copyright (C) 2010 Jamie Nicol <jamie@thenicols.net>
   5  *
   6  * This program is free software; you can redistribute it and/or modify
   7  * it under the terms of the GNU General Public License as published by
   8  * the Free Software Foundation; either version 2, or (at your option)
   9  * any later version.
  10  *
  11  * The Rhythmbox authors hereby grant permission for non-GPL compatible
  12  * GStreamer plugins to be used and distributed together with GStreamer
  13  * and Rhythmbox. This permission is above and beyond the permissions granted
  14  * by the GPL license by which Rhythmbox is covered. If you modify this code
  15  * you may extend this exception to your version of the code, but you are not
  16  * obligated to do so. If you do not wish to do so, delete this exception
  17  * statement from your version.
  18  *
  19  * This program is distributed in the hope that it will be useful,
  20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  22  * GNU General Public License for more details.
  23  *
  24  * You should have received a copy of the GNU General Public License
  25  * along with this program; if not, write to the Free Software
  26  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
  27  */
  28 
  29 #include <string.h>
  30 #include <libsoup/soup.h>
  31 #include <libsoup/soup-gnome.h>
  32 #include <json-glib/json-glib.h>
  33 #include <gdk-pixbuf/gdk-pixbuf.h>
  34 
  35 #include "rb-audioscrobbler-user.h"
  36 #include "rb-debug.h"
  37 #include "rb-file-helpers.h"
  38 
  39 #define USER_PROFILE_IMAGE_SIZE 126
  40 #define LIST_ITEM_IMAGE_SIZE 34
  41 
  42 #define USER_INFO_LIFETIME 86400             /* 24 hours */
  43 #define RECENT_TRACKS_LIFETIME 3600          /* 1 hour */
  44 #define TOP_TRACKS_LIFETIME 86400            /* 24 hours */
  45 #define LOVED_TRACKS_LIFETIME 86400          /* 24 hours */
  46 #define TOP_ARTISTS_LIFETIME 86400           /* 24 hours */
  47 #define RECOMMENDED_ARTISTS_LIFETIME 86400   /* 24 hours */
  48 
  49 static RBAudioscrobblerUserData *
  50 rb_audioscrobbler_user_data_new () {
  51 	RBAudioscrobblerUserData *data = g_slice_new0 (RBAudioscrobblerUserData);
  52 
  53 	data->refcount = 1;
  54 	return data;
  55 }
  56 
  57 static RBAudioscrobblerUserData *
  58 rb_audioscrobbler_user_data_ref (RBAudioscrobblerUserData *data)
  59 {
  60 	data->refcount++;
  61 	return data;
  62 }
  63 
  64 static void
  65 rb_audioscrobbler_user_data_free (RBAudioscrobblerUserData *data)
  66 {
  67 	if (data->image != NULL) {
  68 		g_object_unref (data->image);
  69 	}
  70 	g_free (data->url);
  71 
  72 	switch (data->type) {
  73 	case RB_AUDIOSCROBBLER_USER_DATA_TYPE_USER_INFO:
  74 		g_free (data->user_info.username);
  75 		g_free (data->user_info.playcount);
  76 		break;
  77 	case RB_AUDIOSCROBBLER_USER_DATA_TYPE_TRACK:
  78 		g_free (data->track.title);
  79 		g_free (data->track.artist);
  80 		break;
  81 	case RB_AUDIOSCROBBLER_USER_DATA_TYPE_ARTIST:
  82 		g_free (data->artist.name);
  83 		break;
  84 	}
  85 
  86 	g_slice_free (RBAudioscrobblerUserData, data);
  87 }
  88 
  89 static void
  90 rb_audioscrobbler_user_data_unref (RBAudioscrobblerUserData *data) {
  91 	if (--data->refcount == 0) {
  92 		rb_audioscrobbler_user_data_free (data);
  93 	}
  94 }
  95 
  96 GType
  97 rb_audioscrobbler_user_data_get_type (void)
  98 {
  99 	static GType type = 0;
 100 
 101 	if (G_UNLIKELY (type == 0)) {
 102 		type = g_boxed_type_register_static ("RBAudioscrobblerUserData",
 103 		                                     (GBoxedCopyFunc)rb_audioscrobbler_user_data_ref,
 104 		                                     (GBoxedFreeFunc)rb_audioscrobbler_user_data_unref);
 105 	}
 106 
 107 	return type;
 108 }
 109 
 110 /* unrefs each element and frees the queue */
 111 static void
 112 free_data_queue (gpointer data_queue)
 113 {
 114 	g_queue_free_full (data_queue,
 115 	                   (GDestroyNotify)rb_audioscrobbler_user_data_unref);
 116 }
 117 
 118 struct _RBAudioscrobblerUserPrivate {
 119 	RBAudioscrobblerService *service;
 120 	char *username;
 121 	char *session_key;
 122 
 123 	SoupSession *soup_session;
 124 
 125 	RBAudioscrobblerUserData *user_info;
 126 	GPtrArray *recent_tracks;
 127 	GPtrArray *top_tracks;
 128 	GPtrArray *loved_tracks;
 129 	GPtrArray *top_artists;
 130 	GPtrArray *recommended_artists;
 131 
 132 	/* for image downloads */
 133 	GHashTable *file_to_data_queue_map;
 134 	GHashTable *file_to_cancellable_map;
 135 };
 136 
 137 #define RB_AUDIOSCROBBLER_USER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_AUDIOSCROBBLER_USER, RBAudioscrobblerUserPrivate))
 138 
 139 static void rb_audioscrobbler_user_constructed (GObject *object);
 140 static void rb_audioscrobbler_user_dispose (GObject* object);
 141 static void rb_audioscrobbler_user_finalize (GObject *object);
 142 static void rb_audioscrobbler_user_get_property (GObject *object,
 143                                                  guint prop_id,
 144                                                  GValue *value,
 145                                                  GParamSpec *pspec);
 146 static void rb_audioscrobbler_user_set_property (GObject *object,
 147                                                  guint prop_id,
 148                                                  const GValue *value,
 149                                                  GParamSpec *pspec);
 150 
 151 static void load_from_cache (RBAudioscrobblerUser *user);
 152 
 153 static char * calculate_cached_response_path (RBAudioscrobblerUser *user,
 154                                               const char *request_name);
 155 static gboolean is_cached_response_expired (RBAudioscrobblerUser *user,
 156                                             const char *request_name,
 157                                             long lifetime);
 158 static void save_response_to_cache (RBAudioscrobblerUser *user,
 159                                     const char *request_name,
 160                                     const char *data);
 161 
 162 static GPtrArray * parse_track_array (RBAudioscrobblerUser *user, JsonArray *track_array);
 163 static GPtrArray * parse_artist_array (RBAudioscrobblerUser *user, JsonArray *track_array);
 164 
 165 static void load_cached_user_info (RBAudioscrobblerUser *user);
 166 static void request_user_info (RBAudioscrobblerUser *user);
 167 static void user_info_response_cb (SoupSession *session,
 168                                    SoupMessage *msg,
 169                                    gpointer user_data);
 170 static RBAudioscrobblerUserData * parse_user_info (RBAudioscrobblerUser *user,
 171                                                    const char *data);
 172 
 173 static void load_cached_recent_tracks (RBAudioscrobblerUser *user);
 174 static void request_recent_tracks (RBAudioscrobblerUser *user, int limit);
 175 static void recent_tracks_response_cb (SoupSession *session,
 176                                        SoupMessage *msg,
 177                                        gpointer user_data);
 178 static GPtrArray * parse_recent_tracks (RBAudioscrobblerUser *user,
 179                                         const char *data);
 180 
 181 static void load_cached_top_tracks (RBAudioscrobblerUser *user);
 182 static void request_top_tracks (RBAudioscrobblerUser *user, int limit);
 183 static void top_tracks_response_cb (SoupSession *session,
 184                                     SoupMessage *msg,
 185                                     gpointer user_data);
 186 static GPtrArray * parse_top_tracks (RBAudioscrobblerUser *user,
 187                                      const char *data);
 188 
 189 static void load_cached_loved_tracks (RBAudioscrobblerUser *user);
 190 static void request_loved_tracks (RBAudioscrobblerUser *user, int limit);
 191 static void loved_tracks_response_cb (SoupSession *session,
 192                                       SoupMessage *msg,
 193                                       gpointer user_data);
 194 static GPtrArray * parse_loved_tracks (RBAudioscrobblerUser *user,
 195                                        const char *data);
 196 
 197 static void load_cached_top_artists (RBAudioscrobblerUser *user);
 198 static void request_top_artists (RBAudioscrobblerUser *user, int limit);
 199 static void top_artists_response_cb (SoupSession *session,
 200                                      SoupMessage *msg,
 201                                      gpointer user_data);
 202 static GPtrArray * parse_top_artists (RBAudioscrobblerUser *user,
 203                                       const char *data);
 204 
 205 static void load_cached_recommended_artists (RBAudioscrobblerUser *user);
 206 static void request_recommended_artists (RBAudioscrobblerUser *user, int limit);
 207 static void recommended_artists_response_cb (SoupSession *session,
 208                                              SoupMessage *msg,
 209                                              gpointer user_data);
 210 static GPtrArray * parse_recommended_artists (RBAudioscrobblerUser *user,
 211                                               const char *data);
 212 
 213 static char * calculate_cached_image_path (RBAudioscrobblerUser *user,
 214                                            RBAudioscrobblerUserData *data);
 215 static void download_image (RBAudioscrobblerUser *user,
 216                             const char *image_url,
 217                             RBAudioscrobblerUserData *data);
 218 static void image_download_cb (GObject *source_object,
 219                                GAsyncResult *res,
 220                                gpointer user_data);
 221 
 222 static void love_track_response_cb (SoupSession *session,
 223                                     SoupMessage *msg,
 224                                     gpointer user_data);
 225 static void ban_track_response_cb (SoupSession *session,
 226                                    SoupMessage *msg,
 227                                    gpointer user_data);
 228 enum {
 229 	PROP_0,
 230 	PROP_SERVICE
 231 };
 232 
 233 enum {
 234 	USER_INFO_UPDATED,
 235 	RECENT_TRACKS_UPDATED,
 236 	TOP_TRACKS_UPDATED,
 237 	LOVED_TRACKS_UPDATED,
 238 	TOP_ARTISTS_UPDATED,
 239 	RECOMMENDED_ARTISTS_UPDATED,
 240 	LAST_SIGNAL
 241 };
 242 
 243 static guint rb_audioscrobbler_user_signals[LAST_SIGNAL] = { 0 };
 244 
 245 G_DEFINE_DYNAMIC_TYPE (RBAudioscrobblerUser, rb_audioscrobbler_user, G_TYPE_OBJECT)
 246 
 247 RBAudioscrobblerUser *
 248 rb_audioscrobbler_user_new (RBAudioscrobblerService *service)
 249 {
 250 	return g_object_new (RB_TYPE_AUDIOSCROBBLER_USER,
 251 	                     "service", service,
 252 	                     NULL);
 253 }
 254 
 255 static void
 256 rb_audioscrobbler_user_class_init (RBAudioscrobblerUserClass *klass)
 257 {
 258 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 259 
 260 	object_class->constructed = rb_audioscrobbler_user_constructed;
 261 	object_class->dispose = rb_audioscrobbler_user_dispose;
 262 	object_class->finalize = rb_audioscrobbler_user_finalize;
 263 	object_class->get_property = rb_audioscrobbler_user_get_property;
 264 	object_class->set_property = rb_audioscrobbler_user_set_property;
 265 
 266 	g_object_class_install_property (object_class,
 267 	                                 PROP_SERVICE,
 268 	                                 g_param_spec_object ("service",
 269 	                                                      "Service",
 270 	                                                      "Audioscrobbler service that this should use for requests",
 271 	                                                      RB_TYPE_AUDIOSCROBBLER_SERVICE,
 272 	                                                      G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
 273 
 274 
 275 	rb_audioscrobbler_user_signals[USER_INFO_UPDATED] =
 276 		g_signal_new ("user-info-updated",
 277 		              G_OBJECT_CLASS_TYPE (object_class),
 278 		              G_SIGNAL_RUN_LAST,
 279 		              0,
 280 		              NULL, NULL,
 281 		              g_cclosure_marshal_VOID__BOXED,
 282 		              G_TYPE_NONE,
 283 		              1,
 284 		              RB_TYPE_AUDIOSCROBBLER_USER_DATA);
 285 
 286 	rb_audioscrobbler_user_signals[RECENT_TRACKS_UPDATED] =
 287 		g_signal_new ("recent-tracks-updated",
 288 		              G_OBJECT_CLASS_TYPE (object_class),
 289 		              G_SIGNAL_RUN_LAST,
 290 		              0,
 291 		              NULL, NULL,
 292 		              g_cclosure_marshal_VOID__BOXED,
 293 		              G_TYPE_NONE,
 294 		              1,
 295 		              G_TYPE_PTR_ARRAY);
 296 
 297 	rb_audioscrobbler_user_signals[TOP_TRACKS_UPDATED] =
 298 		g_signal_new ("top-tracks-updated",
 299 		              G_OBJECT_CLASS_TYPE (object_class),
 300 		              G_SIGNAL_RUN_LAST,
 301 		              0,
 302 		              NULL, NULL,
 303 		              g_cclosure_marshal_VOID__BOXED,
 304 		              G_TYPE_NONE,
 305 		              1,
 306 		              G_TYPE_PTR_ARRAY);
 307 
 308 	rb_audioscrobbler_user_signals[LOVED_TRACKS_UPDATED] =
 309 		g_signal_new ("loved-tracks-updated",
 310 		              G_OBJECT_CLASS_TYPE (object_class),
 311 		              G_SIGNAL_RUN_LAST,
 312 		              0,
 313 		              NULL, NULL,
 314 		              g_cclosure_marshal_VOID__BOXED,
 315 		              G_TYPE_NONE,
 316 		              1,
 317 		              G_TYPE_PTR_ARRAY);
 318 
 319 	rb_audioscrobbler_user_signals[TOP_ARTISTS_UPDATED] =
 320 		g_signal_new ("top-artists-updated",
 321 		              G_OBJECT_CLASS_TYPE (object_class),
 322 		              G_SIGNAL_RUN_LAST,
 323 		              0,
 324 		              NULL, NULL,
 325 		              g_cclosure_marshal_VOID__BOXED,
 326 		              G_TYPE_NONE,
 327 		              1,
 328 		              G_TYPE_PTR_ARRAY);
 329 
 330 	rb_audioscrobbler_user_signals[RECOMMENDED_ARTISTS_UPDATED] =
 331 		g_signal_new ("recommended-artists-updated",
 332 		              G_OBJECT_CLASS_TYPE (object_class),
 333 		              G_SIGNAL_RUN_LAST,
 334 		              0,
 335 		              NULL, NULL,
 336 		              g_cclosure_marshal_VOID__BOXED,
 337 		              G_TYPE_NONE,
 338 		              1,
 339 		              G_TYPE_PTR_ARRAY);
 340 
 341 	g_type_class_add_private (klass, sizeof (RBAudioscrobblerUserPrivate));
 342 }
 343 
 344 static void
 345 rb_audioscrobbler_user_class_finalize (RBAudioscrobblerUserClass *klass)
 346 {
 347 }
 348 
 349 static void
 350 rb_audioscrobbler_user_init (RBAudioscrobblerUser *user)
 351 {
 352 	user->priv = RB_AUDIOSCROBBLER_USER_GET_PRIVATE (user);
 353 
 354 	user->priv->soup_session =
 355 		soup_session_async_new_with_options (SOUP_SESSION_ADD_FEATURE_BY_TYPE,
 356 		                                     SOUP_TYPE_GNOME_FEATURES_2_26,
 357 		                                     NULL);
 358 
 359 	user->priv->file_to_data_queue_map = g_hash_table_new_full (g_file_hash,
 360 	                                                            (GEqualFunc) g_file_equal,
 361 	                                                            g_object_unref,
 362 	                                                            free_data_queue);
 363 	user->priv->file_to_cancellable_map = g_hash_table_new_full (g_file_hash,
 364 	                                                             (GEqualFunc) g_file_equal,
 365 	                                                             NULL,
 366 	                                                             g_object_unref);
 367 }
 368 
 369 static void
 370 rb_audioscrobbler_user_constructed (GObject *object)
 371 {
 372 }
 373 
 374 static void
 375 rb_audioscrobbler_user_dispose (GObject* object)
 376 {
 377 	RBAudioscrobblerUser *user = RB_AUDIOSCROBBLER_USER (object);
 378 
 379 	if (user->priv->service != NULL) {
 380 		g_object_unref (user->priv->service);
 381 		user->priv->service = NULL;
 382 	}
 383 
 384 	if (user->priv->soup_session != NULL) {
 385 		soup_session_abort (user->priv->soup_session);
 386 		g_object_unref (user->priv->soup_session);
 387 		user->priv->soup_session = NULL;
 388 	}
 389 
 390 	if (user->priv->user_info != NULL) {
 391 		rb_audioscrobbler_user_data_unref (user->priv->user_info);
 392 		user->priv->user_info = NULL;
 393 	}
 394 
 395 	if (user->priv->recent_tracks != NULL) {
 396 		g_ptr_array_unref (user->priv->recent_tracks);
 397 		user->priv->recent_tracks = NULL;
 398 	}
 399 
 400 	if (user->priv->top_tracks != NULL) {
 401 		g_ptr_array_unref (user->priv->top_tracks);
 402 		user->priv->top_tracks = NULL;
 403 	}
 404 
 405 	if (user->priv->loved_tracks != NULL) {
 406 		g_ptr_array_unref (user->priv->loved_tracks);
 407 		user->priv->loved_tracks = NULL;
 408 	}
 409 
 410 	if (user->priv->top_artists != NULL) {
 411 		g_ptr_array_unref (user->priv->top_artists);
 412 		user->priv->top_artists = NULL;
 413 	}
 414 
 415 	if (user->priv->recommended_artists != NULL) {
 416 		g_ptr_array_unref (user->priv->recommended_artists);
 417 		user->priv->recommended_artists = NULL;
 418 	}
 419 
 420 	/* free this map first because file_to_data_queue_map owns the file reference */
 421 	if (user->priv->file_to_cancellable_map != NULL) {
 422 		GList *key;
 423 
 424 		for (key = g_hash_table_get_keys (user->priv->file_to_cancellable_map);
 425 		     key != NULL;
 426 		     key = g_list_next (key)) {
 427 			GCancellable *cancellable = g_hash_table_lookup (user->priv->file_to_cancellable_map, key->data);
 428 			g_cancellable_cancel (cancellable);
 429 		}
 430 		g_list_free (key);
 431 
 432 		g_hash_table_unref (user->priv->file_to_cancellable_map);
 433 		user->priv->file_to_cancellable_map = NULL;
 434 	}
 435 
 436 	if (user->priv->file_to_data_queue_map != NULL) {
 437 		g_hash_table_unref (user->priv->file_to_data_queue_map);
 438 		user->priv->file_to_data_queue_map = NULL;
 439 	}
 440 }
 441 
 442 static void
 443 rb_audioscrobbler_user_finalize (GObject *object)
 444 {
 445 	RBAudioscrobblerUser *user = RB_AUDIOSCROBBLER_USER (object);
 446 
 447 	g_free (user->priv->username);
 448 	g_free (user->priv->session_key);
 449 }
 450 
 451 static void
 452 rb_audioscrobbler_user_get_property (GObject *object,
 453                                      guint prop_id,
 454                                      GValue *value,
 455                                      GParamSpec *pspec)
 456 {
 457 	switch (prop_id) {
 458 	default:
 459 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 460 		break;
 461 	}
 462 }
 463 
 464 static void
 465 rb_audioscrobbler_user_set_property (GObject *object,
 466                                      guint prop_id,
 467                                      const GValue *value,
 468                                      GParamSpec *pspec)
 469 {
 470 	RBAudioscrobblerUser *user = RB_AUDIOSCROBBLER_USER (object);
 471 	switch (prop_id) {
 472 	case PROP_SERVICE:
 473 		user->priv->service = g_value_dup_object (value);
 474 		break;
 475 	default:
 476 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 477 		break;
 478 	}
 479 }
 480 
 481 void
 482 rb_audioscrobbler_user_set_authentication_details (RBAudioscrobblerUser *user,
 483                                                    const char *username,
 484                                                    const char *session_key)
 485 {
 486 	g_free (user->priv->username);
 487 	user->priv->username = g_strdup (username);
 488 
 489 	g_free (user->priv->session_key);
 490 	user->priv->session_key = g_strdup (session_key);
 491 
 492 	/* cancel pending requests */
 493 	soup_session_abort (user->priv->soup_session);
 494 
 495 	/* load new user from cache (or set to NULL) */
 496 	load_from_cache (user);
 497 }
 498 
 499 void
 500 rb_audioscrobbler_user_update (RBAudioscrobblerUser *user)
 501 {
 502 	if (user->priv->username != NULL) {
 503 		/* update if cached data is no longer valid */
 504 		if (is_cached_response_expired (user, "user_info", USER_INFO_LIFETIME)) {
 505 			rb_debug ("cached user info response is expired, updating");
 506 			request_user_info (user);
 507 		} else {
 508 			rb_debug ("cached user info response is still valid, not updating");
 509 		}
 510 
 511 		if (is_cached_response_expired (user, "recent_tracks", RECENT_TRACKS_LIFETIME)) {
 512 			rb_debug ("cached recent tracks response is expired, updating");
 513 			request_recent_tracks (user, 15);
 514 		} else {
 515 			rb_debug ("cached recent tracks response is still valid, not updating");
 516 		}
 517 
 518 		if (is_cached_response_expired (user, "top_tracks", TOP_TRACKS_LIFETIME)) {
 519 			rb_debug ("cached top tracks response is expired, updating");
 520 			request_top_tracks (user, 15);
 521 		} else {
 522 			rb_debug ("cached top tracks response is still valid, not updating");
 523 		}
 524 
 525 		if (is_cached_response_expired (user, "loved_tracks", LOVED_TRACKS_LIFETIME)) {
 526 			rb_debug ("cached loved tracks response is expired, updating");
 527 			request_loved_tracks (user, 15);
 528 		} else {
 529 			rb_debug ("cached loved tracks response is still valid, not updating");
 530 		}
 531 
 532 		if (is_cached_response_expired (user, "top_artists", TOP_ARTISTS_LIFETIME)) {
 533 			rb_debug ("cached top artists response is expired, updating");
 534 			request_top_artists (user, 15);
 535 		} else {
 536 			rb_debug ("cached top artists is still valid, not updating");
 537 		}
 538 
 539 		if (is_cached_response_expired (user, "recommended_artists", RECOMMENDED_ARTISTS_LIFETIME)) {
 540 			rb_debug ("cached recommended artists response is expired, updating");
 541 			request_recommended_artists (user, 15);
 542 		} else {
 543 			rb_debug ("cached recommended artists response is still valid, not updating");
 544 		}
 545 
 546 	}
 547 }
 548 
 549 void
 550 rb_audioscrobbler_user_force_update (RBAudioscrobblerUser *user)
 551 {
 552 	if (user->priv->username != NULL) {
 553 		rb_debug ("forcing update of user data");
 554 		request_user_info (user);
 555 		request_recent_tracks (user, 15);
 556 		request_top_tracks (user, 15);
 557 		request_loved_tracks (user, 15);
 558 		request_top_artists (user, 15);
 559 		request_recommended_artists (user, 15);
 560 	}
 561 }
 562 
 563 static void
 564 load_from_cache (RBAudioscrobblerUser *user)
 565 {
 566 	/* delete old data */
 567 	if (user->priv->user_info != NULL) {
 568 		rb_audioscrobbler_user_data_unref (user->priv->user_info);
 569 		user->priv->user_info = NULL;
 570 	}
 571 
 572 	if (user->priv->recent_tracks != NULL) {
 573 		g_ptr_array_unref (user->priv->recent_tracks);
 574 		user->priv->recent_tracks = NULL;
 575 	}
 576 
 577 	if (user->priv->top_tracks != NULL) {
 578 		g_ptr_array_unref (user->priv->top_tracks);
 579 		user->priv->top_tracks = NULL;
 580 	}
 581 
 582 	if (user->priv->loved_tracks != NULL) {
 583 		g_ptr_array_unref (user->priv->loved_tracks);
 584 		user->priv->loved_tracks = NULL;
 585 	}
 586 
 587 	if (user->priv->top_artists != NULL) {
 588 		g_ptr_array_unref (user->priv->top_artists);
 589 		user->priv->top_artists = NULL;
 590 	}
 591 
 592 	if (user->priv->recommended_artists != NULL) {
 593 		g_ptr_array_unref (user->priv->recommended_artists);
 594 		user->priv->recommended_artists = NULL;
 595 	}
 596 
 597 	/* if a username is set then attempt to load cached data */
 598 	if (user->priv->username != NULL) {
 599 		load_cached_user_info (user);
 600 		load_cached_recent_tracks (user);
 601 		load_cached_top_tracks (user);
 602 		load_cached_loved_tracks (user);
 603 		load_cached_top_artists (user);
 604 		load_cached_recommended_artists (user);
 605 	}
 606 }
 607 
 608 static char *
 609 calculate_cached_response_path (RBAudioscrobblerUser *user, const char *request_name)
 610 {
 611 	const char *rb_cache_dir;
 612 	rb_cache_dir = rb_user_cache_dir ();
 613 
 614 	return g_build_filename (rb_cache_dir,
 615 	                         "audioscrobbler",
 616 	                         rb_audioscrobbler_service_get_name (user->priv->service),
 617 	                         "ws-responses",
 618 	                         user->priv->username,
 619 	                         request_name,
 620 	                         NULL);
 621 }
 622 
 623 static gboolean
 624 is_cached_response_expired (RBAudioscrobblerUser *user,
 625                             const char *request_name,
 626                             long lifetime)
 627 {
 628 	char *response_path;
 629 	GFile *file;
 630 	GFileInfo *info;
 631 
 632 	response_path = calculate_cached_response_path (user, request_name);
 633 	file = g_file_new_for_path (response_path);
 634 	info = g_file_query_info (file,
 635 	                          G_FILE_ATTRIBUTE_TIME_MODIFIED,
 636 	                          G_FILE_QUERY_INFO_NONE,
 637 	                          NULL,
 638 	                          NULL);
 639 	g_free (response_path);
 640 	g_object_unref (file);
 641 
 642 	if (info == NULL) {
 643 		return TRUE;
 644 	} else {
 645 		GTimeVal now;
 646 		GTimeVal modified;
 647 
 648 		g_get_current_time (&now);
 649 		g_file_info_get_modification_time (info, &modified);
 650 
 651 		g_object_unref (info);
 652 
 653 		return now.tv_sec - modified.tv_sec > lifetime;
 654 	}
 655 }
 656 
 657 static void
 658 save_response_to_cache (RBAudioscrobblerUser *user, const char *request_name, const char *data)
 659 {
 660 	char *filename;
 661 	char *file_uri;
 662 	GError *error;
 663 
 664 	filename = calculate_cached_response_path (user, request_name);
 665 	file_uri = g_filename_to_uri (filename, NULL, NULL);
 666 
 667 	error = NULL;
 668 	if (rb_uri_create_parent_dirs (file_uri, &error)) {
 669 		g_file_set_contents (filename, data, -1, &error);
 670 	}
 671 
 672 	if (error == NULL) {
 673 		rb_debug ("saved %s to cache", request_name);
 674 	} else {
 675 		rb_debug ("error saving %s to cache: %s", request_name, error->message);
 676 		g_error_free (error);
 677 	}
 678 
 679 	g_free (filename);
 680 	g_free (file_uri);
 681 }
 682 
 683 /* general parsing functions (to be used by parse_recent_tracks, parse_recommended artists etc */
 684 static GPtrArray *
 685 parse_track_array (RBAudioscrobblerUser *user, JsonArray *track_array)
 686 {
 687 	GPtrArray *tracks;
 688 	int i;
 689 
 690 	tracks = g_ptr_array_new_with_free_func ((GDestroyNotify)rb_audioscrobbler_user_data_unref);
 691 
 692 	for (i = 0; i < json_array_get_length (track_array); i++) {
 693 		JsonObject *track_object;
 694 		JsonObject *artist_object;
 695 		RBAudioscrobblerUserData *track;
 696 		char *image_path;
 697 
 698 		track_object = json_array_get_object_element (track_array, i);
 699 
 700 		track = rb_audioscrobbler_user_data_new ();
 701 		track->type = RB_AUDIOSCROBBLER_USER_DATA_TYPE_TRACK;
 702 		track->track.title = g_strdup (json_object_get_string_member (track_object, "name"));
 703 
 704 		/* sometimes the artist object has a "name" member,
 705 		 * and other times it has a "#text" member.
 706 		 */
 707 		artist_object = json_object_get_object_member (track_object, "artist");
 708 		if (json_object_has_member (artist_object, "name")) {
 709 			track->track.artist = g_strdup (json_object_get_string_member (artist_object, "name"));
 710 		} else {
 711 			track->track.artist = g_strdup (json_object_get_string_member (artist_object, "#text"));
 712 		}
 713 
 714 		track->url = g_strdup (json_object_get_string_member (track_object, "url"));
 715 
 716 		image_path = calculate_cached_image_path (user, track);
 717 		track->image = gdk_pixbuf_new_from_file_at_size (image_path,
 718 		                                                 LIST_ITEM_IMAGE_SIZE, LIST_ITEM_IMAGE_SIZE,
 719 		                                                 NULL);
 720 		if (track->image == NULL && json_object_has_member (track_object, "image") == TRUE) {
 721 			JsonArray *image_array;
 722 			JsonObject *image_object;
 723 
 724 			image_array = json_object_get_array_member (track_object, "image");
 725 			image_object = json_array_get_object_element (image_array, 0);
 726 			download_image (user, json_object_get_string_member (image_object, "#text"), track);
 727 		}
 728 
 729 		g_ptr_array_add (tracks, track);
 730 
 731 		g_free (image_path);
 732 	}
 733 
 734 	return tracks;
 735 }
 736 
 737 static GPtrArray *
 738 parse_artist_array (RBAudioscrobblerUser *user, JsonArray *artist_array)
 739 {
 740 	GPtrArray *artists;
 741 	int i;
 742 
 743 	artists = g_ptr_array_new_with_free_func ((GDestroyNotify)rb_audioscrobbler_user_data_unref);
 744 
 745 	for (i = 0; i < json_array_get_length (artist_array); i++) {
 746 		JsonObject *artist_object;
 747 		RBAudioscrobblerUserData *artist;
 748 		char *image_path;
 749 
 750 		artist_object = json_array_get_object_element (artist_array, i);
 751 
 752 		artist = rb_audioscrobbler_user_data_new ();
 753 		artist->type = RB_AUDIOSCROBBLER_USER_DATA_TYPE_ARTIST;
 754 		artist->artist.name = g_strdup (json_object_get_string_member (artist_object, "name"));
 755 		artist->url = g_strdup (json_object_get_string_member (artist_object, "url"));
 756 
 757 		image_path = calculate_cached_image_path (user, artist);
 758 		artist->image = gdk_pixbuf_new_from_file_at_size (image_path,
 759 		                                                  LIST_ITEM_IMAGE_SIZE, LIST_ITEM_IMAGE_SIZE,
 760 		                                                  NULL);
 761 		if (artist->image == NULL && json_object_has_member (artist_object, "image") == TRUE) {
 762 			JsonArray *image_array;
 763 			JsonObject *image_object;
 764 
 765 			image_array = json_object_get_array_member (artist_object, "image");
 766 			image_object = json_array_get_object_element (image_array, 0);
 767 			download_image (user, json_object_get_string_member (image_object, "#text"), artist);
 768 		}
 769 
 770 		g_ptr_array_add (artists, artist);
 771 
 772 		g_free (image_path);
 773 	}
 774 
 775 	return artists;
 776 }
 777 
 778 /* user info */
 779 static void
 780 load_cached_user_info (RBAudioscrobblerUser *user)
 781 {
 782 	char *filename;
 783 	char *data;
 784 
 785 	filename = calculate_cached_response_path (user, "user_info");
 786 
 787 	/* delete old data */
 788 	if (user->priv->user_info != NULL) {
 789 		rb_audioscrobbler_user_data_unref (user->priv->user_info);
 790 		user->priv->user_info = NULL;
 791 	}
 792 
 793 	/* load cached data if it exists */
 794 	if (g_file_get_contents (filename, &data, NULL, NULL) == TRUE) {
 795 		rb_debug ("loading cached user_info");
 796 		user->priv->user_info = parse_user_info (user, data);
 797 	}
 798 
 799 	/* emit updated signal */
 800 	g_signal_emit (user, rb_audioscrobbler_user_signals[USER_INFO_UPDATED],
 801 	               0, user->priv->user_info);
 802 
 803 	g_free (filename);
 804 	g_free (data);
 805 }
 806 
 807 static void
 808 request_user_info (RBAudioscrobblerUser *user)
 809 {
 810 	char *msg_url;
 811 	SoupMessage *msg;
 812 
 813 	rb_debug ("requesting user info");
 814 
 815 	msg_url = g_strdup_printf ("%s?method=user.getInfo&user=%s&api_key=%s&format=json",
 816 	                           rb_audioscrobbler_service_get_api_url (user->priv->service),
 817 	                           user->priv->username,
 818 	                           rb_audioscrobbler_service_get_api_key (user->priv->service));
 819 
 820 	msg = soup_message_new ("GET", msg_url);
 821 	soup_session_queue_message (user->priv->soup_session,
 822 	                            msg,
 823 	                            user_info_response_cb,
 824 	                            user);
 825 
 826 	g_free (msg_url);
 827 }
 828 
 829 static void
 830 user_info_response_cb (SoupSession *session,
 831                        SoupMessage *msg,
 832                        gpointer user_data)
 833 {
 834 	RBAudioscrobblerUser *user;
 835 	RBAudioscrobblerUserData *user_info;
 836 
 837 	user = RB_AUDIOSCROBBLER_USER (user_data);
 838 	user_info = parse_user_info (user, msg->response_body->data);
 839 
 840 	if (user_info != NULL) {
 841 		rb_debug ("user info request was successful");
 842 
 843 		if (user->priv->user_info != NULL) {
 844 			rb_audioscrobbler_user_data_unref (user->priv->user_info);
 845 		}
 846 		user->priv->user_info = user_info;
 847 
 848 		save_response_to_cache (user, "user_info", msg->response_body->data);
 849 
 850 		g_signal_emit (user, rb_audioscrobbler_user_signals[USER_INFO_UPDATED],
 851 		               0, user->priv->user_info);
 852 	} else {
 853 		rb_debug ("invalid response from user info request");
 854 	}
 855 }
 856 
 857 static RBAudioscrobblerUserData *
 858 parse_user_info (RBAudioscrobblerUser *user, const char *data)
 859 {
 860 	RBAudioscrobblerUserData *user_info;
 861 	JsonParser *parser;
 862 
 863 	user_info = NULL;
 864 
 865 	parser = json_parser_new ();
 866 	if (data != NULL && json_parser_load_from_data (parser, data, -1, NULL)) {
 867 		JsonObject *root_object;
 868 		root_object = json_node_get_object (json_parser_get_root (parser));
 869 
 870 		if (json_object_has_member (root_object, "user")) {
 871 			JsonObject *user_object;
 872 			user_object = json_object_get_object_member (root_object, "user");
 873 			char *image_path;
 874 
 875 			user_info = rb_audioscrobbler_user_data_new ();
 876 			user_info->type = RB_AUDIOSCROBBLER_USER_DATA_TYPE_USER_INFO;
 877 			user_info->user_info.username = g_strdup (json_object_get_string_member (user_object, "name"));
 878 			user_info->user_info.playcount = g_strdup (json_object_get_string_member (user_object, "playcount"));
 879 			user_info->url = g_strdup (json_object_get_string_member (user_object, "url"));
 880 
 881 			image_path = calculate_cached_image_path (user, user_info);
 882 			user_info->image = gdk_pixbuf_new_from_file_at_size (image_path,
 883 					                                     USER_PROFILE_IMAGE_SIZE, -1, NULL);
 884 			if (user_info->image == NULL && json_object_has_member (user_object, "image") == TRUE) {
 885 				JsonArray *image_array;
 886 				JsonObject *image_object;
 887 
 888 				image_array = json_object_get_array_member (user_object, "image");
 889 				image_object = json_array_get_object_element (image_array, 2);
 890 				download_image (user, json_object_get_string_member (image_object, "#text"), user_info);
 891 			}
 892 
 893 			g_free (image_path);
 894 		} else {
 895 			rb_debug ("error parsing user info response: no user object exists");
 896 		}
 897 	} else {
 898 		rb_debug ("error parsing user info response: empty or invalid response");
 899 	}
 900 
 901 	g_object_unref (parser);
 902 
 903 	return user_info;
 904 }
 905 
 906 /* recent tracks */
 907 static void
 908 load_cached_recent_tracks (RBAudioscrobblerUser *user)
 909 {
 910 	char *filename;
 911 	char *data;
 912 
 913 	filename = calculate_cached_response_path (user, "recent_tracks");
 914 
 915 	/* delete old data */
 916 	if (user->priv->recent_tracks != NULL) {
 917 		g_ptr_array_unref (user->priv->recent_tracks);
 918 		user->priv->recent_tracks = NULL;
 919 	}
 920 
 921 	/* load cached data if it exists */
 922 	if (g_file_get_contents (filename, &data, NULL, NULL) == TRUE) {
 923 		rb_debug ("loading cached recent tracks");
 924 		user->priv->recent_tracks = parse_recent_tracks (user, data);
 925 	}
 926 
 927 	/* emit updated signal */
 928 	g_signal_emit (user, rb_audioscrobbler_user_signals[RECENT_TRACKS_UPDATED],
 929 	               0, user->priv->recent_tracks);
 930 
 931 	g_free (filename);
 932 	g_free (data);
 933 }
 934 
 935 static void
 936 request_recent_tracks (RBAudioscrobblerUser *user, int limit)
 937 {
 938 	char *msg_url;
 939 	SoupMessage *msg;
 940 
 941 	rb_debug ("requesting recent tracks");
 942 
 943 	msg_url = g_strdup_printf ("%s?method=user.getRecentTracks&user=%s&api_key=%s&limit=%i&format=json",
 944 	                           rb_audioscrobbler_service_get_api_url (user->priv->service),
 945 	                           user->priv->username,
 946 	                           rb_audioscrobbler_service_get_api_key (user->priv->service),
 947 	                           limit);
 948 
 949 	msg = soup_message_new ("GET", msg_url);
 950 	soup_session_queue_message (user->priv->soup_session,
 951 	                            msg,
 952 	                            recent_tracks_response_cb,
 953 	                            user);
 954 
 955 	g_free (msg_url);
 956 }
 957 
 958 static void
 959 recent_tracks_response_cb (SoupSession *session,
 960                            SoupMessage *msg,
 961                            gpointer user_data)
 962 {
 963 	RBAudioscrobblerUser *user;
 964 	GPtrArray *recent_tracks;
 965 
 966 	user = RB_AUDIOSCROBBLER_USER (user_data);
 967 	recent_tracks = parse_recent_tracks (user, msg->response_body->data);
 968 
 969 	if (recent_tracks != NULL) {
 970 		rb_debug ("recent tracks request was successful");
 971 
 972 		if (user->priv->recent_tracks != NULL) {
 973 			g_ptr_array_unref (user->priv->recent_tracks);
 974 		}
 975 		user->priv->recent_tracks = recent_tracks;
 976 
 977 		save_response_to_cache (user, "recent_tracks", msg->response_body->data);
 978 
 979 		g_signal_emit (user, rb_audioscrobbler_user_signals[RECENT_TRACKS_UPDATED],
 980 		               0, user->priv->recent_tracks);
 981 	} else {
 982 		rb_debug ("invalid response from recent tracks request");
 983 	}
 984 }
 985 
 986 static GPtrArray *
 987 parse_recent_tracks (RBAudioscrobblerUser *user, const char *data)
 988 {
 989 	GPtrArray *recent_tracks;
 990 	JsonParser *parser;
 991 
 992 	recent_tracks = NULL;
 993 
 994 	parser = json_parser_new ();
 995 	if (data != NULL && json_parser_load_from_data (parser, data, -1, NULL)) {
 996 		JsonObject *root_object;
 997 		root_object = json_node_get_object (json_parser_get_root (parser));
 998 
 999 		if (json_object_has_member (root_object, "recenttracks")) {
1000 			JsonObject *recent_tracks_object;
1001 			recent_tracks_object = json_object_get_object_member (root_object, "recenttracks");
1002 
1003 			if (json_object_has_member (recent_tracks_object, "track") == TRUE) {
1004 				JsonArray *track_array;
1005 
1006 				track_array = json_object_get_array_member (recent_tracks_object, "track");
1007 				recent_tracks = parse_track_array (user, track_array);
1008 			}
1009 		} else {
1010 			rb_debug ("error parsing recent tracks response: no recenttracks object exists");
1011 		}
1012 	} else {
1013 		rb_debug ("error parsing recent tracks response: empty or invalid response");
1014 	}
1015 
1016 	g_object_unref (parser);
1017 
1018 	return recent_tracks;
1019 }
1020 
1021 /* top tracks */
1022 static void
1023 load_cached_top_tracks (RBAudioscrobblerUser *user)
1024 {
1025 	char *filename;
1026 	char *data;
1027 
1028 	filename = calculate_cached_response_path (user, "top_tracks");
1029 
1030 	/* delete old data */
1031 	if (user->priv->top_tracks != NULL) {
1032 		g_ptr_array_unref (user->priv->top_tracks);
1033 		user->priv->top_tracks = NULL;
1034 	}
1035 
1036 	/* load cached data if it exists */
1037 	if (g_file_get_contents (filename, &data, NULL, NULL) == TRUE) {
1038 		rb_debug ("loading cached top tracks");
1039 		user->priv->top_tracks = parse_top_tracks (user, data);
1040 	}
1041 
1042 	/* emit updated signal */
1043 	g_signal_emit (user, rb_audioscrobbler_user_signals[TOP_TRACKS_UPDATED],
1044 	               0, user->priv->top_tracks);
1045 
1046 	g_free (filename);
1047 	g_free (data);
1048 }
1049 
1050 static void
1051 request_top_tracks (RBAudioscrobblerUser *user, int limit)
1052 {
1053 	char *msg_url;
1054 	SoupMessage *msg;
1055 
1056 	rb_debug ("requesting top tracks");
1057 
1058 	msg_url = g_strdup_printf ("%s?method=library.getTracks&user=%s&api_key=%s&limit=%i&format=json",
1059 	                           rb_audioscrobbler_service_get_api_url (user->priv->service),
1060 	                           user->priv->username,
1061 	                           rb_audioscrobbler_service_get_api_key (user->priv->service),
1062 	                           limit);
1063 
1064 	msg = soup_message_new ("GET", msg_url);
1065 	soup_session_queue_message (user->priv->soup_session,
1066 	                            msg,
1067 	                            top_tracks_response_cb,
1068 	                            user);
1069 
1070 	g_free (msg_url);
1071 }
1072 
1073 static void
1074 top_tracks_response_cb (SoupSession *session,
1075                         SoupMessage *msg,
1076                         gpointer user_data)
1077 {
1078 	RBAudioscrobblerUser *user;
1079 	GPtrArray *top_tracks;
1080 
1081 	user = RB_AUDIOSCROBBLER_USER (user_data);
1082 	top_tracks = parse_top_tracks (user, msg->response_body->data);
1083 
1084 	if (top_tracks != NULL) {
1085 		rb_debug ("top tracks request was successful");
1086 
1087 		if (user->priv->top_tracks != NULL) {
1088 			g_ptr_array_unref (user->priv->top_tracks);
1089 		}
1090 		user->priv->top_tracks = top_tracks;
1091 
1092 		save_response_to_cache (user, "top_tracks", msg->response_body->data);
1093 
1094 		g_signal_emit (user, rb_audioscrobbler_user_signals[TOP_TRACKS_UPDATED],
1095 		               0, user->priv->top_tracks);
1096 	} else {
1097 		rb_debug ("invalid response from top tracks request");
1098 	}
1099 }
1100 
1101 static GPtrArray *
1102 parse_top_tracks (RBAudioscrobblerUser *user, const char *data)
1103 {
1104 	GPtrArray *top_tracks;
1105 	JsonParser *parser;
1106 
1107 	top_tracks = NULL;
1108 
1109 	parser = json_parser_new ();
1110 	if (data != NULL && json_parser_load_from_data (parser, data, -1, NULL)) {
1111 		JsonObject *root_object;
1112 		root_object = json_node_get_object (json_parser_get_root (parser));
1113 
1114 		if (json_object_has_member (root_object, "tracks")) {
1115 			JsonObject *top_tracks_object;
1116 			top_tracks_object = json_object_get_object_member (root_object, "tracks");
1117 
1118 			if (json_object_has_member (top_tracks_object, "track") == TRUE) {
1119 				JsonArray *track_array;
1120 
1121 				track_array = json_object_get_array_member (top_tracks_object, "track");
1122 				top_tracks = parse_track_array (user, track_array);
1123 			}
1124 		} else {
1125 			rb_debug ("error parsing top tracks response: no tracks object exists");
1126 		}
1127 	} else {
1128 		rb_debug ("error parsing top tracks response: empty or invalid response");
1129 	}
1130 
1131 	g_object_unref (parser);
1132 
1133 	return top_tracks;
1134 }
1135 
1136 /* loved tracks */
1137 static void
1138 load_cached_loved_tracks (RBAudioscrobblerUser *user)
1139 {
1140 	char *filename;
1141 	char *data;
1142 
1143 	filename = calculate_cached_response_path (user, "loved_tracks");
1144 
1145 	/* delete old data */
1146 	if (user->priv->loved_tracks != NULL) {
1147 		g_ptr_array_unref (user->priv->loved_tracks);
1148 		user->priv->loved_tracks = NULL;
1149 	}
1150 
1151 	/* load cached data if it exists */
1152 	if (g_file_get_contents (filename, &data, NULL, NULL) == TRUE) {
1153 		rb_debug ("loading cached loved tracks");
1154 		user->priv->loved_tracks = parse_loved_tracks (user, data);
1155 	}
1156 
1157 	/* emit updated signal */
1158 	g_signal_emit (user, rb_audioscrobbler_user_signals[LOVED_TRACKS_UPDATED],
1159 	               0, user->priv->loved_tracks);
1160 
1161 	g_free (filename);
1162 	g_free (data);
1163 }
1164 
1165 static void
1166 request_loved_tracks (RBAudioscrobblerUser *user, int limit)
1167 {
1168 	char *msg_url;
1169 	SoupMessage *msg;
1170 
1171 	rb_debug ("requesting loved tracks");
1172 
1173 	msg_url = g_strdup_printf ("%s?method=user.getLovedTracks&user=%s&api_key=%s&limit=%i&format=json",
1174 	                           rb_audioscrobbler_service_get_api_url (user->priv->service),
1175 	                           user->priv->username,
1176 	                           rb_audioscrobbler_service_get_api_key (user->priv->service),
1177 	                           limit);
1178 
1179 	msg = soup_message_new ("GET", msg_url);
1180 	soup_session_queue_message (user->priv->soup_session,
1181 	                            msg,
1182 	                            loved_tracks_response_cb,
1183 	                            user);
1184 
1185 	g_free (msg_url);
1186 }
1187 
1188 static void
1189 loved_tracks_response_cb (SoupSession *session,
1190                           SoupMessage *msg,
1191                           gpointer user_data)
1192 {
1193 	RBAudioscrobblerUser *user;
1194 	GPtrArray *loved_tracks;
1195 
1196 	user = RB_AUDIOSCROBBLER_USER (user_data);
1197 	loved_tracks = parse_loved_tracks (user, msg->response_body->data);
1198 
1199 	if (loved_tracks != NULL) {
1200 		rb_debug ("loved tracks request was successful");
1201 
1202 		if (user->priv->loved_tracks != NULL) {
1203 			g_ptr_array_unref (user->priv->loved_tracks);
1204 		}
1205 		user->priv->loved_tracks = loved_tracks;
1206 
1207 		save_response_to_cache (user, "loved_tracks", msg->response_body->data);
1208 
1209 		g_signal_emit (user, rb_audioscrobbler_user_signals[LOVED_TRACKS_UPDATED],
1210 		               0, user->priv->loved_tracks);
1211 	} else {
1212 		rb_debug ("invalid response from loved tracks request");
1213 	}
1214 }
1215 
1216 static GPtrArray *
1217 parse_loved_tracks (RBAudioscrobblerUser *user, const char *data)
1218 {
1219 	GPtrArray *loved_tracks;
1220 	JsonParser *parser;
1221 
1222 	loved_tracks = NULL;
1223 
1224 	parser = json_parser_new ();
1225 	if (data != NULL && json_parser_load_from_data (parser, data, -1, NULL)) {
1226 		JsonObject *root_object;
1227 		root_object = json_node_get_object (json_parser_get_root (parser));
1228 
1229 		if (json_object_has_member (root_object, "lovedtracks")) {
1230 			JsonObject *loved_tracks_object;
1231 			loved_tracks_object = json_object_get_object_member (root_object, "lovedtracks");
1232 
1233 			if (json_object_has_member (loved_tracks_object, "track") == TRUE) {
1234 				JsonArray *track_array;
1235 
1236 				track_array = json_object_get_array_member (loved_tracks_object, "track");
1237 				loved_tracks = parse_track_array (user, track_array);
1238 			}
1239 		} else {
1240 			rb_debug ("error parsing loved tracks response: no lovedtracks object exists");
1241 		}
1242 	} else {
1243 		rb_debug ("error parsing loved tracks response: empty or invalid response");
1244 	}
1245 
1246 	g_object_unref (parser);
1247 
1248 	return loved_tracks;
1249 }
1250 
1251 /* top artists */
1252 static void
1253 load_cached_top_artists (RBAudioscrobblerUser *user)
1254 {
1255 	char *filename;
1256 	char *data;
1257 
1258 	filename = calculate_cached_response_path (user, "top_artists");
1259 
1260 	/* delete old data */
1261 	if (user->priv->top_artists != NULL) {
1262 		g_ptr_array_unref (user->priv->top_artists);
1263 		user->priv->top_artists = NULL;
1264 	}
1265 
1266 	/* load cached data if it exists */
1267 	if (g_file_get_contents (filename, &data, NULL, NULL) == TRUE) {
1268 		rb_debug ("loading cached top artists");
1269 		user->priv->top_artists = parse_top_artists (user, data);
1270 	}
1271 
1272 	/* emit updated signal */
1273 	g_signal_emit (user, rb_audioscrobbler_user_signals[TOP_ARTISTS_UPDATED],
1274 	               0, user->priv->top_artists);
1275 
1276 	g_free (filename);
1277 	g_free (data);
1278 }
1279 
1280 static void
1281 request_top_artists (RBAudioscrobblerUser *user, int limit)
1282 {
1283 	char *msg_url;
1284 	SoupMessage *msg;
1285 
1286 	rb_debug ("requesting top artists");
1287 
1288 	msg_url = g_strdup_printf ("%s?method=library.getArtists&user=%s&api_key=%s&limit=%i&format=json",
1289 	                           rb_audioscrobbler_service_get_api_url (user->priv->service),
1290 	                           user->priv->username,
1291 	                           rb_audioscrobbler_service_get_api_key (user->priv->service),
1292 	                           limit);
1293 
1294 	msg = soup_message_new ("GET", msg_url);
1295 	soup_session_queue_message (user->priv->soup_session,
1296 	                            msg,
1297 	                            top_artists_response_cb,
1298 	                            user);
1299 
1300 	g_free (msg_url);
1301 }
1302 
1303 static void
1304 top_artists_response_cb (SoupSession *session,
1305                          SoupMessage *msg,
1306                          gpointer user_data)
1307 {
1308 	RBAudioscrobblerUser *user;
1309 	GPtrArray *top_artists;
1310 
1311 	user = RB_AUDIOSCROBBLER_USER (user_data);
1312 	top_artists = parse_top_artists (user, msg->response_body->data);
1313 
1314 	if (top_artists != NULL) {
1315 		rb_debug ("top artists request was successful");
1316 
1317 		if (user->priv->top_artists != NULL) {
1318 			g_ptr_array_unref (user->priv->top_artists);
1319 		}
1320 		user->priv->top_artists = top_artists;
1321 
1322 		save_response_to_cache (user, "top_artists", msg->response_body->data);
1323 
1324 		g_signal_emit (user, rb_audioscrobbler_user_signals[TOP_ARTISTS_UPDATED],
1325 		               0, user->priv->top_artists);
1326 	} else {
1327 		rb_debug ("invalid response from top artists request");
1328 	}
1329 }
1330 
1331 static GPtrArray *
1332 parse_top_artists (RBAudioscrobblerUser *user, const char *data)
1333 {
1334 	GPtrArray *top_artists;
1335 	JsonParser *parser;
1336 
1337 	top_artists = NULL;
1338 
1339 	parser = json_parser_new ();
1340 	if (data != NULL && json_parser_load_from_data (parser, data, -1, NULL)) {
1341 		JsonObject *root_object;
1342 		root_object = json_node_get_object (json_parser_get_root (parser));
1343 
1344 		if (json_object_has_member (root_object, "artists")) {
1345 			JsonObject *top_artists_object;
1346 			top_artists_object = json_object_get_object_member (root_object, "artists");
1347 
1348 			if (json_object_has_member (top_artists_object, "artist") == TRUE) {
1349 				JsonArray *artist_array;
1350 
1351 				artist_array = json_object_get_array_member (top_artists_object, "artist");
1352 				top_artists = parse_artist_array (user, artist_array);
1353 			}
1354 		} else {
1355 			rb_debug ("error parsing top artists response: no artists object exists");
1356 		}
1357 	} else {
1358 		rb_debug ("error parsing top artists response: empty or invalid response");
1359 	}
1360 
1361 	g_object_unref (parser);
1362 
1363 	return top_artists;
1364 }
1365 
1366 /* recommended artists */
1367 static void
1368 load_cached_recommended_artists (RBAudioscrobblerUser *user)
1369 {
1370 	char *filename;
1371 	char *data;
1372 
1373 	filename = calculate_cached_response_path (user, "recommended_artists");
1374 
1375 	/* delete old data */
1376 	if (user->priv->recommended_artists != NULL) {
1377 		g_ptr_array_unref (user->priv->recommended_artists);
1378 		user->priv->recommended_artists = NULL;
1379 	}
1380 
1381 	/* load cached data if it exists */
1382 	if (g_file_get_contents (filename, &data, NULL, NULL) == TRUE) {
1383 		rb_debug ("loading cached recommended artists");
1384 		user->priv->recommended_artists = parse_recommended_artists (user, data);
1385 	}
1386 
1387 	/* emit updated signal */
1388 	g_signal_emit (user, rb_audioscrobbler_user_signals[RECOMMENDED_ARTISTS_UPDATED],
1389 	               0, user->priv->recommended_artists);
1390 
1391 	g_free (filename);
1392 	g_free (data);
1393 }
1394 
1395 static void
1396 request_recommended_artists (RBAudioscrobblerUser *user, int limit)
1397 {
1398 	char *sig_arg;
1399 	char *sig;
1400 	char *msg_url;
1401 	SoupMessage *msg;
1402 
1403 	rb_debug ("requesting recommended artists");
1404 
1405 	sig_arg = g_strdup_printf ("api_key%slimit%imethoduser.getRecommendedArtistssk%s%s",
1406 	                           rb_audioscrobbler_service_get_api_key (user->priv->service),
1407 	                           limit,
1408 	                           user->priv->session_key,
1409 	                           rb_audioscrobbler_service_get_api_secret (user->priv->service));
1410 	sig = g_compute_checksum_for_string (G_CHECKSUM_MD5, sig_arg, -1);
1411 
1412 	msg_url = g_strdup_printf ("%s?method=user.getRecommendedArtists&api_key=%s&api_sig=%s&sk=%s&limit=%i&format=json",
1413 	                           rb_audioscrobbler_service_get_api_url (user->priv->service),
1414 	                           rb_audioscrobbler_service_get_api_key (user->priv->service),
1415 	                           sig,
1416 	                           user->priv->session_key,
1417 	                           limit);
1418 
1419 	msg = soup_message_new ("GET", msg_url);
1420 	soup_session_queue_message (user->priv->soup_session,
1421 	                            msg,
1422 	                            recommended_artists_response_cb,
1423 	                            user);
1424 
1425 	g_free (sig_arg);
1426 	g_free (sig);
1427 	g_free (msg_url);
1428 }
1429 
1430 static void
1431 recommended_artists_response_cb (SoupSession *session,
1432                                  SoupMessage *msg,
1433                                  gpointer user_data)
1434 {
1435 	RBAudioscrobblerUser *user;
1436 	GPtrArray *recommended_artists;
1437 
1438 	user = RB_AUDIOSCROBBLER_USER (user_data);
1439 	recommended_artists = parse_recommended_artists (user, msg->response_body->data);
1440 
1441 	if (recommended_artists != NULL) {
1442 		rb_debug ("recommended artists request was successful");
1443 
1444 		if (user->priv->recommended_artists != NULL) {
1445 			g_ptr_array_unref (user->priv->recommended_artists);
1446 		}
1447 		user->priv->recommended_artists = recommended_artists;
1448 
1449 		save_response_to_cache (user, "recommended_artists", msg->response_body->data);
1450 
1451 		g_signal_emit (user, rb_audioscrobbler_user_signals[RECOMMENDED_ARTISTS_UPDATED],
1452 		               0, user->priv->recommended_artists);
1453 	} else {
1454 		rb_debug ("invalid response from recommended artists request");
1455 	}
1456 }
1457 
1458 static GPtrArray *
1459 parse_recommended_artists (RBAudioscrobblerUser *user, const char *data)
1460 {
1461 	GPtrArray *recommended_artists;
1462 	JsonParser *parser;
1463 
1464 	recommended_artists = NULL;
1465 
1466 	parser = json_parser_new ();
1467 	if (data != NULL && json_parser_load_from_data (parser, data, -1, NULL)) {
1468 		JsonObject *root_object;
1469 		root_object = json_node_get_object (json_parser_get_root (parser));
1470 
1471 		if (json_object_has_member (root_object, "recommendations")) {
1472 			JsonObject *recommended_artists_object;
1473 			recommended_artists_object = json_object_get_object_member (root_object, "recommendations");
1474 
1475 			if (json_object_has_member (recommended_artists_object, "artist") == TRUE) {
1476 				JsonArray *artist_array;
1477 
1478 				artist_array = json_object_get_array_member (recommended_artists_object, "artist");
1479 				recommended_artists = parse_artist_array (user, artist_array);
1480 			}
1481 		} else {
1482 			rb_debug ("error parsing recommended artists response: no recommendations object exists");
1483 			rb_debug ("probably due to authentication error");
1484 		}
1485 	} else {
1486 		rb_debug ("error parsing recommended artists response: empty or invalid response");
1487 	}
1488 
1489 	g_object_unref (parser);
1490 
1491 	return recommended_artists;
1492 }
1493 
1494 static char *
1495 calculate_cached_image_path (RBAudioscrobblerUser *user, RBAudioscrobblerUserData *data)
1496 {
1497 	const char *rb_cache_dir;
1498 	char *cache_dir;
1499 	char *image_path = NULL;
1500 
1501 	rb_cache_dir = rb_user_cache_dir ();
1502 	cache_dir = g_build_filename (rb_cache_dir,
1503 	                              "audioscrobbler",
1504 	                              rb_audioscrobbler_service_get_name (user->priv->service),
1505 	                              "images",
1506 	                              NULL);
1507 
1508 	if (data->type == RB_AUDIOSCROBBLER_USER_DATA_TYPE_USER_INFO) {
1509 		image_path = g_build_filename (cache_dir,
1510 		                               "users",
1511 		                               data->user_info.username,
1512 		                               NULL);
1513 
1514 	} else if (data->type == RB_AUDIOSCROBBLER_USER_DATA_TYPE_TRACK) {
1515 		char *filename = g_strdup_printf ("%s - %s",
1516 		                                  data->track.artist,
1517 		                                  data->track.title);
1518 		image_path = g_build_filename (cache_dir,
1519 		                               "tracks",
1520 		                               filename,
1521 		                               NULL);
1522 		g_free (filename);
1523 
1524 	} else if (data->type == RB_AUDIOSCROBBLER_USER_DATA_TYPE_ARTIST) {
1525 		image_path = g_build_filename (cache_dir,
1526 		                               "artists",
1527 		                               data->artist.name,
1528 		                               NULL);
1529 	}
1530 
1531 	g_free (cache_dir);
1532 	return image_path;
1533 }
1534 
1535 static void
1536 download_image (RBAudioscrobblerUser *user, const char *image_url, RBAudioscrobblerUserData *data)
1537 {
1538 	GFile *src_file;
1539 	GQueue *data_queue;
1540 
1541 	/* check image_url is not null or empty */
1542 	if (image_url == NULL || image_url[0] == '\0') {
1543 		return;
1544 	}
1545 
1546 	src_file = g_file_new_for_uri (image_url);
1547 	data_queue = g_hash_table_lookup (user->priv->file_to_data_queue_map, src_file);
1548 
1549 	/* only start a download if the file is not already being downloaded */
1550 	if (data_queue == NULL) {
1551 		char *dest_filename;
1552 		char *dest_file_uri;
1553 		GError *error;
1554 
1555 		/* ensure the dest dir exists */
1556 		dest_filename = calculate_cached_image_path (user, data);
1557 		dest_file_uri = g_filename_to_uri (dest_filename, NULL, NULL);
1558 		error = NULL;
1559 		rb_uri_create_parent_dirs (dest_file_uri, &error);
1560 
1561 		if (error == NULL) {
1562 			GCancellable *cancellable;
1563 			GFile *dest_file;
1564 
1565 			/* add new queue containing data to map */
1566 			data_queue = g_queue_new ();
1567 			g_queue_push_tail (data_queue, rb_audioscrobbler_user_data_ref (data));
1568 			g_hash_table_insert (user->priv->file_to_data_queue_map,
1569 			                     src_file,
1570 			                     data_queue);
1571 
1572 			/* create a cancellable for this download */
1573 			cancellable = g_cancellable_new ();
1574 			g_hash_table_insert (user->priv->file_to_cancellable_map, src_file, cancellable);
1575 
1576 			/* download the file */
1577 			rb_debug ("downloading image %s to %s", image_url, dest_filename);
1578 			dest_file = g_file_new_for_path (dest_filename);
1579 			g_file_copy_async (src_file,
1580 				           dest_file,
1581 				           G_FILE_COPY_OVERWRITE,
1582 				           G_PRIORITY_DEFAULT,
1583 				           cancellable,
1584 				           NULL,
1585 				           NULL,
1586 				           image_download_cb,
1587 				           user);
1588 
1589 			g_object_unref (dest_file);
1590 		} else {
1591 			rb_debug ("not downloading image: error creating dest dir");
1592 			g_error_free (error);
1593 			g_object_unref (src_file);
1594 		}
1595 
1596 		g_free (dest_filename);
1597 		g_free (dest_file_uri);
1598 	} else {
1599 		/* the file is already being downloaded. add this data to the queue for
1600 		 * the file, so that data will be updated when the download completes */
1601 		rb_debug ("image %s is already being downloaded. adding data to queue", image_url);
1602 		g_queue_push_tail (data_queue, rb_audioscrobbler_user_data_ref (data));
1603 	}
1604 }
1605 
1606 static void
1607 copy_image_for_data (RBAudioscrobblerUser *user, const char *src_file_path, RBAudioscrobblerUserData *dest_data)
1608 {
1609 	GFile *src_file = g_file_new_for_path (src_file_path);
1610 	char *dest_file_path = calculate_cached_image_path (user, dest_data);
1611 	GFile *dest_file = g_file_new_for_path (dest_file_path);
1612 
1613 	if (g_file_equal (src_file, dest_file) == FALSE) {
1614 		rb_debug ("copying cache image %s to %s",
1615 		          src_file_path,
1616 		          dest_file_path);
1617 
1618 		g_file_copy_async (src_file,
1619 		                   dest_file,
1620 		                   G_FILE_COPY_OVERWRITE,
1621 		                   G_PRIORITY_DEFAULT,
1622 		                   NULL,
1623 		                   NULL,
1624 		                   NULL,
1625 		                   NULL,
1626 		                   NULL);
1627 	}
1628 
1629 	g_object_unref (src_file);
1630 	g_free (dest_file_path);
1631 	g_object_unref (dest_file);
1632 }
1633 
1634 static void
1635 image_download_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
1636 {
1637 	RBAudioscrobblerUser *user = RB_AUDIOSCROBBLER_USER (user_data);
1638 	GFile *src_file = G_FILE (source_object);
1639 	GQueue *data_queue;
1640 
1641 	/* free the cancellable */
1642 	g_hash_table_remove (user->priv->file_to_cancellable_map, src_file);
1643 
1644 	data_queue = g_hash_table_lookup (user->priv->file_to_data_queue_map, src_file);
1645 
1646 	if (g_file_copy_finish (src_file, res, NULL)) {
1647 		char *dest_file_path;
1648 		GList *data_i;
1649 
1650 		/* the image was downloaded for the first item in the queue,
1651 		 * so the first item must be used to get the path */
1652 		dest_file_path = calculate_cached_image_path (user, g_queue_peek_head (data_queue));
1653 
1654 		/* iterate through each data item in the queue,
1655 		 * and if necessary update the image and emit appropriate signal */
1656 		for (data_i = g_queue_peek_head_link(data_queue); data_i != NULL; data_i = g_list_next (data_i)) {
1657 			RBAudioscrobblerUserData *data = data_i->data;
1658 
1659 			/* if nobody else has a reference to the data then
1660 			 * there is no need to update the image */
1661 			if (data->refcount <= 1) {
1662 				continue;
1663 			}
1664 
1665 			if (data->image != NULL) {
1666 				g_object_unref (data->image);
1667 			}
1668 
1669 			/* load image at correct size for the data type */
1670 			if (data->type == RB_AUDIOSCROBBLER_USER_DATA_TYPE_USER_INFO) {
1671 				data->image = gdk_pixbuf_new_from_file_at_size (dest_file_path, USER_PROFILE_IMAGE_SIZE, -1, NULL);
1672 			} else {
1673 				data->image = gdk_pixbuf_new_from_file_at_size (dest_file_path, LIST_ITEM_IMAGE_SIZE, LIST_ITEM_IMAGE_SIZE, NULL);
1674 			}
1675 
1676 			/* copy the image to the correct location for this data item, for next time */
1677 			copy_image_for_data (user, dest_file_path, data);
1678 
1679 			/* emit appropriate signal - quite ugly, surely this could be done in a nicer way */
1680 			if (data->type == RB_AUDIOSCROBBLER_USER_DATA_TYPE_USER_INFO) {
1681 				g_signal_emit (user, rb_audioscrobbler_user_signals[USER_INFO_UPDATED],
1682 				               0, data);
1683 			} else if (data->type == RB_AUDIOSCROBBLER_USER_DATA_TYPE_TRACK) {
1684 				int i;
1685 				if (user->priv->recent_tracks != NULL) {
1686 					for (i = 0; i < user->priv->recent_tracks->len; i++) {
1687 						if (g_ptr_array_index (user->priv->recent_tracks, i) == data) {
1688 							g_signal_emit (user, rb_audioscrobbler_user_signals[RECENT_TRACKS_UPDATED],
1689 							               0, user->priv->recent_tracks);
1690 						}
1691 					}
1692 				}
1693 				if (user->priv->top_tracks != NULL) {
1694 					for (i = 0; i < user->priv->top_tracks->len; i++) {
1695 						if (g_ptr_array_index (user->priv->top_tracks, i) == data) {
1696 							g_signal_emit (user, rb_audioscrobbler_user_signals[TOP_TRACKS_UPDATED],
1697 							               0, user->priv->top_tracks);
1698 						}
1699 					}
1700 				}
1701 				if (user->priv->loved_tracks != NULL) {
1702 					for (i = 0; i < user->priv->loved_tracks->len; i++) {
1703 						if (g_ptr_array_index (user->priv->loved_tracks, i) == data) {
1704 							g_signal_emit (user, rb_audioscrobbler_user_signals[LOVED_TRACKS_UPDATED],
1705 							               0, user->priv->loved_tracks);
1706 						}
1707 					}
1708 				}
1709 			} else if (data->type == RB_AUDIOSCROBBLER_USER_DATA_TYPE_ARTIST) {
1710 				int i;
1711 				if (user->priv->top_artists != NULL) {
1712 					for (i = 0; i < user->priv->top_artists->len; i++) {
1713 						if (g_ptr_array_index (user->priv->top_artists, i) == data) {
1714 							g_signal_emit (user, rb_audioscrobbler_user_signals[TOP_ARTISTS_UPDATED],
1715 							               0, user->priv->top_artists);
1716 						}
1717 					}
1718 				}
1719 				if (user->priv->recommended_artists != NULL) {
1720 					for (i = 0; i < user->priv->recommended_artists->len; i++) {
1721 						if (g_ptr_array_index (user->priv->recommended_artists, i) == data) {
1722 							g_signal_emit (user, rb_audioscrobbler_user_signals[RECOMMENDED_ARTISTS_UPDATED],
1723 							               0, user->priv->recommended_artists);
1724 						}
1725 					}
1726 				}
1727 			}
1728 		}
1729 		g_free (dest_file_path);
1730 	} else {
1731 		rb_debug ("error downloading image. possibly due to cancellation");
1732 	}
1733 
1734 	/* cleanup the file and data */
1735 	g_hash_table_remove (user->priv->file_to_data_queue_map, src_file);
1736 }
1737 
1738 void
1739 rb_audioscrobbler_user_love_track (RBAudioscrobblerUser *user,
1740                                    const char *title,
1741                                    const char *artist)
1742 {
1743 	char *sig_arg;
1744 	char *sig;
1745 	char *escaped_title;
1746 	char *escaped_artist;
1747 	char *request;
1748 	SoupMessage *msg;
1749 
1750 	rb_debug ("loving track %s - %s", artist, title);
1751 
1752 	sig_arg = g_strdup_printf ("api_key%sartist%smethodtrack.lovesk%strack%s%s",
1753 	                           rb_audioscrobbler_service_get_api_key (user->priv->service),
1754 	                           artist,
1755 	                           user->priv->session_key,
1756 	                           title,
1757 	                           rb_audioscrobbler_service_get_api_secret (user->priv->service));
1758 
1759 	sig = g_compute_checksum_for_string (G_CHECKSUM_MD5, sig_arg, -1);
1760 
1761 	escaped_title = g_uri_escape_string (title, NULL, FALSE);
1762 	escaped_artist = g_uri_escape_string (artist, NULL, FALSE);
1763 
1764 	request = g_strdup_printf ("method=track.love&track=%s&artist=%s&api_key=%s&api_sig=%s&sk=%s",
1765 	                           escaped_title,
1766 	                           escaped_artist,
1767 	                           rb_audioscrobbler_service_get_api_key (user->priv->service),
1768 	                           sig,
1769 	                           user->priv->session_key);
1770 
1771 	msg = soup_message_new ("POST", rb_audioscrobbler_service_get_api_url (user->priv->service));
1772 	soup_message_set_request (msg,
1773 	                          "application/x-www-form-urlencoded",
1774 	                          SOUP_MEMORY_COPY,
1775 	                          request,
1776 	                          strlen (request));
1777 	soup_session_queue_message (user->priv->soup_session,
1778 	                            msg,
1779 	                            love_track_response_cb,
1780 	                            user);
1781 
1782 	g_free (sig_arg);
1783 	g_free (sig);
1784 	g_free (escaped_title);
1785 	g_free (escaped_artist);
1786 	g_free (request);
1787 }
1788 
1789 static void
1790 love_track_response_cb (SoupSession *session,
1791                         SoupMessage *msg,
1792                         gpointer user_data)
1793 {
1794 	/* Don't know if there's anything to do here,
1795 	 * might want a debug message indicating success or failure?
1796 	 */
1797 }
1798 
1799 void
1800 rb_audioscrobbler_user_ban_track (RBAudioscrobblerUser *user,
1801                                   const char *title,
1802                                   const char *artist)
1803 {
1804 	char *sig_arg;
1805 	char *sig;
1806 	char *escaped_title;
1807 	char *escaped_artist;
1808 	char *request;
1809 	SoupMessage *msg;
1810 
1811 	rb_debug ("banning track %s - %s", artist, title);
1812 
1813 	sig_arg = g_strdup_printf ("api_key%sartist%smethodtrack.bansk%strack%s%s",
1814 	                           rb_audioscrobbler_service_get_api_key (user->priv->service),
1815 	                           artist,
1816 	                           user->priv->session_key,
1817 	                           title,
1818 	                           rb_audioscrobbler_service_get_api_secret (user->priv->service));
1819 
1820 	sig = g_compute_checksum_for_string (G_CHECKSUM_MD5, sig_arg, -1);
1821 
1822 	escaped_title = g_uri_escape_string (title, NULL, FALSE);
1823 	escaped_artist = g_uri_escape_string (artist, NULL, FALSE);
1824 
1825 	request = g_strdup_printf ("method=track.ban&track=%s&artist=%s&api_key=%s&api_sig=%s&sk=%s",
1826 	                           escaped_title,
1827 	                           escaped_artist,
1828 	                           rb_audioscrobbler_service_get_api_key (user->priv->service),
1829 	                           sig,
1830 	                           user->priv->session_key);
1831 
1832 	msg = soup_message_new ("POST", rb_audioscrobbler_service_get_api_url (user->priv->service));
1833 	soup_message_set_request (msg,
1834 	                          "application/x-www-form-urlencoded",
1835 	                          SOUP_MEMORY_COPY,
1836 	                          request,
1837 	                          strlen (request));
1838 	soup_session_queue_message (user->priv->soup_session,
1839 	                            msg,
1840 	                            ban_track_response_cb,
1841 	                            user);
1842 
1843 	g_free (sig_arg);
1844 	g_free (sig);
1845 	g_free (escaped_title);
1846 	g_free (escaped_artist);
1847 	g_free (request);
1848 }
1849 
1850 static void
1851 ban_track_response_cb (SoupSession *session,
1852                        SoupMessage *msg,
1853                        gpointer user_data)
1854 {
1855 	/* Don't know if there's anything to do here,
1856 	 * might want a debug message indicating success or failure?
1857 	 */
1858 }
1859 
1860 void
1861 _rb_audioscrobbler_user_register_type (GTypeModule *module)
1862 {
1863 	rb_audioscrobbler_user_register_type (module);
1864 }