1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 
3 /*
4  *  Goo
5  *
6  *  Copyright (C) 2004 Free Software Foundation, Inc.
7  *
8  *  This program is free software; you can redistribute it and/or modify
9  *  it under the terms of the GNU General Public License as published by
10  *  the Free Software Foundation; either version 2 of the License, or
11  *  (at your option) any later version.
12  *
13  *  This program is distributed in the hope that it will be useful,
14  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  *  GNU General Public License for more details.
17  *
18  *  You should have received a copy of the GNU General Public License
19  *  along with this program; if not, see <http://www.gnu.org/licenses/>.
20  */
21 
22 
23 #include <config.h>
24 #include <string.h>
25 #include <gtk/gtk.h>
26 #if HAVE_LIBCOVERART
27 #include <coverart/caa_c.h>
28 #endif
29 #include "dlg-cover-chooser.h"
30 #include "gio-utils.h"
31 #include "glib-utils.h"
32 #include "gtk-utils.h"
33 #include "goo-window.h"
34 
35 
36 #define BUFFER_SIZE 4096
37 #define COVER_BACKUP_FILENAME "original_cover.png"
38 #define MAX_IMAGES 20
39 #define GET_WIDGET(x) _gtk_builder_get_widget (data->builder, (x))
40 #define _GTK_RESPONSE_RESET 10
41 
42 
43 enum {
44 	URL_COLUMN,
45 	IMAGE_COLUMN,
46 	N_COLUMNS
47 };
48 
49 
50 typedef struct {
51 	GooWindow    *window;
52 	char         *artist;
53 	char         *album;
54 	GtkBuilder   *builder;
55 	GtkWidget    *dialog;
56 	GtkWidget    *icon_view;
57 	GdkPixbuf    *cover_backup;
58 	GList        *file_list;
59 	int           total_files;
60 	GList        *current_file;
61 	int           loaded_files;
62 	GCancellable *cancellable;
63 	gboolean      searching;
64 	gboolean      destroy;
65 } DialogData;
66 
67 
68 static GList *
make_file_list_from_search_result(void * buffer,gsize count,int max_files)69 make_file_list_from_search_result (void  *buffer,
70 				   gsize  count,
71 				   int    max_files)
72 {
73 	GList        *list = NULL;
74 	int           n_files = 0;
75 	gboolean      done = FALSE;
76 	GInputStream *stream;
77 	gssize        n;
78 	char          buf[BUFFER_SIZE];
79 	int           buf_offset = 0;
80 	GString      *partial_url;
81 
82 	stream = g_memory_input_stream_new_from_data (buffer, count, NULL);
83 	partial_url = NULL;
84 	while ((n = g_input_stream_read (stream,
85 					 buf + buf_offset,
86 					 BUFFER_SIZE - buf_offset - 1,
87 					 NULL,
88 					 NULL)) > 0)
89 	{
90 		const char *prefix = "/images?q=tbn:";
91 		int         prefix_len = strlen (prefix);
92 		char       *url_start;
93 		gboolean    copy_tail = TRUE;
94 
95 		buf[buf_offset+n] = 0;
96 
97 		if (partial_url == NULL)
98 			url_start = strstr (buf, prefix);
99 		else
100 			url_start = buf;
101 
102 		while (url_start != NULL) {
103 			char *url_end;
104 
105 			url_end = strstr (url_start, "\"");
106 
107 			if (url_end == NULL) {
108 				if (partial_url == NULL)
109 					partial_url = g_string_new (url_start);
110 				else
111 					g_string_append (partial_url, url_start);
112 				url_start = NULL;
113 				copy_tail = FALSE;
114 			}
115 			else {
116 				char *url_tail = g_strndup (url_start, url_end - url_start);
117 				char *url;
118 				char *complete_url;
119 
120 				if (partial_url != NULL) {
121 					g_string_append (partial_url, url_tail);
122 					g_free (url_tail);
123 					url = partial_url->str;
124 					g_string_free (partial_url, FALSE);
125 					partial_url = NULL;
126 				}
127 				else
128 					url = url_tail;
129 
130 				complete_url = g_strconcat ("http://images.google.com", url, NULL);
131 				g_free (url);
132 
133 				list = g_list_prepend (list, complete_url);
134 				n_files++;
135 				if (n_files >= max_files) {
136 					done = TRUE;
137 					break;
138 				}
139 
140 				url_start = strstr (url_end + 1, prefix);
141 			}
142 		}
143 
144 		if (done)
145 			break;
146 
147 		if (copy_tail) {
148 			prefix_len = MIN (prefix_len, buf_offset + n);
149 			strncpy (buf,
150 				 buf + buf_offset + n - prefix_len,
151 				 prefix_len);
152 			buf_offset = prefix_len;
153 		}
154 		else
155 			buf_offset = 0;
156 	}
157 
158 	if (partial_url != NULL)
159 		g_string_free (partial_url, TRUE);
160 
161 	g_object_unref (stream);
162 
163 	return g_list_reverse (list);
164 }
165 
166 
167 static char *
get_query(const char * album,const char * artist)168 get_query (const char *album,
169 	   const char *artist)
170 {
171 	char *s, *e, *q;
172 
173 	s = g_strdup_printf ("%s %s", album, artist);
174 	e = g_uri_escape_string (s, G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, FALSE);
175 	q = g_strconcat ("http://images.google.com/images?q=", e, NULL);
176 
177 	g_free (e);
178 	g_free (s);
179 
180 	return q;
181 }
182 
183 
184 /* -- dlg_cover_chooser -- */
185 
186 
187 static void
destroy_cb(GtkWidget * widget,DialogData * data)188 destroy_cb (GtkWidget  *widget,
189 	    DialogData *data)
190 {
191 	if (data->searching) {
192 		data->destroy = TRUE;
193 		g_cancellable_cancel (data->cancellable);
194 		return;
195 	}
196 
197 	g_signal_handlers_disconnect_by_data (data->icon_view, data);
198 
199 	g_object_unref (data->cancellable);
200 	_g_string_list_free (data->file_list);
201 	_g_object_unref (data->cover_backup);
202 	g_object_unref (data->builder);
203 	g_free (data->album);
204 	g_free (data->artist);
205 	g_free (data);
206 }
207 
208 
209 static void
search_completed(DialogData * data)210 search_completed (DialogData *data)
211 {
212 	char *text;
213 
214 	data->searching = FALSE;
215 	gtk_widget_set_sensitive (GET_WIDGET ("cancel_search_button"), FALSE);
216 	text = g_strdup_printf ("%u", data->total_files);
217 	gtk_label_set_text (GTK_LABEL (GET_WIDGET ("progress_label")), text);
218 
219 	g_free (text);
220 
221 	if (data->destroy)
222 		destroy_cb (NULL, data);
223 }
224 
225 
226 static void load_current_file (DialogData *data);
227 
228 
229 static void
search_image_data_ready_cb(void * buffer,gsize count,GError * error,gpointer user_data)230 search_image_data_ready_cb (void     *buffer,
231 			    gsize     count,
232 			    GError   *error,
233 			    gpointer  user_data)
234 {
235 	DialogData *data = user_data;
236 
237 	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
238 		search_completed (data);
239 		return;
240 	}
241 
242 	if (error == NULL) {
243 		GInputStream *stream;
244 		GdkPixbuf    *image;
245 
246 		stream = g_memory_input_stream_new_from_data (buffer, count, NULL);
247 		image = gdk_pixbuf_new_from_stream (stream, NULL, &error);
248 		if (image != NULL) {
249 			GtkTreeModel *model;
250 			GtkTreeIter   iter;
251 			char         *url;
252 
253 			model = gtk_icon_view_get_model (GTK_ICON_VIEW (data->icon_view));
254 			gtk_list_store_append (GTK_LIST_STORE (model), &iter);
255 			url = (char *) data->current_file->data;
256 			gtk_list_store_set (GTK_LIST_STORE (model), &iter,
257 					    URL_COLUMN, url,
258 					    IMAGE_COLUMN, image,
259 					    -1);
260 
261 			g_object_unref (image);
262 		}
263 	}
264 
265 	data->loaded_files++;
266 	data->current_file = data->current_file->next;
267 	load_current_file (data);
268 }
269 
270 
271 static void
update_progress_label(DialogData * data)272 update_progress_label (DialogData *data)
273 {
274 	char *text;
275 
276 	if (data->loaded_files < data->total_files)
277 		text = g_strdup_printf (_("%u, loading image: %u"),
278 					data->total_files,
279 					data->loaded_files + 1);
280 	else
281 		text = g_strdup_printf ("%u", data->total_files);
282 	gtk_label_set_text (GTK_LABEL (GET_WIDGET ("progress_label")), text);
283 
284 	g_free (text);
285 }
286 
287 
288 static void
load_current_file(DialogData * data)289 load_current_file (DialogData *data)
290 {
291 	char  *url;
292 	GFile *source;
293 
294 	update_progress_label (data);
295 
296 	if (data->current_file == NULL) {
297 		search_completed (data);
298 		return;
299 	}
300 
301 	url = data->current_file->data;
302 
303 	debug (DEBUG_INFO, "LOADING %s\n", url);
304 
305 	source = g_file_new_for_uri (url);
306 	g_load_file_async (source,
307 			   G_PRIORITY_DEFAULT,
308 			   data->cancellable,
309 			   search_image_data_ready_cb,
310 			   data);
311 
312 	g_object_unref (source);
313 }
314 
315 
316 static void
start_loading_files(DialogData * data)317 start_loading_files (DialogData *data)
318 {
319 	gtk_list_store_clear (GTK_LIST_STORE (gtk_icon_view_get_model (GTK_ICON_VIEW (data->icon_view))));
320 	data->total_files = g_list_length (data->file_list);
321 	data->current_file = data->file_list;
322 	data->loaded_files = 0;
323 	load_current_file (data);
324 }
325 
326 
327 static void
search_query_ready_cb(void * buffer,gsize count,GError * error,gpointer user_data)328 search_query_ready_cb (void     *buffer,
329 		       gsize     count,
330 		       GError   *error,
331 		       gpointer  user_data)
332 {
333 	DialogData *data = user_data;
334 
335 	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
336 		search_completed (data);
337 		return;
338 	}
339 
340 	if (error != NULL) {
341 		_gtk_error_dialog_from_gerror_show (GTK_WINDOW (data->dialog),
342 						    _("Could not search for a cover on Internet"),
343 						    &error);
344 		search_completed (data);
345 		return;
346 	}
347 
348 	data->file_list = make_file_list_from_search_result (buffer, count, MAX_IMAGES);
349 	start_loading_files (data);
350 }
351 
352 
353 static void
start_searching(DialogData * data)354 start_searching (DialogData *data)
355 {
356 	char  *query;
357 	GFile *file;
358 
359 	data->searching = TRUE;
360 	g_cancellable_reset (data->cancellable);
361 	gtk_widget_set_sensitive (GET_WIDGET ("cancel_search_button"), TRUE);
362 
363 	query = get_query (data->album, data->artist);
364 	file = g_file_new_for_uri (query);
365 	g_load_file_async (file,
366 			   G_PRIORITY_DEFAULT,
367 			   data->cancellable,
368 			   search_query_ready_cb,
369 			   data);
370 
371 	g_object_unref (file);
372 	g_free (query);
373 }
374 
375 
376 static void
ok_button_clicked(DialogData * data)377 ok_button_clicked (DialogData *data)
378 {
379 	GList *list;
380 
381 	list = gtk_icon_view_get_selected_items (GTK_ICON_VIEW (data->icon_view));
382 	if (list != NULL) {
383 		GtkTreePath  *path;
384 		GtkTreeModel *model;
385 		GtkTreeIter   iter;
386 
387 		path = list->data;
388 		model = gtk_icon_view_get_model (GTK_ICON_VIEW (data->icon_view));
389 		if (gtk_tree_model_get_iter (model, &iter, path)) {
390 			GdkPixbuf *image;
391 
392 			gtk_tree_model_get (model, &iter, IMAGE_COLUMN, &image, -1);
393 			goo_window_set_cover_image_from_pixbuf (data->window, image);
394 
395 			g_object_unref (image);
396 		}
397 
398 		g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL);
399 		g_list_free (list);
400 	}
401 }
402 
403 
404 static void
icon_view_selection_changed_cb(GtkIconView * icon_view,DialogData * data)405 icon_view_selection_changed_cb (GtkIconView *icon_view,
406 				DialogData  *data)
407 {
408 	GList *list;
409 
410 	list = gtk_icon_view_get_selected_items (GTK_ICON_VIEW (data->icon_view));
411 	gtk_widget_set_sensitive (gtk_dialog_get_widget_for_response (GTK_DIALOG (data->dialog), GTK_RESPONSE_OK), list != NULL);
412 
413 	g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL);
414 	g_list_free (list);
415 }
416 
417 
418 static void
icon_view_item_activated_cb(GtkIconView * icon_view,GtkTreePath * path,DialogData * data)419 icon_view_item_activated_cb (GtkIconView *icon_view,
420 			     GtkTreePath *path,
421 			     DialogData  *data)
422 {
423 	ok_button_clicked (data);
424 }
425 
426 
427 static void
cancel_search_button_clicked_cb(GtkWidget * widget,DialogData * data)428 cancel_search_button_clicked_cb (GtkWidget  *widget,
429        				 DialogData *data)
430 {
431 	g_cancellable_cancel (data->cancellable);
432 }
433 
434 
435 static void
backup_cover_image(DialogData * data)436 backup_cover_image (DialogData *data)
437 {
438 	char *cover_filename;
439 
440 	cover_filename = goo_window_get_cover_filename (data->window);
441 	gtk_widget_set_sensitive (gtk_dialog_get_widget_for_response (GTK_DIALOG (data->dialog), _GTK_RESPONSE_RESET), cover_filename != NULL);
442 	if (cover_filename != NULL)
443 		data->cover_backup = gdk_pixbuf_new_from_file (cover_filename, NULL);
444 
445 	g_free (cover_filename);
446 }
447 
448 
449 static void
dialog_response_cb(GtkWidget * dialog,int response_id,DialogData * data)450 dialog_response_cb (GtkWidget  *dialog,
451 		    int         response_id,
452 		    DialogData *data)
453 {
454 	switch (response_id) {
455 	case GTK_RESPONSE_OK:
456 		ok_button_clicked (data);
457 		gtk_widget_destroy (dialog);
458 		break;
459 
460 	case _GTK_RESPONSE_RESET:
461 		goo_window_set_cover_image_from_pixbuf (data->window, data->cover_backup);
462 		break;
463 
464 	default:
465 		gtk_widget_destroy (dialog);
466 		break;
467 	}
468 }
469 
470 
471 void
dlg_cover_chooser(GooWindow * window,const char * album,const char * artist)472 dlg_cover_chooser (GooWindow  *window,
473 		   const char *album,
474 		   const char *artist)
475 {
476 	DialogData      *data;
477 	GtkListStore    *model;
478 	GtkCellRenderer *renderer;
479 
480 	data = g_new0 (DialogData, 1);
481 	data->window = window;
482 	data->builder = _gtk_builder_new_from_resource ("cover-chooser.ui");
483 	data->album = g_strdup (album);
484 	data->artist = g_strdup (artist);
485 	data->cancellable = g_cancellable_new ();
486 
487 	/* Get the widgets. */
488 
489 	data->dialog = g_object_new (GTK_TYPE_DIALOG,
490 				     "title", _("Choose a CD Cover"),
491 				     "transient-for", GTK_WINDOW (window),
492 				     "modal", TRUE,
493 				     "use-header-bar", _gtk_settings_get_dialogs_use_header (),
494 				     NULL);
495 	gtk_container_add (GTK_CONTAINER (gtk_dialog_get_content_area (GTK_DIALOG (data->dialog))),
496 			   GET_WIDGET ("cover_chooser_dialog"));
497 	gtk_dialog_add_buttons (GTK_DIALOG (data->dialog),
498 				_GTK_LABEL_CANCEL, GTK_RESPONSE_CANCEL,
499 				_GTK_LABEL_OK, GTK_RESPONSE_OK,
500 				NULL);
501 
502 	{
503 		GtkWidget *button;
504 
505 		button = gtk_button_new_from_icon_name ("edit-undo-symbolic", GTK_ICON_SIZE_SMALL_TOOLBAR);
506 		gtk_widget_show (button);
507 		gtk_dialog_add_action_widget (GTK_DIALOG (data->dialog), button, _GTK_RESPONSE_RESET);
508 	}
509 
510 	gtk_style_context_add_class (gtk_widget_get_style_context (gtk_dialog_get_widget_for_response (GTK_DIALOG (data->dialog), GTK_RESPONSE_OK)),
511 				     GTK_STYLE_CLASS_SUGGESTED_ACTION);
512 
513 	model = gtk_list_store_new (N_COLUMNS,
514 				    G_TYPE_STRING,
515 				    GDK_TYPE_PIXBUF);
516 	data->icon_view = gtk_icon_view_new_with_model (GTK_TREE_MODEL (model));
517 	g_object_unref (model);
518 
519 	renderer = gtk_cell_renderer_pixbuf_new ();
520 	g_object_set (renderer, "follow-state", TRUE, NULL);
521 	gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (data->icon_view),
522 				    renderer,
523 				    TRUE);
524 	gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (data->icon_view),
525 					renderer,
526 					"pixbuf", IMAGE_COLUMN,
527 					NULL);
528 
529 	gtk_widget_show (data->icon_view);
530 	gtk_container_add (GTK_CONTAINER (GET_WIDGET ("icon_view_scrolledwindow")), data->icon_view);
531 
532 	/* Set widgets data. */
533 
534 	backup_cover_image (data);
535 
536 	gtk_widget_set_sensitive (gtk_dialog_get_widget_for_response (GTK_DIALOG (data->dialog), GTK_RESPONSE_OK), FALSE);
537 
538 	/* Set the signals handlers. */
539 
540 	g_signal_connect (G_OBJECT (data->dialog),
541 			  "destroy",
542 			  G_CALLBACK (destroy_cb),
543 			  data);
544 	g_signal_connect (G_OBJECT (data->dialog),
545 			  "response",
546 			  G_CALLBACK (dialog_response_cb),
547 			  data);
548 	g_signal_connect (G_OBJECT (data->icon_view),
549 			  "selection-changed",
550 			  G_CALLBACK (icon_view_selection_changed_cb),
551 			  data);
552 	g_signal_connect (G_OBJECT (data->icon_view),
553 			  "item-activated",
554 			  G_CALLBACK (icon_view_item_activated_cb),
555 			  data);
556 	g_signal_connect (GET_WIDGET ("cancel_search_button"),
557 			  "clicked",
558 			  G_CALLBACK (cancel_search_button_clicked_cb),
559 			  data);
560 
561 	/* run dialog. */
562 
563 	gtk_window_set_transient_for (GTK_WINDOW (data->dialog), GTK_WINDOW (window));
564 	gtk_window_set_modal (GTK_WINDOW (data->dialog), FALSE);
565 	gtk_widget_show (data->dialog);
566 
567 	start_searching (data);
568 }
569 
570 
571 /* -- auto fetch functions -- */
572 
573 
574 typedef struct {
575 	GooWindow    *window;
576 	GCancellable *cancellable;
577 } FetchData;
578 
579 
580 static void
fetch_data_free(FetchData * data)581 fetch_data_free (FetchData *data)
582 {
583 	_g_object_unref (data->cancellable);
584 	g_free (data);
585 }
586 
587 
588 static void
image_data_ready_for_query_cb(void * buffer,gsize count,GError * error,gpointer user_data)589 image_data_ready_for_query_cb (void     *buffer,
590 			       gsize     count,
591 			       GError   *error,
592 			       gpointer  user_data)
593 {
594 	FetchData *data = user_data;
595 	gboolean   success;
596 
597 	success = (error == NULL) && (count > 0);
598 	if (success)
599 		success = goo_window_set_cover_image_from_data (data->window, buffer, count);
600 
601 	if (! success)
602 		fetch_cover_image_from_album_info (data->window,
603 						   goo_window_get_album (data->window),
604 						   FETCH_COVER_STAGE_AFTER_WEB_SEARCH,
605 						   data->cancellable);
606 
607 	fetch_data_free (data);
608 }
609 
610 
611 static void
query_ready_cb(void * buffer,gsize count,GError * error,gpointer user_data)612 query_ready_cb (void     *buffer,
613 		gsize     count,
614 		GError   *error,
615 		gpointer  user_data)
616 {
617 	FetchData *data = user_data;
618 	GList     *list;
619 
620 	if (error != NULL) {
621 		fetch_data_free (data);
622 		return;
623 	}
624 
625 	list = make_file_list_from_search_result (buffer, count, 1);
626 	if (list != NULL) {
627 		GFile *file;
628 
629 		file = g_file_new_for_uri ((char *) list->data);
630 		g_load_file_async (file,
631 				   G_PRIORITY_DEFAULT,
632 				   NULL,
633 				   image_data_ready_for_query_cb,
634 				   data);
635 
636 		g_object_unref (file);
637 	}
638 	else
639 		fetch_data_free (data);
640 
641 	_g_string_list_free (list);
642 }
643 
644 
645 void
fetch_cover_image_from_name(GooWindow * window,const char * album,const char * artist,GCancellable * cancellable)646 fetch_cover_image_from_name (GooWindow    *window,
647 		             const char   *album,
648 		             const char   *artist,
649 		             GCancellable *cancellable)
650 {
651 	FetchData *data;
652 	char      *url;
653 	GFile     *file;
654 
655 	data = g_new0 (FetchData, 1);
656 	data->window = window;
657 	data->cancellable = _g_object_ref (cancellable);
658 
659 	url = get_query (album, artist);
660 	file = g_file_new_for_uri (url);
661 	g_load_file_async (file,
662 			   G_PRIORITY_DEFAULT,
663 			   data->cancellable,
664 			   query_ready_cb,
665 			   data);
666 
667 	g_object_unref (file);
668 	g_free (url);
669 }
670 
671 
672 /* -- fetch_cover_image_from_asin -- */
673 
674 
675 static void
image_data_ready_for_asin_cb(void * buffer,gsize count,GError * error,gpointer user_data)676 image_data_ready_for_asin_cb (void     *buffer,
677 			      gsize     count,
678 			      GError   *error,
679 			      gpointer  user_data)
680 {
681 	FetchData *data = user_data;
682 	gboolean   success;
683 
684 	success = (error == NULL) && (count > 0);
685 	if (success)
686 		success = goo_window_set_cover_image_from_data (data->window, buffer, count);
687 
688 	if (! success)
689 		fetch_cover_image_from_album_info (data->window,
690 						   goo_window_get_album (data->window),
691 						   FETCH_COVER_STAGE_AFTER_ASIN,
692 						   data->cancellable);
693 
694 	fetch_data_free (data);
695 }
696 
697 
698 void
fetch_cover_image_from_asin(GooWindow * window,const char * asin,GCancellable * cancellable)699 fetch_cover_image_from_asin (GooWindow    *window,
700 		             const char   *asin,
701 		             GCancellable *cancellable)
702 {
703 	FetchData *data;
704 	char      *url;
705 	GFile     *file;
706 
707 	data = g_new0 (FetchData, 1);
708 	data->window = window;
709 	data->cancellable = _g_object_ref (cancellable);
710 
711 	url = g_strdup_printf ("http://images.amazon.com/images/P/%s.01._SCLZZZZZZZ_.jpg", asin);
712 	file = g_file_new_for_uri (url);
713 	g_load_file_async (file,
714 			   G_PRIORITY_DEFAULT,
715 			   data->cancellable,
716 			   image_data_ready_for_asin_cb,
717 			   data);
718 
719 	g_object_unref (file);
720 	g_free (url);
721 }
722 
723 
724 /* -- fetch_cover_image_from_album_info -- */
725 
726 
727 #if HAVE_LIBCOVERART
728 
729 
730 typedef struct {
731 	GooWindow *window;
732 	AlbumInfo *album;
733 	guchar    *buffer;
734 	gsize      size;
735 } GetCoverArtData;
736 
737 
738 static void
get_cover_art_data_free(GetCoverArtData * data)739 get_cover_art_data_free (GetCoverArtData *data)
740 {
741 	g_object_unref (data->window);
742 	album_info_unref (data->album);
743 	g_free (data->buffer);
744 	g_free (data);
745 }
746 
747 
748 static void
metadata_get_coverart_thread(GTask * task,gpointer source_object,gpointer task_data,GCancellable * cancellable)749 metadata_get_coverart_thread (GTask        *task,
750 			      gpointer      source_object,
751 			      gpointer      task_data,
752 			      GCancellable *cancellable)
753 {
754 	GetCoverArtData *data = task_data;
755 	CaaCoverArt      cover_art;
756 	CaaImageData     image_data;
757 
758 	cover_art = caa_coverart_new (PACKAGE_NAME "-" PACKAGE_VERSION);
759 	image_data = caa_coverart_fetch_front (cover_art, data->album->id);
760 	if (image_data != NULL) {
761 		data->size = caa_imagedata_size (image_data);
762 		data->buffer = g_new (guchar, data->size);
763 		memcpy (data->buffer, caa_imagedata_data (image_data), data->size);
764 	}
765 
766 	caa_coverart_delete (cover_art);
767 }
768 
769 
770 static void
metadata_get_coverart(GooWindow * window,AlbumInfo * album,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)771 metadata_get_coverart (GooWindow           *window,
772 		       AlbumInfo           *album,
773 		       GCancellable        *cancellable,
774 		       GAsyncReadyCallback  callback,
775 		       gpointer             user_data)
776 {
777 	GTask           *task;
778 	GetCoverArtData *data;
779 
780 	task = g_task_new (NULL, cancellable, callback, user_data);
781 
782 	data = g_new0 (GetCoverArtData, 1);
783 	data->window = g_object_ref (window);
784 	data->album = album_info_ref (album);
785 	data->buffer = NULL;
786 	data->size = 0;
787 	g_task_set_task_data (task, data, (GDestroyNotify) get_cover_art_data_free);
788 
789 	g_task_run_in_thread (task, metadata_get_coverart_thread);
790 
791 	g_object_unref (task);
792 }
793 
794 
795 static gboolean
metadata_get_coverart_finish(GAsyncResult * result,guchar ** buffer,gsize * count)796 metadata_get_coverart_finish (GAsyncResult  *result,
797 			      guchar       **buffer,
798 			      gsize         *count)
799 {
800 	GetCoverArtData *data;
801 
802 	g_return_val_if_fail (g_task_is_valid (result, NULL), FALSE);
803 
804         data = g_task_get_task_data (G_TASK (result));
805         if (data->size == 0)
806         	return FALSE;
807 
808         *buffer = data->buffer;
809         *count = data->size;
810 
811         return TRUE;
812 }
813 
814 
815 typedef struct {
816 	GooWindow    *window;
817 	AlbumInfo    *album;
818 	GCancellable *cancellable;
819 } CoverArtData;
820 
821 
822 static void
cover_art_data_free(CoverArtData * data)823 cover_art_data_free (CoverArtData *data)
824 {
825 	g_object_unref (data->window);
826 	album_info_unref (data->album);
827 	_g_object_unref (data->cancellable);
828 	g_free (data);
829 }
830 
831 
832 static void
metadata_get_coverart_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)833 metadata_get_coverart_cb (GObject      *source_object,
834 			  GAsyncResult *result,
835 			  gpointer      user_data)
836 {
837 	CoverArtData *data = user_data;
838 	guchar       *buffer;
839 	gsize         size;
840 	gboolean      success;
841 
842 	success = metadata_get_coverart_finish (result, &buffer, &size);
843 	if (success)
844 		success = goo_window_set_cover_image_from_data (data->window, buffer, size);
845 
846 	if (! success)
847 		fetch_cover_image_from_album_info (data->window,
848 						   data->album,
849 						   FETCH_COVER_STAGE_AFTER_LIBCOVERART,
850 						   data->cancellable);
851 
852 	cover_art_data_free (data);
853 }
854 
855 
856 #endif
857 
858 
859 void
fetch_cover_image_from_album_info(GooWindow * window,AlbumInfo * album,FetchCoverStage after_stage,GCancellable * cancellable)860 fetch_cover_image_from_album_info (GooWindow       *window,
861 				   AlbumInfo       *album,
862 				   FetchCoverStage  after_stage,
863 				   GCancellable    *cancellable)
864 {
865 	if ((cancellable != NULL) && g_cancellable_is_cancelled (cancellable))
866 		return;
867 
868 #if HAVE_LIBCOVERART
869 
870 	if ((FETCH_COVER_STAGE_AFTER_LIBCOVERART > after_stage)
871 	    && (album != NULL)
872 	    && (album->id != NULL))
873 	{
874 		CoverArtData *data;
875 
876 		data = g_new0 (CoverArtData, 1);
877 		data->window = g_object_ref (window);
878 		data->album = album_info_ref (album);
879 		data->cancellable = _g_object_ref (cancellable);
880 		metadata_get_coverart (data->window,
881 				       data->album,
882 				       data->cancellable,
883 				       metadata_get_coverart_cb,
884 				       data);
885 
886 		return;
887 	}
888 
889 #endif
890 
891 	if ((FETCH_COVER_STAGE_AFTER_ASIN > after_stage)
892 	    && (album != NULL)
893 	    && (album->asin != NULL))
894 	{
895 		fetch_cover_image_from_asin (window, album->asin, cancellable);
896 		return;
897 	}
898 
899 	if ((FETCH_COVER_STAGE_AFTER_WEB_SEARCH > after_stage)
900 	    && (album != NULL)
901 	    && (album->title != NULL)
902 	    && (album->artist != NULL))
903 	{
904 		fetch_cover_image_from_name (window, album->title, album->artist, cancellable);
905 	}
906 }
907