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.
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>
38 #include <nautilus-burn-drive.h>
39 #ifndef NAUTILUS_BURN_CHECK_VERSION
40 #define NAUTILUS_BURN_CHECK_VERSION(a,b,c) FALSE
43 #if NAUTILUS_BURN_CHECK_VERSION(2,15,3)
44 #include <nautilus-burn.h>
47 #include "sj-metadata-musicbrainz.h"
48 #include "sj-structures.h"
51 struct SjMetadataMusicbrainzPrivate
{
52 GError
*construct_error
;
57 /* TODO: remove and use an async queue or something l33t */
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"
81 static GObjectClass
*parent_class
= NULL
;
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
);
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"));
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
);
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
));
135 g_warning ("mb_SetProxyCreds() not found, no proxy authorisation possible.");
140 g_object_unref (gconf_client
);
142 if (g_getenv ("MUSICBRAINZ_DEBUG")) {
143 mb_SetDebug (self
->priv
->mb
, TRUE
);
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
;
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
;
169 sj_metadata_musicbrainz_get_type (void)
171 static GType type
= 0;
173 static const GTypeInfo info
= {
174 sizeof (SjMetadataMusicbrainzClass
),
177 (GClassInitFunc
)sj_metadata_musicbrainz_class_init
,
180 sizeof (SjMetadataMusicbrainz
),
182 sj_metadata_musicbrainz_instance_init
,
185 static const GInterfaceInfo metadata_i_info
= {
186 (GInterfaceInitFunc
) metadata_interface_init
,
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
);
196 sj_metadata_musicbrainz_new (void)
198 return g_object_new (sj_metadata_musicbrainz_get_type (), NULL
);
205 #define BYTES_PER_SECTOR 2352
206 #define BYTES_PER_SECOND (44100 / 8) / 16 / 2
209 get_duration_from_sectors (int sectors
)
211 return (sectors
* BYTES_PER_SECTOR
/ BYTES_PER_SECOND
);
215 get_offline_track_listing(SjMetadata
*metadata
, GError
**error
)
217 SjMetadataMusicbrainzPrivate
*priv
;
223 g_return_val_if_fail (metadata
!= NULL
, NULL
);
224 priv
= SJ_METADATA_MUSICBRAINZ (metadata
)->priv
;
226 if (!mb_Query (priv
->mb
, MBQ_GetCDTOC
)) {
228 mb_GetQueryError (priv
->mb
, message
, 255);
230 SJ_ERROR
, SJ_ERROR_CD_LOOKUP_ERROR
,
231 _("Cannot read CD: %s"), message
);
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"));
241 for (i
= 1; i
<= num_tracks
; i
++) {
242 track
= g_new0 (TrackDetails
, 1);
243 track
->album
= album
;
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
);
251 return g_list_append (list
, album
);
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
);
267 mb_get_new_error (SjMetadata
*metadata
)
269 GError
*error
= NULL
;
270 if (metadata
== NULL
|| SJ_METADATA_MUSICBRAINZ (metadata
)->priv
== NULL
) {
272 SJ_ERROR
, SJ_ERROR_INTERNAL_ERROR
,
273 _("MusicBrainz metadata object is not valid. This is bad, check your console for errors."));
276 return SJ_METADATA_MUSICBRAINZ (metadata
)->priv
->construct_error
;
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
;
288 g_free (priv
->cdrom
);
290 priv
->cdrom
= g_strdup (device
);
291 mb_SetDevice (priv
->mb
, priv
->cdrom
);
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
;
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
);
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 */
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
);
334 slash
= strstr (data
, " / ");
336 track
->title
= g_strdup (data
);
339 split
= g_strsplit (data
, " / ", 2);
340 track
->artist
= g_strdup (split
[0]);
341 track
->title
= g_strdup (split
[1]);
347 * Write the RDF in the MusicBrainz object to the file specified.
350 cache_rdf (musicbrainz_t mb
, const char *filename
)
352 GError
*error
= NULL
;
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() */
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
);
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.
383 get_cached_rdf (musicbrainz_t mb
, const char *cachepath
)
385 gboolean ret
= FALSE
;
386 GError
*error
= 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
))
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
);
403 if (mb_SetResultRDF (mb
, rdf
))
412 get_cached_rdf (musicbrainz_t mb
, const char *cachepath
)
417 cache_rdf (musicbrainz_t mb
, const char *filename
) {
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.
426 get_rdf (SjMetadata
*metadata
)
428 SjMetadataMusicbrainzPrivate
*priv
;
430 char *cdindex
= NULL
, *cachepath
= NULL
;
432 g_assert (metadata
!= NULL
);
434 priv
= SJ_METADATA_MUSICBRAINZ (metadata
)->priv
;
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
);
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
);
450 cdindex
= g_strdup (data
);
452 cachepath
= g_build_filename (g_get_home_dir (), ".gnome2", "sound-juicer", "cache", cdindex
, NULL
);
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
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
);
464 cache_rdf (priv
->mb
, cachepath
);
473 lookup_cd (SjMetadata
*metadata
)
475 /** The size of the buffer used in MusicBrainz lookups */
476 SjMetadataMusicbrainzPrivate
*priv
;
477 GList
*albums
= NULL
;
480 int num_albums
, i
, j
;
481 NautilusBurnMediaType type
;
482 #if NAUTILUS_BURN_CHECK_VERSION(2,15,3)
483 NautilusBurnDrive
*drive
;
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 (),
499 type
= nautilus_burn_drive_get_media_type (drive
);
500 nautilus_burn_drive_unref (drive
);
502 type
= nautilus_burn_drive_get_media_type_from_path (priv
->cdrom
);
505 if (type
== NAUTILUS_BURN_MEDIA_TYPE_ERROR
) {
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
;
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
);
520 g_idle_add ((GSourceFunc
)fire_signal_idle
, 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
);
533 for (i
= 1; i
<= num_albums
; i
++) {
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"));
551 if (data
&& mb_GetResultData1(priv
->mb
, MBE_AlbumGetArtistName
, data
, sizeof (data
), 1)) {
552 album
->artist
= g_strdup (data
);
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
);
565 album
->title
= g_strdup (_("Unknown Title"));
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
);
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
);
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
);
596 for (j
= 1; j
<= num_tracks
; j
++) {
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
);
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
);
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
;
653 for (tl
= album
->tracks
; tl
; tl
= tl
->next
) {
654 TrackDetails
*track
= tl
->data
;
657 if (track
->duration
== 0) {
658 sectors
= mb_GetResultInt1 (priv
->mb
, MBE_TOCGetTrackNumSectors
, j
+1);
659 track
->duration
= get_duration_from_sectors (sectors
);
665 priv
->albums
= albums
;
666 g_idle_add ((GSourceFunc
)fire_signal_idle
, metadata
);
671 mb_list_albums (SjMetadata
*metadata
, GError
**error
)
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
) {
680 SJ_ERROR
, SJ_ERROR_INTERNAL_ERROR
,
681 _("Could not create CD lookup thread"));
687 mb_get_submit_url (SjMetadata
*metadata
)
689 SjMetadataMusicbrainzPrivate
*priv
;
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
);