Blob Blame History Raw
/*
|  Copyright (C) 2009 Nikias Bassen <nikias@gmx.li>
|  Copyright (C) 2009 Christophe Fergeau <cfergeau@mandriva.com>
|  Copyright (C) 2009 Hector Martin <hector@marcansoft.com>
|
|  The code contained in this file is free software; you can redistribute
|  it and/or modify it under the terms of the GNU Lesser General Public
|  License as published by the Free Software Foundation; either version
|  2.1 of the License, or (at your option) any later version.
|
|  This file is distributed in the hope that it will be useful,
|  but WITHOUT ANY WARRANTY; without even the implied warranty of
|  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
|  Lesser General Public License for more details.
|
|  You should have received a copy of the GNU Lesser General Public
|  License along with this code; if not, write to the Free Software
|  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
|  USA
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdarg.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>

#include <glib.h>
#include <glib/gstdio.h>
#include <sqlite3.h>
#include <plist/plist.h>

#ifdef HAVE_LIBIMOBILEDEVICE
#include <libimobiledevice/libimobiledevice.h>
#include <libimobiledevice/lockdown.h>
#endif

#include "itdb.h"
#include "itdb_private.h"
#include "itdb_sqlite_queries.h"


/** time zone offset in seconds */
static uint32_t tzoffset = 0;

static uint32_t timeconv(time_t tv)
{
    /* quite strange. this is time_t - 01.01.2001 01:00:00 - tzoffset */
    return tv - 978307200 - tzoffset;
}

enum sort_order {
	ORDER_TITLE,
	ORDER_ARTIST,
	ORDER_ALBUM,
	ORDER_GENRE,
	ORDER_COMPOSER,
	ORDER_ALBUM_ARTIST,
	ORDER_ALBUM_BY_ARTIST,
	ORDER_SERIES_NAME,
	ORDER_END
};

static glong sort_offsets[][4] = {
    { G_STRUCT_OFFSET(Itdb_Track, sort_title),       G_STRUCT_OFFSET(Itdb_Track, title) },
    { G_STRUCT_OFFSET(Itdb_Track, sort_artist),      G_STRUCT_OFFSET(Itdb_Track, artist) },
    { G_STRUCT_OFFSET(Itdb_Track, sort_album),       G_STRUCT_OFFSET(Itdb_Track, album) },
    { G_STRUCT_OFFSET(Itdb_Track, genre),            0 },
    { G_STRUCT_OFFSET(Itdb_Track, sort_composer),    G_STRUCT_OFFSET(Itdb_Track, composer) },
    { G_STRUCT_OFFSET(Itdb_Track, sort_artist),      G_STRUCT_OFFSET(Itdb_Track, artist) },
    /* TODO: iTunes sorts first by album_artist, then by artist. */
    { G_STRUCT_OFFSET(Itdb_Track, sort_albumartist), G_STRUCT_OFFSET(Itdb_Track, albumartist),
      G_STRUCT_OFFSET(Itdb_Track, sort_artist),      G_STRUCT_OFFSET(Itdb_Track, artist) },
    { 0, 0 } /* TODO: Series Name: ignored for now. */
};

static gchar *sort_field(Itdb_Track *track, guint member)
{
    gchar *field;
    int offset;
    int i;

    for (i = 0; i < 4; i++) {
	offset = sort_offsets[member][i];
	field = G_STRUCT_MEMBER(gchar *, track, offset);
        if (offset != 0 && field != NULL && *field) {
            return field;
        }
    }
    return NULL;
}

static int lookup_order(GHashTable **order_hashes, guint member, Itdb_Track *track)
{
    const char *key;

    g_assert (member < ORDER_END);

    key = sort_field(track, member);
    if (key == NULL) {
        return 100;
    }

    return GPOINTER_TO_UINT(g_hash_table_lookup(order_hashes[member], key));
}

static gint compare_track_fields(gconstpointer lhs, gconstpointer rhs)
{
    const gchar *field_lhs = (const gchar *)lhs;
    const gchar *field_rhs = (const gchar *)rhs;

    if (field_lhs == NULL && field_rhs == NULL) {
        return 0;
    }

    if (field_lhs == NULL) {
        return -1;
    }

    if (field_rhs == NULL) {
        return 1;
    }

    /* FIXME: should probably generate collation keys first */
    return g_utf8_collate(field_lhs, field_rhs);
}

static gboolean traverse_tracks(gpointer key, gpointer value, gpointer data)
{
    GHashTable *hash = (GHashTable *)data;

    if (key != NULL) {
        g_hash_table_insert(hash, key, GUINT_TO_POINTER((g_hash_table_size(hash) + 1) * 100));
    }
    return FALSE;
}

static GHashTable **compute_key_orders(GList *tracks)
{
    GHashTable **result;
    int i;

    result = g_new0(GHashTable *, ORDER_END);
    g_assert (result != NULL);

    for (i = 0; i < ORDER_END; i++) {
        GList *li;
        GTree *tree;

        tree = g_tree_new(compare_track_fields);

        for (li = tracks; li != NULL; li = li->next) {
            Itdb_Track *track = (Itdb_Track *)li->data;

            g_tree_insert(tree, sort_field(track, i), 0);
        }

        result[i] = g_hash_table_new(g_str_hash, g_str_equal);
        g_assert (result[i] != NULL);

        g_tree_foreach(tree, traverse_tracks, (gpointer)result[i]);
        g_tree_destroy(tree);
    }

    return result;
}

static void destroy_key_orders(GHashTable **orders)
{
    int i;

    for (i = 0; i < ORDER_END; i++) {
        g_hash_table_destroy(orders[i]);
    }

    g_free(orders);
}

static int mk_Dynamic(Itdb_iTunesDB *itdb, const char *outpath)
{
    int res = -1;
    gchar *dbf = NULL;
    sqlite3 *db = NULL;
    sqlite3_stmt *stmt = NULL;
    char *errmsg = NULL;
    struct stat fst;
    int idx = 0;
    GList *gl = NULL;

    dbf = g_build_filename(outpath, "Dynamic.itdb", NULL);
    printf("[%s] Processing '%s'\n", __func__, dbf);
    if (stat(dbf, &fst) != 0) {
	if (errno == ENOENT) {
	    /* file is not present. so we'll create it */
	} else {
	    fprintf(stderr, "[%s] Error: stat: %s\n", __func__, strerror(errno));
	    goto leave;
	}
    } else {
	/* file is present. delete it, we'll re-create it. */
	if (unlink(dbf) != 0) {
	    fprintf(stderr, "[%s] could not delete '%s': %s\n", __func__, dbf, strerror(errno));
	    goto leave;
	}
    }

    if (SQLITE_OK != sqlite3_open((const char*)dbf, &db)) {
	fprintf(stderr, "Error opening database '%s': %s\n", dbf, sqlite3_errmsg(db));
	goto leave;
    }

    sqlite3_exec(db, "PRAGMA synchronous = OFF;", NULL, NULL, NULL);

    fprintf(stderr, "[%s] creating table structure\n", __func__);
    /* db structure needs to be created. */
    if (SQLITE_OK != sqlite3_exec(db, Dynamic_create, NULL, NULL, &errmsg)) {
	fprintf(stderr, "[%s] sqlite3_exec error: %s\n", __func__, sqlite3_errmsg(db));
	if (errmsg) {
	    fprintf(stderr, "[%s] additional error information: %s\n", __func__, errmsg);
	    sqlite3_free(errmsg);
	    errmsg = NULL;
	}
	goto leave;
    }

    sqlite3_exec(db, "BEGIN;", NULL, NULL, NULL);

    if (itdb->tracks) {
	printf("[%s] - processing %d tracks\n", __func__, g_list_length(itdb->tracks));
	if (SQLITE_OK != sqlite3_prepare_v2(db, "INSERT INTO \"item_stats\" (item_pid,has_been_played,date_played,play_count_user,play_count_recent,date_skipped,skip_count_user,skip_count_recent,bookmark_time_ms,bookmark_time_ms_common,user_rating,user_rating_common) VALUES(?,?,?,?,?,?,?,?,?,?,?,?);", -1, &stmt, NULL)) {
	    fprintf(stderr, "[%s] sqlite3_prepare error: %s\n", __func__, sqlite3_errmsg(db));
	    goto leave;
	}
	for (gl=itdb->tracks; gl; gl=gl->next) {
	    Itdb_Track *track = gl->data;
	    if (!track->ipod_path) {
		continue;
	    }
	    res = sqlite3_reset(stmt);
	    if (res != SQLITE_OK) {
		fprintf(stderr, "[%s] 1 sqlite3_reset returned %d\n", __func__, res);
	    }
	    idx = 0;
	    /* item_pid */
	    sqlite3_bind_int64(stmt, ++idx, track->dbid);
	    /* has_been_played */
	    sqlite3_bind_int(stmt, ++idx, (track->playcount > 0) ? 1 : 0);
	    /* date_played */
	    sqlite3_bind_int(stmt, ++idx, timeconv(track->time_played));
	    /* play_count_user */
	    sqlite3_bind_int(stmt, ++idx, track->playcount);
	    /* play_count_recent */
	    sqlite3_bind_int(stmt, ++idx, track->recent_playcount);
	    /* date_skipped */
	    sqlite3_bind_int(stmt, ++idx, timeconv(track->last_skipped));
	    /* skip_count_user */
	    sqlite3_bind_int(stmt, ++idx, track->skipcount);
	    /* skip_count_recent */
	    sqlite3_bind_int(stmt, ++idx, track->recent_skipcount);
	    /* bookmark_time_ms */
	    sqlite3_bind_double(stmt, ++idx, track->bookmark_time);
	    /* bookmark_time_ms_common */
	    /* FIXME: is this the same as bookmark_time_ms ??! */
	    sqlite3_bind_double(stmt, ++idx, track->bookmark_time);
	    /* user_rating */
	    sqlite3_bind_int(stmt, ++idx, track->rating);
	    /* user_rating_common */
	    /* FIXME: don't know if this is the right value */
	    sqlite3_bind_int(stmt, ++idx, track->app_rating);

	    res = sqlite3_step(stmt);
	    if (res == SQLITE_DONE) {
		/* expected result */
	    } else {
		fprintf(stderr, "[%s] 1 sqlite3_step returned %d\n", __func__, res);
	    }
	}
	if (stmt) {
	    sqlite3_finalize(stmt);
	    stmt = NULL;
	}
    } else {
	printf("[%s] - No tracks available, none written.\n", __func__);
    }

    /* add playlist info to container_ui */
    if (SQLITE_OK != sqlite3_prepare_v2(db, "INSERT INTO \"container_ui\" VALUES(?,?,?,?,?,?,?);", -1, &stmt, NULL)) {
	    fprintf(stderr, "[%s] 2 sqlite3_prepare error: %s\n", __func__, sqlite3_errmsg(db));
	    goto leave;
    }

    printf("[%s] - processing %d playlists\n", __func__, g_list_length(itdb->playlists));
    for (gl = itdb->playlists; gl; gl = gl->next) {
	Itdb_Playlist *pl = (Itdb_Playlist*)gl->data;

	res = sqlite3_reset(stmt);
	if (res != SQLITE_OK) {
	    fprintf(stderr, "[%s] 2 sqlite3_reset returned %d\n", __func__, res);
	}
	idx = 0;
	/* container_pid */
	sqlite3_bind_int64(stmt, ++idx, pl->id);
	/* play_order TODO: where does this value come from? */
	/* FIXME: 5 --> 7, 24 --> 39 ??!? */
	switch (pl->sortorder) {
	    case 5:
		sqlite3_bind_int(stmt, ++idx, 7);
		break;
	    case 24:
		sqlite3_bind_int(stmt, ++idx, 39);
		break;
	    default:
		sqlite3_bind_int(stmt, ++idx, 7);
		break;
	}
	/* is_reversed TODO where does this value come from? */
	sqlite3_bind_int(stmt, ++idx, 0);
	/* album_field_order TODO where does this value come from? */
	sqlite3_bind_int(stmt, ++idx, 1);
	/* repeat_mode TODO where does this value come from? */
	sqlite3_bind_int(stmt, ++idx, 0);
	/* shuffle_items TODO where does this value come from? */
	sqlite3_bind_int(stmt, ++idx, 0);
	/* has_been_shuffled TODO where does this value come from? */
	sqlite3_bind_int(stmt, ++idx, 0);

	res = sqlite3_step(stmt);
	if (res == SQLITE_DONE) {
	    /* expected result */
	} else {
	    fprintf(stderr, "[%s] 2 sqlite3_step returned %d\n", __func__, res);
	}
    }

    sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL);

    if (stmt) {
	sqlite3_finalize(stmt);
	stmt = NULL;
    }

    res = 0;
    printf("[%s] done.\n", __func__);
leave:
    if (db) {
	sqlite3_close(db);
    }
    if (dbf) {
	g_free(dbf);
    }
    return res;
}

static int mk_Extras(Itdb_iTunesDB *itdb, const char *outpath)
{
    int res = -1;
    int rebuild = 0;
    gchar *dbf = NULL;
    sqlite3 *db = NULL;
    sqlite3_stmt *stmt_chapter = NULL;
    char *errmsg = NULL;
    struct stat fst;
    GList *gl = NULL;

    dbf = g_build_filename(outpath, "Extras.itdb", NULL);
    printf("[%s] Processing '%s'\n", __func__, dbf);
    if (stat(dbf, &fst) != 0) {
	if (errno == ENOENT) {
	    /* file is not present. so we need to create the tables in it ;) */
	    rebuild = 1;
	} else {
	    fprintf(stderr, "[%s] Error: stat: %s\n", __func__, strerror(errno));
	    goto leave;
	}
    }

    if (SQLITE_OK != sqlite3_open((const char*)dbf, &db)) {
	fprintf(stderr, "Error opening database '%s': %s\n", dbf, sqlite3_errmsg(db));
	goto leave;
    }

    sqlite3_exec(db, "PRAGMA synchronous = OFF;", NULL, NULL, NULL);

    if (rebuild) {
	fprintf(stderr, "[%s] re-building table structure\n", __func__);
	/* db structure needs to be created. */
	if (SQLITE_OK != sqlite3_exec(db, Extras_create, NULL, NULL, &errmsg)) {
	    fprintf(stderr, "[%s] sqlite3_exec error: %s\n", __func__, sqlite3_errmsg(db));
	    if (errmsg) {
		fprintf(stderr, "[%s] additional error information: %s\n", __func__, errmsg);
		sqlite3_free(errmsg);
		errmsg = NULL;
	    }
	    goto leave;
	}
    }

    sqlite3_exec(db, "BEGIN;", NULL, NULL, NULL);
    if (SQLITE_OK != sqlite3_prepare_v2(db, "INSERT INTO \"chapter\" VALUES(?,?);", -1, &stmt_chapter, NULL)) {
	fprintf(stderr, "[%s] sqlite3_prepare error: %s\n", __func__, sqlite3_errmsg(db));
	goto leave;
    }

    /* kill all entries in 'chapter' as they will be re-inserted */
    if (SQLITE_OK != sqlite3_exec(db, "DELETE FROM chapter;", NULL, NULL, &errmsg)) {
	fprintf(stderr, "[%s] sqlite3_exec error: %s\n", __func__, sqlite3_errmsg(db));
	if (errmsg) {
	    fprintf(stderr, "[%s] additional error information: %s\n", __func__, errmsg);
	    sqlite3_free(errmsg);
	    errmsg = NULL;
	}
	goto leave;
    }

    /* kill all entries in 'lyrics' as they will be re-inserted */
    /* TODO: we do not support this in the moment, so don't touch this */
    /*if (SQLITE_OK != sqlite3_exec(db, "DELETE FROM lyrics;", NULL, NULL, &errmsg)) {
	fprintf(stderr, "[%s] sqlite3_exec error: %s\n", __func__, sqlite3_errmsg(db));
	if (errmsg) {
	    fprintf(stderr, "[%s] additional error information: %s\n", __func__, errmsg);
	    sqlite3_free(errmsg);
	    errmsg = NULL;
	}
	goto leave;
    }*/

    for (gl = itdb->tracks; gl; gl = gl->next) {
	Itdb_Track *track = gl->data;
	if (track->chapterdata) {
	    int idx = 0;
	    GByteArray *chapter_blob = itdb_chapterdata_build_chapter_blob(track->chapterdata, FALSE);
	    /* printf("[%s] -- inserting into \"chapter\"\n", __func__); */
	    res = sqlite3_reset(stmt_chapter);
	    if (res != SQLITE_OK) {
		fprintf(stderr, "[%s] 1 sqlite3_reset returned %d\n", __func__, res);
	    }
	    /* item_pid INTEGER NOT NULL */
	    sqlite3_bind_int64(stmt_chapter, ++idx, track->dbid);
	    /* data BLOB */
	    sqlite3_bind_blob(stmt_chapter, ++idx, chapter_blob->data, chapter_blob->len, SQLITE_TRANSIENT);

	    res = sqlite3_step(stmt_chapter);
	    if (res == SQLITE_DONE) {
		/* expected result */
	    } else {
		fprintf(stderr, "[%s] 8 sqlite3_step returned %d\n", __func__, res);
	    }
	    g_byte_array_free(chapter_blob, TRUE);
	}
    }
    sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL);

    res = 0;
    printf("[%s] done.\n", __func__);
leave:
    if (stmt_chapter) {
	sqlite3_finalize(stmt_chapter);
    }
    if (db) {
	sqlite3_close(db);
    }
    if (dbf) {
	g_free(dbf);
    }
    return res;
}

static int mk_Genius(Itdb_iTunesDB *itdb, const char *outpath)
{
    int res = -1;
    int rebuild = 0;
    gchar *dbf = NULL;
    sqlite3 *db = NULL;
    char *errmsg = NULL;
    struct stat fst;

    dbf = g_build_filename(outpath, "Genius.itdb", NULL);
    printf("[%s] Processing '%s'\n", __func__, dbf);
    if (stat(dbf, &fst) != 0) {
	if (errno == ENOENT) {
	    /* file is not present. so we need to create the tables in it ;) */
	    rebuild = 1;
	} else {
	    fprintf(stderr, "[%s] Error: stat: %s\n", __func__, strerror(errno));
	    goto leave;
	}
    }

    if (SQLITE_OK != sqlite3_open((const char*)dbf, &db)) {
	fprintf(stderr, "Error opening database '%s': %s\n", dbf, sqlite3_errmsg(db));
	goto leave;
    }

    sqlite3_exec(db, "PRAGMA synchronous = OFF;", NULL, NULL, NULL);

    if (rebuild) {
	fprintf(stderr, "[%s] re-building table structure\n", __func__);
	/* db structure needs to be created. */
	if (SQLITE_OK != sqlite3_exec(db, Genius_create, NULL, NULL, &errmsg)) {
	    fprintf(stderr, "[%s] sqlite3_exec error: %s\n", __func__, sqlite3_errmsg(db));
	    if (errmsg) {
		fprintf(stderr, "[%s] additional error information: %s\n", __func__, errmsg);
		sqlite3_free(errmsg);
		errmsg = NULL;
	    }
	    goto leave;
	}
    }

    sqlite3_exec(db, "BEGIN;", NULL, NULL, NULL);

    /* kill all entries in 'genius_config' as they will be re-inserted */
    /* TODO: we do not support this in the moment, so don't touch this */
    /*if (SQLITE_OK != sqlite3_exec(db, "DELETE FROM genius_config;", NULL, NULL, &errmsg)) {
	fprintf(stderr, "[%s] sqlite3_exec error: %s\n", __func__, sqlite3_errmsg(db));
	if (errmsg) {
	    fprintf(stderr, "[%s] additional error information: %s\n", __func__, errmsg);
	    sqlite3_free(errmsg);
	    errmsg = NULL;
	}
	goto leave;
    }*/

    /* kill all entries in 'genius_metadata' as they will be re-inserted */
    /* TODO: we do not support this in the moment, so don't touch this */
    /*if (SQLITE_OK != sqlite3_exec(db, "DELETE FROM genius_metadata;", NULL, NULL, &errmsg)) {
	fprintf(stderr, "[%s] sqlite3_exec error: %s\n", __func__, sqlite3_errmsg(db));
	if (errmsg) {
	    fprintf(stderr, "[%s] additional error information: %s\n", __func__, errmsg);
	    sqlite3_free(errmsg);
	    errmsg = NULL;
	}
	goto leave;
    }*/

    /* kill all entries in 'genius_similarities' as they will be re-inserted */
    /* TODO: we do not support this in the moment, so don't touch this */
    /*if (SQLITE_OK != sqlite3_exec(db, "DELETE FROM genius_similarities;", NULL, NULL, &errmsg)) {
	fprintf(stderr, "[%s] sqlite3_exec error: %s\n", __func__, sqlite3_errmsg(db));
	if (errmsg) {
	    fprintf(stderr, "[%s] additional error information: %s\n", __func__, errmsg);
	    sqlite3_free(errmsg);
	    errmsg = NULL;
	}
	goto leave;
    }*/

    sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL);

    res = 0;
    printf("[%s] done.\n", __func__);
leave:
    if (db) {
	sqlite3_close(db);
    }
    if (dbf) {
	g_free(dbf);
    }
    return res;
}

static void free_key_val_strings(gpointer key, gpointer value, gpointer user_data)
{
	g_free(key);
	g_free(value);
}

static void bind_first_text(sqlite3_stmt *stmt, int idx, int n, ...)
{
    va_list ap;
    int i;

    va_start(ap, n);
    for (i = 0; i < n; i++) {
        char *str = va_arg(ap, char*);
        if (str && *str) {
	    sqlite3_bind_text(stmt, idx, str, -1, SQLITE_STATIC);
            goto done;
        }
    }
    sqlite3_bind_null(stmt, idx);
done:
    va_end(ap);
}

static int mk_Library(Itdb_iTunesDB *itdb,
		       GHashTable *album_ids, GHashTable *artist_ids,
		       GHashTable *composer_ids, const char *outpath)
{
    int res = -1;
    gchar *dbf = NULL;
    sqlite3 *db = NULL;
    sqlite3_stmt *stmt_version_info = NULL;
    sqlite3_stmt *stmt_db_info = NULL;
    sqlite3_stmt *stmt_container = NULL;
    sqlite3_stmt *stmt_genre_map = NULL;
    sqlite3_stmt *stmt_item = NULL;
    sqlite3_stmt *stmt_location_kind_map = NULL;
    sqlite3_stmt *stmt_avformat_info = NULL;
    sqlite3_stmt *stmt_item_to_container = NULL;
    sqlite3_stmt *stmt_album = NULL;
    sqlite3_stmt *stmt_artist = NULL;
    sqlite3_stmt *stmt_composer = NULL;
    sqlite3_stmt *stmt_video_info = NULL;
    char *errmsg = NULL;
    struct stat fst;
    GList *gl = NULL;
    Itdb_Playlist *dev_playlist = NULL;
    int idx = 0;
    int pos = 0;
    GHashTable *genre_map = NULL;
    GHashTable **orders = NULL;
    guint32 genre_index;
    printf("library_persistent_id = 0x%016"G_GINT64_MODIFIER"x\n", itdb->priv->pid);

    dbf = g_build_filename(outpath, "Library.itdb", NULL);
    printf("[%s] Processing '%s'\n", __func__, dbf);
    if (stat(dbf, &fst) != 0) {
	if (errno == ENOENT) {
	    /* file is not present. so we will create it */
	} else {
	    fprintf(stderr, "[%s] Error: stat: %s\n", __func__, strerror(errno));
	    goto leave;
	}
    } else {
	/* file is present. delete it, we'll re-create it. */
	if (unlink(dbf) != 0) {
	    fprintf(stderr, "[%s] could not delete '%s': %s\n", __func__, dbf, strerror(errno));
	    goto leave;
	}
    }

    if (SQLITE_OK != sqlite3_open((const char*)dbf, &db)) {
	fprintf(stderr, "Error opening database '%s': %s\n", dbf, sqlite3_errmsg(db));
	goto leave;
    }

    sqlite3_exec(db, "PRAGMA synchronous = OFF;", NULL, NULL, NULL);

    fprintf(stderr, "[%s] building table structure\n", __func__);
    /* db structure needs to be created. */
    if (SQLITE_OK != sqlite3_exec(db, Library_create, NULL, NULL, &errmsg)) {
	fprintf(stderr, "[%s] sqlite3_exec error: %s\n", __func__, sqlite3_errmsg(db));
	if (errmsg) {
	    fprintf(stderr, "[%s] additional error information: %s\n", __func__, errmsg);
	    sqlite3_free(errmsg);
	    errmsg = NULL;
	}
	goto leave;
    }

    sqlite3_exec(db, "BEGIN;", NULL, NULL, NULL);

    fprintf(stderr, "[%s] compiling SQL statements\n", __func__);
    if (SQLITE_OK != sqlite3_prepare_v2(db, "INSERT INTO \"version_info\" VALUES(?,?,?,?,?,?,?);", -1, &stmt_version_info, NULL)) {
	fprintf(stderr, "[%s] sqlite3_prepare error: %s\n", __func__, sqlite3_errmsg(db));
	goto leave;
    }
    if (SQLITE_OK != sqlite3_prepare_v2(db, "INSERT INTO \"db_info\" VALUES(?,?,?,?,?,?,?,?);", -1, &stmt_db_info, NULL)) {
	fprintf(stderr, "[%s] sqlite3_prepare error: %s\n", __func__, sqlite3_errmsg(db));
	goto leave;
    }
    if (SQLITE_OK != sqlite3_prepare_v2(db, "INSERT INTO \"container\" "
	    "VALUES(?,?,?,?,:name,?,?,?,?,?,?,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);", -1, &stmt_container, NULL)) {
	fprintf(stderr, "[%s] sqlite3_prepare error: %s\n", __func__, sqlite3_errmsg(db));
	goto leave;
    }
    if (SQLITE_OK != sqlite3_prepare_v2(db, "INSERT INTO \"genre_map\" (id,genre,genre_order) VALUES(?,?,0);", -1, &stmt_genre_map, NULL)) {
	fprintf(stderr, "[%s] sqlite3_prepare error: %s\n", __func__, sqlite3_errmsg(db));
	goto leave;
    }
    if (SQLITE_OK != sqlite3_prepare_v2(db, "INSERT INTO \"item\" "
	    "(pid,media_kind,date_modified,year,is_compilation,remember_bookmark,exclude_from_shuffle,artwork_status,artwork_cache_id,"
	    "start_time_ms,stop_time_ms,total_time_ms,total_burn_time_ms,track_number,track_count,disc_number,disc_count,"
	    "bpm,relative_volume,eq_preset,radio_stream_status,genre_id,album_pid,artist_pid,composer_pid,title,artist,album,"
	    "album_artist,composer,sort_title,sort_artist,sort_album,sort_album_artist,sort_composer,title_order,artist_order,"
	    "album_order,genre_order,composer_order,album_artist_order,album_by_artist_order,series_name_order,comment,grouping,"
	    "description,description_long) "
	    "VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?);", -1, &stmt_item, NULL)) {
	fprintf(stderr, "[%s] sqlite3_prepare error: %s\n", __func__, sqlite3_errmsg(db));
	goto leave;
    }
    if (SQLITE_OK != sqlite3_prepare_v2(db, "INSERT OR IGNORE INTO \"location_kind_map\" VALUES(?,:kind);", -1, &stmt_location_kind_map, NULL)) {
	fprintf(stderr, "[%s] sqlite3_prepare error: %s\n", __func__, sqlite3_errmsg(db));
	goto leave;
    }
    if (SQLITE_OK != sqlite3_prepare_v2(db, "INSERT INTO \"avformat_info\" VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?);", -1, &stmt_avformat_info, NULL)) {
	fprintf(stderr, "[%s] sqlite3_prepare error: %s\n", __func__, sqlite3_errmsg(db));
	goto leave;
    }
    if (SQLITE_OK != sqlite3_prepare_v2(db, "INSERT INTO \"item_to_container\" VALUES(?,?,?,?);", -1, &stmt_item_to_container, NULL)) {
	fprintf(stderr, "[%s] sqlite3_prepare error: %s\n", __func__, sqlite3_errmsg(db));
	goto leave;
    }
    if (SQLITE_OK != sqlite3_prepare_v2(db, "INSERT OR IGNORE INTO \"album\" VALUES(?,?,?,?,?,?,?,?,?,?,?);", -1, &stmt_album, NULL)) {
	fprintf(stderr, "[%s] sqlite3_prepare error: %s\n", __func__, sqlite3_errmsg(db));
	goto leave;
    }
    if (SQLITE_OK != sqlite3_prepare_v2(db, "INSERT OR IGNORE INTO \"artist\" VALUES(?,?,?,?,?,?,?);", -1, &stmt_artist, NULL)) {
	fprintf(stderr, "[%s] sqlite3_prepare error: %s\n", __func__, sqlite3_errmsg(db));
	goto leave;
    }
    if (SQLITE_OK != sqlite3_prepare_v2(db, "INSERT OR IGNORE INTO \"composer\" (pid,name,name_order,sort_name) VALUES(?,?,?,?);", -1, &stmt_composer, NULL)) {
	fprintf(stderr, "[%s] sqlite3_prepare error: %s\n", __func__, sqlite3_errmsg(db));
	goto leave;
    }
    if (SQLITE_OK != sqlite3_prepare_v2(db, "INSERT INTO \"video_info\" VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?);", -1, &stmt_video_info, NULL)) {
	fprintf(stderr, "[%s] sqlite3_prepare error: %s\n", __func__, sqlite3_errmsg(db));
	goto leave;
    }

    printf("[%s] - inserting into \"version_info\"\n", __func__);
    /* INSERT INTO "version_info" VALUES(1,2,40,0,0,0,2); */
    idx = 0;
    /* id */
    sqlite3_bind_int(stmt_version_info, ++idx, 1);
    /* major */
    /* FIXME: the versioning scheme needs to be updated in libgpod using */
    /* major+minor version, this is a workaround */
    sqlite3_bind_int(stmt_version_info, ++idx, (itdb->version >= 0x28) ? 2 : 1);
    /* minor */
    sqlite3_bind_int(stmt_version_info, ++idx, itdb->version);
    /* compatibility */
    /* This has an unknown meaning. use 0. */
    sqlite3_bind_int(stmt_version_info, ++idx, 0);
    /* update_level */
    /* This has an unknown meaning. use 0. */
    sqlite3_bind_int(stmt_version_info, ++idx, 0);
    /* device_update_level, default is 0 */
    sqlite3_bind_int(stmt_version_info, ++idx, 0);
    /* platform, 1 = MacOS, 2 = Windows */
    sqlite3_bind_int(stmt_version_info, ++idx, itdb->priv->platform);

    res = sqlite3_step(stmt_version_info);
    if (res == SQLITE_DONE) {
	/* expected result */
    } else {
	fprintf(stderr, "[%s] 2 sqlite3_step returned %d\n", __func__, res);
    }

    printf("[%s] - inserting into \"genre_map\"\n", __func__);
    genre_map = g_hash_table_new(g_str_hash, g_str_equal);

    /* build genre_map */
    genre_index = 1;
    for (gl = itdb->tracks; gl; gl = gl->next) {
	Itdb_Track *track = gl->data;
	if ((track->genre == NULL) || (g_hash_table_lookup(genre_map, track->genre) != NULL))
		continue;
	g_hash_table_insert(genre_map, track->genre, GUINT_TO_POINTER(genre_index));

	res = sqlite3_reset(stmt_genre_map);
	if (res != SQLITE_OK) {
	    fprintf(stderr, "[%s] 1 sqlite3_reset returned %d\n", __func__, res);
	}

	idx = 0;
	/* id */
	sqlite3_bind_int(stmt_genre_map, ++idx, genre_index++);
	/* genre */
	sqlite3_bind_text(stmt_genre_map, ++idx, track->genre, -1, SQLITE_STATIC);

	res = sqlite3_step(stmt_genre_map);
	if (res != SQLITE_DONE) {
	    fprintf(stderr, "[%s] sqlite3_step returned %d\n", __func__, res);
	    goto leave;
	}
    }

    pos = 0;

    for (gl = itdb->playlists; gl; gl = gl->next) {
	int tpos = 0;
	GList *glt = NULL;
	guint32 types = 0;
	Itdb_Playlist *pl = (Itdb_Playlist*)gl->data;

	if (pl->type == 1) {
	    dev_playlist = pl;
	}

	printf("[%s] - inserting songs into \"item_to_container\"\n", __func__);

	for (glt = pl->members; glt; glt = glt->next) {
	    Itdb_Track *track = glt->data;

	    /* printf("[%s] -- inserting into \"item_to_container\"\n", __func__); */
	    res = sqlite3_reset(stmt_item_to_container);
	    if (res != SQLITE_OK) {
		fprintf(stderr, "[%s] 1 sqlite3_reset returned %d\n", __func__, res);
	    }	/* INSERT INTO "item_to_container" VALUES(-6197982141081478573,959107999841118509,0,NULL); */
	    idx = 0;
	    /* item_pid */
	    sqlite3_bind_int64(stmt_item_to_container, ++idx, track->dbid);
	    /* container_pid */
	    sqlite3_bind_int64(stmt_item_to_container, ++idx, pl->id);
	    /* physical_order */
	    sqlite3_bind_int(stmt_item_to_container, ++idx, tpos++);
	    /* shuffle_order */
	    /* TODO what's this? set to NULL as iTunes does */
	    sqlite3_bind_null(stmt_item_to_container, ++idx);

	    res = sqlite3_step(stmt_item_to_container);
	    if (res == SQLITE_DONE) {
		/* expected result */
	    } else {
		fprintf(stderr, "[%s] 8 sqlite3_step returned %d\n", __func__, res);
	    }

	    types |= track->mediatype;
	}

	printf("[%s] - inserting playlist '%s' into \"container\"\n", __func__, pl->name);
	res = sqlite3_reset(stmt_container);
	if (res != SQLITE_OK) {
	    fprintf(stderr, "[%s] 1 sqlite3_reset returned %d\n", __func__, res);
	}
	/* INSERT INTO "container" VALUES(959107999841118509,0,267295295,'Hamouda',400,0,1,0,1,0,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); */
	idx = 0;
	/* pid */
	sqlite3_bind_int64(stmt_container, ++idx, pl->id);
	/* distinguished_kind */
	/* TODO: more values? */
	if (pl->podcastflag) {
	    sqlite3_bind_int(stmt_container, ++idx, 11);
	} else {
	    sqlite3_bind_int(stmt_container, ++idx, 0);
	}
	/* date_created */
	sqlite3_bind_int(stmt_container, ++idx, timeconv(pl->timestamp));
	/* date_modified */
	sqlite3_bind_int(stmt_container, ++idx, timeconv(pl->timestamp));
	/* name */
	sqlite3_bind_text(stmt_container, ++idx, pl->name, -1, SQLITE_STATIC);
	/* name order */
	sqlite3_bind_int(stmt_container, ++idx, pos++);
	/* parent_pid */
	/* TODO: unkown meaning, always 0? */
	sqlite3_bind_int(stmt_container, ++idx, 0);
	/* media_kinds */
	sqlite3_bind_int(stmt_container, ++idx, types);
	/* workout_template_id */
	/* TODO: seems to be always 0 */
	sqlite3_bind_int(stmt_container, ++idx, 0);
	/* is_hidden */
	/* 1 for dev and podcasts, 0 elsewhere */
	if (pl->podcastflag || pl->type == 1) {
	    sqlite3_bind_int(stmt_container, ++idx, 1);
	} else {
	    sqlite3_bind_int(stmt_container, ++idx, 0);
	}
	/* smart_is_folder */
	/* TODO: smart playlist stuff? */
	sqlite3_bind_int(stmt_container, ++idx, 0);
	/* iTunes leaves everything else NULL for normal playlists */

	res = sqlite3_step(stmt_container);
	if (res == SQLITE_DONE) {
	    /* expected result */
	} else {
	    fprintf(stderr, "[%s] 4 sqlite3_step returned %d\n", __func__, res);
	}
    }

    if (!dev_playlist) {
	fprintf(stderr, "Could not find special device playlist!\n");
	goto leave;
    }
    printf("library_persistent_id = 0x%016"G_GINT64_MODIFIER"x\n", itdb->priv->pid);

    if (!dev_playlist->name) {
	fprintf(stderr, "Could not fetch device name from itdb!\n");
	goto leave;
    }
    printf("device name = %s\n", dev_playlist->name);

    printf("[%s] - inserting into \"db_info\"\n", __func__);
    idx = 0;
    /* pid */
    sqlite3_bind_int64(stmt_db_info, ++idx, itdb->priv->pid);
    /* primary_container_pid */
    /* ... stored in the playlists where the device name is stored too. */
    sqlite3_bind_int64(stmt_db_info, ++idx, dev_playlist->id);
    /* media_folder_url */
    sqlite3_bind_null(stmt_db_info, ++idx);
    /* audio_language  */
    /*  this is +0xA0 */
    sqlite3_bind_int(stmt_db_info, ++idx, itdb->priv->audio_language);
    /* subtitle_language */
    /*  this is +0xA2 */
    sqlite3_bind_int(stmt_db_info, ++idx, itdb->priv->subtitle_language);
    /* genius cuid */
    if (itdb->priv->genius_cuid) {
	sqlite3_bind_text(stmt_db_info, ++idx, itdb->priv->genius_cuid, -1, SQLITE_STATIC);
    } else {
	sqlite3_bind_null(stmt_db_info, ++idx);
    }
    /* bib */
    /* TODO: unkown meaning, set to NULL */
    sqlite3_bind_null(stmt_db_info, ++idx);
    /* rib */
    /* TODO: unkown meaning, set to NULL */
    sqlite3_bind_null(stmt_db_info, ++idx);

    res = sqlite3_step(stmt_db_info);
    if (res == SQLITE_DONE) {
	/* expected result */
    } else {
	fprintf(stderr, "[%s] 3 sqlite3_step returned %d\n", __func__, res);
    }

    /* for each track: */
    printf("[%s] - processing %d tracks\n", __func__, g_list_length(itdb->tracks));
    orders = compute_key_orders(itdb->tracks);

    for (gl = itdb->tracks; gl; gl = gl->next) {
	Itdb_Track *track = gl->data;
	Itdb_Item_Id *this_album = NULL;
	Itdb_Item_Id *this_artist = NULL;
	Itdb_Item_Id *this_composer = NULL;
	uint64_t album_pid = 0;
	uint64_t artist_pid = 0;
	uint64_t composer_pid = 0;
	gpointer genre_id = NULL;
	int audio_format;
	int aw_id;

	/* printf("[%s] -- inserting into \"item\"\n", __func__); */
	res = sqlite3_reset(stmt_item);
	if (res != SQLITE_OK) {
	    fprintf(stderr, "[%s] 1 sqlite3_reset returned %d\n", __func__, res);
	}
	/* INSERT INTO "item" VALUES(-6197982141081478573,NULL,1,1,0,0,0,0,0,0,0,0,0,0,2004,0,0,0,0,0,0,2,0,0.0,0.0,213812.0,NULL,1,0,0,0,0,0,NULL,NULL,0,0,0,-7670716306765259718,-7576753259813584028,0,'Enjoy the silence 2004 (Reinterpreted by Mike Shinoda)','Depeche Mode','Enjoy The Silence 04 CDS','Depeche Mode',NULL,'Enjoy the silence 2004 (Reinterpreted by Mike Shinoda)','Depeche Mode','Enjoy The Silence 04 CDS','Depeche Mode',NULL,100,100,100,100,100,100,NULL,100,'Lbl MUTE C:MUTE RCDBONG034',NULL,NULL,NULL,1,0,0,0,0,1,1,NULL,NULL,NULL,NULL,NULL,NULL,NULL); */
	idx = 0;
	/* pid */
	sqlite3_bind_int64(stmt_item, ++idx, track->dbid);
	/* media_kind */
	/* NOTE: this is one of  */
	/*  song = 1 */
	/*  audio_book = 8 */
	/*  music_video = 32 */
	/*  movie = 2 */
	/*  tv_show = 64 */
	/*  ringtone = 16384 */
	/*  podcast = 4 */
	/*  rental = 32768 */
	sqlite3_bind_int(stmt_item, ++idx, track->mediatype);
	/* date_modified */
	sqlite3_bind_int(stmt_item, ++idx, timeconv(track->time_modified));
	/* year */
	sqlite3_bind_int(stmt_item, ++idx, track->year);
	/* is_compilation */
	sqlite3_bind_int(stmt_item, ++idx, track->compilation);
	/* remember_bookmark */
	sqlite3_bind_int(stmt_item, ++idx, track->remember_playback_position);
	/* exclude_from_shuffle */
	sqlite3_bind_int(stmt_item, ++idx, track->skip_when_shuffling);
	/* artwork_status 1 = has artwork, 2 = doesn't */
	sqlite3_bind_int(stmt_item, ++idx, track->has_artwork);
	/* artwork_cache_id */
	/* TODO check if this value is correct */
	aw_id = 0;
	if (track->artwork) {
	    aw_id = track->artwork->id;
	}
	sqlite3_bind_int(stmt_item, ++idx, aw_id);
	/* start_time_ms */
	sqlite3_bind_double(stmt_item, ++idx, track->starttime);
	/* stop_time_ms */
	sqlite3_bind_double(stmt_item, ++idx, track->stoptime);
	/* total_time_ms */
	sqlite3_bind_double(stmt_item, ++idx, track->tracklen);
	/* total_burn_time_ms --> set to NULL */
	/* TODO where is this stored? */
	sqlite3_bind_null(stmt_item, ++idx);
	/* track_number */
	sqlite3_bind_int(stmt_item, ++idx, track->track_nr);
	/* track_count */
	sqlite3_bind_int(stmt_item, ++idx, track->tracks);
	/* disc_number */
	sqlite3_bind_int(stmt_item, ++idx, track->cd_nr);
	/* disc_count */
	sqlite3_bind_int(stmt_item, ++idx, track->cds);
	/* bpm */
	sqlite3_bind_int(stmt_item, ++idx, track->BPM);
	/* relative_volume */
	sqlite3_bind_int(stmt_item, ++idx, track->volume);
	/* eq_preset --> set to NULL */
	sqlite3_bind_null(stmt_item, ++idx);
	/* radio_stream_status --> set to NULL */
	sqlite3_bind_null(stmt_item, ++idx);
	/* genre_id */
	genre_id = NULL;
	if (track->genre && *track->genre && (genre_id = g_hash_table_lookup(genre_map, track->genre)) != NULL) {
	    sqlite3_bind_int(stmt_item, ++idx, GPOINTER_TO_UINT(genre_id));
	}
	else
	    sqlite3_bind_int(stmt_item, ++idx, 0);
	/* album_pid -7670716306765259718 */
	this_album = g_hash_table_lookup(album_ids, track);
	if (this_album) {
	    album_pid = this_album->sql_id;
	}
	sqlite3_bind_int64(stmt_item, ++idx, album_pid);
	/* artist_pid -7576753259813584028 */
	this_artist = g_hash_table_lookup(artist_ids, track);
	if (this_artist) {
	    artist_pid = this_artist->sql_id;
	}
	sqlite3_bind_int64(stmt_item, ++idx, artist_pid);
	/* composer_pid */
	this_composer = g_hash_table_lookup(composer_ids, track);
	if (this_composer) {
	    composer_pid = this_composer->sql_id;
	}
	sqlite3_bind_int64(stmt_item, ++idx, composer_pid);
	/* title */
        bind_first_text(stmt_item, ++idx, 1, track->title);
	/* artist */
        bind_first_text(stmt_item, ++idx, 1, track->artist);
	/* album */
        bind_first_text(stmt_item, ++idx, 1, track->album);
	/* album_artist */
        bind_first_text(stmt_item, ++idx, 1, track->albumartist);
	/* composer */
        bind_first_text(stmt_item, ++idx, 1, track->composer);
	/* sort_title */
        bind_first_text(stmt_item, ++idx, 2, track->sort_title, track->title);
	/* sort_artist */
        bind_first_text(stmt_item, ++idx, 2, track->sort_artist, track->artist);
	/* sort_album */
        bind_first_text(stmt_item, ++idx, 2, track->sort_album, track->album);
	/* sort_album_artist */
        bind_first_text(stmt_item, ++idx, 2, track->sort_albumartist, track->albumartist);
	/* sort_composer */
        bind_first_text(stmt_item, ++idx, 2, track->sort_composer, track->composer);

	/* TODO figure out where these values are stored */
	/* title_order */
	sqlite3_bind_int(stmt_item, ++idx, lookup_order(orders, ORDER_TITLE, track));
	/* artist_order */
	sqlite3_bind_int(stmt_item, ++idx, lookup_order(orders, ORDER_ARTIST, track));
	/* album_order */
	sqlite3_bind_int(stmt_item, ++idx, lookup_order(orders, ORDER_ALBUM, track));
	/* genre_order */
	sqlite3_bind_int(stmt_item, ++idx, lookup_order(orders, ORDER_GENRE, track));
	/* composer_order */
	sqlite3_bind_int(stmt_item, ++idx, lookup_order(orders, ORDER_COMPOSER, track));
	/* album_artist_order */
	sqlite3_bind_int(stmt_item, ++idx, lookup_order(orders, ORDER_ALBUM_ARTIST, track));
	/* album_by_artist_order */
	sqlite3_bind_int(stmt_item, ++idx, lookup_order(orders, ORDER_ALBUM_BY_ARTIST, track));
	/* series_name_order */
	sqlite3_bind_int(stmt_item, ++idx, lookup_order(orders, ORDER_SERIES_NAME, track));
	/* comment */
	sqlite3_bind_text(stmt_item, ++idx, track->comment, -1, SQLITE_STATIC);
	/* grouping */
        bind_first_text(stmt_item, ++idx, 1, track->grouping);
	/* description */
        bind_first_text(stmt_item, ++idx, 1, track->description);
	/* description_long */
	/* TODO libgpod doesn't know about it */
	sqlite3_bind_null(stmt_item, ++idx);

	res = sqlite3_step(stmt_item);
	if (res != SQLITE_DONE) {
	    fprintf(stderr, "[%s] 6 sqlite3_step returned %d\n", __func__, res);
	    goto leave;
	}

	/* printf("[%s] -- inserting into \"location_kind_map\" (if required)\n", __func__); */
	res = sqlite3_reset(stmt_location_kind_map);
	if (res != SQLITE_OK) {
	    fprintf(stderr, "[%s] 1 sqlite3_reset returned %d\n", __func__, res);
	}	/* INSERT INTO "location_kind_map" VALUES(1,'MPEG-Audiodatei'); */
	idx = 0;
	/* id */
	sqlite3_bind_int(stmt_location_kind_map, ++idx, track->mediatype);
	/* kind */
	sqlite3_bind_text(stmt_location_kind_map, ++idx, track->filetype, -1, SQLITE_STATIC);

	res = sqlite3_step(stmt_location_kind_map);
	if (res == SQLITE_DONE) {
	    /* expected result */
	} else {
	    fprintf(stderr, "[%s] 5 sqlite3_step returned %d: %s\n", __func__, res, sqlite3_errmsg(db));
	}

	/* printf("[%s] -- inserting into \"avformat_info\"\n", __func__); */
	res = sqlite3_reset(stmt_avformat_info);
	if (res != SQLITE_OK) {
	    fprintf(stderr, "[%s] 1 sqlite3_reset returned %d\n", __func__, res);
	}	/* INSERT INTO "avformat_info" VALUES(-6197982141081478573,0,301,232,44100.0,9425664,1,576,2880,6224207,0,0,0); */
	idx = 0;
	/* item_pid */
	sqlite3_bind_int64(stmt_avformat_info, ++idx, track->dbid);
	/* sub_id */
	/* TODO what is this? set to 0 */
	sqlite3_bind_int64(stmt_avformat_info, ++idx, 0);
	/* audio_format, TODO what's this? */
	switch (track->filetype_marker) {
	    case 0x4d503320:
		audio_format = 301;
		break;
	    case 0x4d344120:
	    case 0x4d345220:
	    case 0x4d503420:
		audio_format = 502;
		break;
	    default:
		audio_format = 0;
	}
	sqlite3_bind_int(stmt_avformat_info, ++idx, audio_format);
	/* bit_rate */
	sqlite3_bind_int(stmt_avformat_info, ++idx, track->bitrate);
	/* sample_rate */
	sqlite3_bind_double(stmt_avformat_info, ++idx, track->samplerate);
	/* duration (in samples) (track->tracklen is in ms) */
	/* iTunes sometimes set it to 0, do that for now since it's easier */
	sqlite3_bind_int(stmt_avformat_info, ++idx, 0);
	/* gapless_heuristic_info */
	sqlite3_bind_int(stmt_avformat_info, ++idx, track->gapless_track_flag);
	/* gapless_encoding_delay */
	sqlite3_bind_int(stmt_avformat_info, ++idx, track->pregap);
	/* gapless_encoding_drain */
	sqlite3_bind_int(stmt_avformat_info, ++idx, track->postgap);
	/* gapless_last_frame_resynch */
	sqlite3_bind_int(stmt_avformat_info, ++idx, track->gapless_data);
	/* analysis_inhibit_flags */
	/* TODO don't know where this belongs to */
	sqlite3_bind_int(stmt_avformat_info, ++idx, 0);
	/* audio_fingerprint */
	/* TODO this either */
	sqlite3_bind_int(stmt_avformat_info, ++idx, 0);
	/* volume_normalization_energy */
	sqlite3_bind_int(stmt_avformat_info, ++idx, track->soundcheck);

	res = sqlite3_step(stmt_avformat_info);
	if (res == SQLITE_DONE) {
	    /* expected result */
	} else {
	    fprintf(stderr, "[%s] 7 sqlite3_step returned %d\n", __func__, res);
	}

	/* this is done by a trigger, so we don't need to do this :-D */
	/* INSERT INTO "ext_item_view_membership" VALUES(-6197982141081478573,0,0); */

	if (this_album) {
	    /* printf("[%s] -- inserting into \"album\" (if required)\n", __func__); */
	    res = sqlite3_reset(stmt_album);
	    if (res != SQLITE_OK) {
		fprintf(stderr, "[%s] 1 sqlite3_reset returned %d\n", __func__, res);
	    }
	    /* INSERT INTO "album" VALUES(-7670716306765259718,2,0,0,-7576753259813584028,0,'Enjoy The Silence 04 CDS',100,0,NULL,0); */
	    idx = 0;
	    /* pid */
	    sqlite3_bind_int64(stmt_album, ++idx, album_pid);
	    /* kind */
	    /* TODO use field from mhia */
	    sqlite3_bind_int(stmt_album, ++idx, 2);
	    /* artwork_status */
	    /* TODO */
	    sqlite3_bind_int(stmt_album, ++idx, 0);
	    /* artwork_item_pid */
	    /* TODO */
	    sqlite3_bind_int(stmt_album, ++idx, 0);
	    /* artist_pid */
	    sqlite3_bind_int64(stmt_album, ++idx, artist_pid);
	    /* user_rating */
	    sqlite3_bind_int(stmt_album, ++idx, 0);
	    /* name */
	    sqlite3_bind_text(stmt_album, ++idx, track->album, -1, SQLITE_STATIC);
	    /* name_order */
	    sqlite3_bind_int(stmt_album, ++idx, lookup_order(orders, ORDER_ALBUM_ARTIST, track));
	    /* all_compilations */
	    /* TODO */
	    sqlite3_bind_int(stmt_album, ++idx, 0);
	    /* feed_url */
	    /* TODO this is for podcasts? */
	    sqlite3_bind_null(stmt_album, ++idx);
	    /* season_number */
	    /* TODO this is for tv shows?! */
	    sqlite3_bind_int(stmt_album, ++idx, 0);

	    res = sqlite3_step(stmt_album);
	    if (res == SQLITE_DONE) {
		/* expected result */
	    } else {
		fprintf(stderr, "[%s] 8 sqlite3_step returned %d\n", __func__, res);
	    }
	}


	if (this_artist) {
	    /* printf("[%s] -- inserting into \"artist\" (if required)\n", __func__); */
	    res = sqlite3_reset(stmt_artist);
	    if (res != SQLITE_OK) {
		fprintf(stderr, "[%s] 1 sqlite3_reset returned %d\n", __func__, res);
	    }
	    /* INSERT INTO "artist" VALUES(-7576753259813584028,2,0,0,'Depeche Mode',100,'Depeche Mode'); */
	    idx = 0;
	    /* pid */
	    sqlite3_bind_int64(stmt_artist, ++idx, artist_pid);
	    /* kind */
	    /* TODO use field from mhii */
	    sqlite3_bind_int(stmt_artist, ++idx, 2);
	    /* artwork_status */
	    /* TODO */
	    sqlite3_bind_int(stmt_artist, ++idx, 0);
	    /* artwork_album_pid */
	    /* TODO */
	    sqlite3_bind_int(stmt_artist, ++idx, 0);
	    /* name */
	    sqlite3_bind_text(stmt_artist, ++idx, track->artist, -1, SQLITE_STATIC);
	    /* name_order */
	    sqlite3_bind_int(stmt_artist, ++idx, lookup_order(orders, ORDER_ARTIST, track));
	    /* sort_name */
	    /* TODO always same as 'name'? */
	    sqlite3_bind_text(stmt_artist, ++idx, track->artist, -1, SQLITE_STATIC);

	    res = sqlite3_step(stmt_artist);
	    if (res == SQLITE_DONE) {
		/* expected result */
	    } else {
		fprintf(stderr, "[%s] 8 sqlite3_step returned %d\n", __func__, res);
	    }
	}

	if (this_composer) {
	    /* printf("[%s] -- inserting into \"composer\" (if required)\n", __func__); */
	    res = sqlite3_reset(stmt_composer);
	    if (res != SQLITE_OK) {
		fprintf(stderr, "[%s] 1 sqlite3_reset returned %d\n", __func__, res);
	    }
	    idx = 0;
	    /* pid */
	    sqlite3_bind_int64(stmt_composer, ++idx, composer_pid);
	    /* name */
	    sqlite3_bind_text(stmt_composer, ++idx, track->composer, -1, SQLITE_STATIC);
	    /* name_order */
	    sqlite3_bind_int(stmt_composer, ++idx, lookup_order(orders, ORDER_COMPOSER, track));
	    /* sort_name */
	    /* TODO always same as 'name'? */
	    sqlite3_bind_text(stmt_composer, ++idx, track->composer, -1, SQLITE_STATIC);

	    res = sqlite3_step(stmt_composer);
	    if (res == SQLITE_DONE) {
		/* expected result */
	    } else {
		fprintf(stderr, "[%s] 8 sqlite3_step returned %d\n", __func__, res);
	    }
	}
	/* if it's a movie, music video or tv show */
	if ((track->mediatype & ITDB_MEDIATYPE_MOVIE)
		|| (track->mediatype & ITDB_MEDIATYPE_MUSICVIDEO)
		|| (track->mediatype & ITDB_MEDIATYPE_TVSHOW)) {
	    /* printf("[%s] -- inserting into \"video_info\"\n", __func__); */
	    res = sqlite3_reset(stmt_video_info);
	    if (res != SQLITE_OK) {
		fprintf(stderr, "[%s] 1 sqlite3_reset returned %d\n", __func__, res);
	    }
	    idx = 0;
	    /* item_pid INTEGER NOT NULL */
	    sqlite3_bind_int64(stmt_video_info, ++idx, track->dbid);
	    /* has_alternate_audio INTEGER */
	    sqlite3_bind_int(stmt_video_info, ++idx, 0);
	    /* has_subtitles INTEGER */
	    sqlite3_bind_int(stmt_video_info, ++idx, 0);
	    /* characteristics_valid INTEGER */
	    sqlite3_bind_int(stmt_video_info, ++idx, 0);
	    /* has_closed_captions INTEGER */
	    sqlite3_bind_int(stmt_video_info, ++idx, 0);
	    /* is_self_contained INTEGER */
	    sqlite3_bind_int(stmt_video_info, ++idx, 0);
	    /* is_compressed INTEGER */
	    sqlite3_bind_int(stmt_video_info, ++idx, 0);
	    /* is_anamorphic INTEGER */
	    sqlite3_bind_int(stmt_video_info, ++idx, 0);
	    /* season_number INTEGER */
	    sqlite3_bind_int(stmt_video_info, ++idx, track->season_nr);
	    /* audio_language INTEGER */
	    sqlite3_bind_int(stmt_video_info, ++idx, 0);
	    /* audio_track_index INTEGER */
	    sqlite3_bind_int(stmt_video_info, ++idx, 0);
	    /* audio_track_id INTEGER */
	    sqlite3_bind_int(stmt_video_info, ++idx, 0);
	    /* subtitle_language INTEGER */
	    sqlite3_bind_int(stmt_video_info, ++idx, 0);
	    /* subtitle_track_index INTEGER */
	    sqlite3_bind_int(stmt_video_info, ++idx, 0);
	    /* subtitle_track_id INTEGER */
	    sqlite3_bind_int(stmt_video_info, ++idx, 0);
	    /* series_name TEXT */
	    sqlite3_bind_text(stmt_video_info, ++idx, track->tvshow, -1, SQLITE_STATIC);
	    /* sort_series_name TEXT */
	    bind_first_text(stmt_item, ++idx, 2, track->sort_tvshow, track->tvshow);
	    /* episode_id TEXT */
	    sqlite3_bind_text(stmt_video_info, ++idx, track->tvepisode, -1, SQLITE_STATIC);
	    /* episode_sort_id INTEGER */
	    sqlite3_bind_int(stmt_video_info, ++idx, track->season_nr << 16 | track->episode_nr);
	    /* network_name TEXT */
	    sqlite3_bind_text(stmt_video_info, ++idx, track->tvnetwork, -1, SQLITE_STATIC);
	    /* extended_content_rating TEXT */
	    sqlite3_bind_null(stmt_video_info, ++idx);
	    /* movie_info TEXT */
	    sqlite3_bind_null(stmt_video_info, ++idx);

	    res = sqlite3_step(stmt_video_info);
	    if (res == SQLITE_DONE) {
		/* expected result */
	    } else {
		fprintf(stderr, "[%s] 8 sqlite3_step returned %d\n", __func__, res);
	    }
	}
    }

    sqlite3_exec(db, "UPDATE album SET artwork_item_pid = (SELECT item.pid FROM item WHERE item.artwork_cache_id != 0 AND item.album_pid = album.pid LIMIT 1);", NULL, NULL, NULL);

    sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL);

    res = 0;
    printf("[%s] done.\n", __func__);
leave:
    if (orders != NULL) {
        destroy_key_orders(orders);
    }

    if (stmt_version_info) {
	sqlite3_finalize(stmt_version_info);
    }
    if (stmt_db_info) {
	sqlite3_finalize(stmt_db_info);
    }
    if (stmt_container) {
	sqlite3_finalize(stmt_container);
    }
    if (stmt_genre_map) {
	sqlite3_finalize(stmt_genre_map);
    }
    if (stmt_item) {
	sqlite3_finalize(stmt_item);
    }
    if (stmt_location_kind_map) {
	sqlite3_finalize(stmt_location_kind_map);
    }
    if (stmt_avformat_info) {
	sqlite3_finalize(stmt_avformat_info);
    }
    if (stmt_item_to_container) {
	sqlite3_finalize(stmt_item_to_container);
    }
    if (stmt_album) {
	sqlite3_finalize(stmt_album);
    }
    if (stmt_artist) {
	sqlite3_finalize(stmt_artist);
    }
    if (stmt_composer) {
	sqlite3_finalize(stmt_composer);
    }
    if (stmt_video_info) {
	sqlite3_finalize(stmt_video_info);
    }
    if (genre_map) {
	g_hash_table_destroy(genre_map);
    }
    if (db) {
	sqlite3_close(db);
    }
    if (dbf) {
	g_free(dbf);
    }
    return res;
}

static int mk_Locations(Itdb_iTunesDB *itdb, const char *outpath, const char *uuid)
{
    int res = -1;
    gchar *dbf = NULL;
    sqlite3 *db = NULL;
    sqlite3_stmt *stmt = NULL;
    char *errmsg = NULL;
    int idx = 0;

    dbf = g_build_filename(outpath, "Locations.itdb", NULL);
    printf("[%s] Processing '%s'\n", __func__, dbf);
    /* file is present. delete it, we'll re-create it. */
    if (g_unlink(dbf) != 0) {
	if (errno != ENOENT) {
	    fprintf(stderr, "[%s] could not delete '%s': %s\n", __func__, dbf, strerror(errno));
	    goto leave;
	}
    }

    if (SQLITE_OK != sqlite3_open((const char*)dbf, &db)) {
	fprintf(stderr, "Error opening database '%s': %s\n", dbf, sqlite3_errmsg(db));
	goto leave;
    }

    sqlite3_exec(db, "PRAGMA synchronous = OFF;", NULL, NULL, NULL);

    fprintf(stderr, "[%s] re-building table structure\n", __func__);
    /* db structure needs to be created. */
    if (SQLITE_OK != sqlite3_exec(db, Locations_create, NULL, NULL, &errmsg)) {
	fprintf(stderr, "[%s] sqlite3_exec error: %s\n", __func__, sqlite3_errmsg(db));
	if (errmsg) {
	    fprintf(stderr, "[%s] additional error information: %s\n", __func__, errmsg);
	    sqlite3_free(errmsg);
	    errmsg = NULL;
	}
	goto leave;
    }

    sqlite3_exec(db, "BEGIN;", NULL, NULL, NULL);

    if (SQLITE_OK != sqlite3_prepare_v2(db, "INSERT INTO \"base_location\" (id, path) VALUES (?,?);", -1, &stmt, NULL)) {
	fprintf(stderr, "[%s] sqlite3_prepare error: %s\n", __func__, sqlite3_errmsg(db));
	goto leave;
    }
    idx = 0;
    res = sqlite3_reset(stmt);
    if (res != SQLITE_OK) {
	fprintf(stderr, "[%s] sqlite3_reset returned %d\n", __func__, res);
    }

    sqlite3_bind_int(stmt, ++idx, 1);
    if (itdb_device_is_iphone_family(itdb->device)) {
	sqlite3_bind_text(stmt, ++idx, "iTunes_Control/Music", -1, SQLITE_STATIC);
    } else {
	sqlite3_bind_text(stmt, ++idx, "iPod_Control/Music", -1, SQLITE_STATIC);
    }
    res = sqlite3_step(stmt);
    if (res == SQLITE_DONE) {
	/* expected result */
    } else {
	fprintf(stderr, "[%s] sqlite3_step returned %d\n", __func__, res);
    }

    if (itdb_device_is_iphone_family(itdb->device)) {
	idx = 0;
	res = sqlite3_reset(stmt);
	if (res != SQLITE_OK) {
	    fprintf(stderr, "[%s] sqlite3_reset returned %d\n", __func__, res);
	}
	sqlite3_bind_int(stmt, ++idx, 4);
	sqlite3_bind_text(stmt, ++idx, "Podcasts", -1, SQLITE_STATIC);
	res = sqlite3_step(stmt);
	if (res == SQLITE_DONE) {
	    /* expected result */
	} else {
	    fprintf(stderr, "[%s] sqlite3_step returned %d\n", __func__, res);
	}

	idx = 0;
	res = sqlite3_reset(stmt);
	if (res != SQLITE_OK) {
	    fprintf(stderr, "[%s] sqlite3_reset returned %d\n", __func__, res);
	}
	sqlite3_bind_int(stmt, ++idx, 6);
	sqlite3_bind_text(stmt, ++idx, "iTunes_Control/Ringtones", -1, SQLITE_STATIC);
	res = sqlite3_step(stmt);
	if (res == SQLITE_DONE) {
	    /* expected result */
	} else {
	    fprintf(stderr, "[%s] sqlite3_step returned %d\n", __func__, res);
	}
    }

    if (stmt) {
	sqlite3_finalize(stmt);
    }
 
    if (SQLITE_OK != sqlite3_prepare_v2(db, "INSERT INTO \"location\" (item_pid, sub_id, base_location_id, location_type, location, extension, kind_id, date_created, file_size) VALUES(?,?,?,?,?,?,?,?,?);", -1, &stmt, NULL)) {
	fprintf(stderr, "[%s] sqlite3_prepare error: %s\n", __func__, sqlite3_errmsg(db));
	goto leave;
    }

    if (itdb->tracks) {
	GList *gl = NULL;

	printf("[%s] Processing %d tracks...\n", __func__, g_list_length(itdb->tracks));
	for (gl=itdb->tracks; gl; gl=gl->next) {
	    Itdb_Track *track = gl->data;
	    char *ipod_path;
	    int i = 0;
	    int cnt = 0;
	    int pos = 0;
	    int res;

	    if (!track->ipod_path) {
		continue;
	    }
	    ipod_path = strdup(track->ipod_path);
	    idx = 0;
	    res = sqlite3_reset(stmt);
	    if (res != SQLITE_OK) {
		fprintf(stderr, "[%s] sqlite3_reset returned %d\n", __func__, res);
	    }
	    /* item_pid */
	    sqlite3_bind_int64(stmt, ++idx, track->dbid);
	    /* sub_id */
	    /* TODO subitle id? set to 0. */
	    sqlite3_bind_int64(stmt, ++idx, 0);
	    /* base_location_id */
	    /*  use 1 here as this is 'iTunes_Control/Music' */
	    sqlite3_bind_int(stmt, ++idx, 1);
	    /* location_type */
	    /* TODO this should always be 0x46494C45 = "FILE" for now, */
	    /*  perhaps later libgpod will support more. */
	    sqlite3_bind_int(stmt, ++idx, 0x46494C45);
	    /* location */
	    for (i = 0; i < strlen(ipod_path); i++) {
		/* replace all ':' with '/' so that the path is valid */
		if (ipod_path[i] == ':') {
		    ipod_path[i] = '/';
		    cnt++;
		    if (cnt == 3) {
			pos = i+1;
		    }
		}
	    }
	    sqlite3_bind_text(stmt, ++idx, ipod_path + pos, -1, SQLITE_STATIC);
	    /* extension */
	    sqlite3_bind_int(stmt, ++idx, track->filetype_marker);
	    /* kind_id, should match track->mediatype */
	    sqlite3_bind_int(stmt, ++idx, track->mediatype);
	    /* date_created */
	    sqlite3_bind_int(stmt, ++idx, timeconv(track->time_modified));
	    /* file_size */
	    sqlite3_bind_int(stmt, ++idx, track->size);
#if 0
	    /* file_creator */
	    /* TODO unknown, set to NULL */
	    sqlite3_bind_null(stmt, ++idx);
	    /* file_type */
	    /* TODO unknown, set to NULL */
	    sqlite3_bind_null(stmt, ++idx);
	    /* num_dir_levels_file */
	    /* TODO unknown, set to NULL */
	    sqlite3_bind_null(stmt, ++idx);
	    /* num_dir_levels_lib */
	    /* TODO unknown, set to NULL */
	    sqlite3_bind_null(stmt, ++idx);
#endif
	    res = sqlite3_step(stmt);
	    if (res == SQLITE_DONE) {
		/* expected result */
	    } else {
		fprintf(stderr, "[%s] 10 sqlite3_step returned %d\n", __func__, res);
	    }
	    if (ipod_path) {
		free(ipod_path);
		ipod_path = NULL;
	    }
	}
    } else {
	printf("[%s] No tracks available, none written.\n", __func__);
    }

    sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL);

    res = 0;
    printf("[%s] done.\n", __func__);
leave:
    if (stmt) {
	sqlite3_finalize(stmt);
    }
    if (db) {
	sqlite3_close(db);
    }
    if (dbf) {
	g_free(dbf);
    }
    return res;
}

static void sort_key_get_buffer_boundaries(const char* sval, int *length, int *word_offset)
{
	int word_count = 0;
	int i = 0;
	int l = 0;
	int o = 0;
	char *sval_uppercase = NULL;
	if (sval && strlen(sval)) {
		sval_uppercase = g_ascii_strup(sval, strlen(sval));
		while (sval_uppercase[i]) {
			if (g_ascii_isalnum(sval_uppercase[i])) {
				l++;
			} else {
				switch (sval_uppercase[i]) {
					case ' ':
						word_count++;
						l++;
						break;
					case ':':
					case '-':
					case ',':
					case '.':
					case '\'':
					default:
						l += 2;
						break;
				}
			}
			i++;
		}
		free(sval_uppercase);

		word_count++;
		/* magic + transformed string + length + word weights + null */
		o = 1 + l + 3;
		l = o + (word_count*2) + 1;
	} else {
		l = 4;
	}

	*length = l;
	*word_offset = o;
}

static void sqlite_func_iphone_sort_key(sqlite3_context *context, int argc, sqlite3_value **argv)
{
	const char *sval;
	char *sval_uppercase = NULL;

	char *buffer = NULL;

	int word_count = 0;
	int word_offset = 0;
	int word_length = 0;
	int i = 0;
	int buffer_index = 0;
	int buffer_size = 0;

	if (argc != 1)
		fprintf(stderr, "[%s] Error: Unexpected number of arguments: %d\n", __func__, argc);

	switch (sqlite3_value_type(argv[0])) {
		case SQLITE_TEXT:
			sval = (const char*)sqlite3_value_text(argv[0]);
			sort_key_get_buffer_boundaries(sval, &buffer_size, &word_offset);
			buffer = (char*)malloc(buffer_size);
			memset(buffer, '\0', buffer_size);
			buffer[buffer_index] = 0x31;
			if (sval) {
				if (buffer_size > 4) {
					buffer[buffer_index++] = 0x30;
					/* transform text value */
					/* uppercase the text */
					sval_uppercase = g_ascii_strup(sval, strlen(sval));
					while (sval_uppercase[i]) {
						word_length++;
						if (g_ascii_isalnum(sval_uppercase[i])) {
							/* transform regular character */
							buffer[buffer_index++] = sval_uppercase[i] - (0x55 - sval_uppercase[i]);
						} else {
							/* transform special chars (punctuation,special) */
							switch (sval_uppercase[i]) {
								case ' ':
									buffer[buffer_index++] = 0x06;
									word_length--;

									/* since we reached word end, calculate word weight */
									buffer[word_offset + word_count*2] = 0x8f;
									buffer[word_offset + word_count*2 + 1] = (char)(0x86 - word_length);
									word_count++;
									word_length = 0;
									break;
								case ':':
									buffer[buffer_index++] = 0x07;
									buffer[buffer_index++] = 0xd8;
									break;
								case '-':
									buffer[buffer_index++] = 0x07;
									buffer[buffer_index++] = 0x90;
									break;
								case ',':
									buffer[buffer_index++] = 0x07;
									buffer[buffer_index++] = 0xb2;
									break;
								case '.':
									buffer[buffer_index++] = 0x08;
									buffer[buffer_index++] = 0x51;
									break;
								case '\'':
									buffer[buffer_index++] = 0x07;
									buffer[buffer_index++] = 0x31;
									break;
								default:
									/* FIXME: We just simulate "-" for any other char, needs proper conversion */
									buffer[buffer_index++] = 0x07;
									buffer[buffer_index++] = 0x90;
									break;
							}
						}
						i++;
					}
					g_free(sval_uppercase);

					/* calculate word weight for last word */
					buffer[word_offset + word_count*2] = 0x8f;
					buffer[word_offset + word_count*2 + 1] = 3 + word_length;
					word_count++;
					word_length = 0;

					/* write length of input string */
					buffer[word_offset - 3] = 0x01;
					buffer[word_offset - 2] = i + 4; /* length of input string + 4 */
					buffer[word_offset - 1] = 0x01;
				} else {
					buffer[0] = 0x31;
					buffer[1] = 0x01;
					buffer[2] = 0x01;
				}
			}
			sqlite3_result_blob(context, buffer, buffer_size, free);
			break;
		case SQLITE_NULL:
			buffer = (char*)malloc(4);
			memcpy(buffer, "\x31\x01\x01\x00", 4);
			sqlite3_result_blob(context, buffer, 4, free);
			break;
		default:
			sqlite3_result_null(context);
			break;
	}
}

static void sqlite_func_iphone_sort_section(sqlite3_context *context, int argc, sqlite3_value **argv)
{
	const unsigned char *sval;
	int res = 26;

	if (argc != 1)
		fprintf(stderr, "[%s] Error: Unexpected number of arguments: %d\n", __func__, argc);

	switch (sqlite3_value_type(argv[0])) {
		case SQLITE_BLOB:
		case SQLITE_TEXT:
			sval = sqlite3_value_text(argv[0]);
			if (sval && (sval[0] == 0x30) && (sval[1] >= 0x2D) && (sval[1] <= 0x5F)) {
				res = (sval[1] - 0x2D) / 2;
			}
			break;
		default:
			break;
	}
	sqlite3_result_int(context, res);
}

static void run_post_process_commands(Itdb_iTunesDB *itdb, const char *outpath, const char *uuid)
{
    plist_t plist_node = NULL;
    plist_t ppc_dict = NULL;
    const gchar *basedb = "Library.itdb";
    const gchar *otherdbs[] = {"Dynamic.itdb", "Extras.itdb", "Genius.itdb", "Locations.itdb", NULL};
    int res;
    sqlite3 *db = NULL;

#ifdef HAVE_LIBIMOBILEDEVICE
    if (itdb_device_is_iphone_family(itdb->device)) {
	/* get SQL post process commands via lockdown (iPhone/iPod Touch) */
	lockdownd_client_t client = NULL;
	idevice_t phone = NULL;
	idevice_error_t ret = IDEVICE_E_UNKNOWN_ERROR;
	lockdownd_error_t lockdownerr = LOCKDOWN_E_UNKNOWN_ERROR;

	ret = idevice_new(&phone, uuid);
	if (ret != IDEVICE_E_SUCCESS) {
	    printf("[%s] ERROR: Could not find device with uuid %s, is it plugged in?\n", __func__, uuid);
	    goto leave;
	}

	if (LOCKDOWN_E_SUCCESS != lockdownd_client_new_with_handshake(phone, &client, "libgpod")) {
	    printf("[%s] ERROR: Could not connect to device's lockdownd!\n", __func__);
	    idevice_free(phone);
	    goto leave;
	}

	lockdownerr = lockdownd_get_value(client, "com.apple.mobile.iTunes.SQLMusicLibraryPostProcessCommands", NULL, &plist_node);
	lockdownd_client_free(client);
	idevice_free(phone);

	if (lockdownerr == LOCKDOWN_E_SUCCESS) {
	    ppc_dict = plist_node;
	} else {
	    if (plist_node) {
		plist_free(plist_node);
		plist_node = NULL;
	    }
	}
    } else 
#endif
    if (itdb->device->sysinfo_extended != NULL) {
	/* try to get SQL post process commands via sysinfo_extended */
	gchar *dev_path = itdb_get_device_dir(itdb->device->mountpoint);
	if (dev_path) {
	    const gchar *p_sysinfo_ex[] = {"SysInfoExtended", NULL};
	    gchar *sysinfo_ex_path = itdb_resolve_path(dev_path, p_sysinfo_ex);
	    g_free(dev_path);
	    if (sysinfo_ex_path) {
		/* open plist file */
		char *xml_contents = NULL;
		gsize xml_length = 0;
		if (g_file_get_contents(sysinfo_ex_path, &xml_contents, &xml_length, NULL)) {
		    plist_from_xml(xml_contents, xml_length, &plist_node);
		    if (plist_node) {
			/* locate specific key */
			ppc_dict = plist_dict_get_item(plist_node, "com.apple.mobile.iTunes.SQLMusicLibraryPostProcessCommands");
		    }
		}
		if (xml_contents) {
		    g_free(xml_contents);
		}
		g_free(sysinfo_ex_path);
	    }
	}
    }

    if (ppc_dict) {
	plist_dict_iter iter = NULL;
	plist_t sql_cmds = NULL;
	plist_t user_ver_cmds = NULL;

	printf("[%s] Getting SQL post process commands\n", __func__);

	sql_cmds = plist_dict_get_item(ppc_dict, "SQLCommands");
	user_ver_cmds = plist_dict_get_item(ppc_dict, "UserVersionCommandSets");

	if (sql_cmds && user_ver_cmds) {
	    /* we found the SQLCommands and the UserVersionCommandSets keys */
	    char *key = NULL;
	    unsigned long int maxver = 0;
	    plist_t curnode = user_ver_cmds;
	    plist_t subnode = NULL;

	    user_ver_cmds = NULL;

	    /* now look for numbered subkey in the UserVersionCommandsSets */
	    plist_dict_new_iter(curnode, &iter);
	    if (iter) {
		plist_dict_next_item(curnode, iter, &key, &subnode);
		while (subnode) {
		    unsigned long int intval = strtoul(key, NULL, 0);
		    if ((intval > 0) && (intval > maxver)) {
			user_ver_cmds = subnode;
			maxver = intval;
		    }
		    subnode = NULL;
		    free(key);
		    key = NULL;
		    plist_dict_next_item(curnode, iter, &key, &subnode);
		}
		free(iter);
		iter = NULL;
	    }
	    if (user_ver_cmds) {
		/* found numbered key (usually '8', for Nano 5G it is '9') */
		curnode = user_ver_cmds;
		/* now get the commands array */
		user_ver_cmds = plist_dict_get_item(curnode, "Commands");
		if (user_ver_cmds && (plist_get_node_type(user_ver_cmds) == PLIST_ARRAY)) {
		    /* We found our array with the commands to execute, now
		     * make a hashmap for the SQLCommands to find them faster
		     * when we actually execute them in correct order. */
	    	    GHashTable *sqlcmd_map = g_hash_table_new(g_str_hash, g_str_equal);
		    if (sqlcmd_map) {
			char *val = NULL;
		    	gchar *dbf = NULL;
		    	gchar *attach_str = NULL;
			char *errmsg = NULL;
			guint32 i = 0, cnt = 0, ok_cnt = 0;

			plist_dict_new_iter(sql_cmds, &iter);
			if (iter) {
			    plist_dict_next_item(sql_cmds, iter, &key, &subnode);
			    while (subnode) {
				if (plist_get_node_type(subnode) == PLIST_STRING) {
				    plist_get_string_val(subnode, &val);
				    g_hash_table_insert(sqlcmd_map, key, val);
				    val = NULL;
				} else {
				    printf("[%s] WARNING: ignoring non-string value for key '%s'\n", __func__, key);
				    free(key);
				}
				subnode = NULL;
				key = NULL;
				plist_dict_next_item(sql_cmds, iter, &key, &subnode);
			    }
			    free(iter);
			    iter = NULL;
			}

			/* open Library.itdb first */
			dbf = g_build_filename(outpath, basedb, NULL);

			if (SQLITE_OK != sqlite3_open((const char*)dbf, &db)) {
			    fprintf(stderr, "Error opening database '%s': %s\n", dbf, sqlite3_errmsg(db));
			    g_free(dbf);
			    goto leave;
			}
			g_free(dbf);

			/* now attach other required database files */
			i = 0;
			while (otherdbs[i]) {
			    errmsg = NULL;
			    dbf = g_build_filename(outpath, otherdbs[i], NULL);
			    attach_str = g_strdup_printf("ATTACH DATABASE '%s' AS '%s';", dbf, otherdbs[i]);
			    g_free(dbf);
			    res = sqlite3_exec(db, attach_str, NULL, NULL, &errmsg);
			    g_free(attach_str);
			    if (res != SQLITE_OK) {
				printf("[%s] WARNING: Could not attach database '%s': %s\n", __func__, otherdbs[i], errmsg);
			    }
			    if (errmsg) {
				free(errmsg);
			    }
			    i++;
			}

			printf("[%s] binding functions\n", __func__);
			sqlite3_create_function(db, "iPhoneSortKey", 1, SQLITE_ANY, NULL, &sqlite_func_iphone_sort_key, NULL, NULL);
			sqlite3_create_function(db, "iPhoneSortSection", 1, SQLITE_ANY, NULL, &sqlite_func_iphone_sort_section, NULL, NULL);

			cnt = plist_array_get_size(user_ver_cmds);
			printf("[%s] Running %d post process commands now\n", __func__, cnt);

			sqlite3_exec(db, "BEGIN;", NULL, NULL, NULL);
			for (i=0; i<cnt; i++) {
			    subnode = plist_array_get_item(user_ver_cmds, i);
			    plist_get_string_val(subnode, &key);
			    if (key) {
				val = g_hash_table_lookup(sqlcmd_map, key);
				if (val) {
				    char *errmsg = NULL; 
				    if (SQLITE_OK == sqlite3_exec(db, val, NULL, NULL, &errmsg)) {
				        /*printf("[%s] executing '%s': OK", __func__, key);*/
					ok_cnt++;
				    } else {
					printf("[%s] ERROR when executing '%s': %s\n", __func__, key, errmsg);
				    }
				    if (errmsg) {
					sqlite3_free(errmsg);
				    }
				} else {
				    printf("[%s] value for '%s' not found in hashmap!\n", __func__, key);
				}
				free(key);
				key = NULL;
			    }
			}
			g_hash_table_foreach(sqlcmd_map, free_key_val_strings, NULL);
			g_hash_table_destroy(sqlcmd_map);

			printf("[%s] %d out of %d post process commands successfully executed\n", __func__, ok_cnt, cnt);
			/* TODO perhaps we want to roll back when an error has occured ? */
			sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL);
		    } else {
			printf("[%s]: Error: could not create hash table!\n", __func__);
		    }
		} else {
		    printf("[%s]: Error fetching commands array\n", __func__);
		}
	    } else {
		printf("[%s]: Error fetching user version command set\n", __func__);
	    }
	} else {
	    printf("[%s]: Error fetching post process commands from device!\n", __func__);
	}
    }
   
    printf("[%s] done.\n", __func__);

leave:
    if (db) {
	sqlite3_close(db);
    }
    if (plist_node) {
	plist_free(plist_node);
    }

}

static int cbk_calc_sha1_one_block(FILE *f, unsigned char sha1[20])
{
    const guint BLOCK_SIZE = 1024;
    unsigned char block[BLOCK_SIZE];
    size_t read_count;
    GChecksum *checksum;
    gsize sha1_len;

    read_count = fread(block, BLOCK_SIZE, 1, f);
    if ((read_count != 1)) {
	if (feof(f)) {
	    return 1;
	} else {
	    return -1;
	}
    }

    sha1_len = g_checksum_type_get_length(G_CHECKSUM_SHA1);
    g_assert (sha1_len == 20);
    checksum = g_checksum_new(G_CHECKSUM_SHA1);
    g_checksum_update(checksum, block, BLOCK_SIZE);
    g_checksum_get_digest(checksum, sha1, &sha1_len);
    g_checksum_free(checksum);

    return 0;
}

static gboolean cbk_calc_sha1s(const char *filename, GArray *sha1s)
{
    FILE *f;
    int calc_ok;

    f = fopen(filename, "rb");
    if (f == NULL) {
	return FALSE;
    }

    do {
	unsigned char sha1[20];
	calc_ok = cbk_calc_sha1_one_block(f, sha1);
	if (calc_ok != 0) {
	    break;
	}
	g_array_append_vals(sha1s, sha1, sizeof(sha1));
    } while (calc_ok == 0);

    if (calc_ok < 0) {
	goto error;
    }

    fclose(f);
    return TRUE;

error:
    fclose(f);
    return FALSE;
}

static void cbk_calc_sha1_of_sha1s(GArray *cbk, guint cbk_header_size)
{
    GChecksum *checksum;
    unsigned char* final_sha1;
    unsigned char* sha1s;
    gsize final_sha1_len;

    g_assert (cbk->len > cbk_header_size + 20);

    final_sha1 = &g_array_index(cbk, guchar, cbk_header_size);
    sha1s = &g_array_index(cbk, guchar, cbk_header_size + 20);
    final_sha1_len = g_checksum_type_get_length(G_CHECKSUM_SHA1);
    g_assert (final_sha1_len == 20);

    checksum = g_checksum_new(G_CHECKSUM_SHA1);
    g_checksum_update(checksum, sha1s, cbk->len - (cbk_header_size + 20));
    g_checksum_get_digest(checksum, final_sha1, &final_sha1_len);
    g_checksum_free(checksum);
}

static gboolean mk_Locations_cbk(Itdb_iTunesDB *itdb, const char *dirname)
{
    char *locations_filename;
    char *cbk_filename;
    GArray *cbk;
    gboolean success;
    guchar *cbk_hash;
    guchar *final_sha1;
    guint cbk_header_size = 0;
    guint checksum_type;

    checksum_type = itdb_device_get_checksum_type(itdb->device);
    switch (checksum_type) {
	case ITDB_CHECKSUM_HASHAB:
	    cbk_header_size = 57;
	    break;
	case ITDB_CHECKSUM_HASH58:
	    /* the nano 5g advertises DBVersion 3 but expects an hash72 on
	     * its cbk file.
	     */
	case ITDB_CHECKSUM_HASH72:
	    cbk_header_size = 46;
	    break;
	default:
	    break;
    }
    if (cbk_header_size == 0) {
	fprintf(stderr, "ERROR: Unsupported checksum type '%d' in cbk file generation!\n", checksum_type);
	return FALSE;
    }

    cbk = g_array_sized_new(FALSE, TRUE, 1,
			    cbk_header_size + 20);
    g_array_set_size(cbk, cbk_header_size + 20);

    locations_filename = g_build_filename(dirname, "Locations.itdb", NULL);
    success = cbk_calc_sha1s(locations_filename, cbk);
    g_free(locations_filename);
    if (!success) {
	g_array_free(cbk, TRUE);
	return FALSE;
    }
    cbk_calc_sha1_of_sha1s(cbk, cbk_header_size);
    final_sha1 = &g_array_index(cbk, guchar, cbk_header_size);
    cbk_hash = &g_array_index(cbk, guchar, 0);
    switch (checksum_type) {
	case ITDB_CHECKSUM_HASHAB:
	    success = itdb_hashAB_compute_hash_for_sha1 (itdb->device, final_sha1, cbk_hash, NULL);
	    break;
	case ITDB_CHECKSUM_HASH58:
	    /* the nano 5g advertises DBVersion 3 but expects an hash72 on
	     * its cbk file.
	     */
	case ITDB_CHECKSUM_HASH72:
	    success = itdb_hash72_compute_hash_for_sha1 (itdb->device, final_sha1, cbk_hash, NULL);
	    break;
	default:
	    break;
    }
    if (!success) {
	g_array_free(cbk, TRUE);
	return FALSE;
    }

    cbk_filename = g_build_filename(dirname, "Locations.itdb.cbk", NULL);
    success = g_file_set_contents(cbk_filename, cbk->data, cbk->len, NULL);
    g_free(cbk_filename);
    g_array_free(cbk, TRUE);
    return success;
}

static int build_itdb_files(Itdb_iTunesDB *itdb,
			     GHashTable *album_ids, GHashTable *artist_ids,
			     GHashTable *composer_ids,
			     const char *outpath, const char *uuid,
                             GError **error)
{
    if (mk_Dynamic(itdb, outpath) != 0) {
	g_set_error (error, ITDB_ERROR, ITDB_ERROR_SQLITE,
		     "an error occurred during Dynamic.itdb generation");
	return -1;
    }
    if (mk_Extras(itdb, outpath) != 0) {
	g_set_error (error, ITDB_ERROR, ITDB_ERROR_SQLITE,
		     "an error occurred during Extras.itdb generation");
	return -1;
    }
    if (mk_Genius(itdb, outpath) != 0) {
	g_set_error (error, ITDB_ERROR, ITDB_ERROR_SQLITE,
		     "an error occurred during Genius.itdb generation");
	return -1;
    }
    if (mk_Library(itdb, album_ids, artist_ids, composer_ids, outpath) != 0) {
	g_set_error (error, ITDB_ERROR, ITDB_ERROR_SQLITE,
		     "an error occurred during Library.itdb generation");
	return -1;
    }
    if (mk_Locations(itdb, outpath, uuid) != 0) {
	g_set_error (error, ITDB_ERROR, ITDB_ERROR_SQLITE,
		     "an error occurred during Locations.itdb generation");
	return -1;
    }

    run_post_process_commands(itdb, outpath, uuid);

    if (!mk_Locations_cbk(itdb, outpath)) {
	g_set_error (error, ITDB_ERROR, ITDB_ERROR_SQLITE,
		     "an error occurred during Locations.itdb.cbk generation");
	return -1;
    }

    return 0;
}

static int ensure_itlp_dir_exists(const char *itlpdir, GError **error)
{
    /* check if directory exists */
    if (!g_file_test(itlpdir, G_FILE_TEST_EXISTS)) {
	if (g_mkdir(itlpdir, 0755) != 0) {
	    g_set_error (error, G_FILE_ERROR,
			 g_file_error_from_errno(errno),
			 "Could not create directory '%s': %s",
			 itlpdir, strerror(errno));
	    return FALSE;
	}
    } else if (!g_file_test(itlpdir, G_FILE_TEST_IS_DIR)) {
	g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_NOTDIR,
		     "'%s' is not a directory as it should be",
		     itlpdir);
	return FALSE;
    }

    return TRUE;
}

static int copy_itdb_file(const gchar *from_dir, const gchar *to_dir,
			  const gchar *fname, GError **error)
{
    int res = 0;

    gchar *srcname = g_build_filename(from_dir, fname, NULL);
    gchar *dstname = g_build_filename(to_dir, fname, NULL);

    if (itdb_cp(srcname, dstname, error)) {
	fprintf(stderr, "itdbprep: copying '%s'\n", fname);
	res++;
    }
    if (error && *error) {
	fprintf(stderr, "Error copying '%s' to '%s': %s\n", srcname, dstname, (*error)->message);
    }

    if (srcname) {
	g_free(srcname);
    }

    if (dstname) {
	g_free(dstname);
    }

    return res;
}

static void rmdir_recursive(gchar *path)
{
    GDir *cur_dir;
    const gchar *dir_file;

    cur_dir = g_dir_open(path, 0, NULL);
    if (cur_dir) while ((dir_file = g_dir_read_name(cur_dir)))
    {
	gchar *fpath = g_build_filename(path, dir_file, NULL);
	if (fpath) {
	    if (g_file_test(fpath, G_FILE_TEST_IS_DIR)) {
		rmdir_recursive(fpath);
	    } else {
		g_unlink(fpath);
	    }
	    g_free(fpath);
	}
    }
    if (cur_dir) {
	g_dir_close(cur_dir);
    }
    g_rmdir(path);
}

int itdb_sqlite_generate_itdbs(FExport *fexp)
{
    int res = 0;
    gchar *itlpdir;
    gchar *dirname;
    gchar *tmpdir = NULL;

    printf("libitdbprep: %s called with file %s and uuid %s\n", __func__,
	   fexp->itdb->filename, itdb_device_get_uuid(fexp->itdb->device));

    dirname = itdb_get_itunes_dir(itdb_get_mountpoint(fexp->itdb));
    itlpdir = g_build_filename(dirname, "iTunes Library.itlp", NULL);
    g_free(dirname);

    printf("itlp directory='%s'\n", itlpdir);

    if (!ensure_itlp_dir_exists(itlpdir, &fexp->error)) {
	res = -1;
	goto leave;
    }

    printf("*.itdb files will be stored in '%s'\n", itlpdir);

    g_assert(fexp->itdb != NULL);
    g_assert(fexp->itdb->playlists != NULL);

    tzoffset = fexp->itdb->tzoffset;

    tmpdir = g_build_path(g_get_tmp_dir(), tmpnam(NULL), NULL);
    if (g_mkdir(tmpdir, 0755) != 0) {
	g_set_error (&fexp->error, G_FILE_ERROR, g_file_error_from_errno(errno),
		     "Could not create temporary directory '%s': %s",
		     tmpdir, strerror(errno));
	res = -1;
	goto leave;
    }

    /* generate itdb files in temporary directory */
    if (build_itdb_files(fexp->itdb, fexp->albums, fexp->artists, fexp->composers, tmpdir,
		     itdb_device_get_uuid(fexp->itdb->device), &fexp->error) != 0) {
	g_prefix_error (&fexp->error, "Failed to generate sqlite database: ");
	res = -1;
	goto leave;
    } else {
	/* copy files */
	const char *itdb_files[] = { "Dynamic.itdb", "Extras.itdb",
				     "Genius.itdb", "Library.itdb",
				     "Locations.itdb", "Locations.itdb.cbk",
				     NULL };
	const char **file;
	GError *error = NULL;
	g_assert (fexp->error == NULL);
	for (file = itdb_files; *file != NULL; file++) {
	    copy_itdb_file(tmpdir, itlpdir, *file, &error);
	    if (error) {
		res = -1;
		/* only the last error will be reported, but this way we
		 * copy as many files as possible even when errors occur
		 */
		g_clear_error (&fexp->error);
		g_propagate_error (&fexp->error, error);
	    }
	}
	if (fexp->error) {
	    goto leave;
	}
    }

    res = 0;
leave:
    if (itlpdir) {
	g_free(itlpdir);
    }
    if (tmpdir) {
	/* cleanup tmpdir, this will also delete tmpdir itself */
	rmdir_recursive(tmpdir);
	g_free(tmpdir);
    }

    return res;
}