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