hythmbox-2.98/plugins/audiocd/rb-musicbrainz-lookup.c

No issues found

Incomplete coverage

Tool Failure ID Location Function Message Data
clang-analyzer no-output-found rb-musicbrainz-lookup.c Message(text='Unable to locate XML output from invoke-clang-analyzer') None
Failure running clang-analyzer ('no-output-found')
Message
Unable to locate XML output from invoke-clang-analyzer
  1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
  2  *
  3  * Copyright (C) 2012 Jonathan Matthew
  4  *
  5  * This program is free software; you can redistribute it and/or modify
  6  * it under the terms of the GNU General Public License as published by
  7  * the Free Software Foundation; either version 2, or (at your option)
  8  * any later version.
  9  * 
 10  * The Rhythmbox authors hereby grant permission for non-GPL compatible
 11  * GStreamer plugins to be used and distributed together with GStreamer
 12  * and Rhythmbox. This permission is above and beyond the permissions granted
 13  * by the GPL license by which Rhythmbox is covered. If you modify this code
 14  * you may extend this exception to your version of the code, but you are not
 15  * obligated to do so. If you do not wish to do so, delete this exception
 16  * statement from your version.
 17  *
 18  * This program is distributed in the hope that it will be useful,
 19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 21  * GNU General Public License for more details.
 22  *
 23  * You should have received a copy of the GNU General Public License
 24  * along with this program; if not, write to the Free Software
 25  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
 26  */
 27 
 28 #include "config.h"
 29 
 30 #include <stdlib.h>
 31 
 32 #include <glib.h>
 33 #include <glib/gi18n.h>
 34 #include <libsoup/soup.h>
 35 #include <libsoup/soup-gnome.h>
 36 
 37 #include "rb-musicbrainz-lookup.h"
 38 
 39 
 40 struct ParseAttrMap {
 41 	const char *path;
 42 	const char *xml_attr;
 43 	const char *attr;
 44 };
 45 
 46 struct _RBMusicBrainzData {
 47 	char *type;
 48 	GHashTable *attrs;
 49 	GList *children;
 50 	RBMusicBrainzData *parent;
 51 
 52 	GList *path_start;
 53 };
 54 
 55 typedef struct {
 56 	RBMusicBrainzData *current;
 57 	RBMusicBrainzData *root;
 58 
 59 	GQueue path;
 60 	const char *item;
 61 	GString text;
 62 
 63 	struct ParseAttrMap *map;
 64 } RBMusicBrainzParseContext;
 65 
 66 
 67 static struct ParseAttrMap root_attr_map[] = {
 68 	{ NULL, NULL, NULL }
 69 };
 70 
 71 static struct ParseAttrMap release_attr_map[] = {
 72 	{ "/release", "id", RB_MUSICBRAINZ_ATTR_ALBUM_ID },
 73 	{ "/release/asin", NULL, RB_MUSICBRAINZ_ATTR_ASIN },
 74 	{ "/release/country", NULL, RB_MUSICBRAINZ_ATTR_COUNTRY },
 75 	{ "/release/date", NULL, RB_MUSICBRAINZ_ATTR_DATE },
 76 	{ "/release/title", NULL, RB_MUSICBRAINZ_ATTR_ALBUM },
 77 	{ "/release/artist-credit/name-credit/artist", "id", RB_MUSICBRAINZ_ATTR_ALBUM_ARTIST_ID },
 78 	{ "/release/artist-credit/name-credit/artist/name", NULL, RB_MUSICBRAINZ_ATTR_ALBUM_ARTIST },
 79 	{ "/release/artist-credit/name-credit/artist/sort-name", NULL, RB_MUSICBRAINZ_ATTR_ALBUM_ARTIST_SORTNAME },
 80 	{ NULL, NULL, NULL }
 81 };
 82 
 83 static struct ParseAttrMap medium_attr_map[] = {
 84 	{ "/medium/position", NULL, RB_MUSICBRAINZ_ATTR_DISC_NUMBER },
 85 	{ "/medium/track-list", "count", RB_MUSICBRAINZ_ATTR_TRACK_COUNT },
 86 	{ "/medium/disc-list/disc", "id", RB_MUSICBRAINZ_ATTR_DISC_ID },
 87 	{ NULL, NULL, NULL }
 88 };
 89 
 90 static struct ParseAttrMap track_attr_map[] = {
 91 	{ "/track/number", NULL, RB_MUSICBRAINZ_ATTR_TRACK_NUMBER },
 92 	{ "/track/length", NULL, RB_MUSICBRAINZ_ATTR_DURATION },
 93 	{ "/track/recording", "id", RB_MUSICBRAINZ_ATTR_TRACK_ID },
 94 	{ "/track/recording/title", NULL, RB_MUSICBRAINZ_ATTR_TITLE },
 95 	{ "/track/recording/artist-credit/name-credit/artist", "id", RB_MUSICBRAINZ_ATTR_ARTIST_ID },
 96 	{ "/track/recording/artist-credit/name-credit/artist/name", NULL, RB_MUSICBRAINZ_ATTR_ARTIST },
 97 	{ "/track/recording/artist-credit/name-credit/artist/sort-name", NULL, RB_MUSICBRAINZ_ATTR_ARTIST_SORTNAME },
 98 	{ NULL, NULL, NULL }
 99 };
100 
101 static struct ParseAttrMap relation_attr_map[] = {
102 	{ "/relation", "type", RB_MUSICBRAINZ_ATTR_RELATION_TYPE },
103 	{ "/relation/target", NULL, RB_MUSICBRAINZ_ATTR_RELATION_TARGET },
104 	{ "/relation/artist", "id", RB_MUSICBRAINZ_ATTR_ARTIST_ID },
105 	{ "/relation/artist/name", NULL, RB_MUSICBRAINZ_ATTR_ARTIST },
106 	{ "/relation/artist/sortname", NULL, RB_MUSICBRAINZ_ATTR_ARTIST_SORTNAME },
107 	{ "/relation/work", "id", RB_MUSICBRAINZ_ATTR_WORK_ID },
108 	{ "/relation/work/title", NULL, RB_MUSICBRAINZ_ATTR_WORK_TITLE },
109 	{ NULL, NULL, NULL }
110 };
111 
112 static struct {
113 	const char *name;
114 	struct ParseAttrMap *map;
115 } object_types[] = {
116 	{ "root", root_attr_map },
117 	{ "release", release_attr_map },
118 	{ "medium", medium_attr_map },
119 	{ "track", track_attr_map },
120 	{ "relation", relation_attr_map }
121 };
122 
123 GQuark
124 rb_musicbrainz_error_quark (void)
125 {
126 	static GQuark quark = 0;
127 	if (!quark)
128 		quark = g_quark_from_static_string ("rb_musicbrainz_error");
129 
130 	return quark;
131 }
132 
133 #define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }
134 GType
135 rb_musicbrainz_error_get_type (void)
136 {
137 	static GType etype = 0;
138 
139 	if (etype == 0) {
140 		static const GEnumValue values[] = {
141 			ENUM_ENTRY (RB_MUSICBRAINZ_ERROR_NOT_FOUND, "not-found"),
142 			ENUM_ENTRY (RB_MUSICBRAINZ_ERROR_SERVER, "server-error"),
143 			ENUM_ENTRY (RB_MUSICBRAINZ_ERROR_NETWORK, "network-error"),
144 			{ 0, 0, 0 }
145 		};
146 
147 		etype = g_enum_register_static ("RBMusicBrainzError", values);
148 	}
149 
150 	return etype;
151 }
152 
153 
154 static void
155 free_values (GQueue *attrs)
156 {
157 	g_queue_free_full (attrs, (GDestroyNotify) g_free);
158 }
159 
160 void
161 rb_musicbrainz_data_free (RBMusicBrainzData *data)
162 {
163 	g_hash_table_unref (data->attrs);
164 	g_list_free_full (data->children, (GDestroyNotify) rb_musicbrainz_data_free);
165 	g_free (data->type);
166 	g_free (data);
167 }
168 const char *
169 rb_musicbrainz_data_get_attr_value (RBMusicBrainzData *data, const char *attr)
170 {
171 	GQueue *d;
172 	d = g_hash_table_lookup (data->attrs, attr);
173 	if (d == NULL) {
174 		return NULL;
175 	}
176 
177 	return d->head->data;
178 }
179 
180 GList *
181 rb_musicbrainz_data_get_attr_names (RBMusicBrainzData *data)
182 {
183 	return g_hash_table_get_keys (data->attrs);
184 }
185 
186 RBMusicBrainzData *
187 rb_musicbrainz_data_find_child (RBMusicBrainzData *data, const char *attr, const char *value)
188 {
189 	GList *l;
190 	for (l = data->children; l != NULL; l = l->next) {
191 		RBMusicBrainzData *child = l->data;
192 		GQueue *d;
193 		GList *i;
194 
195 		d = g_hash_table_lookup (child->attrs, attr);
196 		if (d == NULL)
197 			continue;
198 		for (i = d->head; i != NULL; i = i->next) {
199 			if (g_strcmp0 (value, i->data) == 0)
200 				return child;
201 		}
202 	}
203 
204 	return NULL;
205 }
206 
207 GList *
208 rb_musicbrainz_data_get_children (RBMusicBrainzData *data)
209 {
210 	return g_list_copy (data->children);
211 }
212 
213 const char *
214 rb_musicbrainz_data_get_data_type (RBMusicBrainzData *data)
215 {
216 	return data->type;
217 }
218 
219 static RBMusicBrainzData *
220 new_data (RBMusicBrainzData *parent, const char *type)
221 {
222 	RBMusicBrainzData *d = g_new0 (RBMusicBrainzData, 1);
223 	d->type = g_strdup (type);
224 	d->parent = parent;
225 	d->attrs = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify)free_values);
226 
227 	if (parent) {
228 		parent->children = g_list_append (parent->children, d);
229 	}
230 	return d;
231 }
232 
233 static void
234 add_attr (RBMusicBrainzData *data, const char *attr, const char *value)
235 {
236 	GQueue *d;
237 
238 	d = g_hash_table_lookup (data->attrs, attr);
239 	if (d == NULL) {
240 		d = g_queue_new ();
241 		g_hash_table_insert (data->attrs, (char *)attr, d);
242 	}
243 
244 	g_queue_push_tail (d, g_strdup (value));
245 }
246 
247 static char *
248 get_path (RBMusicBrainzParseContext *ctx)
249 {
250 	GString s = {0,};
251 	GList *l;
252 
253 	for (l = ctx->current->path_start; l != NULL; l = l->next) {
254 		g_string_append (&s, "/");
255 		g_string_append (&s, l->data);
256 	}
257 
258 	return s.str;
259 }
260 
261 static void
262 start_element (GMarkupParseContext *pctx,
263 	       const gchar *element_name,
264 	       const gchar **attribute_names,
265 	       const gchar **attribute_values,
266 	       gpointer user_data,
267 	       GError **error)
268 {
269 	RBMusicBrainzParseContext *ctx = user_data;
270 	char *path;
271 	int i;
272 	int j;
273 
274 	g_queue_push_tail (&ctx->path, g_strdup (element_name));
275 
276 	for (i = 0; i < G_N_ELEMENTS (object_types); i++) {
277 		if (g_strcmp0 (element_name, object_types[i].name) == 0) {
278 			RBMusicBrainzData *d = new_data (ctx->current, element_name);
279 			d->path_start = ctx->path.tail;
280 			ctx->current = d;
281 			ctx->map = object_types[i].map;
282 			break;
283 		}
284 	}
285 
286 	path = get_path (ctx);
287 	for (i = 0; ctx->map[i].path != NULL; i++) {
288 		if (g_strcmp0 (path, ctx->map[i].path) == 0) {
289 			if (ctx->map[i].xml_attr != NULL) {
290 				for (j = 0; attribute_names[j] != NULL; j++) {
291 					if (g_strcmp0 (attribute_names[j], ctx->map[i].xml_attr) == 0) {
292 						add_attr (ctx->current,
293 							  (char *)ctx->map[i].attr,
294 							  attribute_values[j]);
295 					}
296 				}
297 			} else {
298 				ctx->item = ctx->map[i].attr;
299 			}
300 			break;
301 		}
302 	}
303 
304 	g_free (path);
305 }
306 
307 static void
308 end_element (GMarkupParseContext *pctx,
309 	     const gchar *element_name,
310 	     gpointer user_data,
311 	     GError **error)
312 {
313 	RBMusicBrainzParseContext *ctx = user_data;
314 
315 	if (ctx->item) {
316 		add_attr (ctx->current, (char *)ctx->item, ctx->text.str);
317 		ctx->item = NULL;
318 	}
319 
320 	if (ctx->path.tail == ctx->current->path_start) {
321 		ctx->current->path_start = NULL;
322 		ctx->current = ctx->current->parent;
323 	}
324 
325 	g_free (g_queue_pop_tail (&ctx->path));
326 
327 	g_free (ctx->text.str);
328 	ctx->text.str = NULL;
329 	ctx->text.len = 0;
330 	ctx->text.allocated_len = 0;
331 }
332 
333 static void
334 text (GMarkupParseContext *pctx,
335       const gchar *text,
336       gsize text_len,
337       gpointer user_data,
338       GError **error)
339 {
340 	RBMusicBrainzParseContext *ctx = user_data;
341 
342 	if (ctx->item) {
343 		g_string_append (&ctx->text, text);
344 	}
345 }
346 
347 
348 RBMusicBrainzData *
349 rb_musicbrainz_data_parse (const char *data, gssize len, GError **error)
350 {
351 	RBMusicBrainzParseContext ctx;
352 	GMarkupParser parser = {
353 		start_element,
354 		end_element,
355 		text
356 	};
357 	GMarkupParseContext *pctx;
358 
359 	ctx.root = new_data (NULL, "root");
360 	ctx.current = ctx.root;
361 	ctx.text.str = NULL;
362 	ctx.text.len = 0;
363 	ctx.text.allocated_len = 0;
364 	g_queue_init (&ctx.path);
365 
366 	pctx = g_markup_parse_context_new (&parser, 0, &ctx, NULL);
367 	if (g_markup_parse_context_parse (pctx, data, len, error) == FALSE) {
368 		rb_musicbrainz_data_free (ctx.root);
369 		return NULL;
370 	}
371 
372 	if (g_markup_parse_context_end_parse (pctx, error) == FALSE) {
373 		rb_musicbrainz_data_free (ctx.root);
374 		return NULL;
375 	}
376 	g_markup_parse_context_free (pctx);
377 
378 	return ctx.root;
379 }
380 
381 GList *
382 rb_musicbrainz_data_get_attr_values (RBMusicBrainzData *data, const char *attr)
383 {
384 	GQueue *d;
385 	d = g_hash_table_lookup (data->attrs, attr);
386 	if (d == NULL) {
387 		return NULL;
388 	}
389 
390 	return g_list_copy (d->head);
391 }
392 
393 
394 static void
395 lookup_cb (SoupSession *session, SoupMessage *msg, GSimpleAsyncResult *result)
396 {
397 	RBMusicBrainzData *data;
398 	int code;
399 	GError *error = NULL;
400 
401 	g_object_get (msg, SOUP_MESSAGE_STATUS_CODE, &code, NULL);
402 	if (code == SOUP_STATUS_NOT_FOUND || code == SOUP_STATUS_BAD_REQUEST)  {
403 		g_simple_async_result_set_error (result,
404 						 RB_MUSICBRAINZ_ERROR,
405 						 RB_MUSICBRAINZ_ERROR_NOT_FOUND,
406 						 _("Not found"));
407 	} else if (code < 100) {
408 		g_simple_async_result_set_error (result,
409 						 RB_MUSICBRAINZ_ERROR,
410 						 RB_MUSICBRAINZ_ERROR_NETWORK,
411 						 _("Unable to connect to Musicbrainz server"));
412 	} else if (code != SOUP_STATUS_OK || msg->response_body->data == NULL) {
413 		g_simple_async_result_set_error (result,
414 						 RB_MUSICBRAINZ_ERROR,
415 						 RB_MUSICBRAINZ_ERROR_SERVER,
416 						 _("Musicbrainz server error"));
417 	} else {
418 		data = rb_musicbrainz_data_parse (msg->response_body->data,
419 						  msg->response_body->length,
420 						  &error);
421 		if (data == NULL) {
422 			g_simple_async_result_set_from_error (result, error);
423 			g_clear_error (&error);
424 		} else {
425 			g_simple_async_result_set_op_res_gpointer (result, data, NULL);
426 		}
427 	}
428 
429 	g_simple_async_result_complete (result);
430 	g_object_unref (result);
431 	g_object_unref (session);
432 }
433 
434 void
435 rb_musicbrainz_lookup (const char *entity,
436 		       const char *entity_id,
437 		       const char **includes,
438 		       GCancellable *cancellable,
439 		       GAsyncReadyCallback callback,
440 		       gpointer user_data)
441 {
442 	GSimpleAsyncResult *result;
443 	SoupURI *uri;
444 	SoupMessage *message;
445 	SoupSession *session;
446 	char *uri_str;
447 	char *inc;
448 
449 	result = g_simple_async_result_new (NULL,
450 					    callback,
451 					    user_data,
452 					    rb_musicbrainz_lookup);
453 	g_simple_async_result_set_check_cancellable (result, cancellable);
454 
455 	session = soup_session_async_new_with_options (SOUP_SESSION_ADD_FEATURE_BY_TYPE,
456 						       SOUP_TYPE_GNOME_FEATURES_2_26,
457 						       SOUP_SESSION_USER_AGENT,
458 						       "Rhythmbox/" VERSION " ",
459 						       NULL);
460 	uri_str = g_strdup_printf ("http://musicbrainz.org/ws/2/%s/%s", entity, entity_id);
461 	uri = soup_uri_new (uri_str);
462 	g_free (uri_str);
463 
464 	if (includes != NULL) {
465 		inc = g_strjoinv ("+", (char **)includes);
466 		soup_uri_set_query_from_fields (uri, "inc", inc, NULL);
467 		g_free (inc);
468 	}
469 
470 	message = soup_message_new_from_uri (SOUP_METHOD_GET, uri);
471 	soup_uri_free (uri);
472 
473 	soup_session_queue_message (session,
474 				    message,
475 				    (SoupSessionCallback) lookup_cb,
476 				    result);
477 }
478 
479 RBMusicBrainzData *
480 rb_musicbrainz_lookup_finish (GAsyncResult *result,
481 			      GError **error)
482 {
483 	g_return_val_if_fail (g_simple_async_result_is_valid (result,
484 							      NULL,
485 							      rb_musicbrainz_lookup),
486 			      NULL);
487 	if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error))
488 		return NULL;
489 
490 	return g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (result));
491 }
492 
493 char *
494 rb_musicbrainz_create_submit_url (const char *disc_id, const char *full_disc_id)
495 {
496 	char **bits;
497 	int *intbits;
498 	GString *url;
499 	int i;
500 	int n;
501 
502 	/* full disc id is a space-delimited list of hex numbers.. */
503 	bits = g_strsplit (full_disc_id, " ", 0);
504 	n = g_strv_length (bits);
505 	intbits = g_new0 (int, n+1);
506 	for (i = 0; i < n; i++) {
507 		intbits[i] = strtol (bits[i], 0, 16);
508 	}
509 	g_strfreev (bits);
510 
511 	url = g_string_new ("http://mm.musicbrainz.org/bare/cdlookup.html?id=");
512 
513 	g_string_append (url, disc_id);		/* urlencode? */
514 	g_string_append_printf (url, "&tracks=%d&toc=%d", intbits[1], intbits[0]);
515 
516 	/* .. that we put in the url in decimal */
517 	for (i = 1; i < n; i++) {
518 		g_string_append_printf (url, "+%d", intbits[i]);
519 	}
520 	
521 	g_free (intbits);
522 
523 	return g_string_free (url, FALSE);
524 }