1 /*
2 * Copyright (C) 2005 Marc Pavot <marc.pavot@gmail.com>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2, or (at your option)
7 * any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17 *
18 */
19
20 #include "shell/ario-shell-similarartists.h"
21 #include <config.h>
22 #include <gtk/gtk.h>
23 #include <stdlib.h>
24 #include <libxml/parser.h>
25 #include <glib/gi18n.h>
26
27 #include "ario-debug.h"
28 #include "ario-util.h"
29 #include "lib/gtk-builder-helpers.h"
30 #include "servers/ario-server.h"
31 #include "widgets/ario-playlist.h"
32
33 static gboolean ario_shell_similarartists_window_delete_cb (GtkWidget *window,
34 GdkEventAny *event,
35 ArioShellSimilarartists *shell_similarartists);
36 G_MODULE_EXPORT void ario_shell_similarartists_close_cb (GtkButton *button,
37 ArioShellSimilarartists *shell_similarartists);
38 G_MODULE_EXPORT void ario_shell_similarartists_lastfm_cb (GtkButton *button,
39 ArioShellSimilarartists *shell_similarartists);
40 G_MODULE_EXPORT void ario_shell_similarartists_add_cb (GtkButton *button,
41 ArioShellSimilarartists *shell_similarartists);
42 G_MODULE_EXPORT void ario_shell_similarartists_addall_cb (GtkButton *button,
43 ArioShellSimilarartists *shell_similarartists);
44
45 #define LASTFM_URI "http://ws.audioscrobbler.com/1.0/artist/%s/similar.xml"
46 #define MAX_ARTISTS 10
47 #define IMAGE_SIZE 120
48
49 /* Private attributes */
50 struct ArioShellSimilarartistsPrivate
51 {
52 GtkTreeSelection *selection;
53 GtkListStore *liststore;
54 GThread *thread;
55
56 gboolean closed;
57 const gchar* artist;
58 };
59
60 /* Tree model columns */
61 enum
62 {
63 IMAGE_COLUMN,
64 ARTIST_COLUMN,
65 SONGS_COLUMN,
66 IMAGEURL_COLUMN,
67 URL_COLUMN,
68 N_COLUMN
69 };
70
71 #define ARIO_SHELL_SIMILARARTISTS_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), TYPE_ARIO_SHELL_SIMILARARTISTS, ArioShellSimilarartistsPrivate))
G_DEFINE_TYPE(ArioShellSimilarartists,ario_shell_similarartists,GTK_TYPE_WINDOW)72 G_DEFINE_TYPE (ArioShellSimilarartists, ario_shell_similarartists, GTK_TYPE_WINDOW)
73
74 static void
75 ario_shell_similarartists_class_init (ArioShellSimilarartistsClass *klass)
76 {
77 ARIO_LOG_FUNCTION_START;
78 /* Private attributes */
79 g_type_class_add_private (klass, sizeof (ArioShellSimilarartistsPrivate));
80 }
81
82 static void
ario_shell_similarartists_init(ArioShellSimilarartists * shell_similarartists)83 ario_shell_similarartists_init (ArioShellSimilarartists *shell_similarartists)
84 {
85 ARIO_LOG_FUNCTION_START;
86 shell_similarartists->priv = ARIO_SHELL_SIMILARARTISTS_GET_PRIVATE (shell_similarartists);
87
88 /* Connect signal for window deletion */
89 g_signal_connect (shell_similarartists,
90 "delete_event",
91 G_CALLBACK (ario_shell_similarartists_window_delete_cb),
92 shell_similarartists);
93
94 /* Set window properties */
95 gtk_window_set_title (GTK_WINDOW (shell_similarartists), "Ario");
96 gtk_window_set_resizable (GTK_WINDOW (shell_similarartists), TRUE);
97 gtk_container_set_border_width (GTK_CONTAINER (shell_similarartists), 5);
98 }
99
100 static GSList *
ario_shell_similarartists_parse_xml_file(char * xmldata,int size)101 ario_shell_similarartists_parse_xml_file (char *xmldata,
102 int size)
103 {
104 ARIO_LOG_FUNCTION_START;
105 xmlDocPtr doc;
106 xmlNodePtr cur;
107 xmlNodePtr cur2;
108 GSList *similar_artists = NULL;
109 ArioSimilarArtist *similar_artist;
110
111 /* Parse XML file */
112 doc = xmlParseMemory (xmldata, size);
113 if (doc == NULL ) {
114 return NULL;
115 }
116
117 /* Get root of document */
118 cur = xmlDocGetRootElement(doc);
119 if (!cur) {
120 xmlFreeDoc (doc);
121 return NULL;
122 }
123
124 /* Check that the root node name is "similarartists" */
125 if (xmlStrcmp (cur->name, (const xmlChar *) "similarartists")) {
126 xmlFreeDoc (doc);
127 return NULL;
128 }
129
130 for (cur = cur->xmlChildrenNode; cur; cur = cur->next) {
131 /* For each artist node */
132 if (!xmlStrcmp (cur->name, (const xmlChar *) "artist")) {
133 /* Create an ArioSimilarArtist */
134 similar_artist = (ArioSimilarArtist *) g_malloc0 (sizeof (ArioSimilarArtist));
135 for (cur2 = cur->xmlChildrenNode; cur2; cur2 = cur2->next) {
136 if ((!xmlStrcmp (cur2->name, (const xmlChar *) "name"))) {
137 /* Fill name */
138 similar_artist->name = xmlNodeListGetString (doc, cur2->xmlChildrenNode, 1);
139 } else if ((!xmlStrcmp (cur2->name, (const xmlChar *) "image"))) {
140 /* Fill image */
141 similar_artist->image = xmlNodeListGetString (doc, cur2->xmlChildrenNode, 1);
142 } else if ((!xmlStrcmp (cur2->name, (const xmlChar *) "url"))) {
143 /* Fill URL */
144 similar_artist->url = xmlNodeListGetString (doc, cur2->xmlChildrenNode, 1);
145 }
146 }
147 /* Append ArioSimilarArtist to the list */
148 similar_artists = g_slist_append (similar_artists, similar_artist);
149 }
150 }
151
152 xmlFreeDoc (doc);
153
154 return similar_artists;
155 }
156
157 void
ario_shell_similarartists_free_similarartist(ArioSimilarArtist * similar_artist)158 ario_shell_similarartists_free_similarartist (ArioSimilarArtist *similar_artist)
159 {
160 if (similar_artist) {
161 g_free (similar_artist->name);
162 g_free (similar_artist->url);
163 g_free (similar_artist->image);
164 g_free (similar_artist);
165 }
166 }
167
168 static gboolean
ario_shell_similarartists_get_images_foreach(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,ArioShellSimilarartists * shell_similarartists)169 ario_shell_similarartists_get_images_foreach (GtkTreeModel *model,
170 GtkTreePath *path,
171 GtkTreeIter *iter,
172 ArioShellSimilarartists *shell_similarartists)
173 {
174 ARIO_LOG_FUNCTION_START;
175 int size;
176 char *data;
177 GdkPixbufLoader *loader;
178 GdkPixbuf *pixbuf, *tmp_pixbuf;
179 int width, height;
180 gchar *image_url;
181
182 /* Get image URL of current row */
183 gtk_tree_model_get (model,
184 iter,
185 IMAGEURL_COLUMN, &image_url,
186 -1);
187
188 /* Download image */
189 ario_util_download_file (image_url,
190 NULL, 0, NULL,
191 &size,
192 &data);
193 g_free (image_url);
194
195 if (size == 0 || !data)
196 return FALSE;
197
198 /* Create pixbuf from image data */
199 loader = gdk_pixbuf_loader_new ();
200 gdk_pixbuf_loader_write (loader,
201 (const guchar *) data,
202 size,
203 NULL);
204 gdk_pixbuf_loader_close (loader, NULL);
205 g_free (data);
206
207 pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
208 if (!pixbuf)
209 return FALSE;
210
211 /* Resize image to IMAGE_SIZE, keeping proportions */
212 width = gdk_pixbuf_get_width (pixbuf);
213 height = gdk_pixbuf_get_height (pixbuf);
214 if (width > height) {
215 tmp_pixbuf = gdk_pixbuf_scale_simple (pixbuf,
216 IMAGE_SIZE,
217 height * IMAGE_SIZE / width,
218 GDK_INTERP_BILINEAR);
219 } else {
220 tmp_pixbuf = gdk_pixbuf_scale_simple (pixbuf,
221 width * IMAGE_SIZE / height,
222 IMAGE_SIZE,
223 GDK_INTERP_BILINEAR);
224 }
225 g_object_unref (G_OBJECT (pixbuf));
226 pixbuf = tmp_pixbuf;
227
228 /* Set pixbuf in current row */
229 gtk_list_store_set (shell_similarartists->priv->liststore, iter,
230 IMAGE_COLUMN, pixbuf,
231 -1);
232 g_object_unref (G_OBJECT (pixbuf));
233
234 return FALSE;
235 }
236
237 static gpointer
ario_shell_similarartists_get_images(ArioShellSimilarartists * shell_similarartists)238 ario_shell_similarartists_get_images (ArioShellSimilarartists *shell_similarartists)
239 {
240 ARIO_LOG_FUNCTION_START;
241
242 /* Get images of each row */
243 gtk_tree_model_foreach (GTK_TREE_MODEL (shell_similarartists->priv->liststore),
244 (GtkTreeModelForeachFunc) ario_shell_similarartists_get_images_foreach,
245 shell_similarartists);
246
247 return NULL;
248 }
249
250 GSList *
ario_shell_similarartists_get_similar_artists(const gchar * artist)251 ario_shell_similarartists_get_similar_artists (const gchar *artist)
252 {
253 ARIO_LOG_FUNCTION_START;
254 char *keyword;
255 char *xml_uri;
256 int xml_size;
257 char *xml_data;
258 GSList *similar_artists;
259
260 /* Format artist */
261 keyword = ario_util_format_keyword (artist);
262
263 /* Get last.fm uri */
264 xml_uri = g_strdup_printf (LASTFM_URI, keyword);
265 g_free (keyword);
266
267 /* Download XML file */
268 ario_util_download_file (xml_uri,
269 NULL, 0, NULL,
270 &xml_size,
271 &xml_data);
272 g_free (xml_uri);
273 if (xml_size == 0) {
274 return NULL;
275 }
276
277 /* Parse XML file */
278 similar_artists = ario_shell_similarartists_parse_xml_file (xml_data,
279 xml_size);
280 g_free (xml_data);
281
282 return similar_artists;
283 }
284
285 static void
ario_shell_similarartists_get_artists(ArioShellSimilarartists * shell_similarartists)286 ario_shell_similarartists_get_artists (ArioShellSimilarartists *shell_similarartists)
287 {
288 ARIO_LOG_FUNCTION_START;
289 GSList *similar_artists, *tmp;
290 ArioSimilarArtist *similar_artist;
291 GtkTreeIter iter;
292 int i = 0;
293 gchar *songs_txt;
294 GSList *songs = NULL;
295 ArioServerAtomicCriteria atomic_criteria;
296 ArioServerCriteria *criteria = NULL;
297
298 /* Get list of similar artists */
299 similar_artists = ario_shell_similarartists_get_similar_artists (shell_similarartists->priv->artist);
300
301 atomic_criteria.tag = ARIO_TAG_ARTIST;
302 criteria = g_slist_append (criteria, &atomic_criteria);
303
304 /* For each similar artist */
305 for (tmp = similar_artists; tmp; tmp = g_slist_next (tmp)) {
306 /* Stop here if needed */
307 if (++i > MAX_ARTISTS || shell_similarartists->priv->closed)
308 break;
309
310 similar_artist = tmp->data;
311 atomic_criteria.value = (gchar *) similar_artist->name;
312
313 /* Get all songs of artist */
314 songs = ario_server_get_songs (criteria, TRUE);
315
316 /* Format number of songs for display */
317 if (songs)
318 songs_txt = g_strdup_printf (_("%d songs"), g_slist_length (songs));
319 else
320 songs_txt = g_strdup ("");
321 g_slist_foreach (songs, (GFunc) ario_server_free_song, NULL);
322 g_slist_free (songs);
323
324 /* Append row */
325 gtk_list_store_append (shell_similarartists->priv->liststore, &iter);
326 gtk_list_store_set (shell_similarartists->priv->liststore, &iter,
327 ARTIST_COLUMN, similar_artist->name,
328 SONGS_COLUMN, songs_txt,
329 IMAGEURL_COLUMN, similar_artist->image,
330 URL_COLUMN, similar_artist->url,
331 -1);
332 g_free (songs_txt);
333 }
334
335 g_slist_foreach (similar_artists, (GFunc) ario_shell_similarartists_free_similarartist, NULL);
336 g_slist_free (similar_artists);
337
338 /* Launch a thread to download artist images */
339 shell_similarartists->priv->thread = g_thread_new ("artistimage",
340 (GThreadFunc) ario_shell_similarartists_get_images,
341 shell_similarartists);
342 g_slist_free (criteria);
343 }
344
345 GtkWidget *
ario_shell_similarartists_new(void)346 ario_shell_similarartists_new (void)
347 {
348 ARIO_LOG_FUNCTION_START;
349 ArioShellSimilarartists *shell_similarartists;
350 GtkBuilder *builder;
351 GtkWidget *treeview;
352 gchar *artist;
353
354 artist = ario_server_get_current_artist ();
355
356 if (!artist)
357 return NULL;
358
359 shell_similarartists = g_object_new (TYPE_ARIO_SHELL_SIMILARARTISTS, NULL);
360
361 g_return_val_if_fail (shell_similarartists->priv != NULL, NULL);
362 shell_similarartists->priv->closed = FALSE;
363
364 /* Build UI using GtkBuilder */
365 builder = gtk_builder_helpers_new (UI_PATH "similar-artists.ui",
366 shell_similarartists);
367
368 /* Get pointers to various widgets */
369 treeview = GTK_WIDGET (gtk_builder_get_object (builder, "treeview"));
370 shell_similarartists->priv->liststore =
371 GTK_LIST_STORE (gtk_builder_get_object (builder, "liststore"));
372
373 /* Get tree selection */
374 shell_similarartists->priv->selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview));
375 gtk_tree_selection_set_mode (shell_similarartists->priv->selection,
376 GTK_SELECTION_BROWSE);
377
378 /* Set window properties */
379 gtk_window_set_resizable (GTK_WINDOW (shell_similarartists), TRUE);
380 gtk_window_set_default_size (GTK_WINDOW (shell_similarartists), 350, 500);
381 gtk_window_set_position (GTK_WINDOW (shell_similarartists), GTK_WIN_POS_CENTER);
382 gtk_container_add (GTK_CONTAINER (shell_similarartists), GTK_WIDGET (gtk_builder_get_object (builder, "vbox")));
383
384 gtk_widget_show_all (GTK_WIDGET (shell_similarartists));
385
386 /* Refresh UI */
387 while (gtk_events_pending ())
388 gtk_main_iteration ();
389
390 /* Get similar artists and fill tree */
391 shell_similarartists->priv->artist = artist;
392 ario_shell_similarartists_get_artists (shell_similarartists);
393 g_object_unref (builder);
394
395 return GTK_WIDGET (shell_similarartists);
396 }
397
398 static gboolean
ario_shell_similarartists_window_delete_cb(GtkWidget * window,GdkEventAny * event,ArioShellSimilarartists * shell_similarartists)399 ario_shell_similarartists_window_delete_cb (GtkWidget *window,
400 GdkEventAny *event,
401 ArioShellSimilarartists *shell_similarartists)
402 {
403 ARIO_LOG_FUNCTION_START;
404 shell_similarartists->priv->closed = TRUE;
405
406 /* Wait for end of images download thread */
407 g_thread_join (shell_similarartists->priv->thread);
408
409 /* Destroy window */
410 gtk_widget_hide (GTK_WIDGET (shell_similarartists));
411 gtk_widget_destroy (GTK_WIDGET (shell_similarartists));
412
413 return TRUE;
414 }
415
416 void
ario_shell_similarartists_close_cb(GtkButton * button,ArioShellSimilarartists * shell_similarartists)417 ario_shell_similarartists_close_cb (GtkButton *button,
418 ArioShellSimilarartists *shell_similarartists)
419 {
420 ARIO_LOG_FUNCTION_START;
421 shell_similarartists->priv->closed = TRUE;
422
423 /* Wait for end of images download thread */
424 g_thread_join (shell_similarartists->priv->thread);
425
426 /* Destroy window */
427 gtk_widget_hide (GTK_WIDGET (shell_similarartists));
428 gtk_widget_destroy (GTK_WIDGET (shell_similarartists));
429 }
430
431 void
ario_shell_similarartists_lastfm_cb(GtkButton * button,ArioShellSimilarartists * shell_similarartists)432 ario_shell_similarartists_lastfm_cb (GtkButton *button,
433 ArioShellSimilarartists *shell_similarartists)
434 {
435 ARIO_LOG_FUNCTION_START;
436 GtkTreeModel *treemodel;
437 GtkTreeIter iter;
438 gchar *url;
439
440 /* Get selected row */
441 if (gtk_tree_selection_get_selected (shell_similarartists->priv->selection,
442 &treemodel,
443 &iter)) {
444 /* Get URL of selected row */
445 gtk_tree_model_get (treemodel, &iter,
446 URL_COLUMN, &url, -1);
447
448 /* Open last.fm URL in web browser */
449 ario_util_load_uri (url);
450 g_free (url);
451 }
452 }
453
454 void
ario_shell_similarartists_add_cb(GtkButton * button,ArioShellSimilarartists * shell_similarartists)455 ario_shell_similarartists_add_cb (GtkButton *button,
456 ArioShellSimilarartists *shell_similarartists)
457 {
458 ARIO_LOG_FUNCTION_START;
459 GtkTreeModel *treemodel;
460 GtkTreeIter iter;
461 GSList *artists = NULL;
462 gchar *artist;
463
464 /* Get selected row */
465 if (gtk_tree_selection_get_selected (shell_similarartists->priv->selection,
466 &treemodel,
467 &iter)) {
468 /* Get artist of selected row */
469 gtk_tree_model_get (treemodel, &iter,
470 ARTIST_COLUMN, &artist, -1);
471
472 /* Append artist songs to playlist */
473 artists = g_slist_append (artists, artist);
474 ario_server_playlist_append_artists (artists, PLAYLIST_ADD, -1);
475
476 g_slist_foreach (artists, (GFunc) g_free, NULL);
477 g_slist_free (artists);
478 }
479 }
480
481 static gboolean
ario_shell_similarartists_addall_foreach(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,GSList ** artists)482 ario_shell_similarartists_addall_foreach (GtkTreeModel *model,
483 GtkTreePath *path,
484 GtkTreeIter *iter,
485 GSList **artists)
486 {
487 ARIO_LOG_FUNCTION_START;
488 gchar *artist;
489
490 /* Get artist of row */
491 gtk_tree_model_get (model,
492 iter,
493 ARTIST_COLUMN, &artist,
494 -1);
495
496 /* Append artist to the list */
497 *artists = g_slist_append (*artists, artist);
498
499 return FALSE;
500 }
501
502 void
ario_shell_similarartists_addall_cb(GtkButton * button,ArioShellSimilarartists * shell_similarartists)503 ario_shell_similarartists_addall_cb (GtkButton *button,
504 ArioShellSimilarartists *shell_similarartists)
505 {
506 ARIO_LOG_FUNCTION_START;
507 GSList *artists = NULL;
508
509 /* Get list of all artists */
510 gtk_tree_model_foreach (GTK_TREE_MODEL (shell_similarartists->priv->liststore),
511 (GtkTreeModelForeachFunc) ario_shell_similarartists_addall_foreach,
512 &artists);
513
514 /* Appends songs of all artists to playlist */
515 ario_server_playlist_append_artists (artists, PLAYLIST_ADD, -1);
516
517 g_slist_foreach (artists, (GFunc) g_free, NULL);
518 g_slist_free (artists);
519 }
520
521 void
ario_shell_similarartists_add_similar_to_playlist(const gchar * artist,const int nb_entries)522 ario_shell_similarartists_add_similar_to_playlist (const gchar *artist,
523 const int nb_entries)
524 {
525 ARIO_LOG_FUNCTION_START;
526 ArioSimilarArtist *similar_artist;
527 GSList *artists = NULL, *similar_artists, *tmp;
528
529 /* Get list of similar artists */
530 similar_artists = ario_shell_similarartists_get_similar_artists (artist);
531
532 /* For each similar artist */
533 for (tmp = similar_artists; tmp; tmp = g_slist_next (tmp)) {
534 /* Build a list of artists names */
535 similar_artist = tmp->data;
536 artists = g_slist_append (artists, similar_artist->name);
537 }
538
539 /* Append songs of artists to playlist */
540 ario_server_playlist_append_artists (artists, PLAYLIST_ADD, nb_entries);
541
542 g_slist_foreach (similar_artists, (GFunc) ario_shell_similarartists_free_similarartist, NULL);
543 g_slist_free (similar_artists);
544 g_slist_free (artists);
545 }
546