1 Introduction 

This tutorial describes the steps that you, as a developer, have to take when using libcddb. It starts with the creation and configuration of the CDDB connection. Afterwards it explains how you can retrieve data from the server and even submit new entries. Finally, it shows you what to do if you do not need the connection or any related in-memory structures anymore.

Throughout this page there are a lot of direct links to the libcddb API reference. In the tutorial text they are easily located because of their different color. But similar links are also present in the example source code. Every libcddb function, structure and definition in those code snippets will link directly to the corresponding API page.

If you have any questions or remarks about this tutorial, if something is not completely clear, if you find any errors or broken links, do not hesitate to contact me so I can clarify and/or correct the issue. My coordinates can be found on Contact Info page.

2 Coding suggestions 

No, I'm not going to bother you with where you place your braces and stuff like that. That's not my problem.  :-)  But what I would like to suggest is that whenever you have to retrieve something from a libcddb data structure or when you have to change something in such a structure, that you use the provided get and set functions for that field. This is good OO practice and it will make your life easier if and when I decide to change the implementation of one of those structures. If you want to access something for which there is no getter and or setter, then just report it to me and I'll add it.

Another thing I would like to mention is that you only have to include one header file to use libcddb. The other header files depend on each other and have to be included in the correct order for it to work. So, the following statement is enough:

#include <cddb/cddb.h>
3 Working with tracks and discs 

Before we start with the sections that explain how you can access a CDDB server, we have to explain two important libcddb data structures: the track and the disc. These structures are used as parameters and return values in the functions that access the server. The following two section describe how you can create, use and destroy these tracks and discs.

3.1 Tracks 

Creation and destruction of a track can be done as shown below.

cddb_track_t *track = NULL;

/* create */
track = cddb_track_new();
if (track == NULL) {
    fprintf(stderr, "out of memory, unable to create track");
}

/* ... use ... */

/* destroy */
cddb_track_destroy(track);

Next to the two functions used in this example, there is another way to create a new track structure. An existing track can be duplicated with cddb_track_clone. This function will make an exact copy of the existing track, reserving new blocks of memory along the way. It is used internally by libcddb but can also be useful for developers in need of a duplicate of a certain track.

A similar function is cddb_track_copy. It requires two existing track structures and will copy data from one to the other. Note that a field that is not present in the source track will not cause a reset of the corresponding field in the destination track.

All the other track functions are getters and setters for the data fields of the track. In this section a description of those fields is given. Examples of how to use these will be shown in code snippets throughout the next sections.

You might wonder how you could retrieve or calculate the frame offsets for the tracks of a CD you possess. I already tried two solutions myself. The first one is to use the command-line tool cdparanoia. The example below shows how you can query a CD in your CD-ROM drive.

$ cdparanoia -Q
cdparanoia III release 9.8 (March 23, 2001)
(C) 2001 Monty <monty@xiph.org> and Xiphophorus
 
Report bugs to paranoia@xiph.org
http://www.xiph.org/paranoia/
 
 
 
Table of contents (audio tracks only):
track        length               begin        copy pre ch
===========================================================
  1.     2334 [00:31.09]        0 [00:00.00]    OK   no  2
  2.    19054 [04:14.04]     2334 [00:31.09]    OK   no  2
 ...
 15.    16432 [03:39.07]   250987 [55:46.37]    OK   no  2
 16.    15924 [03:32.24]   267419 [59:25.44]    OK   no  2
TOTAL  283343 [62:57.68]    (audio only)

From the begin column you can derive the frame offset of the tracks. The only thing you have to do with these values is add two seconds of lead-in. You can use the libcddb SECONDS_TO_FRAMES macro to convert these two seconds to a frame count. This first solution is useful when you quickly want to try out some stuff.

The second solution is to use libcdio, a library for accessing and controlling a CD-ROM device (see links page for coordinates). The example program supplied with libcddb uses this library, if installed, to access the CD-ROM. The following code snippet shows how to retrieve the frame offset with this library. For the complete code check out the cd_access.c file in the libcddb examples directory.

CdIo_t *cdio = (NULL, DRIVER_DEVICE);/* libcdio CD access structure */
track_t t;                           /* libcdio track number */
lba_t lba;                           /* libcdio Logical Block Address */
int frame_offset;                    /* libcddb frame offset */

t = ...                              /* initialize track t */
lba = cdio_get_track_lba(cdio, t);
if (lba == CDIO_INVALID_LBA) {
    libcdio_error_exit("track %d has invalid Logical Block Address", t);
}

The use of this library is probably more elegant than the use of an external program. Especially if you are working on some code that is also going to be used by other people.

Note: the libcdio_error_exit function is not part of the libcdio library, but a macro defined in the example code.

3.2 Discs 

The life time of a disc is started and ended with similar functions as that of a track (see example below).

cddb_disc_t *disc = NULL;

/* create */
disc = cddb_disc_new();
if (disc == NULL) {
    fprintf(stderr, "out of memory, unable to create disc");
}

/* ... use ... */

/* destroy */
cddb_disc_destroy(disc);

As with tracks, it is also possible to duplicate or copy a disc using either cddb_disc_clone or cddb_disc_copy. The clone function will make a brand new copy of the disc and all of its tracks. The copy function will copy data from the source disc to the destination disc. Existing data fields and tracks will be overwritten with the new data and tracks. If the source disc has more tracks than the destination disc, then new tracks will be added.

3.3 Tracks on discs 

Now that you have seen tracks and discs, let's combine them and put some tracks in a disc structure. With the current API it is only possible to append a new track to the end of an existing track list. On the other hand, there are two methods to iterate over the tracks on a disc. The following example illustrates this.

cddb_disc_t *disc;
cddb_track_t *track, *track1, *track2, *track3;
int i, cnt;

/* ... initialize disc and tracks ... */

/* add some tracks */
cddb_disc_add_track(disc, track1);
cddb_disc_add_track(disc, track2);
cddb_disc_add_track(disc, track3);

/* iteration method 1 */
cnt = cddb_disc_get_track_count(disc);
for (t = 0; t < cnt; t++) {
    track = cddb_disc_get_track(disc, t);
    if (track != NULL) {

        /* ... use track ... */

    }
}

/* iteration method 2 */
track = cddb_disc_get_track_first(disc);
while (track != NULL) {

    /* ... use track ...  */

    track = cddb_disc_get_track_next(disc);
}

When using the first iteration method you should know that the cddb_disc_get_track function starts counting at zero. Also note that the track number, that you get when calling cddb_track_get_number, starts at one. The cddb_disc_get_track function is probably more useful when you already know the number of the track that you want to retrieve. It will return the actual track if the number is valid, or NULL when it is not.

For the second method we first reset the internal iteration pointer with cddb_disc_get_track_first. The function also returns the first track. Any subsequent tracks can be retrieved with the cddb_disc_get_track_next function. This function returns the next track in the list or NULL if there are no more tracks left.

4 Connection set up 

The first thing to do when you want to start using any of the libcddb functionality is to create a connection structure.

cddb_conn_t *conn = NULL;

conn = cddb_new();
if (conn == NULL) {
    fprintf(stderr, "out of memory, "
                    "unable to create connection structure");
}

The cddb_new function will return a new cddb_conn_t structure. This structure keeps the state of the current connection and will have to be passed to most of the other libcddb functions. If something goes wrong during the creation of this structure, NULL will be returned to signal that error.

The structure that is returned will have a set of default connection settings. If you use this connection without changing any of these settings:

These default values can be changed after you have created the structure. The following sections explain how to do this.

4.1 Server settings 

The first thing to do when you want to change the server settings, is decide how you want to contact the server. The list below describes the three options.

The example below shows the steps you have to take when you want to change the server settings for the options described above. Of course you only have to make the changes for the option you choose. The example also specifies which settings are required with the /* REQ */ comment at the end of those lines. The values that are set for the optional fields are also the hard coded defaults used by libcddb.

/* CDDBP settings */
cddb_set_server_name(conn, "freedb.org");
cddb_set_server_port(conn, 888);

/* HTTP settings */
cddb_http_enable(conn);                                /* REQ */
cddb_set_server_port(conn, 80);                        /* REQ */
cddb_set_server_name(conn, "freedb.org");
cddb_set_http_path_query(conn, "/~cddb/cddb.cgi");
cddb_set_http_path_submit(conn, "/~cddb/submit.cgi");

/* HTTP proxy settings */
cddb_http_proxy_enable(conn);                          /* REQ */
cddb_set_http_proxy_server_name(conn, "my.proxy.com"); /* REQ */
cddb_set_http_proxy_server_port(conn, 8080);           /* REQ */
cddb_set_server_port(conn, 80);                        /* REQ */
cddb_set_server_name(conn, "freedb.org");
cddb_set_http_path_query(conn, "/~cddb/cddb.cgi");
cddb_set_http_path_submit(conn, "/~cddb/submit.cgi");
/* option 1: stores cleartext credentials */
cddb_set_http_proxy_username(conn, "myuser");
cddb_set_http_proxy_password(conn, "mypassword");
/* option 2: does not store cleartext credentials */
cddb_set_http_proxy_credentials(conn, "myuser", "mypassword");

As shown in the example the actual location of the CDDB server (not proxy server) can be changed with calls to cddb_set_server_name and cddb_set_server_port. That first function accepts both DNS names and IP addresses.

Note that when you enable HTTP, you have to correctly initialize the server port. The cddb_http_enable function will not automatically set the server port to 80. When using an HTTP proxy server you have to make sure that you correctly initialize both the address of the CDDB server and that of the proxy server as shown in the example.

All the set functions shown above also have a get counterpart. You can consult the API reference for the correct syntax.

4.2 Cache settings 

As stated above, the default libcddb cache directory is $HOME/.cddbslave. You can change this directory with the cddb_cache_set_dir function. It will copy the string that is passed to it, so any memory used by that string can be freed after the function returns. This function will also expand a '~' as the first character to the contents of the $HOME environment variable. If you want to check what the current cache directory is, use the cddb_cache_get_dir function. It returns a reference to a field inside the connection structure. You should not alter this string directly but use the set-function.

Caching can be used in three different modes:

If you want to check what the current caching mode is, you should us cddb_cache_mode. This function will return one of three values: CACHE_ON, CACHE_ONLY or CACHE_OFF. Each of these values maps to the corresponding mode described above.

4.3 Miscellaneous settings 

When contacting a CDDB server the protocol requires you to send a user and host name before asking any data from the server. As described above, libcddb will use 'anonymous' and 'localhost' as the defaults. These two values are also used when submitting new disc entries to the server. They will be concatenated to form the required e-mail address (user@hostname). This has to be a valid e-mail address for the submit to work correctly. It is not possible to submit a disc entry when the default values have not been changed. You can change both these fields in one go with cddb_set_email_address.

Another set of values that are also required by the protocol are the client name and version. By default these are set to 'libcddb' and the version number of libcddb that you are using. It is however possible to change these values to the name and version of the program that is using the library. Use the cddb_set_client function to change these client settings.

Finally, the libcddb networking code supports time outs. This means that any function that connects to, reads from and writes to a remote CDDB server, will not block indefinitely when there is a network problem. By default any of these actions will time-out after ten seconds and return with an error status. You can change this time-out interval with the cddb_set_timeout function.

5 Querying the database 

Unless you know the exact disc ID and category of a certain disc--in which case you can skip to the section about reading disc details--you will first have to query the database to see whether there are any matches for your disc. A CDDB query can return

Each match that is found will contain the disc ID and disc category for that matching disc. With these two fields a CDDB read command can be executed to get the other disc data. This is discussed below.

To perform a query operation you will need a disc structure. The disc length has to be initialized in this structure and the tracks within need to have their frame offset set. An illustration follows.

static int disc_length = 3822;
static int frame_offsets[11] = {
    150, 28690, 51102, 75910, 102682, 121522,
    149040, 175772, 204387, 231145, 268065
};

cddb_disc_t *disc = NULL;
cddb_track_t *track = NULL;
int i, matches;

/* (1) initialize disc and tracks */
disc = cddb_disc_new();
if (disc == NULL) {
    fprintf(stderr, "out of memory, unable to create disc");
    exit(-1);
}
cddb_disc_set_length(disc, disc_length);
for (i = 0; i < 11; i++) {
    track = cddb_track_new();
    if (track == NULL) {
        fprintf(stderr, "out of memory, unable to create track");
        exit(-1);
    }
    cddb_track_set_frame_offset(track, frame_offsets[i]);
    cddb_disc_add_track(disc, track);
}

/* (2) execute query command */
matches = cddb_query(conn, disc);
if (matches == -1) {
    /* something went wrong, print error */
    cddb_error_print(cddb_errno(conn));
    exit(-1);
}

while (matches > 0) {

    /* (3) ... use disc ... */

    /* (4) get next query result if there is one left */
    matches--;
    if (matches > 0) {
        if (!cddb_query_next(conn, disc)) {
            fprintf(stderr, "query index out of bounds");
            exit(-1);
        }
    }
}

The example first creates a disc structure, initializes the total length of the disc to 3822 seconds and adds eleven tracks to it (1). Then it calls the cddb_query function (2). This function will recalculate the disc ID of the disc provided to it and then query the local cache or the remote server for the requested information. The first disc in the query can now be processed (3). We decrement the match counter and if it has not yet reached zero, we retrieve the next match with cddb_query_next (4).

6 Searching the database 

An alternative to querying is searching. If you do not know the frame offsets of the tracks on the disc then querying as described above is not possible. In this case you can still search for disc data using a text string. The results returned by the search command are similar to those of the query command.

static char *search_str = "mezzanine";

cddb_disc_t *disc = NULL;
int i, matches;

/* (1) initialize disc */
disc = cddb_disc_new();
if (disc == NULL) {
    fprintf(stderr, "out of memory, unable to create disc");
    exit(-1);
}

/* (2) execute search command */
matches = cddb_query(conn, disc, search_str);
if (matches == -1) {
    /* something went wrong, print error */
    cddb_error_print(cddb_errno(conn));
    exit(-1);
}

while (matches > 0) {

    /* (3) ... use disc ... */

    /* (4) get next search result if there is one left */
    matches--;
    if (matches > 0) {
        if (!cddb_search_next(conn, disc)) {
            fprintf(stderr, "search index out of bounds");
            exit(-1);
        }
    }
}

Again the example code first creates a disc structure in which the results will be returned (1). Then it calls the cddb_search function (2). This function will perform a text search on the freedb.org web server. The first disc in the query can now be processed (3). We decrement the match counter and if it has not yet reached zero, we retrieve the next match with cddb_search_next (4).

By default the text search command will only consider the artist name field and the disc title field when searching. It will search in every category for the specified string. You can fine-tune this behaviour as shown in the example below.

unsigned int fields = SEARCH_ALL;
unsigned int fields = SEARCH_ARTIST | SEARCH_TRACK;

cddb_search_set_fields(conn, fields);

unsigned int cats = SEARCH_ALL;
unsigned int cats = SEARCHCAT(CDDB_CAT_ROCK) | SEARCHCAT(CDDB_CAT_MISC);

cddb_search_set_categories(conn, cats);

For the configuration of the fields any of the cddb_search_t values can be used. When specifying which categories to search you can use the SEARCH_ALL value or you can specify individual categories. When specifying individual cddb_cat_t categories you have to use the SEARCHCAT macro as shown in the example.

NOTE: The query and search commands should not be mixed. They both use the same internal list for storing their results. When starting a new query or search, this list is flushed.

7 Reading disc details 

Once you know the category and disc ID of a certain CD, you are ready to retrieve the rest of the data from the server (or cache).

cddb_disc_t *disc = NULL;
int success;

/* (1) initialize disc */
disc = cddb_disc_new();
if (disc == NULL) {
    fprintf(stderr, "out of memory, unable to create disc");
    exit(-1);
}
cddb_disc_set_category_str(disc, "misc");
cddb_disc_set_discid(disc, 0x920ef00b);

/* (2) execute read command */
success = cddb_read(conn, disc);
if (!success) {
    /* something went wrong, print error */
    cddb_error_print(cddb_errno(conn));
    exit(-1);
}

/* (3) ... use disc ... */

After we have created the disc structure we initialize the two parameters that are required by the read command: the category and the disc ID. An alternative method for setting the category is to use the cddb_disc_set_category function with CDDB_CAT_MISC as the second parameter. The only thing left to be done, is calling the cddb_read function. This function will return true on success and false on failure.

8 Cleaning up 

When you do not need a libcddb connection anymore, you should call the cddb_destroy function. This function will properly disconnect from the server (if needed) and free all resources that were being used by the connection.

As already mentioned in the track and disc section of this tutorial you should call cddb_track_destroy and cddb_disc_destroy for freeing the memory used by those structures.

If you are completely done with all libcddb functionality you should also call the libcddb_shutdown function. This function will free up any global resources that are being shared by all connections.

9 Miscellaneous 

This section talks about some details that do not fit in any of the section above, but nevertheless they are important enough to be mentioned

9.1 Error handling 

As with all programs, with libcddb it is also possible that something goes wrong. The connection structure also keeps information about the result of the last function that has been executed. If this functioned completed successfully, then no other actions have to be taken. On the other hand most functions signal an error condition when something goes wrong. To determine whether a return value corresponds to an error you will have to consult the API documentation for that function.

If something went wrong, then you can retrieve the error code with the cddb_errno function. This function will return one of the values defined by the cddb_error_t enumeration. Some other error handling functions are provided to turn this error code into a more descriptive error message. As a start you can use cddb_error_str to retrieve a string that describes the error condition for a given error code. Alternatively, it is also possible to directly print that string to stderr or any other stream with cddb_error_print and cddb_error stream_print respectively.

9.2 Logging 

Libcddb has a logging framework that can be customized to your needs. Any log message generated by the library functions can be categorized into one of five levels: debug, informational, warning, error and critical (in order of significance).

The default log handler will print out any log messages to the standard error stream if it has a log level equal or higher than a certain minimum level. By default this minimum is set to warning, but it can be changed with the cddb_log_set_level function. This function accepts any of the log levels from the cddb_log_level_t enumeration. One of these levels is CDDB_LOG_NONE, which can be used to disable the printing of any log messages.

You can change the library's default minimum log level by providing the --enable-loglevel parameter to the configure script when compiling libcddb.

If you want to do something else with the log messages instead of printing them to stderr, then you can create your own log handler and set it with cddb_log_set_handler. The signature of the handler function is defined by the cddb_log_handler_t type. The example below shows how you can write a log handler that writes its messages to a file.

FILE *_logfile;

void log_to_file(cddb_log_level_t level, const char *message)
{
    fprintf(_logfile, "(%d) %s", level, message);
    fflush(_logfile);
}
Last modified: Mon Apr 6 23:31:59 UTC 2009