Updated Finnish translation
[rhythmbox.git] / metadata / sj-metadata-musicbrainz.c
blob428cdc859e23a560e28c63ba485ac270eb8e9e54
1 /*
2 * sj-metadata-musicbrainz.c
3 * Copyright (C) 2003 Ross Burton <ross@burtonini.com>
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
15 * You should have received a copy of the GNU Library General Public
16 * License along with this library; if not, write to the
17 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18 * Boston, MA 02110-1301 USA.
21 #include "config.h"
23 #include <string.h>
24 #include <stdio.h>
25 #include <glib-object.h>
26 #include <glib/gi18n.h>
27 #include <glib/gerror.h>
28 #include <glib/glist.h>
29 #include <glib/gstrfuncs.h>
30 #include <glib/gmessages.h>
31 #include <gconf/gconf.h>
32 #include <gconf/gconf-client.h>
33 #include <musicbrainz/queries.h>
34 #include <musicbrainz/mb_c.h>
35 #include <stdlib.h>
36 #include <unistd.h>
38 #include <nautilus-burn-drive.h>
39 #ifndef NAUTILUS_BURN_CHECK_VERSION
40 #define NAUTILUS_BURN_CHECK_VERSION(a,b,c) FALSE
41 #endif
43 #if NAUTILUS_BURN_CHECK_VERSION(2,15,3)
44 #include <nautilus-burn.h>
45 #endif
47 #include "sj-metadata-musicbrainz.h"
48 #include "sj-structures.h"
49 #include "sj-error.h"
51 struct SjMetadataMusicbrainzPrivate {
52 GError *construct_error;
53 musicbrainz_t mb;
54 char *http_proxy;
55 int http_proxy_port;
56 char *cdrom;
57 /* TODO: remove and use an async queue or something l33t */
58 GList *albums;
59 GError *error;
62 static GError* mb_get_new_error (SjMetadata *metadata);
63 static void mb_set_cdrom (SjMetadata *metadata, const char* device);
64 static void mb_set_proxy (SjMetadata *metadata, const char* proxy);
65 static void mb_set_proxy_port (SjMetadata *metadata, const int port);
66 static void mb_list_albums (SjMetadata *metadata, GError **error);
67 static char *mb_get_submit_url (SjMetadata *metadata);
69 #define GCONF_MUSICBRAINZ_SERVER "/apps/sound-juicer/musicbrainz_server"
70 #define GCONF_PROXY_USE_PROXY "/system/http_proxy/use_http_proxy"
71 #define GCONF_PROXY_HOST "/system/http_proxy/host"
72 #define GCONF_PROXY_PORT "/system/http_proxy/port"
73 #define GCONF_PROXY_USE_AUTHENTICATION "/system/http_proxy/use_authentication"
74 #define GCONF_PROXY_USERNAME "/system/http_proxy/authentication_user"
75 #define GCONF_PROXY_PASSWORD "/system/http_proxy/authentication_password"
77 /**
78 * GObject methods
81 static GObjectClass *parent_class = NULL;
83 static void
84 sj_metadata_musicbrainz_finalize (GObject *object)
86 SjMetadataMusicbrainzPrivate *priv;
87 g_return_if_fail (object != NULL);
88 priv = SJ_METADATA_MUSICBRAINZ (object)->priv;
90 g_free (priv->http_proxy);
91 g_free (priv->cdrom);
92 mb_Delete (priv->mb);
93 g_free (priv);
96 static void
97 sj_metadata_musicbrainz_instance_init (GTypeInstance *instance, gpointer g_class)
99 GConfClient *gconf_client;
100 char *server_name = NULL;
101 SjMetadataMusicbrainz *self = (SjMetadataMusicbrainz*)instance;
102 self->priv = g_new0 (SjMetadataMusicbrainzPrivate, 1);
103 self->priv->construct_error = NULL;
104 self->priv->mb = mb_New ();
105 /* TODO: something. :/ */
106 if (!self->priv->mb) {
107 g_set_error (&self->priv->construct_error,
108 SJ_ERROR, SJ_ERROR_CD_LOOKUP_ERROR,
109 _("Cannot create MusicBrainz client"));
110 return;
112 mb_UseUTF8 (self->priv->mb, TRUE);
114 gconf_client = gconf_client_get_default ();
115 server_name = gconf_client_get_string (gconf_client, GCONF_MUSICBRAINZ_SERVER, NULL);
116 if (server_name) {
117 g_strstrip (server_name);
119 if (server_name && strcmp (server_name, "") != 0) {
120 mb_SetServer (self->priv->mb, server_name, 80);
121 g_free (server_name);
124 /* Set the HTTP proxy */
125 if (gconf_client_get_bool (gconf_client, GCONF_PROXY_USE_PROXY, NULL)) {
126 mb_SetProxy (self->priv->mb,
127 gconf_client_get_string (gconf_client, GCONF_PROXY_HOST, NULL),
128 gconf_client_get_int (gconf_client, GCONF_PROXY_PORT, NULL));
129 if (gconf_client_get_bool (gconf_client, GCONF_PROXY_USE_AUTHENTICATION, NULL)) {
130 #if HAVE_MB_SETPROXYCREDS
131 mb_SetProxyCreds (self->priv->mb,
132 gconf_client_get_string (gconf_client, GCONF_PROXY_USERNAME, NULL),
133 gconf_client_get_string (gconf_client, GCONF_PROXY_USERNAME, NULL));
134 #else
135 g_warning ("mb_SetProxyCreds() not found, no proxy authorisation possible.");
136 #endif
140 g_object_unref (gconf_client);
142 if (g_getenv ("MUSICBRAINZ_DEBUG")) {
143 mb_SetDebug (self->priv->mb, TRUE);
147 static void
148 metadata_interface_init (gpointer g_iface, gpointer iface_data)
150 SjMetadataClass *klass = (SjMetadataClass*)g_iface;
151 klass->get_new_error = mb_get_new_error;
152 klass->set_cdrom = mb_set_cdrom;
153 klass->set_proxy = mb_set_proxy;
154 klass->set_proxy_port = mb_set_proxy_port;
155 klass->list_albums = mb_list_albums;
156 klass->get_submit_url = mb_get_submit_url;
159 static void
160 sj_metadata_musicbrainz_class_init (SjMetadataMusicbrainzClass *class)
162 GObjectClass *object_class;
163 parent_class = g_type_class_peek_parent (class);
164 object_class = (GObjectClass*) class;
165 object_class->finalize = sj_metadata_musicbrainz_finalize;
168 GType
169 sj_metadata_musicbrainz_get_type (void)
171 static GType type = 0;
172 if (type == 0) {
173 static const GTypeInfo info = {
174 sizeof (SjMetadataMusicbrainzClass),
175 NULL,
176 NULL,
177 (GClassInitFunc)sj_metadata_musicbrainz_class_init,
178 NULL,
179 NULL,
180 sizeof (SjMetadataMusicbrainz),
182 sj_metadata_musicbrainz_instance_init,
183 NULL
185 static const GInterfaceInfo metadata_i_info = {
186 (GInterfaceInitFunc) metadata_interface_init,
187 NULL, NULL
189 type = g_type_register_static (G_TYPE_OBJECT, "SjMetadataMusicBrainzClass", &info, 0);
190 g_type_add_interface_static (type, SJ_TYPE_METADATA, &metadata_i_info);
192 return type;
195 GObject *
196 sj_metadata_musicbrainz_new (void)
198 return g_object_new (sj_metadata_musicbrainz_get_type (), NULL);
202 * Private methods
205 #define BYTES_PER_SECTOR 2352
206 #define BYTES_PER_SECOND (44100 / 8) / 16 / 2
208 static int
209 get_duration_from_sectors (int sectors)
211 return (sectors * BYTES_PER_SECTOR / BYTES_PER_SECOND);
214 static GList*
215 get_offline_track_listing(SjMetadata *metadata, GError **error)
217 SjMetadataMusicbrainzPrivate *priv;
218 GList* list = NULL;
219 AlbumDetails *album;
220 TrackDetails *track;
221 int num_tracks, i;
223 g_return_val_if_fail (metadata != NULL, NULL);
224 priv = SJ_METADATA_MUSICBRAINZ (metadata)->priv;
226 if (!mb_Query (priv->mb, MBQ_GetCDTOC)) {
227 char message[255];
228 mb_GetQueryError (priv->mb, message, 255);
229 g_set_error (error,
230 SJ_ERROR, SJ_ERROR_CD_LOOKUP_ERROR,
231 _("Cannot read CD: %s"), message);
232 return NULL;
235 num_tracks = mb_GetResultInt (priv->mb, MBE_TOCGetLastTrack);
237 album = g_new0 (AlbumDetails, 1);
238 album->artist = g_strdup (_("Unknown Artist"));
239 album->title = g_strdup (_("Unknown Title"));
240 album->genre = NULL;
241 for (i = 1; i <= num_tracks; i++) {
242 track = g_new0 (TrackDetails, 1);
243 track->album = album;
244 track->number = i;
245 track->title = g_strdup_printf (_("Track %d"), i);
246 track->artist = g_strdup (album->artist);
247 track->duration = get_duration_from_sectors (mb_GetResultInt1 (priv->mb, MBE_TOCGetTrackNumSectors, i+1));
248 album->tracks = g_list_append (album->tracks, track);
249 album->number++;
251 return g_list_append (list, album);
254 static gboolean
255 fire_signal_idle (SjMetadataMusicbrainz *m)
257 g_return_val_if_fail (SJ_IS_METADATA_MUSICBRAINZ (m), FALSE);
258 g_signal_emit_by_name (G_OBJECT (m), "metadata", m->priv->albums, m->priv->error);
259 return FALSE;
263 * Virtual methods
266 static GError*
267 mb_get_new_error (SjMetadata *metadata)
269 GError *error = NULL;
270 if (metadata == NULL || SJ_METADATA_MUSICBRAINZ (metadata)->priv == NULL) {
271 g_set_error (&error,
272 SJ_ERROR, SJ_ERROR_INTERNAL_ERROR,
273 _("MusicBrainz metadata object is not valid. This is bad, check your console for errors."));
274 return error;
276 return SJ_METADATA_MUSICBRAINZ (metadata)->priv->construct_error;
279 static void
280 mb_set_cdrom (SjMetadata *metadata, const char* device)
282 SjMetadataMusicbrainzPrivate *priv;
283 g_return_if_fail (metadata != NULL);
284 g_return_if_fail (device != NULL);
285 priv = SJ_METADATA_MUSICBRAINZ (metadata)->priv;
287 if (priv->cdrom) {
288 g_free (priv->cdrom);
290 priv->cdrom = g_strdup (device);
291 mb_SetDevice (priv->mb, priv->cdrom);
294 static void
295 mb_set_proxy (SjMetadata *metadata, const char* proxy)
297 SjMetadataMusicbrainzPrivate *priv;
298 g_return_if_fail (metadata != NULL);
299 priv = SJ_METADATA_MUSICBRAINZ (metadata)->priv;
301 if (proxy == NULL) {
302 proxy = "";
304 if (priv->http_proxy) {
305 g_free (priv->http_proxy);
307 priv->http_proxy = g_strdup (proxy);
308 mb_SetProxy (priv->mb, priv->http_proxy, priv->http_proxy_port);
311 static void
312 mb_set_proxy_port (SjMetadata *metadata, const int port)
314 SjMetadataMusicbrainzPrivate *priv;
315 g_return_if_fail (metadata != NULL);
316 priv = SJ_METADATA_MUSICBRAINZ (metadata)->priv;
318 priv->http_proxy_port = port;
319 mb_SetProxy (priv->mb, priv->http_proxy, priv->http_proxy_port);
322 /* Data imported from FreeDB is horrendeous for compilations,
323 * Try to split the 'Various' artist */
324 static void
325 artist_and_title_from_title (TrackDetails *track, gpointer data)
327 char *slash, **split;
329 if (g_ascii_strncasecmp (MBI_VARIOUS_ARTIST_ID, track->album->artist_id, 64) != 0 && track->album->artist_id[0] != '\0' && track->artist_id[0] != '\0') {
330 track->title = g_strdup (data);
331 return;
334 slash = strstr (data, " / ");
335 if (slash == NULL) {
336 track->title = g_strdup (data);
337 return;
339 split = g_strsplit (data, " / ", 2);
340 track->artist = g_strdup (split[0]);
341 track->title = g_strdup (split[1]);
342 g_strfreev (split);
345 #if WITH_CACHE
347 * Write the RDF in the MusicBrainz object to the file specified.
349 static void
350 cache_rdf (musicbrainz_t mb, const char *filename)
352 GError *error = NULL;
353 int len;
354 char *path, *rdf;
356 g_assert (mb != NULL);
357 g_assert (filename != NULL);
359 /* Create the folder for the file */
360 path = g_path_get_dirname (filename);
361 g_mkdir_with_parents (path, 0755); /* Handle errors in set_contents() */
362 g_free (path);
364 /* How much data is there to save? */
365 len = mb_GetResultRDFLen (mb);
366 rdf = g_malloc0 (len);
368 /* Get the RDF and save it */
369 mb_GetResultRDF (mb, rdf, len);
370 if (!g_file_set_contents (filename, rdf, len, &error)) {
371 g_warning ("Cannot write cache file %s: %s", filename, error->message);
372 g_error_free (error);
375 g_free (rdf);
379 * Load into the MusicBrainz object the RDF from the specified cache file if it
380 * exists and is valid then return TRUE, otherwise return FALSE.
382 static gboolean
383 get_cached_rdf (musicbrainz_t mb, const char *cachepath)
385 gboolean ret = FALSE;
386 GError *error = NULL;
387 char *rdf = NULL;
389 g_assert (mb != NULL);
390 g_assert (cachepath != NULL);
392 if (!g_file_test (cachepath, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR))
393 goto done;
395 /* Cache file exists, open it */
396 if (!g_file_get_contents (cachepath, &rdf, NULL, &error)) {
397 g_warning ("Cannot open cache file %s: %s", cachepath, error->message);
398 g_error_free (error);
399 goto done;
402 /* Set the RDF */
403 if (mb_SetResultRDF (mb, rdf))
404 ret = TRUE;
406 done:
407 g_free (rdf);
408 return ret;
410 #else
411 static gboolean
412 get_cached_rdf (musicbrainz_t mb, const char *cachepath)
414 return FALSE;
416 static void
417 cache_rdf (musicbrainz_t mb, const char *filename) {
419 #endif
422 * Fill the MusicBrainz object with RDF. Basically get the CD Index and check
423 * the local cache, if that fails then lookup the data online.
425 static void
426 get_rdf (SjMetadata *metadata)
428 SjMetadataMusicbrainzPrivate *priv;
429 char data[256];
430 char *cdindex = NULL, *cachepath = NULL;
432 g_assert (metadata != NULL);
434 priv = SJ_METADATA_MUSICBRAINZ (metadata)->priv;
436 #if WITH_CACHE
437 /* Get the Table of Contents */
438 if (!mb_Query (priv->mb, MBQ_GetCDTOC)) {
439 mb_GetQueryError (priv->mb, data, sizeof (data));
440 g_print (_("This CD could not be queried: %s\n"), data);
441 return;
444 /* Extract the CD Index */
445 if (!mb_GetResultData(priv->mb, MBE_TOCGetCDIndexId, data, sizeof (data))) {
446 mb_GetQueryError (priv->mb, data, sizeof (data));
447 g_print (_("This CD could not be queried: %s\n"), data);
448 return;
450 cdindex = g_strdup (data);
452 cachepath = g_build_filename (g_get_home_dir (), ".gnome2", "sound-juicer", "cache", cdindex, NULL);
453 #endif
455 if (!get_cached_rdf (priv->mb, cachepath)) {
456 /* Don't re-use the CD Index as that doesn't send enough data to the server.
457 By doing this we also pass track lengths, which can be proxied to FreeDB
458 if required. */
459 if (!mb_Query (priv->mb, MBQ_GetCDInfo)) {
460 mb_GetQueryError (priv->mb, data, sizeof (data));
461 g_print (_("This CD could not be queried: %s\n"), data);
462 goto done;
464 cache_rdf (priv->mb, cachepath);
467 done:
468 g_free (cachepath);
469 g_free (cdindex);
472 static gpointer
473 lookup_cd (SjMetadata *metadata)
475 /** The size of the buffer used in MusicBrainz lookups */
476 SjMetadataMusicbrainzPrivate *priv;
477 GList *albums = NULL;
478 GList *al, *tl;
479 char data[256];
480 int num_albums, i, j;
481 NautilusBurnMediaType type;
482 #if NAUTILUS_BURN_CHECK_VERSION(2,15,3)
483 NautilusBurnDrive *drive;
484 #endif
486 /* TODO: fire error signal */
487 g_return_val_if_fail (metadata != NULL, NULL);
488 g_return_val_if_fail (SJ_IS_METADATA_MUSICBRAINZ (metadata), NULL);
489 priv = SJ_METADATA_MUSICBRAINZ (metadata)->priv;
490 g_return_val_if_fail (priv->cdrom != NULL, NULL);
491 priv->error = NULL; /* TODO: hack */
493 #if NAUTILUS_BURN_CHECK_VERSION(2,15,3)
494 drive = nautilus_burn_drive_monitor_get_drive_for_device (nautilus_burn_get_drive_monitor (),
495 priv->cdrom);
496 if (drive == NULL) {
497 return NULL;
499 type = nautilus_burn_drive_get_media_type (drive);
500 nautilus_burn_drive_unref (drive);
501 #else
502 type = nautilus_burn_drive_get_media_type_from_path (priv->cdrom);
503 #endif
505 if (type == NAUTILUS_BURN_MEDIA_TYPE_ERROR) {
506 char *msg;
507 SjError err;
509 if (access (priv->cdrom, W_OK) == 0) {
510 msg = g_strdup_printf (_("Device '%s' does not contain any media"), priv->cdrom);
511 err = SJ_ERROR_CD_NO_MEDIA;
512 } else {
513 msg = g_strdup_printf (_("Device '%s' could not be opened. Check the access permissions on the device."), priv->cdrom);
514 err = SJ_ERROR_CD_PERMISSION_ERROR;
516 priv->error = g_error_new (SJ_ERROR, err, _("Cannot read CD: %s"), msg);
517 g_free (msg);
519 priv->albums = NULL;
520 g_idle_add ((GSourceFunc)fire_signal_idle, metadata);
521 return NULL;
524 get_rdf (metadata);
526 num_albums = mb_GetResultInt(priv->mb, MBE_GetNumAlbums);
527 if (num_albums < 1) {
528 priv->albums = get_offline_track_listing (metadata, &(priv->error));
529 g_idle_add ((GSourceFunc)fire_signal_idle, metadata);
530 return priv->albums;
533 for (i = 1; i <= num_albums; i++) {
534 int num_tracks;
535 AlbumDetails *album;
537 mb_Select1(priv->mb, MBS_SelectAlbum, i);
538 album = g_new0 (AlbumDetails, 1);
540 if (mb_GetResultData(priv->mb, MBE_AlbumGetAlbumId, data, sizeof (data))) {
541 mb_GetIDFromURL (priv->mb, data, data, sizeof (data));
542 album->album_id = g_strdup (data);
545 if (mb_GetResultData (priv->mb, MBE_AlbumGetAlbumArtistId, data, sizeof (data))) {
546 mb_GetIDFromURL (priv->mb, data, data, sizeof (data));
547 album->artist_id = g_strdup (data);
548 if (g_ascii_strncasecmp (MBI_VARIOUS_ARTIST_ID, data, 64) == 0) {
549 album->artist = g_strdup (_("Various"));
550 } else {
551 if (data && mb_GetResultData1(priv->mb, MBE_AlbumGetArtistName, data, sizeof (data), 1)) {
552 album->artist = g_strdup (data);
553 } else {
554 album->artist = g_strdup (_("Unknown Artist"));
556 if (data && mb_GetResultData1(priv->mb, MBE_AlbumGetArtistSortName, data, sizeof (data), 1)) {
557 album->artist_sortname = g_strdup (data);
562 if (mb_GetResultData(priv->mb, MBE_AlbumGetAlbumName, data, sizeof (data))) {
563 album->title = g_strdup (data);
564 } else {
565 album->title = g_strdup (_("Unknown Title"));
569 int num_releases;
570 num_releases = mb_GetResultInt (priv->mb, MBE_AlbumGetNumReleaseDates);
571 if (num_releases > 0) {
572 mb_Select1(priv->mb, MBS_SelectReleaseDate, 1);
573 if (mb_GetResultData(priv->mb, MBE_ReleaseGetDate, data, sizeof (data))) {
574 int matched, year=1, month=1, day=1;
575 matched = sscanf(data, "%u-%u-%u", &year, &month, &day);
576 if (matched >= 1) {
577 album->release_date = g_date_new_dmy (day, month, year);
580 mb_Select(priv->mb, MBS_Back);
584 num_tracks = mb_GetResultInt(priv->mb, MBE_AlbumGetNumTracks);
585 if (num_tracks < 1) {
586 g_free (album->artist);
587 g_free (album->artist_sortname);
588 g_free (album->title);
589 g_free (album);
590 g_warning (_("Incomplete metadata for this CD"));
591 priv->albums = get_offline_track_listing (metadata, &(priv->error));
592 g_idle_add ((GSourceFunc)fire_signal_idle, metadata);
593 return priv->albums;
596 for (j = 1; j <= num_tracks; j++) {
597 TrackDetails *track;
598 track = g_new0 (TrackDetails, 1);
600 track->album = album;
602 track->number = j; /* replace with number lookup? */
604 if (mb_GetResultData1(priv->mb, MBE_AlbumGetTrackId, data, sizeof (data), j)) {
605 mb_GetIDFromURL (priv->mb, data, data, sizeof (data));
606 track->track_id = g_strdup (data);
609 if (mb_GetResultData1(priv->mb, MBE_AlbumGetArtistId, data, sizeof (data), j)) {
610 mb_GetIDFromURL (priv->mb, data, data, sizeof (data));
611 track->artist_id = g_strdup (data);
614 if (mb_GetResultData1(priv->mb, MBE_AlbumGetTrackName, data, sizeof (data), j)) {
615 if (track->artist_id != NULL) {
616 artist_and_title_from_title (track, data);
617 } else {
618 track->title = g_strdup (data);
622 if (track->artist == NULL && mb_GetResultData1(priv->mb, MBE_AlbumGetArtistName, data, sizeof (data), j)) {
623 track->artist = g_strdup (data);
626 if (mb_GetResultData1(priv->mb, MBE_AlbumGetArtistSortName, data, sizeof (data), j)) {
627 track->artist_sortname = g_strdup (data);
630 if (mb_GetResultData1(priv->mb, MBE_AlbumGetTrackDuration, data, sizeof (data), j)) {
631 track->duration = atoi (data) / 1000;
634 album->tracks = g_list_append (album->tracks, track);
635 album->number++;
638 albums = g_list_append (albums, album);
640 mb_Select (priv->mb, MBS_Rewind);
643 /* For each album, we need to insert the duration data if necessary
644 * We need to query this here because otherwise we would flush the
645 * data queried from the server */
646 /* TODO: scan for 0 duration before doing the query to avoid another lookup if
647 we don't need to do it */
648 mb_Query (priv->mb, MBQ_GetCDTOC);
649 for (al = albums; al; al = al->next) {
650 AlbumDetails *album = al->data;
652 j = 1;
653 for (tl = album->tracks; tl; tl = tl->next) {
654 TrackDetails *track = tl->data;
655 int sectors;
657 if (track->duration == 0) {
658 sectors = mb_GetResultInt1 (priv->mb, MBE_TOCGetTrackNumSectors, j+1);
659 track->duration = get_duration_from_sectors (sectors);
661 j++;
665 priv->albums = albums;
666 g_idle_add ((GSourceFunc)fire_signal_idle, metadata);
667 return albums;
670 static void
671 mb_list_albums (SjMetadata *metadata, GError **error)
673 GThread *thread;
675 g_return_if_fail (SJ_IS_METADATA_MUSICBRAINZ (metadata));
677 thread = g_thread_create ((GThreadFunc)lookup_cd, metadata, TRUE, error);
678 if (thread == NULL) {
679 g_set_error (error,
680 SJ_ERROR, SJ_ERROR_INTERNAL_ERROR,
681 _("Could not create CD lookup thread"));
682 return;
686 static char *
687 mb_get_submit_url (SjMetadata *metadata)
689 SjMetadataMusicbrainzPrivate *priv;
690 char url[1025];
692 g_return_val_if_fail (metadata != NULL, NULL);
694 priv = SJ_METADATA_MUSICBRAINZ (metadata)->priv;
696 if (mb_GetWebSubmitURL(priv->mb, url, 1024)) {
697 return g_strdup(url);
698 } else {
699 return NULL;