No issues found
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 |
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 }