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., 59 Temple Place - Suite 330,
18 * Boston, MA 02111-1307, USA.
23 #endif /* HAVE_CONFIG_H */
27 #include <glib-object.h>
28 #include <glib/gi18n.h>
29 #include <glib/gerror.h>
30 #include <glib/glist.h>
31 #include <glib/gstrfuncs.h>
32 #include <glib/gmessages.h>
33 #include <gconf/gconf-client.h>
34 #include <musicbrainz/queries.h>
35 #include <musicbrainz/mb_c.h>
36 #include <nautilus-burn.h>
40 #include "sj-metadata-musicbrainz.h"
41 #include "sj-structures.h"
44 struct SjMetadataMusicbrainzPrivate
{
45 GError
*construct_error
;
50 /* TODO: remove and use an async queue or something l33t */
55 static GError
* mb_get_new_error (SjMetadata
*metadata
);
56 static void mb_set_cdrom (SjMetadata
*metadata
, const char* device
);
57 static void mb_set_proxy (SjMetadata
*metadata
, const char* proxy
);
58 static void mb_set_proxy_port (SjMetadata
*metadata
, const int port
);
59 static void mb_list_albums (SjMetadata
*metadata
, GError
**error
);
60 static char *mb_get_submit_url (SjMetadata
*metadata
);
62 #define GCONF_MUSICBRAINZ_SERVER "/apps/sound-juicer/musicbrainz_server"
63 #define GCONF_PROXY_USE_PROXY "/system/http_proxy/use_http_proxy"
64 #define GCONF_PROXY_HOST "/system/http_proxy/host"
65 #define GCONF_PROXY_PORT "/system/http_proxy/port"
66 #define GCONF_PROXY_USE_AUTHENTICATION "/system/http_proxy/use_authentication"
67 #define GCONF_PROXY_USERNAME "/system/http_proxy/authentication_user"
68 #define GCONF_PROXY_PASSWORD "/system/http_proxy/authentication_password"
74 static GObjectClass
*parent_class
= NULL
;
77 sj_metadata_musicbrainz_finalize (GObject
*object
)
79 SjMetadataMusicbrainzPrivate
*priv
;
80 g_return_if_fail (object
!= NULL
);
81 priv
= SJ_METADATA_MUSICBRAINZ (object
)->priv
;
83 g_free (priv
->http_proxy
);
90 sj_metadata_musicbrainz_instance_init (GTypeInstance
*instance
, gpointer g_class
)
92 GConfClient
*gconf_client
;
93 char *server_name
= NULL
;
94 SjMetadataMusicbrainz
*self
= (SjMetadataMusicbrainz
*)instance
;
95 self
->priv
= g_new0 (SjMetadataMusicbrainzPrivate
, 1);
96 self
->priv
->construct_error
= NULL
;
97 self
->priv
->mb
= mb_New ();
98 /* TODO: something. :/ */
99 if (!self
->priv
->mb
) {
100 g_set_error (&self
->priv
->construct_error
,
101 SJ_ERROR
, SJ_ERROR_CD_LOOKUP_ERROR
,
102 _("Cannot create MusicBrainz client"));
105 mb_UseUTF8 (self
->priv
->mb
, TRUE
);
107 gconf_client
= gconf_client_get_default ();
108 server_name
= gconf_client_get_string (gconf_client
, GCONF_MUSICBRAINZ_SERVER
, NULL
);
110 g_strstrip (server_name
);
112 if (server_name
&& strcmp (server_name
, "") != 0) {
113 mb_SetServer (self
->priv
->mb
, server_name
, 80);
114 g_free (server_name
);
117 /* Set the HTTP proxy */
118 if (gconf_client_get_bool (gconf_client
, GCONF_PROXY_USE_PROXY
, NULL
)) {
119 char *proxy_host
= gconf_client_get_string (gconf_client
, GCONF_PROXY_HOST
, NULL
);
120 mb_SetProxy (self
->priv
->mb
, proxy_host
,
121 gconf_client_get_int (gconf_client
, GCONF_PROXY_PORT
, NULL
));
123 if (gconf_client_get_bool (gconf_client
, GCONF_PROXY_USE_AUTHENTICATION
, NULL
)) {
124 #if HAVE_MB_SETPROXYCREDS
125 char *username
= gconf_client_get_string (gconf_client
, GCONF_PROXY_USERNAME
, NULL
);
126 char *password
= gconf_client_get_string (gconf_client
, GCONF_PROXY_PASSWORD
, NULL
);
127 mb_SetProxyCreds (self
->priv
->mb
, username
, password
);
131 g_warning ("mb_SetProxyCreds() not found, no proxy authorisation possible.");
136 g_object_unref (gconf_client
);
138 if (g_getenv("MUSICBRAINZ_DEBUG")) {
139 mb_SetDebug (self
->priv
->mb
, TRUE
);
144 metadata_interface_init (gpointer g_iface
, gpointer iface_data
)
146 SjMetadataClass
*klass
= (SjMetadataClass
*)g_iface
;
147 klass
->get_new_error
= mb_get_new_error
;
148 klass
->set_cdrom
= mb_set_cdrom
;
149 klass
->set_proxy
= mb_set_proxy
;
150 klass
->set_proxy_port
= mb_set_proxy_port
;
151 klass
->list_albums
= mb_list_albums
;
152 klass
->get_submit_url
= mb_get_submit_url
;
156 sj_metadata_musicbrainz_class_init (SjMetadataMusicbrainzClass
*class)
158 GObjectClass
*object_class
;
159 parent_class
= g_type_class_peek_parent (class);
160 object_class
= (GObjectClass
*) class;
161 object_class
->finalize
= sj_metadata_musicbrainz_finalize
;
165 sj_metadata_musicbrainz_get_type (void)
167 static GType type
= 0;
169 static const GTypeInfo info
= {
170 sizeof (SjMetadataMusicbrainzClass
),
173 (GClassInitFunc
)sj_metadata_musicbrainz_class_init
,
176 sizeof (SjMetadataMusicbrainz
),
178 sj_metadata_musicbrainz_instance_init
,
181 static const GInterfaceInfo metadata_i_info
= {
182 (GInterfaceInitFunc
) metadata_interface_init
,
185 type
= g_type_register_static (G_TYPE_OBJECT
, "SjMetadataMusicBrainzClass", &info
, 0);
186 g_type_add_interface_static (type
, SJ_TYPE_METADATA
, &metadata_i_info
);
192 sj_metadata_musicbrainz_new (void)
194 return g_object_new (sj_metadata_musicbrainz_get_type (), NULL
);
201 #define BYTES_PER_SECTOR 2352
202 #define BYTES_PER_SECOND (44100 / 8) / 16 / 2
205 get_duration_from_sectors (int sectors
)
207 return (sectors
* BYTES_PER_SECTOR
/ BYTES_PER_SECOND
);
211 get_offline_track_listing(SjMetadata
*metadata
, GError
**error
)
213 SjMetadataMusicbrainzPrivate
*priv
;
219 g_return_val_if_fail (metadata
!= NULL
, NULL
);
220 priv
= SJ_METADATA_MUSICBRAINZ (metadata
)->priv
;
222 if (!mb_Query (priv
->mb
, MBQ_GetCDTOC
)) {
224 mb_GetQueryError (priv
->mb
, message
, 255);
226 SJ_ERROR
, SJ_ERROR_CD_LOOKUP_ERROR
,
227 _("Cannot read CD: %s"), message
);
231 num_tracks
= mb_GetResultInt (priv
->mb
, MBE_TOCGetLastTrack
);
233 album
= g_new0 (AlbumDetails
, 1);
234 album
->artist
= g_strdup (_("Unknown Artist"));
235 album
->title
= g_strdup (_("Unknown Title"));
237 for (i
= 1; i
<= num_tracks
; i
++) {
238 track
= g_new0 (TrackDetails
, 1);
239 track
->album
= album
;
241 track
->title
= g_strdup_printf (_("Track %d"), i
);
242 track
->artist
= g_strdup (album
->artist
);
243 track
->duration
= get_duration_from_sectors (mb_GetResultInt1 (priv
->mb
, MBE_TOCGetTrackNumSectors
, i
+1));
244 album
->tracks
= g_list_append (album
->tracks
, track
);
247 return g_list_append (list
, album
);
251 fire_signal_idle (SjMetadataMusicbrainz
*m
)
253 g_return_val_if_fail (SJ_IS_METADATA_MUSICBRAINZ (m
), FALSE
);
254 g_signal_emit_by_name (G_OBJECT (m
), "metadata", m
->priv
->albums
, m
->priv
->error
);
263 mb_get_new_error (SjMetadata
*metadata
)
265 GError
*error
= NULL
;
266 if (metadata
== NULL
|| SJ_METADATA_MUSICBRAINZ (metadata
)->priv
== NULL
) {
268 SJ_ERROR
, SJ_ERROR_INTERNAL_ERROR
,
269 _("MusicBrainz metadata object is not valid. This is bad, check your console for errors."));
272 return SJ_METADATA_MUSICBRAINZ (metadata
)->priv
->construct_error
;
276 mb_set_cdrom (SjMetadata
*metadata
, const char* device
)
278 SjMetadataMusicbrainzPrivate
*priv
;
279 g_return_if_fail (metadata
!= NULL
);
280 g_return_if_fail (device
!= NULL
);
281 priv
= SJ_METADATA_MUSICBRAINZ (metadata
)->priv
;
284 g_free (priv
->cdrom
);
286 priv
->cdrom
= g_strdup (device
);
287 mb_SetDevice (priv
->mb
, priv
->cdrom
);
291 mb_set_proxy (SjMetadata
*metadata
, const char* proxy
)
293 SjMetadataMusicbrainzPrivate
*priv
;
294 g_return_if_fail (metadata
!= NULL
);
295 priv
= SJ_METADATA_MUSICBRAINZ (metadata
)->priv
;
300 if (priv
->http_proxy
) {
301 g_free (priv
->http_proxy
);
303 priv
->http_proxy
= g_strdup (proxy
);
304 mb_SetProxy (priv
->mb
, priv
->http_proxy
, priv
->http_proxy_port
);
308 mb_set_proxy_port (SjMetadata
*metadata
, const int port
)
310 SjMetadataMusicbrainzPrivate
*priv
;
311 g_return_if_fail (metadata
!= NULL
);
312 priv
= SJ_METADATA_MUSICBRAINZ (metadata
)->priv
;
314 priv
->http_proxy_port
= port
;
315 mb_SetProxy (priv
->mb
, priv
->http_proxy
, priv
->http_proxy_port
);
318 /* Data imported from FreeDB is horrendeous for compilations,
319 * Try to split the 'Various' artist */
321 artist_and_title_from_title (TrackDetails
*track
, gpointer data
)
323 char *slash
, **split
;
325 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') {
326 track
->title
= g_strdup (data
);
330 slash
= strstr (data
, " / ");
332 track
->title
= g_strdup (data
);
335 split
= g_strsplit (data
, " / ", 2);
336 track
->artist
= g_strdup (split
[0]);
337 track
->title
= g_strdup (split
[1]);
343 * Write the RDF in the MusicBrainz object to the file specified.
346 cache_rdf (musicbrainz_t mb
, const char *filename
)
348 GError
*error
= NULL
;
352 g_assert (mb
!= NULL
);
353 g_assert (filename
!= NULL
);
355 /* Create the folder for the file */
356 path
= g_path_get_dirname (filename
);
357 g_mkdir_with_parents (path
, 0755); /* Handle errors in set_contents() */
360 /* How much data is there to save? */
361 len
= mb_GetResultRDFLen (mb
);
362 rdf
= g_malloc0 (len
);
364 /* Get the RDF and save it */
365 mb_GetResultRDF (mb
, rdf
, len
);
366 if (!g_file_set_contents (filename
, rdf
, len
, &error
)) {
367 g_warning ("Cannot write cache file %s: %s", filename
, error
->message
);
368 g_error_free (error
);
375 * Load into the MusicBrainz object the RDF from the specified cache file if it
376 * exists and is valid then return TRUE, otherwise return FALSE.
379 get_cached_rdf (musicbrainz_t mb
, const char *cachepath
)
381 gboolean ret
= FALSE
;
382 GError
*error
= NULL
;
385 g_assert (mb
!= NULL
);
386 g_assert (cachepath
!= NULL
);
388 if (!g_file_test (cachepath
, G_FILE_TEST_EXISTS
| G_FILE_TEST_IS_REGULAR
))
391 /* Cache file exists, open it */
392 if (!g_file_get_contents (cachepath
, &rdf
, NULL
, &error
)) {
393 g_warning ("Cannot open cache file %s: %s", cachepath
, error
->message
);
394 g_error_free (error
);
399 if (mb_SetResultRDF (mb
, rdf
))
408 get_cached_rdf (musicbrainz_t mb
, const char *cachepath
)
413 cache_rdf (musicbrainz_t mb
, const char *filename
) {
418 * Fill the MusicBrainz object with RDF. Basically get the CD Index and check
419 * the local cache, if that fails then lookup the data online.
422 get_rdf (SjMetadata
*metadata
)
424 SjMetadataMusicbrainzPrivate
*priv
;
426 char *cdindex
= NULL
, *cachepath
= NULL
;
428 g_assert (metadata
!= NULL
);
430 priv
= SJ_METADATA_MUSICBRAINZ (metadata
)->priv
;
433 /* Get the Table of Contents */
434 if (!mb_Query (priv
->mb
, MBQ_GetCDTOC
)) {
435 mb_GetQueryError (priv
->mb
, data
, sizeof (data
));
436 g_print (_("This CD could not be queried: %s\n"), data
);
440 /* Extract the CD Index */
441 if (!mb_GetResultData(priv
->mb
, MBE_TOCGetCDIndexId
, data
, sizeof (data
))) {
442 mb_GetQueryError (priv
->mb
, data
, sizeof (data
));
443 g_print (_("This CD could not be queried: %s\n"), data
);
446 cdindex
= g_strdup (data
);
448 cachepath
= g_build_filename (g_get_home_dir (), ".gnome2", "sound-juicer", "cache", cdindex
, NULL
);
451 if (!get_cached_rdf (priv
->mb
, cachepath
)) {
452 /* Don't re-use the CD Index as that doesn't send enough data to the server.
453 By doing this we also pass track lengths, which can be proxied to FreeDB
455 if (!mb_Query (priv
->mb
, MBQ_GetCDInfo
)) {
456 mb_GetQueryError (priv
->mb
, data
, sizeof (data
));
457 g_print (_("This CD could not be queried: %s\n"), data
);
460 cache_rdf (priv
->mb
, cachepath
);
469 lookup_cd (SjMetadata
*metadata
)
471 /** The size of the buffer used in MusicBrainz lookups */
472 SjMetadataMusicbrainzPrivate
*priv
;
473 GList
*albums
= NULL
;
476 int num_albums
, i
, j
;
477 NautilusBurnMediaType type
;
478 NautilusBurnDriveMonitor
*monitor
;
479 NautilusBurnDrive
*drive
;
481 /* TODO: fire error signal */
482 g_return_val_if_fail (metadata
!= NULL
, NULL
);
483 g_return_val_if_fail (SJ_IS_METADATA_MUSICBRAINZ (metadata
), NULL
);
484 priv
= SJ_METADATA_MUSICBRAINZ (metadata
)->priv
;
485 g_return_val_if_fail (priv
->cdrom
!= NULL
, NULL
);
486 priv
->error
= NULL
; /* TODO: hack */
488 if (! nautilus_burn_initialized ()) {
489 nautilus_burn_init ();
491 monitor
= nautilus_burn_get_drive_monitor ();
492 drive
= nautilus_burn_drive_monitor_get_drive_for_device (monitor
, priv
->cdrom
);
496 type
= nautilus_burn_drive_get_media_type (drive
);
497 nautilus_burn_drive_unref (drive
);
499 if (type
== NAUTILUS_BURN_MEDIA_TYPE_ERROR
) {
503 if (access (priv
->cdrom
, W_OK
) == 0) {
504 msg
= g_strdup_printf (_("Device '%s' does not contain any media"), priv
->cdrom
);
505 err
= SJ_ERROR_CD_NO_MEDIA
;
507 msg
= g_strdup_printf (_("Device '%s' could not be opened. Check the access permissions on the device."), priv
->cdrom
);
508 err
= SJ_ERROR_CD_PERMISSION_ERROR
;
510 priv
->error
= g_error_new (SJ_ERROR
, err
, _("Cannot read CD: %s"), msg
);
514 g_idle_add ((GSourceFunc
)fire_signal_idle
, metadata
);
520 num_albums
= mb_GetResultInt(priv
->mb
, MBE_GetNumAlbums
);
521 if (num_albums
< 1) {
522 priv
->albums
= get_offline_track_listing (metadata
, &(priv
->error
));
523 g_idle_add ((GSourceFunc
)fire_signal_idle
, metadata
);
527 for (i
= 1; i
<= num_albums
; i
++) {
531 mb_Select1(priv
->mb
, MBS_SelectAlbum
, i
);
532 album
= g_new0 (AlbumDetails
, 1);
534 if (mb_GetResultData(priv
->mb
, MBE_AlbumGetAlbumId
, data
, sizeof (data
))) {
535 mb_GetIDFromURL (priv
->mb
, data
, data
, sizeof (data
));
536 album
->album_id
= g_strdup (data
);
539 if (mb_GetResultData (priv
->mb
, MBE_AlbumGetAlbumArtistId
, data
, sizeof (data
))) {
540 mb_GetIDFromURL (priv
->mb
, data
, data
, sizeof (data
));
541 album
->artist_id
= g_strdup (data
);
542 if (g_ascii_strncasecmp (MBI_VARIOUS_ARTIST_ID
, data
, 64) == 0) {
543 album
->artist
= g_strdup (_("Various"));
545 if (*data
&& mb_GetResultData1(priv
->mb
, MBE_AlbumGetArtistName
, data
, sizeof (data
), 1)) {
546 album
->artist
= g_strdup (data
);
548 album
->artist
= g_strdup (_("Unknown Artist"));
550 if (*data
&& mb_GetResultData1(priv
->mb
, MBE_AlbumGetArtistSortName
, data
, sizeof (data
), 1)) {
551 album
->artist_sortname
= g_strdup (data
);
556 if (mb_GetResultData(priv
->mb
, MBE_AlbumGetAlbumName
, data
, sizeof (data
))) {
557 album
->title
= g_strdup (data
);
559 album
->title
= g_strdup (_("Unknown Title"));
564 num_releases
= mb_GetResultInt (priv
->mb
, MBE_AlbumGetNumReleaseDates
);
565 if (num_releases
> 0) {
566 mb_Select1(priv
->mb
, MBS_SelectReleaseDate
, 1);
567 if (mb_GetResultData(priv
->mb
, MBE_ReleaseGetDate
, data
, sizeof (data
))) {
568 int matched
, year
=1, month
=1, day
=1;
569 matched
= sscanf(data
, "%u-%u-%u", &year
, &month
, &day
);
571 album
->release_date
= g_date_new_dmy ((day
== 0) ? 1 : day
, (month
== 0) ? 1 : month
, year
);
574 mb_Select(priv
->mb
, MBS_Back
);
578 num_tracks
= mb_GetResultInt(priv
->mb
, MBE_AlbumGetNumTracks
);
579 if (num_tracks
< 1) {
580 g_free (album
->artist
);
581 g_free (album
->artist_sortname
);
582 g_free (album
->title
);
584 g_warning (_("Incomplete metadata for this CD"));
585 priv
->albums
= get_offline_track_listing (metadata
, &(priv
->error
));
586 g_idle_add ((GSourceFunc
)fire_signal_idle
, metadata
);
590 for (j
= 1; j
<= num_tracks
; j
++) {
592 track
= g_new0 (TrackDetails
, 1);
594 track
->album
= album
;
596 track
->number
= j
; /* replace with number lookup? */
598 if (mb_GetResultData1(priv
->mb
, MBE_AlbumGetTrackId
, data
, sizeof (data
), j
)) {
599 mb_GetIDFromURL (priv
->mb
, data
, data
, sizeof (data
));
600 track
->track_id
= g_strdup (data
);
603 if (mb_GetResultData1(priv
->mb
, MBE_AlbumGetArtistId
, data
, sizeof (data
), j
)) {
604 mb_GetIDFromURL (priv
->mb
, data
, data
, sizeof (data
));
605 track
->artist_id
= g_strdup (data
);
608 if (mb_GetResultData1(priv
->mb
, MBE_AlbumGetTrackName
, data
, sizeof (data
), j
)) {
609 if (track
->artist_id
!= NULL
) {
610 artist_and_title_from_title (track
, data
);
612 track
->title
= g_strdup (data
);
616 if (track
->artist
== NULL
&& mb_GetResultData1(priv
->mb
, MBE_AlbumGetArtistName
, data
, sizeof (data
), j
)) {
617 track
->artist
= g_strdup (data
);
620 if (mb_GetResultData1(priv
->mb
, MBE_AlbumGetArtistSortName
, data
, sizeof (data
), j
)) {
621 track
->artist_sortname
= g_strdup (data
);
624 if (mb_GetResultData1(priv
->mb
, MBE_AlbumGetTrackDuration
, data
, sizeof (data
), j
)) {
625 track
->duration
= atoi (data
) / 1000;
628 album
->tracks
= g_list_append (album
->tracks
, track
);
632 albums
= g_list_append (albums
, album
);
634 mb_Select (priv
->mb
, MBS_Rewind
);
637 /* For each album, we need to insert the duration data if necessary
638 * We need to query this here because otherwise we would flush the
639 * data queried from the server */
640 /* TODO: scan for 0 duration before doing the query to avoid another lookup if
641 we don't need to do it */
642 mb_Query (priv
->mb
, MBQ_GetCDTOC
);
643 for (al
= albums
; al
; al
= al
->next
) {
644 AlbumDetails
*album
= al
->data
;
647 for (tl
= album
->tracks
; tl
; tl
= tl
->next
) {
648 TrackDetails
*track
= tl
->data
;
651 if (track
->duration
== 0) {
652 sectors
= mb_GetResultInt1 (priv
->mb
, MBE_TOCGetTrackNumSectors
, j
+1);
653 track
->duration
= get_duration_from_sectors (sectors
);
659 priv
->albums
= albums
;
660 g_idle_add ((GSourceFunc
)fire_signal_idle
, metadata
);
665 mb_list_albums (SjMetadata
*metadata
, GError
**error
)
669 g_return_if_fail (SJ_IS_METADATA_MUSICBRAINZ (metadata
));
671 thread
= g_thread_create ((GThreadFunc
)lookup_cd
, metadata
, TRUE
, error
);
672 if (thread
== NULL
) {
674 SJ_ERROR
, SJ_ERROR_INTERNAL_ERROR
,
675 _("Could not create CD lookup thread"));
681 mb_get_submit_url (SjMetadata
*metadata
)
683 SjMetadataMusicbrainzPrivate
*priv
;
686 g_return_val_if_fail (metadata
!= NULL
, NULL
);
688 priv
= SJ_METADATA_MUSICBRAINZ (metadata
)->priv
;
690 if (mb_GetWebSubmitURL(priv
->mb
, url
, 1024)) {
691 return g_strdup(url
);