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 }