Blob Blame History Raw
/*
 *  Copyright (C) 2005-2007 Christophe Fergeau
 *
 *
 *  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
 *
 *  iTunes and iPod are trademarks of Apple
 *
 *  This product is not supported/written/published by Apple!
 *
 */

#include <config.h>
#include "itdb.h"
#include "itdb_device.h"
#include "itdb_private.h"
#include "db-artwork-parser.h"

#if HAVE_GDKPIXBUF
#include <gdk-pixbuf/gdk-pixbuf.h>

#include "db-artwork-debug.h"
#include "db-itunes-parser.h"
#include "db-image-parser.h"
#include "itdb_endianness.h"

#include <glib/gstdio.h>

#include <errno.h>
#include <fcntl.h>
#include <string.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <sys/stat.h>
#include <stdio.h>
#include <sys/types.h>

#define DEFAULT_GSTRING_SIZE 128*1024

struct iPodSharedDataBuffer {
	GString *data;
	char *filename;
	int ref_count;
};

struct _iPodBuffer {
	struct iPodSharedDataBuffer *shared;
	off_t offset;
	guint byte_order;
	DbType db_type;
};

typedef struct _iPodBuffer iPodBuffer;

static gboolean
ipod_gstring_flush (struct iPodSharedDataBuffer *shared, GError **error)
{
	gboolean success;

	success = g_file_set_contents (shared->filename, 
	 	 		       shared->data->str, shared->data->len,
				       error);
	if (!success) {
                return FALSE;
	}
	g_string_free (shared->data, TRUE);
	g_free (shared->filename);
	g_free (shared);

	return TRUE;
}

static void
ipod_buffer_destroy (iPodBuffer *buffer)
{
	buffer->shared->ref_count--;
	if (buffer->shared->ref_count == 0) {
		ipod_gstring_flush (buffer->shared, NULL);
	}
	g_free (buffer);
}


static void *
ipod_buffer_get_pointer (iPodBuffer *buffer)
{
	if (buffer->shared->data->str == NULL) {
		return NULL;
	}
	g_assert (buffer->offset < buffer->shared->data->len);
	return &((unsigned char *)buffer->shared->data->str)[buffer->offset];
}

static void
ipod_buffer_maybe_grow (iPodBuffer *buffer, off_t size)
{
	g_string_set_size (buffer->shared->data, 
			   buffer->shared->data->len + size);
}

static iPodBuffer *
ipod_buffer_get_sub_buffer (iPodBuffer *buffer, off_t offset)
{
	iPodBuffer *sub_buffer;

	g_assert (buffer->offset + offset <= buffer->shared->data->len);

	sub_buffer = g_new0 (iPodBuffer, 1);
	if (sub_buffer == NULL) {
		return NULL;
	}
	sub_buffer->shared = buffer->shared;
	sub_buffer->offset = buffer->offset + offset;
	sub_buffer->byte_order = buffer->byte_order;
	sub_buffer->db_type = buffer->db_type;

	buffer->shared->ref_count++;

	return sub_buffer;
}

static iPodBuffer *
ipod_buffer_new (const char *filename, guint byte_order, DbType db_type)
{
	struct iPodSharedDataBuffer *shared;
	iPodBuffer *buffer;

	shared = g_new0 (struct iPodSharedDataBuffer, 1);
	if (shared == NULL) {
		return NULL;
	}
	shared->filename = g_strdup (filename);
	shared->data = g_string_sized_new (DEFAULT_GSTRING_SIZE);
	shared->ref_count = 1;

	buffer = g_new0 (iPodBuffer, 1);
	if (buffer == NULL) {
		g_free (shared->filename);
		g_string_free (shared->data, TRUE);
		g_free (shared);
		return NULL;
	}
	buffer->shared = shared;
	buffer->byte_order = byte_order;
	buffer->db_type = db_type;

	return buffer;
}

enum MhsdType {
	MHSD_TYPE_MHLI = 1,
	MHSD_TYPE_MHLA = 2,
	MHSD_TYPE_MHLF = 3
};

#define RETURN_SIZE_FOR(id, size) if (strncmp (id, header_id, 4) == 0) return (size)



/* Returns the "real" size for a header, ie the size iTunes uses for it
 * (padding included)
 */
static int
get_padded_header_size (gchar header_id[4])
{
	RETURN_SIZE_FOR ("mhni", 0x4c);
	RETURN_SIZE_FOR ("mhii", 0x98);
	RETURN_SIZE_FOR ("mhsd", 0x60);
	RETURN_SIZE_FOR ("mhfd", 0x84);
	RETURN_SIZE_FOR ("mhli", 0x5c);
	RETURN_SIZE_FOR ("mhla", 0x5c);
	RETURN_SIZE_FOR ("mhlf", 0x5c);
	RETURN_SIZE_FOR ("mhif", 0x7c);
	RETURN_SIZE_FOR ("mhba", 0x94);

	return 0;
}

static void *
init_header (iPodBuffer *buffer, gchar _header_id[4], guint header_len)
{
	MHeader *mh;
	int padded_size;
	gchar *header_id;

	padded_size = get_padded_header_size (_header_id);
	if (padded_size != 0) {
		header_len = padded_size;
	}
	g_assert (header_len > sizeof (MHeader));
	ipod_buffer_maybe_grow (buffer, header_len);

	mh = (MHeader*)ipod_buffer_get_pointer (buffer);
	if (mh == NULL) {
		return NULL;
	}
	memset (mh, 0, header_len);

	header_id = g_strndup (_header_id, 4);
	if (buffer->byte_order == G_BIG_ENDIAN) {
		g_strreverse (header_id);
	}
	strncpy ((char *)mh->header_id, header_id, 4);
	mh->header_len = get_gint32 (header_len, buffer->byte_order);

	g_free (header_id);
	return mh;
}


static int
write_mhod_type_1 (gchar *string, iPodBuffer *buffer)
{
	ArtworkDB_MhodHeaderString *mhod;
	unsigned int total_bytes;
	int len;
	int padding;

	g_assert (string != NULL);

	total_bytes = sizeof (ArtworkDB_MhodHeaderString);
	mhod = (ArtworkDB_MhodHeaderString *)init_header (buffer, "mhod",
                                                          total_bytes);
	if (mhod == NULL) {
		return -1;
	}
	mhod->total_len = get_gint32 (total_bytes, buffer->byte_order);
	/* Modify header length, since iTunes only puts the length of
	 * MhodHeader in header_len
	 */
	mhod->header_len = get_gint32 (sizeof (ArtworkDB_MhodHeader), buffer->byte_order);
	mhod->encoding = get_gint32 (0x01, buffer->byte_order);
	len = strlen (string);
	mhod->string_len = get_gint32 (len, buffer->byte_order);

	padding = 4 - ( (total_bytes + len) % 4 );
	if (padding == 4)
	    padding = 0;
	mhod->padding_len = padding;
	mhod->type = get_gint16 (0x01, buffer->byte_order);

	/* Make sure we have enough free space to write the string */
	ipod_buffer_maybe_grow (buffer, len + padding);
	mhod = ipod_buffer_get_pointer (buffer);
	if (mhod == NULL) {
		return -1;
	}
	memcpy (mhod->string, string, len);
	total_bytes += len + padding;
	mhod->total_len = get_gint32 (total_bytes, buffer->byte_order);

	dump_mhod_string (mhod);

	return total_bytes;
}

static int
write_mhod_type_3 (gchar *string, iPodBuffer *buffer)
{
	ArtworkDB_MhodHeaderString *mhod;
	unsigned int total_bytes;
	glong len;
	const gint g2l = sizeof (gunichar2);
	gunichar2 *utf16, *strp;
	int i, padding;

	g_assert (string != NULL);

	total_bytes = sizeof (ArtworkDB_MhodHeaderString);
	mhod = (ArtworkDB_MhodHeaderString *) init_header (buffer, "mhod", 
                                                           total_bytes);
	if (mhod == NULL) {
		return -1;
	}
	mhod->total_len = get_gint32 (total_bytes, buffer->byte_order);
	/* Modify header length, since iTunes only puts the length of
	 * MhodHeader in header_len
	 */
	mhod->header_len = get_gint32 (sizeof (ArtworkDB_MhodHeader),
				       buffer->byte_order);
	mhod->type = get_gint16 (3, buffer->byte_order);

	/* FIXME: Tidy this up, combine cases more */
	/* Some magic: endianess-reversed (BE) mobile phones use UTF8
	 * (version 1) with padding, standard iPods (LE) use UTF16
	 * (version 2).*/
	switch (buffer->byte_order)
	{
	case G_LITTLE_ENDIAN:
	    utf16 = g_utf8_to_utf16 (string, -1, NULL, &len, NULL);
	    if (utf16 == NULL) {
		return -1;
	    }

	    mhod->encoding = 2; /* 8 bit field, no need to byteswap */

	    /* number of bytes of the string encoded in UTF-16 */
	    mhod->string_len = get_gint32 (g2l * len, buffer->byte_order);
	    padding = 4 - ( (total_bytes + g2l*len) % 4 );
	    if (padding == 4)
		padding = 0;
	    mhod->padding_len = padding; /* 8 bit field, no need to byteswap */

 	    total_bytes += g2l*len + padding;

	    /* Make sure we have enough free space to write the string */
	    ipod_buffer_maybe_grow (buffer, g2l*len + padding);
	    mhod = ipod_buffer_get_pointer (buffer);
	    if (mhod == NULL) {
		    g_free (utf16);
		    return  -1;
	    }
	    strp = (gunichar2 *)mhod->string;
	    for (i = 0; i < len; i++) {
		strp[i] = get_gint16 (utf16[i], buffer->byte_order);
	    }
	    g_free (utf16);
	    memset (mhod->string + g2l*len, 0, padding);
	    break;
	case G_BIG_ENDIAN:
	    mhod->encoding = 1; /* 8 bit field, no need to byteswap */
            /* FIXME: len isn't initialized */
	    mhod->string_len = get_gint32 (len, buffer->byte_order);
	    /* pad string if necessary */
	    /* e.g. len = 7 bytes, len%4 = 3, 4-3=1 -> requires 1 byte
	       padding */
	    padding = 4 - ( (total_bytes + len) % 4 );
	    if (padding == 4)
		padding = 0;
	    mhod->padding_len = padding; /* 8 bit field, no need to byteswap */

	    /* Make sure we have enough free space to write the string */
	    ipod_buffer_maybe_grow (buffer, len+padding);
	    mhod = ipod_buffer_get_pointer (buffer);
	    if (mhod == NULL) {
		    return -1;
	    }
	    memcpy (mhod->string, string, len);
	    memset (mhod->string + len, 0, padding);
	    total_bytes += (len+padding);
	}
	mhod->total_len = get_gint32 (total_bytes, buffer->byte_order);

	dump_mhod_string (mhod);

	return total_bytes;
}

static int
write_mhni (Itdb_DB *db, Itdb_Thumb_Ipod_Item *item, iPodBuffer *buffer)
{
	MhniHeader *mhni;
	unsigned int total_bytes;
	int bytes_written;
	iPodBuffer *sub_buffer;
        const Itdb_ArtworkFormat *format;

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

	mhni = (MhniHeader *)init_header (buffer, "mhni",
					  sizeof (MhniHeader));
	if (mhni == NULL) {
		return -1;
	}
	total_bytes =          get_gint32 (mhni->header_len,
					   buffer->byte_order);
	mhni->total_len =      get_gint32 (total_bytes,
					   buffer->byte_order);
        format = item->format;
	mhni->format_id = get_gint32 (format->format_id, buffer->byte_order);
	mhni->image_width =    get_gint16 (item->width, buffer->byte_order);
	mhni->image_height =   get_gint16 (item->height, buffer->byte_order);
	mhni->image_size =     get_gint32 (item->size, buffer->byte_order);
	mhni->ithmb_offset =   get_gint32 (item->offset, buffer->byte_order);
	mhni->vertical_padding = get_gint16 (item->vertical_padding,
					     buffer->byte_order);
	mhni->horizontal_padding = get_gint16 (item->horizontal_padding,
					       buffer->byte_order);

	sub_buffer = ipod_buffer_get_sub_buffer (buffer, total_bytes);
	if (sub_buffer == NULL) {
		return  -1;
	}
	bytes_written = write_mhod_type_3 (item->filename, sub_buffer);
	ipod_buffer_destroy (sub_buffer);
	if (bytes_written == -1) {
		return -1;
	}
	total_bytes += bytes_written;
	mhni = ipod_buffer_get_pointer (buffer);
	mhni->total_len = get_gint32 (total_bytes, buffer->byte_order);
	/* Only update number of children when all went well to try to get
	 * something somewhat consistent when there are errors
	 */
	mhni->num_children = get_gint32 (1, buffer->byte_order);

	dump_mhni (mhni);

	return total_bytes;
}

static int
write_mhod (Itdb_DB *db, Itdb_Thumb_Ipod_Item *thumb, iPodBuffer *buffer)
{
	ArtworkDB_MhodHeader *mhod;
	unsigned int total_bytes;
	int bytes_written;
	iPodBuffer *sub_buffer;

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

	mhod = (ArtworkDB_MhodHeader *)
	    init_header (buffer, "mhod",
			 sizeof (ArtworkDB_MhodHeader));
	if (mhod == NULL) {
		return -1;
	}
	total_bytes = sizeof (ArtworkDB_MhodHeader);
	mhod->total_len = get_gint32 (total_bytes, buffer->byte_order);
	mhod->type = get_gint16 (MHOD_TYPE_LOCATION, buffer->byte_order);
	sub_buffer = ipod_buffer_get_sub_buffer (buffer, total_bytes);
	if (sub_buffer == NULL) {
		return -1;
	}
	bytes_written = write_mhni (db, thumb, sub_buffer);
	ipod_buffer_destroy (sub_buffer);
	if (bytes_written == -1) {
		return -1;
	}
	total_bytes += bytes_written;
	mhod = ipod_buffer_get_pointer (buffer);
	mhod->total_len = get_gint32 (total_bytes, buffer->byte_order);

	dump_mhod (mhod);

	return total_bytes;
}

static int
write_mhii (Itdb_DB *db, void *data, iPodBuffer *buffer)
{
	MhiiHeader *mhii;
	unsigned int total_bytes;
	int bytes_written;
	int num_children;
	const GList *it = NULL;
	Itdb_Track *song;
	Itdb_Artwork *artwork;
	guint64 mactime;
	Itdb_Device *device = db_get_device (db);

	mhii = (MhiiHeader *)init_header (buffer, "mhii", sizeof (MhiiHeader));
	if (mhii == NULL) {
		return -1;
	}
	total_bytes = get_gint32 (mhii->header_len, buffer->byte_order);

	switch( buffer->db_type) {
	case DB_TYPE_ITUNES:
		song = (Itdb_Track *)data;
		artwork = song->artwork;
		mhii->song_id = get_gint64 (song->dbid, buffer->byte_order);
		break;
	case DB_TYPE_PHOTO:
		artwork = (Itdb_Artwork *)data;
		mhii->song_id = get_gint64 (artwork->id + 2, buffer->byte_order);
		break;
	default:
	        g_return_val_if_reached (-1);
	}
	mhii->image_id = get_guint32 (artwork->id, buffer->byte_order);
	mhii->unknown4 = get_gint32 (artwork->unk028, buffer->byte_order);
	mhii->rating = get_gint32 (artwork->rating, buffer->byte_order);
	mhii->unknown6 = get_gint32 (artwork->unk036, buffer->byte_order);

	mactime = device_time_time_t_to_mac (device, artwork->creation_date);
	mhii->orig_date = get_guint32 (mactime, buffer->byte_order);

	mactime = device_time_time_t_to_mac (device, artwork->digitized_date);
	mhii->digitized_date = get_guint32 (mactime, buffer->byte_order);

	mhii->orig_img_size = get_gint32 (artwork->artwork_size, buffer->byte_order);
	num_children = 0;
        /* Before trying to write the artwork or photo database, the ithmb
         * files have been written, which will have converted all thumbnails 
         * attached to the tracks to ITDB_THUMB_TYPE_IPOD thumbnails.
         */
        g_assert (artwork->thumbnail->data_type == ITDB_THUMB_TYPE_IPOD);
	for (it=itdb_thumb_ipod_get_thumbs ((Itdb_Thumb_Ipod *)artwork->thumbnail); 
             it!=NULL; 
             it=it->next)
	{
		iPodBuffer *sub_buffer;
		Itdb_Thumb_Ipod_Item *thumb;

                thumb = (Itdb_Thumb_Ipod_Item *)it->data;
                if (thumb->format == NULL) {
		    /* skip this thumb */
		    continue;
		}

		mhii->num_children = get_gint32 (num_children,
						 buffer->byte_order);
		mhii->total_len = get_gint32 (total_bytes, buffer->byte_order);
		sub_buffer = ipod_buffer_get_sub_buffer (buffer, total_bytes);
		if (sub_buffer == NULL) {
			return -1;
		}
		bytes_written = write_mhod (db, thumb, sub_buffer);
		ipod_buffer_destroy (sub_buffer);
		if (bytes_written == -1) {
			return -1;
		}
		total_bytes += bytes_written;
		mhii = ipod_buffer_get_pointer (buffer);
		num_children++;
	}

	mhii->num_children = get_gint32 (num_children, buffer->byte_order);
	mhii->total_len = get_gint32 (total_bytes, buffer->byte_order);

	dump_mhii (mhii);

	return total_bytes;
}

static int
write_mhli (Itdb_DB *db, iPodBuffer *buffer )
{
	GList *it = NULL;
	MhliHeader *mhli;
	unsigned int total_bytes;
	int num_thumbs;

	mhli = (MhliHeader *)init_header (buffer, "mhli", sizeof (MhliHeader));
	if (mhli == NULL) {
		return -1;
	}

	num_thumbs = 0;
	total_bytes = get_gint32 (mhli->header_len, buffer->byte_order);
	switch (buffer->db_type) {
	case DB_TYPE_PHOTO:
		it = db_get_photodb(db)->photos;
		break;
	case DB_TYPE_ITUNES:
		it = db_get_itunesdb(db)->tracks;
		break;
	default:
	        g_return_val_if_reached (-1);
	}
	while (it != NULL) {
		Itdb_Track *song;
		int bytes_written;
		iPodBuffer *sub_buffer;
		if (buffer->db_type == DB_TYPE_ITUNES) {
			song = (Itdb_Track*)it->data;
			if (!song->artwork->thumbnail || (song->artwork->dbid == 0)) {
				it = it->next;
				continue;
			}
		}
		sub_buffer = ipod_buffer_get_sub_buffer (buffer, total_bytes);
		if (sub_buffer == NULL) {
			break;
		}
		bytes_written = write_mhii (db, it->data, sub_buffer);
		ipod_buffer_destroy (sub_buffer);
		if (bytes_written != -1) {
			num_thumbs++;
			total_bytes += bytes_written;
		}
		it = it->next;
	}
	mhli = ipod_buffer_get_pointer (buffer);
	mhli->num_children = get_gint32 (num_thumbs, buffer->byte_order);
	dump_mhl ((MhlHeader *)mhli, "mhli");

	return total_bytes;
}

static int
write_mhia (gint image_id, iPodBuffer *buffer)
{
	MhiaHeader *mhia;
	unsigned int total_bytes;


	mhia = (MhiaHeader *)init_header (buffer, "mhia", 40);
	if (mhia == NULL) {
		return -1;
	}

	mhia->total_len = mhia->header_len;
	mhia->image_id = get_gint32 (image_id, buffer->byte_order);
	total_bytes = get_gint32 (mhia->header_len, buffer->byte_order);
	dump_mhia( mhia );
	return total_bytes;
}

static int
write_mhba (Itdb_PhotoAlbum *album, iPodBuffer *buffer)
{
	GList *it;
	MhbaHeader *mhba;
	iPodBuffer *sub_buffer;
	unsigned int total_bytes;
	unsigned int bytes_written;

	mhba = (MhbaHeader *)init_header (buffer, "mhba", sizeof (MhbaHeader));
	if (mhba == NULL) {
		return -1;
	}
	mhba->num_mhods = get_gint32(1, buffer->byte_order);
	mhba->num_mhias = get_gint32(g_list_length (album->members),
				     buffer->byte_order);
	mhba->album_id = get_gint32(album->album_id, buffer->byte_order);
	mhba->unk024 = get_gint32(album->unk024, buffer->byte_order);
	mhba->unk028 = get_gint16(album->unk028, buffer->byte_order);
	mhba->album_type = album->album_type;
	mhba->playmusic = album->playmusic;
	mhba->repeat = album->repeat;
	mhba->random = album->random;
	mhba->show_titles = album->show_titles;
	mhba->transition_direction = album->transition_direction;
	mhba->slide_duration = get_gint32(album->slide_duration,
					  buffer->byte_order);
	mhba->transition_duration = get_gint32(album->transition_duration,
					       buffer->byte_order);
	mhba->unk044 = get_gint32(album->unk044, buffer->byte_order);
	mhba->unk048 = get_gint32(album->unk048, buffer->byte_order);
	mhba->song_id = get_gint64(album->song_id, buffer->byte_order);
	mhba->prev_album_id = get_gint32(album->prev_album_id,
					 buffer->byte_order);

	total_bytes = get_gint32 (mhba->header_len, buffer->byte_order);

	/* FIXME: Write other mhods */
	/* Write album title */
	sub_buffer = ipod_buffer_get_sub_buffer (buffer, total_bytes);
	if (sub_buffer == NULL) {
	    return -1;
	}
	bytes_written = write_mhod_type_1 (album->name, sub_buffer);
	ipod_buffer_destroy (sub_buffer);
	if (bytes_written == -1) {
	    return -1;
	}
	total_bytes += bytes_written;

	for (it = album->members; it != NULL; it = it->next) {
	        Itdb_Artwork *photo = it->data;
		g_return_val_if_fail (photo, -1);

		sub_buffer = ipod_buffer_get_sub_buffer (buffer, total_bytes);
		if (sub_buffer == NULL) {
		    return -1;
		}
		bytes_written = write_mhia (photo->id, sub_buffer);
		ipod_buffer_destroy (sub_buffer);
		if (bytes_written == -1) {
		    return -1;
		}
		total_bytes += bytes_written;
	}
	mhba = ipod_buffer_get_pointer (buffer);
	mhba->total_len = get_gint32( total_bytes, buffer->byte_order );
	dump_mhba ( mhba );
	return total_bytes;
}

static int
write_mhla (Itdb_DB *db, iPodBuffer *buffer)
{
	GList *it;
	MhlaHeader *mhla;
	iPodBuffer *sub_buffer;
	unsigned int total_bytes;

	mhla = (MhlaHeader *)init_header (buffer, "mhla", sizeof (MhlaHeader));
	if (mhla == NULL) {
		return -1;
	}
	total_bytes = get_gint32 (mhla->header_len, buffer->byte_order);
	if (buffer->db_type == DB_TYPE_PHOTO) {
	    unsigned int bytes_written;
            unsigned int num_children = 0;
	    for (it = db_get_photodb(db)->photoalbums; it != NULL; it = it->next) {
		Itdb_PhotoAlbum *album = (Itdb_PhotoAlbum *)it->data;

		sub_buffer = ipod_buffer_get_sub_buffer (buffer, total_bytes);
		if (sub_buffer == NULL) {
		    return -1;
		}
		bytes_written = write_mhba (album, sub_buffer);
		ipod_buffer_destroy (sub_buffer);
		if (bytes_written == -1) {
		    return -1;
		}
		total_bytes += bytes_written;
		mhla = ipod_buffer_get_pointer (buffer);
		num_children++;
		mhla->num_children = get_gint32 (num_children,
                                                 buffer->byte_order);
	    }
	}

	dump_mhl ((MhlHeader *)mhla, "mhla");

	return total_bytes;
}

static int
write_mhif (Itdb_DB *db, iPodBuffer *buffer,
            const Itdb_ArtworkFormat *img_info)
{
	MhifHeader *mhif;

	mhif = (MhifHeader *)init_header (buffer, "mhif", sizeof (MhifHeader));
	if (mhif == NULL) {
		return -1;
	}
	mhif->total_len = mhif->header_len;

	mhif->format_id = get_gint32 (img_info->format_id,
		               	      buffer->byte_order);
	mhif->image_size = get_gint32 (img_info->height * img_info->width * 2,
				       buffer->byte_order);

	dump_mhif (mhif);

	return get_gint32 (mhif->header_len, buffer->byte_order);
}

static int
write_mhlf (Itdb_DB *db, iPodBuffer *buffer)
{
	MhlfHeader *mhlf;
	unsigned int total_bytes;
	int bytes_written;
        GList *formats; 
        GList *it;
        unsigned int num_children;

	mhlf = (MhlfHeader *)init_header (buffer, "mhlf", sizeof (MhlfHeader));
	if (mhlf == NULL) {
		return -1;
	}

	total_bytes = get_gint32 (mhlf->header_len, buffer->byte_order);
        num_children = 0;
        mhlf->num_files = get_gint32 (num_children, buffer->byte_order);

        formats = NULL;
        switch (buffer->db_type) {
        case DB_TYPE_ITUNES:
            formats = itdb_device_get_cover_art_formats(db_get_device(db));
            break;
	case DB_TYPE_PHOTO:
            formats = itdb_device_get_photo_formats(db_get_device(db));
            break;
        }
        if (formats == NULL) {
                return total_bytes;
        }

        for (it = formats; it != NULL; it = it->next) {
                const Itdb_ArtworkFormat *format;
	        iPodBuffer *sub_buffer;
                
                format = (const Itdb_ArtworkFormat *)it->data;
        	sub_buffer = ipod_buffer_get_sub_buffer (buffer, total_bytes);
        	if (sub_buffer == NULL) {
                        g_list_free (formats);
        		return -1;
        	}

        	bytes_written = write_mhif (db, sub_buffer, format);
	        			    
        	ipod_buffer_destroy (sub_buffer);
        	if (bytes_written == -1) {
        		return -1;
        	}
        	total_bytes += bytes_written;
		mhlf = ipod_buffer_get_pointer (buffer);

                num_children++;
        	/* Only update number of children when all went well to try 
                 * to get something somewhat consistent when there are errors
        	 */
        	mhlf->num_files = get_gint32 (num_children, buffer->byte_order);
        }
	dump_mhl ((MhlHeader *)mhlf, "mhlf");
        g_list_free (formats);

	return total_bytes;
}


static int
write_mhsd (Itdb_DB *db, iPodBuffer *buffer, enum MhsdType type)
{
	ArtworkDB_MhsdHeader *mhsd;
	unsigned int total_bytes;
	int bytes_written;
	iPodBuffer *sub_buffer;

	g_assert (type >= MHSD_TYPE_MHLI);
	g_assert (type <= MHSD_TYPE_MHLF);
	mhsd = (ArtworkDB_MhsdHeader *)init_header (buffer, "mhsd", sizeof (ArtworkDB_MhsdHeader));
	if (mhsd == NULL) {
		return -1;
	}
	total_bytes = get_gint32 (mhsd->header_len, buffer->byte_order);
	mhsd->total_len = get_gint32 (total_bytes, buffer->byte_order);
	mhsd->index = get_gint16 (type, buffer->byte_order);
	bytes_written = -1;

	sub_buffer = ipod_buffer_get_sub_buffer (buffer, total_bytes);
	if (sub_buffer == NULL) {
		return -1;
	}
	switch (type) {
	case MHSD_TYPE_MHLI:
		bytes_written = write_mhli (db, sub_buffer);
		break;
	case MHSD_TYPE_MHLA:
		bytes_written = write_mhla (db, sub_buffer);
		break;
	case MHSD_TYPE_MHLF:
		bytes_written = write_mhlf (db, sub_buffer);
		break;
	}
	ipod_buffer_destroy (sub_buffer);
	if (bytes_written == -1) {
		return -1;
	} else {
		total_bytes += bytes_written;
		mhsd = ipod_buffer_get_pointer (buffer);
		mhsd->total_len = get_gint32 (total_bytes, buffer->byte_order);
	}

	dump_mhsd (mhsd);

	return total_bytes;
}

static int
write_mhfd (Itdb_DB *db, iPodBuffer *buffer, int id_max)
{
	MhfdHeader *mhfd;
	unsigned int total_bytes;
	int bytes_written;
	int i;

	
	mhfd = (MhfdHeader *)init_header (buffer, "mhfd", sizeof (MhfdHeader));
	if (mhfd == NULL) {
		return -1;
	}
	total_bytes = get_gint32 (mhfd->header_len, buffer->byte_order);
	mhfd->total_len = get_gint32 (total_bytes, buffer->byte_order);
	switch (buffer->db_type) {
	case DB_TYPE_PHOTO:
		mhfd->unknown2 = get_gint32 (2, buffer->byte_order);
		break;
	case DB_TYPE_ITUNES:
		mhfd->unknown2 = get_gint32 (2, buffer->byte_order);
		break;
	}
	mhfd->next_id = get_gint32 (id_max, buffer->byte_order);
	mhfd->unknown_flag1 = 2;
	for (i = 1 ; i <= 3; i++) {
		iPodBuffer *sub_buffer;

		sub_buffer = ipod_buffer_get_sub_buffer (buffer, total_bytes);
		if (sub_buffer == NULL) {
			continue;
		}
		bytes_written = write_mhsd (db, sub_buffer, i);
		ipod_buffer_destroy (sub_buffer);
		if (bytes_written == -1) {
			return -1;
		}
		total_bytes += bytes_written;
		mhfd = ipod_buffer_get_pointer (buffer);
		mhfd->total_len = get_gint32 (total_bytes, buffer->byte_order);
		mhfd->num_children = get_gint32 (i, buffer->byte_order);
	}

	dump_mhfd (mhfd);

	return total_bytes;
}

/* renumber the artwork IDs for all tracks containing artwork and with
   an ID of != 0 */
/* if the iPod does not support sparse artwork, renumber consecutively
   and all artwork */
/* returns the highest assigned ID or 0 if no IDs were used */
static guint32
ipod_artwork_db_set_ids (Itdb_iTunesDB *db)
{
    GList *gl;
    const guint32 min_id = 0x64;
    guint32 cur_id;

    cur_id = min_id;

    if (itdb_device_supports_sparse_artwork (db->device))
    {
	GHashTable *id_hash;

	id_hash = g_hash_table_new (g_direct_hash, g_direct_equal);

	for (gl = db->tracks; gl != NULL; gl = gl->next)
	{
	    Itdb_Track *song;
	    Itdb_Artwork *artwork;

	    song = gl->data;
	    g_return_val_if_fail (song, -1);

	    artwork = song->artwork;
	    g_return_val_if_fail (artwork, -1);

	    if (itdb_track_has_thumbnails (song) && (artwork->id != 0))
	    {
		gpointer orig_key;
		gpointer orig_val;

		if (g_hash_table_lookup_extended (id_hash, GINT_TO_POINTER (artwork->id),
						  &orig_key, &orig_val))
		{   /* ID was encountered before */
		    artwork->id = GPOINTER_TO_INT (orig_val);
		    artwork->dbid = 0;
		}
		else
		{   /* first time we see this ID */
		    g_hash_table_insert (id_hash, GINT_TO_POINTER (artwork->id),
					 GINT_TO_POINTER (cur_id));
		    artwork->id = cur_id++;
		    artwork->dbid = song->dbid;
		}
		song->mhii_link = artwork->id;
	    }
	    else
	    {
		song->mhii_link = 0;
	    }
	}
	g_hash_table_destroy (id_hash);
    }
    else 
    {   /* iPod does not support sparse artwork -- just renumber */
	for (gl = db->tracks; gl != NULL; gl = gl->next)
	{
	    Itdb_Track *song;

	    song = gl->data;
	    g_return_val_if_fail (song, -1);
	    g_return_val_if_fail (song->artwork, -1);

	    song->mhii_link = 0;

	    if (itdb_track_has_thumbnails (song))
	    {
		song->artwork->id = cur_id++;
		song->artwork->dbid = song->dbid;
	    }
	    song->mhii_link = song->artwork->id;
	}
    }	

    if (cur_id == min_id)
	return 0;
    else
	return cur_id-1;
}



/* for all tracks with new artwork (id == 0) do the following:
   - try to identify if this artwork is identical to previous artwork
     (within the same album)
   - if no: assign new ID
   - if yes: assign same ID

   Returns the highest ID used.
*/
static guint32
ipod_artwork_mark_new_doubles (Itdb_iTunesDB *itdb, guint max_id)
{
    GList *gl;
    GHashTable *hash_file, *hash_memory, *hash_pixbuf;

    hash_file =   g_hash_table_new_full (g_str_hash, g_str_equal,
					 g_free, NULL);
    hash_memory = g_hash_table_new_full (g_str_hash, g_str_equal,
					 g_free, NULL);
    hash_pixbuf = g_hash_table_new_full (g_str_hash, g_str_equal,
					 g_free, NULL);

    for (gl=itdb->tracks; gl; gl=gl->next)
    {
	Itdb_Artwork *artwork;
	Itdb_Track *track;

	track= gl->data;
	g_return_val_if_fail (track, max_id);
	artwork = track->artwork;
	g_return_val_if_fail (artwork, max_id);

	if ((artwork->id == 0) && itdb_track_has_thumbnails (track))
	{
	    const gchar *checkstring;
	    GHashTable *hash=NULL;
	    gpointer orig_val = NULL;
	    Itdb_Thumb *thumb = artwork->thumbnail;
	    GChecksum *checksum = g_checksum_new (G_CHECKSUM_SHA1);

	    /* use the album name as part of the checksum */
	    if (track->album && *track->album)
	    {
		g_checksum_update (checksum,
				   (guchar *)track->album, strlen (track->album));
	    }

	    switch (thumb->data_type)
	    {
	    case ITDB_THUMB_TYPE_MEMORY:
	    {
		Itdb_Thumb_Memory *mthumb = (Itdb_Thumb_Memory *)thumb;
		g_checksum_update (checksum,
				   mthumb->image_data, mthumb->image_data_len);
		hash = hash_memory;
		break;
	    }
	    case ITDB_THUMB_TYPE_PIXBUF:
	    {
		Itdb_Thumb_Pixbuf *pthumb = (Itdb_Thumb_Pixbuf *)thumb;
		g_return_val_if_fail (pthumb->pixbuf, max_id);
		g_checksum_update (checksum,
				   gdk_pixbuf_get_pixels (pthumb->pixbuf),
				   gdk_pixbuf_get_height (pthumb->pixbuf) * gdk_pixbuf_get_rowstride (pthumb->pixbuf));
		hash = hash_pixbuf;
		break;
	    }
	    case ITDB_THUMB_TYPE_FILE:
	    {
		Itdb_Thumb_File *fthumb = (Itdb_Thumb_File *)thumb;
		g_return_val_if_fail (fthumb->filename, max_id);
		g_checksum_update (checksum,
				   (guchar *)fthumb->filename,
				   strlen (fthumb->filename));
		hash = hash_file;
		break;
	    }
	    case ITDB_THUMB_TYPE_INVALID:
		/* programming error */
		g_print ("encountered invalid thumb.\n");
		g_return_val_if_reached (max_id);
		break;
	    case ITDB_THUMB_TYPE_IPOD:
		/* thumbs on the iPod are definitely not expected to
		   have an ID of 0 */
		g_print ("encountered iPod thumb with ID = 0.\n");
		g_return_val_if_reached (max_id);
		break;
	    }

	    checkstring = g_checksum_get_string (checksum);
	    if (g_hash_table_lookup_extended (hash, checkstring, NULL, &orig_val))
	    {   /* same artwork was used before */
		Itdb_Artwork *previous_artwork = orig_val;
		artwork->id = previous_artwork->id;
		artwork->dbid = 0;
	    }
	    else
	    {   /* first occurence of this artwork */
		artwork->id = ++max_id;
		artwork->dbid = track->dbid;
		g_hash_table_insert (hash, g_strdup (checkstring), artwork);
	    }
	    track->mhii_link = artwork->id;
	    g_checksum_free (checksum);
	}
    }

    g_hash_table_destroy (hash_memory);
    g_hash_table_destroy (hash_file);
    g_hash_table_destroy (hash_pixbuf);

    return max_id;
}


/* returns the highest ID used */
static guint32 itdb_prepare_thumbnails (Itdb_iTunesDB *itdb)
{
    gint max_id;

    /* first renumber the old thumbnails to make sure we don't get too
     * high */
    max_id = ipod_artwork_db_set_ids (itdb);

    if (itdb_device_supports_sparse_artwork (itdb->device))
    {
	/* go through all newly added artwork and pass out new IDs. the
	   same ID will be assigned to identical artwork within one album */
	max_id = ipod_artwork_mark_new_doubles (itdb, max_id);

	/* set the IDs again to make sure they are in the right order */
	max_id = ipod_artwork_db_set_ids (itdb);
    }

    return max_id;
}




G_GNUC_INTERNAL int
ipod_write_artwork_db (Itdb_iTunesDB *itdb)
{
	iPodBuffer *buf;
	int bytes_written;
	char *filename;
	int id_max;
	Itdb_DB db;
	int status;

	db.db_type = DB_TYPE_ITUNES;
	db.db.itdb = itdb;

	id_max = itdb_prepare_thumbnails (itdb);

	/* First, let's write the .ithmb files, this will create the
	 * various thumbnails as well */

	status = itdb_write_ithumb_files (&db);
	if (status != 0) {
		return -1;
	}

	filename = ipod_db_get_artwork_db_path (itdb_get_mountpoint (itdb));
	if (filename == NULL) {
		/* FIXME: the iTunesDB will be inconsistent wrt artwork_count
		 * it might be better to 0 out this field in all tracks
		 * when we encounter an error
		 */
		return -1;
	}
	buf = ipod_buffer_new (filename, itdb->device->byte_order, DB_TYPE_ITUNES);
	if (buf == NULL) {
		g_print ("Couldn't create %s\n", filename);
	        g_free (filename);
		return -1;
	}
	bytes_written = write_mhfd (&db, buf, id_max);

	/* Refcount of the shared buffer should drop to 0 and this should
	 * sync buffered data to disk
	 */
	ipod_buffer_destroy (buf);

	if (bytes_written == -1) {
		g_print ("Failed to save %s\n", filename);
		/* FIXME: maybe should unlink the file we may have created */
		g_free (filename);
		return -1;
	}
	g_free (filename);
	return 0;
}

int
ipod_write_photo_db (Itdb_PhotoDB *photodb)
{
	iPodBuffer *buf;
	int bytes_written;
	char *filename;
	int id_max;
	Itdb_DB db;
	int status;

	db.db_type = DB_TYPE_PHOTO;
	db.db.photodb = photodb;

	filename = ipod_db_get_photos_db_path (db_get_mountpoint (&db));

	status = itdb_write_ithumb_files (&db);
	if (status != 0) {
		return -1;
	}

	if (filename == NULL) {
		return -1;
	}
	buf = ipod_buffer_new (filename, photodb->device->byte_order, DB_TYPE_PHOTO);
	if (buf == NULL) {
		g_print ("Couldn't create %s\n", filename);
		g_free (filename);
		return -1;
	}
	id_max = itdb_get_max_photo_id( photodb );
	bytes_written = write_mhfd (&db, buf, id_max+1);

	/* Refcount of the shared buffer should drop to 0 and this should
	 * sync buffered data to disk
	 */
	ipod_buffer_destroy (buf);

	if (bytes_written == -1) {
		g_print ("Failed to save %s\n", filename);
		/* FIXME: maybe should unlink the file we may have created */
	        g_free (filename);
		return -1;
	}
        g_free (filename);

	return 0;
}
#else
G_GNUC_INTERNAL int
ipod_write_artwork_db (Itdb_iTunesDB *itdb)
{
    return -1;
}

int
ipod_write_photo_db (Itdb_PhotoDB *photodb)
{
    return -1;
}
#endif