/*
|  Copyright (C) 2002-2007 Jorg Schuler <jcsjcs at users sourceforge net>
|  Part of the gtkpod project.
| 
|  URL: http://www.gtkpod.org/
|  URL: http://gtkpod.sourceforge.net/
|
|  Part of this code is based on ipod-device.c from the libipoddevice
|  project (http://64.14.94.162/index.php/Libipoddevice).
|
|  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!
|
|  $Id$
*/
#include <config.h>

#include "db-itunes-parser.h"
#include "itdb_device.h"
#include "itdb_private.h"
#include <ctype.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <glib/gi18n-lib.h>

static const Itdb_IpodInfo ipod_info_table [] = {
    /* Handle idiots who hose their iPod file system, or lucky people
       with iPods we don't yet know about*/
    {"Invalid", 0,  ITDB_IPOD_MODEL_INVALID,     ITDB_IPOD_GENERATION_UNKNOWN, 0},
    {"Unknown", 0,  ITDB_IPOD_MODEL_UNKNOWN,     ITDB_IPOD_GENERATION_UNKNOWN, 0},

    /* First Generation */
    /* Mechanical buttons arranged around rotating "scroll wheel".
       8513, 8541 and 8709 are Mac types, 8697 is PC */
    {"8513",  5, ITDB_IPOD_MODEL_REGULAR,     ITDB_IPOD_GENERATION_FIRST,  20},
    {"8541",  5, ITDB_IPOD_MODEL_REGULAR,     ITDB_IPOD_GENERATION_FIRST,  20},
    {"8697",  5, ITDB_IPOD_MODEL_REGULAR,     ITDB_IPOD_GENERATION_FIRST,  20},
    {"8709", 10, ITDB_IPOD_MODEL_REGULAR,     ITDB_IPOD_GENERATION_FIRST,  20},

    /* Second Generation */
    /* Same buttons as First Generation but around touch-sensitive
       "touch wheel". 8737 and 8738 are Mac types, 8740 and 8741 * are
       PC */
    {"8737", 10, ITDB_IPOD_MODEL_REGULAR,     ITDB_IPOD_GENERATION_SECOND, 20},
    {"8740", 10, ITDB_IPOD_MODEL_REGULAR,     ITDB_IPOD_GENERATION_SECOND, 20},
    {"8738", 20, ITDB_IPOD_MODEL_REGULAR,     ITDB_IPOD_GENERATION_SECOND, 50},
    {"8741", 20, ITDB_IPOD_MODEL_REGULAR,     ITDB_IPOD_GENERATION_SECOND, 50},

    /* Third Generation */
    /* Touch sensitive buttons and arranged in a line above "touch
       wheel". Docking connector was introduced here, same models for
       Mac and PC from now on. */
    {"8976", 10, ITDB_IPOD_MODEL_REGULAR,     ITDB_IPOD_GENERATION_THIRD,  20},
    {"8946", 15, ITDB_IPOD_MODEL_REGULAR,     ITDB_IPOD_GENERATION_THIRD,  50},
    {"9460", 15, ITDB_IPOD_MODEL_REGULAR,     ITDB_IPOD_GENERATION_THIRD,  50},
    {"9244", 20, ITDB_IPOD_MODEL_REGULAR,     ITDB_IPOD_GENERATION_THIRD,  50},
    {"8948", 30, ITDB_IPOD_MODEL_REGULAR,     ITDB_IPOD_GENERATION_THIRD,  50},
    {"9245", 40, ITDB_IPOD_MODEL_REGULAR,     ITDB_IPOD_GENERATION_THIRD,  50},

    /* Fourth Generation */
    /* Buttons are now integrated into the "touch wheel". */
    {"9282", 20, ITDB_IPOD_MODEL_REGULAR,     ITDB_IPOD_GENERATION_FOURTH, 50},
    {"9787", 25, ITDB_IPOD_MODEL_REGULAR_U2,  ITDB_IPOD_GENERATION_FOURTH, 50},
    {"9268", 40, ITDB_IPOD_MODEL_REGULAR,     ITDB_IPOD_GENERATION_FOURTH, 50},

    /* First Generation Mini */
    {"9160",  4, ITDB_IPOD_MODEL_MINI,        ITDB_IPOD_GENERATION_MINI_1,  6},
    {"9436",  4, ITDB_IPOD_MODEL_MINI_BLUE,   ITDB_IPOD_GENERATION_MINI_1,  6},
    {"9435",  4, ITDB_IPOD_MODEL_MINI_PINK,   ITDB_IPOD_GENERATION_MINI_1,  6},
    {"9434",  4, ITDB_IPOD_MODEL_MINI_GREEN,  ITDB_IPOD_GENERATION_MINI_1,  6},
    {"9437",  4, ITDB_IPOD_MODEL_MINI_GOLD,   ITDB_IPOD_GENERATION_MINI_1,  6},	

    /* Second Generation Mini */
    {"9800",  4, ITDB_IPOD_MODEL_MINI,        ITDB_IPOD_GENERATION_MINI_2,  6},
    {"9802",  4, ITDB_IPOD_MODEL_MINI_BLUE,   ITDB_IPOD_GENERATION_MINI_2,  6},
    {"9804",  4, ITDB_IPOD_MODEL_MINI_PINK,   ITDB_IPOD_GENERATION_MINI_2,  6},
    {"9806",  4, ITDB_IPOD_MODEL_MINI_GREEN,  ITDB_IPOD_GENERATION_MINI_2,  6},
    {"9801",  6, ITDB_IPOD_MODEL_MINI,        ITDB_IPOD_GENERATION_MINI_2, 20},
    {"9803",  6, ITDB_IPOD_MODEL_MINI_BLUE,   ITDB_IPOD_GENERATION_MINI_2, 20},
    {"9805",  6, ITDB_IPOD_MODEL_MINI_PINK,   ITDB_IPOD_GENERATION_MINI_2, 20},
    {"9807",  6, ITDB_IPOD_MODEL_MINI_GREEN,  ITDB_IPOD_GENERATION_MINI_2, 20},	

    /* Photo / Fourth Generation */
    /* Buttons are integrated into the "touch wheel". */
    {"A079", 20, ITDB_IPOD_MODEL_COLOR,       ITDB_IPOD_GENERATION_PHOTO,  50},
    {"A127", 20, ITDB_IPOD_MODEL_COLOR_U2,    ITDB_IPOD_GENERATION_PHOTO,  50},
    {"9829", 30, ITDB_IPOD_MODEL_COLOR,       ITDB_IPOD_GENERATION_PHOTO,  50},
    {"9585", 40, ITDB_IPOD_MODEL_COLOR,       ITDB_IPOD_GENERATION_PHOTO,  50},
    {"9830", 60, ITDB_IPOD_MODEL_COLOR,       ITDB_IPOD_GENERATION_PHOTO,  50},
    {"9586", 60, ITDB_IPOD_MODEL_COLOR,       ITDB_IPOD_GENERATION_PHOTO,  50},

    /* Shuffle / First Generation */
    {"9724", 0.5,ITDB_IPOD_MODEL_SHUFFLE,     ITDB_IPOD_GENERATION_SHUFFLE_1, 3},
    {"9725", 1,  ITDB_IPOD_MODEL_SHUFFLE,     ITDB_IPOD_GENERATION_SHUFFLE_1, 3},
    /* Shuffle / Second Generation */
    /* Square, connected to computer via cable */
    {"A546", 1,  ITDB_IPOD_MODEL_SHUFFLE_SILVER, ITDB_IPOD_GENERATION_SHUFFLE_2, 3},
    {"A947", 1,  ITDB_IPOD_MODEL_SHUFFLE_PINK,   ITDB_IPOD_GENERATION_SHUFFLE_2, 3},
    {"A949", 1,  ITDB_IPOD_MODEL_SHUFFLE_BLUE,   ITDB_IPOD_GENERATION_SHUFFLE_2, 3},
    {"A951", 1,  ITDB_IPOD_MODEL_SHUFFLE_GREEN,  ITDB_IPOD_GENERATION_SHUFFLE_2, 3},
    {"A953", 1,  ITDB_IPOD_MODEL_SHUFFLE_ORANGE, ITDB_IPOD_GENERATION_SHUFFLE_2, 3},
    {"C167", 1,  ITDB_IPOD_MODEL_SHUFFLE_GOLD,   ITDB_IPOD_GENERATION_SHUFFLE_2, 3},
    /* Shuffle / Second Generation, reloaded, some kind of 2.5 generation */
    /* Square, connected to computer via cable -- look identical to
     * Second Generation*/
    {"B225", 1,  ITDB_IPOD_MODEL_SHUFFLE_SILVER, ITDB_IPOD_GENERATION_SHUFFLE_2, 3},
    {"B233", 1,  ITDB_IPOD_MODEL_SHUFFLE_PURPLE, ITDB_IPOD_GENERATION_SHUFFLE_2, 3},
    {"B231", 1,  ITDB_IPOD_MODEL_SHUFFLE_RED,    ITDB_IPOD_GENERATION_SHUFFLE_2, 3},
    {"B227", 1,  ITDB_IPOD_MODEL_SHUFFLE_BLUE,   ITDB_IPOD_GENERATION_SHUFFLE_2, 3},
    {"B228", 1,  ITDB_IPOD_MODEL_SHUFFLE_BLUE,   ITDB_IPOD_GENERATION_SHUFFLE_2, 3},
    {"B229", 1,  ITDB_IPOD_MODEL_SHUFFLE_GREEN,  ITDB_IPOD_GENERATION_SHUFFLE_2, 3},
    {"B518", 2,  ITDB_IPOD_MODEL_SHUFFLE_SILVER, ITDB_IPOD_GENERATION_SHUFFLE_2, 3},
    {"B520", 2,  ITDB_IPOD_MODEL_SHUFFLE_BLUE,   ITDB_IPOD_GENERATION_SHUFFLE_2, 3},
    {"B522", 2,  ITDB_IPOD_MODEL_SHUFFLE_GREEN,  ITDB_IPOD_GENERATION_SHUFFLE_2, 3},
    {"B524", 2,  ITDB_IPOD_MODEL_SHUFFLE_RED,    ITDB_IPOD_GENERATION_SHUFFLE_2, 3},
    {"B526", 2,  ITDB_IPOD_MODEL_SHUFFLE_PURPLE, ITDB_IPOD_GENERATION_SHUFFLE_2, 3},

    /* Shuffle / Third Generation */
    /* Bar, button-less, speaking */
    {"C306", 2,  ITDB_IPOD_MODEL_SHUFFLE_SILVER,    ITDB_IPOD_GENERATION_SHUFFLE_3, 3},
    {"C323", 2,  ITDB_IPOD_MODEL_SHUFFLE_BLACK,     ITDB_IPOD_GENERATION_SHUFFLE_3, 3},
    {"C381", 2,  ITDB_IPOD_MODEL_SHUFFLE_GREEN,     ITDB_IPOD_GENERATION_SHUFFLE_3, 3},
    {"C384", 2,  ITDB_IPOD_MODEL_SHUFFLE_BLUE,      ITDB_IPOD_GENERATION_SHUFFLE_3, 3},
    {"C387", 2,  ITDB_IPOD_MODEL_SHUFFLE_PINK,      ITDB_IPOD_GENERATION_SHUFFLE_3, 3},
    {"B867", 4,  ITDB_IPOD_MODEL_SHUFFLE_SILVER,    ITDB_IPOD_GENERATION_SHUFFLE_3, 3},
    {"C164", 4,  ITDB_IPOD_MODEL_SHUFFLE_BLACK,     ITDB_IPOD_GENERATION_SHUFFLE_3, 3},
    {"C303", 4,  ITDB_IPOD_MODEL_SHUFFLE_STAINLESS, ITDB_IPOD_GENERATION_SHUFFLE_3, 3},
    {"C307", 4,  ITDB_IPOD_MODEL_SHUFFLE_GREEN,     ITDB_IPOD_GENERATION_SHUFFLE_3, 3},
    {"C328", 4,  ITDB_IPOD_MODEL_SHUFFLE_BLUE,      ITDB_IPOD_GENERATION_SHUFFLE_3, 3},
    {"C331", 4,  ITDB_IPOD_MODEL_SHUFFLE_PINK,      ITDB_IPOD_GENERATION_SHUFFLE_3, 3},

    /* Shuffle / Fourth Generation */
    /* Square, connected to computer via cable, voice-over */
    {"C584", 2,  ITDB_IPOD_MODEL_SHUFFLE_SILVER,    ITDB_IPOD_GENERATION_SHUFFLE_4, 3},
    {"C585", 2,  ITDB_IPOD_MODEL_SHUFFLE_PINK,      ITDB_IPOD_GENERATION_SHUFFLE_4, 3},
    {"C749", 2,  ITDB_IPOD_MODEL_SHUFFLE_ORANGE,    ITDB_IPOD_GENERATION_SHUFFLE_4, 3},
    {"C750", 2,  ITDB_IPOD_MODEL_SHUFFLE_GREEN,     ITDB_IPOD_GENERATION_SHUFFLE_4, 3},
    {"C751", 2,  ITDB_IPOD_MODEL_SHUFFLE_BLUE,      ITDB_IPOD_GENERATION_SHUFFLE_4, 3},

    /* Nano / Fifth Generation (first nano generation) */
    /* Buttons are integrated into the "touch wheel". */
    {"A350",  1, ITDB_IPOD_MODEL_NANO_WHITE,  ITDB_IPOD_GENERATION_NANO_1,   3},
    {"A352",  1, ITDB_IPOD_MODEL_NANO_BLACK,  ITDB_IPOD_GENERATION_NANO_1,   3},
    {"A004",  2, ITDB_IPOD_MODEL_NANO_WHITE,  ITDB_IPOD_GENERATION_NANO_1,   3},
    {"A099",  2, ITDB_IPOD_MODEL_NANO_BLACK,  ITDB_IPOD_GENERATION_NANO_1,   3},
    {"A005",  4, ITDB_IPOD_MODEL_NANO_WHITE,  ITDB_IPOD_GENERATION_NANO_1,   6},
    {"A107",  4, ITDB_IPOD_MODEL_NANO_BLACK,  ITDB_IPOD_GENERATION_NANO_1,   6},

    /* Video / Fifth Generation */
    /* Buttons are integrated into the "touch wheel". */
    {"A002", 30, ITDB_IPOD_MODEL_VIDEO_WHITE, ITDB_IPOD_GENERATION_VIDEO_1,  50},
    {"A146", 30, ITDB_IPOD_MODEL_VIDEO_BLACK, ITDB_IPOD_GENERATION_VIDEO_1,  50},
    {"A003", 60, ITDB_IPOD_MODEL_VIDEO_WHITE, ITDB_IPOD_GENERATION_VIDEO_1,  50},
    {"A147", 60, ITDB_IPOD_MODEL_VIDEO_BLACK, ITDB_IPOD_GENERATION_VIDEO_1,  50},
    {"A452", 30, ITDB_IPOD_MODEL_VIDEO_U2,    ITDB_IPOD_GENERATION_VIDEO_1,  50},

    /* Video / Sixth Generation */
    /* Pretty much identical to fifth generation with better display,
     * extended battery operation time and gap-free playback */
    {"A444", 30, ITDB_IPOD_MODEL_VIDEO_WHITE, ITDB_IPOD_GENERATION_VIDEO_2,  50},
    {"A446", 30, ITDB_IPOD_MODEL_VIDEO_BLACK, ITDB_IPOD_GENERATION_VIDEO_2,  50},
    {"A664", 30, ITDB_IPOD_MODEL_VIDEO_U2,    ITDB_IPOD_GENERATION_VIDEO_2,  50},
    {"A448", 80, ITDB_IPOD_MODEL_VIDEO_WHITE, ITDB_IPOD_GENERATION_VIDEO_2,  50},
    {"A450", 80, ITDB_IPOD_MODEL_VIDEO_BLACK, ITDB_IPOD_GENERATION_VIDEO_2,  50},

    /* Nano / Sixth Generation (second nano generation) */
    /* Pretty much identical to fifth generation with better display,
     * extended battery operation time and gap-free playback */
    {"A477",  2, ITDB_IPOD_MODEL_NANO_SILVER, ITDB_IPOD_GENERATION_NANO_2,   3},
    {"A426",  4, ITDB_IPOD_MODEL_NANO_SILVER, ITDB_IPOD_GENERATION_NANO_2,   6},
    {"A428",  4, ITDB_IPOD_MODEL_NANO_BLUE,   ITDB_IPOD_GENERATION_NANO_2,   6},
    {"A487",  4, ITDB_IPOD_MODEL_NANO_GREEN,  ITDB_IPOD_GENERATION_NANO_2,   6},
    {"A489",  4, ITDB_IPOD_MODEL_NANO_PINK,   ITDB_IPOD_GENERATION_NANO_2,   6},
    {"A725",  4, ITDB_IPOD_MODEL_NANO_RED,    ITDB_IPOD_GENERATION_NANO_2,   6},
    {"A726",  8, ITDB_IPOD_MODEL_NANO_RED,    ITDB_IPOD_GENERATION_NANO_2,   6},
    {"A497",  8, ITDB_IPOD_MODEL_NANO_BLACK,  ITDB_IPOD_GENERATION_NANO_2,  14},

    /* HP iPods, need contributions for this table */
    /* Buttons are integrated into the "touch wheel". */
    {"E436", 40, ITDB_IPOD_MODEL_REGULAR,     ITDB_IPOD_GENERATION_FOURTH, 50},
    {"S492", 30, ITDB_IPOD_MODEL_COLOR,       ITDB_IPOD_GENERATION_PHOTO,  50},

    /* iPod Classic G1 */
    /* First generation with "cover flow" */
    {"B029",  80, ITDB_IPOD_MODEL_CLASSIC_SILVER, ITDB_IPOD_GENERATION_CLASSIC_1, 50},
    {"B147",  80, ITDB_IPOD_MODEL_CLASSIC_BLACK,  ITDB_IPOD_GENERATION_CLASSIC_1, 50},
    {"B145", 160, ITDB_IPOD_MODEL_CLASSIC_SILVER, ITDB_IPOD_GENERATION_CLASSIC_1, 50},
    {"B150", 160, ITDB_IPOD_MODEL_CLASSIC_BLACK,  ITDB_IPOD_GENERATION_CLASSIC_1, 50},

    /* iPod Classic G2 */
    {"B562", 120, ITDB_IPOD_MODEL_CLASSIC_SILVER, ITDB_IPOD_GENERATION_CLASSIC_2, 50},
    {"B565", 120, ITDB_IPOD_MODEL_CLASSIC_BLACK,  ITDB_IPOD_GENERATION_CLASSIC_2, 50},

    /* iPod Classic G3 */
    {"C293", 160, ITDB_IPOD_MODEL_CLASSIC_SILVER, ITDB_IPOD_GENERATION_CLASSIC_3, 50},
    {"C297", 160, ITDB_IPOD_MODEL_CLASSIC_BLACK,  ITDB_IPOD_GENERATION_CLASSIC_3, 50},

    /* iPod nano video G1 (Third Nano Generation) */
    /* First generation of video support for nano */
    {"A978",   4, ITDB_IPOD_MODEL_NANO_SILVER,    ITDB_IPOD_GENERATION_NANO_3,  6},
    {"A980",   8, ITDB_IPOD_MODEL_NANO_SILVER,    ITDB_IPOD_GENERATION_NANO_3, 14},
    {"B261",   8, ITDB_IPOD_MODEL_NANO_BLACK,     ITDB_IPOD_GENERATION_NANO_3, 14},
    {"B249",   8, ITDB_IPOD_MODEL_NANO_BLUE,      ITDB_IPOD_GENERATION_NANO_3, 14},
    {"B253",   8, ITDB_IPOD_MODEL_NANO_GREEN,     ITDB_IPOD_GENERATION_NANO_3, 14},
    {"B257",   8, ITDB_IPOD_MODEL_NANO_RED,       ITDB_IPOD_GENERATION_NANO_3, 14},

    /* iPod nano video G2 (Fourth Nano Generation) */
    {"B480",   4, ITDB_IPOD_MODEL_NANO_SILVER,    ITDB_IPOD_GENERATION_NANO_4, 14},
    {"B651",   4, ITDB_IPOD_MODEL_NANO_BLUE,      ITDB_IPOD_GENERATION_NANO_4, 14},
    {"B654",   4, ITDB_IPOD_MODEL_NANO_PINK,      ITDB_IPOD_GENERATION_NANO_4, 14},
    {"B657",   4, ITDB_IPOD_MODEL_NANO_PURPLE,    ITDB_IPOD_GENERATION_NANO_4, 14},
    {"B660",   4, ITDB_IPOD_MODEL_NANO_ORANGE,    ITDB_IPOD_GENERATION_NANO_4, 14},
    {"B663",   4, ITDB_IPOD_MODEL_NANO_GREEN,     ITDB_IPOD_GENERATION_NANO_4, 14},
    {"B666",   4, ITDB_IPOD_MODEL_NANO_YELLOW,    ITDB_IPOD_GENERATION_NANO_4, 14},

    {"B598",   8, ITDB_IPOD_MODEL_NANO_SILVER,    ITDB_IPOD_GENERATION_NANO_4, 14},
    {"B732",   8, ITDB_IPOD_MODEL_NANO_BLUE,      ITDB_IPOD_GENERATION_NANO_4, 14},
    {"B735",   8, ITDB_IPOD_MODEL_NANO_PINK,      ITDB_IPOD_GENERATION_NANO_4, 14},
    {"B739",   8, ITDB_IPOD_MODEL_NANO_PURPLE,    ITDB_IPOD_GENERATION_NANO_4, 14},
    {"B742",   8, ITDB_IPOD_MODEL_NANO_ORANGE,    ITDB_IPOD_GENERATION_NANO_4, 14},
    {"B745",   8, ITDB_IPOD_MODEL_NANO_GREEN,     ITDB_IPOD_GENERATION_NANO_4, 14},
    {"B748",   8, ITDB_IPOD_MODEL_NANO_YELLOW,    ITDB_IPOD_GENERATION_NANO_4, 14},
    {"B751",   8, ITDB_IPOD_MODEL_NANO_RED,       ITDB_IPOD_GENERATION_NANO_4, 14},
    {"B754",   8, ITDB_IPOD_MODEL_NANO_BLACK,     ITDB_IPOD_GENERATION_NANO_4, 14},

    {"B903",  16, ITDB_IPOD_MODEL_NANO_SILVER,    ITDB_IPOD_GENERATION_NANO_4, 14},
    {"B905",  16, ITDB_IPOD_MODEL_NANO_BLUE,      ITDB_IPOD_GENERATION_NANO_4, 14},
    {"B907",  16, ITDB_IPOD_MODEL_NANO_PINK,      ITDB_IPOD_GENERATION_NANO_4, 14},
    {"B909",  16, ITDB_IPOD_MODEL_NANO_PURPLE,    ITDB_IPOD_GENERATION_NANO_4, 14},
    {"B911",  16, ITDB_IPOD_MODEL_NANO_ORANGE,    ITDB_IPOD_GENERATION_NANO_4, 14},
    {"B913",  16, ITDB_IPOD_MODEL_NANO_GREEN,     ITDB_IPOD_GENERATION_NANO_4, 14},
    {"B915",  16, ITDB_IPOD_MODEL_NANO_YELLOW,    ITDB_IPOD_GENERATION_NANO_4, 14},
    {"B917",  16, ITDB_IPOD_MODEL_NANO_RED,       ITDB_IPOD_GENERATION_NANO_4, 14},
    {"B918",  16, ITDB_IPOD_MODEL_NANO_BLACK,     ITDB_IPOD_GENERATION_NANO_4, 14},

    /* iPod nano with camera (Fifth Nano Generation) */
    {"C027",   8, ITDB_IPOD_MODEL_NANO_SILVER,    ITDB_IPOD_GENERATION_NANO_5, 14},
    {"C031",   8, ITDB_IPOD_MODEL_NANO_BLACK,     ITDB_IPOD_GENERATION_NANO_5, 14},
    {"C034",   8, ITDB_IPOD_MODEL_NANO_PURPLE,    ITDB_IPOD_GENERATION_NANO_5, 14},
    {"C037",   8, ITDB_IPOD_MODEL_NANO_BLUE,      ITDB_IPOD_GENERATION_NANO_5, 14},
    {"C040",   8, ITDB_IPOD_MODEL_NANO_GREEN,     ITDB_IPOD_GENERATION_NANO_5, 14},
    {"C043",   8, ITDB_IPOD_MODEL_NANO_YELLOW,    ITDB_IPOD_GENERATION_NANO_5, 14},
    {"C046",   8, ITDB_IPOD_MODEL_NANO_ORANGE,    ITDB_IPOD_GENERATION_NANO_5, 14},
    {"C049",   8, ITDB_IPOD_MODEL_NANO_RED,       ITDB_IPOD_GENERATION_NANO_5, 14},
    {"C050",   8, ITDB_IPOD_MODEL_NANO_PINK,      ITDB_IPOD_GENERATION_NANO_5, 14},

    {"C060",  16, ITDB_IPOD_MODEL_NANO_SILVER,    ITDB_IPOD_GENERATION_NANO_5, 14},
    {"C062",  16, ITDB_IPOD_MODEL_NANO_BLACK,     ITDB_IPOD_GENERATION_NANO_5, 14},
    {"C064",  16, ITDB_IPOD_MODEL_NANO_PURPLE,    ITDB_IPOD_GENERATION_NANO_5, 14},
    {"C066",  16, ITDB_IPOD_MODEL_NANO_BLUE,      ITDB_IPOD_GENERATION_NANO_5, 14},
    {"C068",  16, ITDB_IPOD_MODEL_NANO_GREEN,     ITDB_IPOD_GENERATION_NANO_5, 14},
    {"C070",  16, ITDB_IPOD_MODEL_NANO_YELLOW,    ITDB_IPOD_GENERATION_NANO_5, 14},
    {"C072",  16, ITDB_IPOD_MODEL_NANO_ORANGE,    ITDB_IPOD_GENERATION_NANO_5, 14},
    {"C074",  16, ITDB_IPOD_MODEL_NANO_RED,       ITDB_IPOD_GENERATION_NANO_5, 14},
    {"C075",  16, ITDB_IPOD_MODEL_NANO_PINK,      ITDB_IPOD_GENERATION_NANO_5, 14},

    /* iPod nano touch (Sixth Generation) */
    {"C525",   8, ITDB_IPOD_MODEL_NANO_SILVER,    ITDB_IPOD_GENERATION_NANO_6, 14},
    {"C688",   8, ITDB_IPOD_MODEL_NANO_BLACK,     ITDB_IPOD_GENERATION_NANO_6, 14},
    {"C689",   8, ITDB_IPOD_MODEL_NANO_BLUE,      ITDB_IPOD_GENERATION_NANO_6, 14},
    {"C690",   8, ITDB_IPOD_MODEL_NANO_GREEN,     ITDB_IPOD_GENERATION_NANO_6, 14},
    {"C691",   8, ITDB_IPOD_MODEL_NANO_ORANGE,    ITDB_IPOD_GENERATION_NANO_6, 14},
    {"C692",   8, ITDB_IPOD_MODEL_NANO_PINK,      ITDB_IPOD_GENERATION_NANO_6, 14},
    {"C693",   8, ITDB_IPOD_MODEL_NANO_RED,       ITDB_IPOD_GENERATION_NANO_6, 14},

    {"C526",  16, ITDB_IPOD_MODEL_NANO_SILVER,    ITDB_IPOD_GENERATION_NANO_6, 14},
    {"C694",  16, ITDB_IPOD_MODEL_NANO_BLACK,     ITDB_IPOD_GENERATION_NANO_6, 14},
    {"C695",  16, ITDB_IPOD_MODEL_NANO_BLUE,      ITDB_IPOD_GENERATION_NANO_6, 14},
    {"C696",  16, ITDB_IPOD_MODEL_NANO_GREEN,     ITDB_IPOD_GENERATION_NANO_6, 14},
    {"C697",  16, ITDB_IPOD_MODEL_NANO_ORANGE,    ITDB_IPOD_GENERATION_NANO_6, 14},
    {"C698",  16, ITDB_IPOD_MODEL_NANO_PINK,      ITDB_IPOD_GENERATION_NANO_6, 14},
    {"C699",  16, ITDB_IPOD_MODEL_NANO_RED,       ITDB_IPOD_GENERATION_NANO_6, 14},

    /* iPod Touch 1st gen */
    {"A623",   8, ITDB_IPOD_MODEL_TOUCH_SILVER,   ITDB_IPOD_GENERATION_TOUCH_1, 50},
    {"A627",  16, ITDB_IPOD_MODEL_TOUCH_SILVER,   ITDB_IPOD_GENERATION_TOUCH_1, 50},
    {"B376",  32, ITDB_IPOD_MODEL_TOUCH_SILVER,   ITDB_IPOD_GENERATION_TOUCH_1, 50},

    /* iPod Touch 2nd gen */
    {"B528",   8, ITDB_IPOD_MODEL_TOUCH_SILVER,   ITDB_IPOD_GENERATION_TOUCH_2, 50},
    {"B531",  16, ITDB_IPOD_MODEL_TOUCH_SILVER,   ITDB_IPOD_GENERATION_TOUCH_2, 50},
    {"B533",  32, ITDB_IPOD_MODEL_TOUCH_SILVER,   ITDB_IPOD_GENERATION_TOUCH_2, 50},

    /* iPod Touch 3rd gen */
    /* The 8GB model is marked as 2nd gen because it's actually what the 
     * hardware is even if Apple markets it the same as the 2 bigger models
     */
    {"C086",   8, ITDB_IPOD_MODEL_TOUCH_SILVER,   ITDB_IPOD_GENERATION_TOUCH_2, 50},
    {"C008",  32, ITDB_IPOD_MODEL_TOUCH_SILVER,   ITDB_IPOD_GENERATION_TOUCH_3, 50},
    {"C011",  64, ITDB_IPOD_MODEL_TOUCH_SILVER,   ITDB_IPOD_GENERATION_TOUCH_3, 50},

    /* iPod Touch 4th gen */
    {"C540",   8, ITDB_IPOD_MODEL_TOUCH_SILVER,   ITDB_IPOD_GENERATION_TOUCH_4, 50},
    {"C544",  32, ITDB_IPOD_MODEL_TOUCH_SILVER,   ITDB_IPOD_GENERATION_TOUCH_4, 50},
    {"C547",  64, ITDB_IPOD_MODEL_TOUCH_SILVER,   ITDB_IPOD_GENERATION_TOUCH_4, 50},

    /* iPhone */
    {"A501",   4, ITDB_IPOD_MODEL_IPHONE_1,       ITDB_IPOD_GENERATION_IPHONE_1, 50},
    {"A712",   8, ITDB_IPOD_MODEL_IPHONE_1,       ITDB_IPOD_GENERATION_IPHONE_1, 50},
    {"B384",  16, ITDB_IPOD_MODEL_IPHONE_1,       ITDB_IPOD_GENERATION_IPHONE_1, 50},
    /* iPhone G2 aka iPhone 3G (yeah, confusing ;) */
    {"B046",   8, ITDB_IPOD_MODEL_IPHONE_BLACK,   ITDB_IPOD_GENERATION_IPHONE_2, 50},
    {"B500",  16, ITDB_IPOD_MODEL_IPHONE_WHITE,   ITDB_IPOD_GENERATION_IPHONE_2, 50},
    {"B048",  16, ITDB_IPOD_MODEL_IPHONE_BLACK,   ITDB_IPOD_GENERATION_IPHONE_2, 50},
    {"B496",  16, ITDB_IPOD_MODEL_IPHONE_BLACK,   ITDB_IPOD_GENERATION_IPHONE_2, 50},
    /* iPhone 3GS */
    {"C131",  16, ITDB_IPOD_MODEL_IPHONE_BLACK,   ITDB_IPOD_GENERATION_IPHONE_3, 50},
    {"C133",  32, ITDB_IPOD_MODEL_IPHONE_BLACK,   ITDB_IPOD_GENERATION_IPHONE_3, 50},
    {"C134",  32, ITDB_IPOD_MODEL_IPHONE_WHITE,   ITDB_IPOD_GENERATION_IPHONE_3, 50},
    /* iPhone 4G */
    {"C603",  16, ITDB_IPOD_MODEL_IPHONE_BLACK,   ITDB_IPOD_GENERATION_IPHONE_4, 50},
    {"C605",  32, ITDB_IPOD_MODEL_IPHONE_BLACK,   ITDB_IPOD_GENERATION_IPHONE_4, 50},

    /* iPad */
    {"B292",  16, ITDB_IPOD_MODEL_IPAD,           ITDB_IPOD_GENERATION_IPAD_1,   50},
    {"B293",  32, ITDB_IPOD_MODEL_IPAD,           ITDB_IPOD_GENERATION_IPAD_1,   50},
    {"B294",  64, ITDB_IPOD_MODEL_IPAD,           ITDB_IPOD_GENERATION_IPAD_1,   50},
    /* iPad with 3G */
    {"C349",  16, ITDB_IPOD_MODEL_IPAD,           ITDB_IPOD_GENERATION_IPAD_1,   50},
    {"C496",  32, ITDB_IPOD_MODEL_IPAD,           ITDB_IPOD_GENERATION_IPAD_1,   50},
    {"C497",  64, ITDB_IPOD_MODEL_IPAD,           ITDB_IPOD_GENERATION_IPAD_1,   50},

    /* No known model number -- create a Device/SysInfo file with
     * one entry, e.g.:
       ModelNumStr: Mmobile1
    */
    {"mobile1", -1, ITDB_IPOD_MODEL_MOBILE_1, ITDB_IPOD_GENERATION_MOBILE,  6},

    {NULL, 0, 0, 0, 0}
};


/* One entry for each of Itdb_IpodModel (itdb.h) */
static const gchar *ipod_model_name_table [] = {
	N_("Invalid"),
	N_("Unknown"),
	N_("Color"),
	N_("Color U2"),
	N_("Grayscale"),
	N_("Grayscale U2"),
	N_("Mini (Silver)"),
	N_("Mini (Blue)"),
	N_("Mini (Pink)"),
	N_("Mini (Green)"),
	N_("Mini (Gold)"),
	N_("Shuffle"),
	N_("Nano (White)"),
	N_("Nano (Black)"),
	N_("Video (White)"),
	N_("Video (Black)"),
	N_("Mobile (1)"),
	N_("Video U2"),
	N_("Nano (Silver)"),
	N_("Nano (Blue)"),
	N_("Nano (Green)"),
	N_("Nano (Pink)"),
	N_("Nano (Red)"),
	N_("Nano (Yellow)"),
	N_("Nano (Purple)"),
	N_("Nano (Orange)"),
	N_("iPhone (1)"),
	N_("Shuffle (Silver)"),
	N_("Shuffle (Pink)"),
	N_("Shuffle (Blue)"),
	N_("Shuffle (Green)"),
	N_("Shuffle (Orange)"),
	N_("Shuffle (Purple)"),
	N_("Shuffle (Red)"),
	N_("Classic (Silver)"),
	N_("Classic (Black)"),
	N_("Touch (Silver)"),
	N_("Shuffle (Black)"),
	N_("iPhone (White)"),
	N_("iPhone (Black)"),
	N_("Shuffle (Gold)"),
	N_("Shuffle (Stainless)"),
	N_("iPad"),
	NULL
};

/* One entry for each of Itdb_IpodGeneration (itdb.h) */
static const gchar *ipod_generation_name_table [] = {
	N_("Unknown"),
	N_("Regular (1st Gen.)"),
	N_("Regular (2nd Gen.)"),
	N_("Regular (3rd Gen.)"),
	N_("Regular (4th Gen.)"),
	N_("Photo"),
	N_("Mobile Phones"),
	N_("Mini (1st Gen.)"),
	N_("Mini (2nd Gen.)"),
	N_("Shuffle (1st Gen.)"),
	N_("Shuffle (2nd Gen.)"),
	N_("Shuffle (3rd Gen.)"),
	N_("Nano (1st Gen.)"),
	N_("Nano (2nd Gen.)"),
	N_("Nano Video (3rd Gen.)"),
	N_("Nano Video (4th Gen.)"),
	N_("Video (1st Gen.)"),
	N_("Video (2nd Gen.)"),
	N_("Classic"),
	N_("Classic"),
	N_("Touch"),
	N_("iPhone"),
	N_("Shuffle (4th Gen.)"),
	N_("Touch (2nd Gen.)"),
	N_("iPhone 3G"),
	N_("iPhone 3GS"),
	N_("Classic"),
	N_("Nano with camera (5th Gen.)"),
	N_("Touch (3rd Gen.)"),
	N_("iPad"),
	N_("iPhone 4"),
	N_("Touch (4th Gen.)"),
	N_("Nano touch (6th Gen.)"),
	NULL
};


static const Itdb_ArtworkFormat ipod_photo_cover_art_info[] = {
    {1017,  56,  56, THUMB_FORMAT_RGB565_LE},
    {1016, 140, 140, THUMB_FORMAT_RGB565_LE},
    { -1,   -1,  -1, -1}
};

static const Itdb_ArtworkFormat ipod_photo_photo_info[] = {
    {1009,  42,  30, THUMB_FORMAT_RGB565_LE},
    {1015, 130,  88, THUMB_FORMAT_RGB565_LE},
    {1013, 220, 176, THUMB_FORMAT_RGB565_BE_90},
    {1019, 720, 480, THUMB_FORMAT_UYVY_BE},
    {  -1,  -1,  -1, -1}
};

static const Itdb_ArtworkFormat ipod_nano_cover_art_info[] = {
    {1031,  42,  42, THUMB_FORMAT_RGB565_LE},
    {1027, 100, 100, THUMB_FORMAT_RGB565_LE},
    {  -1,  -1,  -1, -1}
};

static const Itdb_ArtworkFormat ipod_nano_photo_info[] = {
    {1032,  42,  37, THUMB_FORMAT_RGB565_LE},
    {1023, 176, 132, THUMB_FORMAT_RGB565_BE},
    {  -1,  -1,  -1, -1}
};

static const Itdb_ArtworkFormat ipod_nano4g_cover_art_info[] = {
    {1055, 128, 128, THUMB_FORMAT_RGB565_LE},
    {1068, 128, 128, THUMB_FORMAT_RGB565_LE},
    {1071, 240, 240, THUMB_FORMAT_RGB565_LE},
    {1074,  50,  50, THUMB_FORMAT_RGB565_LE},
    {1078,  80,  80, THUMB_FORMAT_RGB565_LE},
    {1084, 240, 240, THUMB_FORMAT_RGB565_LE},
    {  -1,  -1,  -1, -1}
};

static const Itdb_ArtworkFormat ipod_nano4g_photo_info[] = {
    {1024, 320, 240, THUMB_FORMAT_RGB565_LE},
    {1066,  64,  64, THUMB_FORMAT_RGB565_LE},
    {1079,  80,  80, THUMB_FORMAT_RGB565_LE},
    /*
     * To be implemented, THUMB_FORMAT_JPEG 
    {1081, 640, 480, THUMB_FORMAT_JPEG},
    */
    {1083, 240, 320, THUMB_FORMAT_RGB565_LE},
    {  -1,  -1,  -1, -1}
};

static const Itdb_ArtworkFormat ipod_nano4g_chapter_image_info[] = {
    {1071, 240, 240, THUMB_FORMAT_RGB565_LE},
};

static const Itdb_ArtworkFormat ipod_nano5g_cover_art_info[] = {
    {1056, 128, 128, THUMB_FORMAT_RGB565_LE},
    {1078,  80,  80, THUMB_FORMAT_RGB565_LE},
    {1073,  240,  240, THUMB_FORMAT_RGB565_LE},
    {1074,  50,  50, THUMB_FORMAT_RGB565_LE},
    {  -1,  -1,  -1, -1}
};

static const Itdb_ArtworkFormat ipod_nano5g_photo_info[] = {
    {1087, 384, 384, THUMB_FORMAT_RGB565_LE},
    /*
     * To be implemented, THUMB_FORMAT_JPEG 
     {1081,  640,  480, THUMB_FORMAT_JPEG},
     */
    {1079,  80,  80, THUMB_FORMAT_RGB565_LE},
    {1066,  64,  64, THUMB_FORMAT_RGB565_LE},
    {  -1,  -1,  -1, -1}
};

static const Itdb_ArtworkFormat ipod_video_cover_art_info[] = {
    {1028, 100, 100, THUMB_FORMAT_RGB565_LE},
    {1029, 200, 200, THUMB_FORMAT_RGB565_LE},
    {  -1,  -1,  -1, -1}
};

static const Itdb_ArtworkFormat ipod_video_photo_info[] = {
    {1036,  50,  41, THUMB_FORMAT_RGB565_LE},
    {1015, 130,  88, THUMB_FORMAT_RGB565_LE},
    {1024, 320, 240, THUMB_FORMAT_RGB565_LE},
    {1019, 720, 480, THUMB_FORMAT_UYVY_BE},
    {  -1,  -1,  -1, -1}
};

static const Itdb_ArtworkFormat ipod_mobile_1_cover_art_info[] = {
    {2002,  50,  50, THUMB_FORMAT_RGB565_BE},
    {2003, 150, 150, THUMB_FORMAT_RGB565_BE},
    {  -1,  -1,  -1, -1}
};

static const Itdb_ArtworkFormat ipod_touch_1_cover_art_info[] = {
    {3001, 256, 256, THUMB_FORMAT_REC_RGB555_LE},
    {3002, 128, 128, THUMB_FORMAT_REC_RGB555_LE},
    {3003,  64,  64, THUMB_FORMAT_REC_RGB555_LE},
    {3005, 320, 320, THUMB_FORMAT_RGB555_LE},
    {3006,  56,  56, THUMB_FORMAT_RGB555_LE,  8192}, /*pad data to  8192 bytes */
    {3007,  88,  88, THUMB_FORMAT_RGB555_LE, 16384}, /*pad data to 16384 bytes */
    {  -1,  -1,  -1, -1}
};

static const Itdb_ArtworkFormat ipod_touch_1_photo_info[] = {
    /* In the album list, if a photo is being used to represent a whole album,
       PHOTO_SMALL is used.  We specify TRUE for the crop option so we fill
       the square completely. */
    {3004,  56,  55, THUMB_FORMAT_RGB555_LE, 8192, TRUE},
    /* In thumbnail view, PHOTO_LARGE is used.  It's actually 79x79, with a 4px
       white border on the right and bottom.  We specify TRUE for the crop option
       so we fill the square completely. */
    {3011,  80,  79, THUMB_FORMAT_RGB555_LE, 0, TRUE},
    {3009, 160, 120, THUMB_FORMAT_RGB555_LE},
    /* When viewing an individual photo, PHOTO_TV_SCREEN is used.  Note that it
       is acceptable to write a thumbnail less than the specified width or
       height, the iPhone / iTouch will scale it to fit the screen.  This is
       important for images that are taller than they wide. */
    {3008, 640, 480, THUMB_FORMAT_RGB555_LE},
    {  -1,  -1,  -1, -1}
};

/* also used for 3G Nano */
static const Itdb_ArtworkFormat ipod_classic_1_cover_art_info[] = {
    /* officially 55x55 -- verify! */
    {1061,  56,  56, THUMB_FORMAT_RGB565_LE},
    {1055, 128, 128, THUMB_FORMAT_RGB565_LE},
    {1068, 128, 128, THUMB_FORMAT_RGB565_LE},
    {1060, 320, 320, THUMB_FORMAT_RGB565_LE},
    {  -1,  -1,  -1, -1}
};

/* also used for 3G Nano */
static const Itdb_ArtworkFormat ipod_classic_1_photo_info[] = {
    {1067, 720, 480, THUMB_FORMAT_I420_LE},
    {1024, 320, 240, THUMB_FORMAT_RGB565_LE},
    {1066,  64,  64, THUMB_FORMAT_RGB565_LE},
    {  -1,  -1,  -1, -1}
};

/* also used for 3G Nano */
static const Itdb_ArtworkFormat ipod_classic_1_chapter_image_info[] = {
    /*  These are the same as for the iPod video... -- labeled by the iPod as
        "chapter images" */
    {1055, 128, 128, THUMB_FORMAT_RGB565_LE},
    {1029, 200, 200, THUMB_FORMAT_RGB565_LE},
    {  -1,  -1,  -1, -1}
};

enum ArtworkType {
    ARTWORK_TYPE_COVER_ART,
    ARTWORK_TYPE_PHOTO,
    ARTWORK_TYPE_CHAPTER_IMAGE
};

struct _ArtworkCapabilities {
    Itdb_IpodGeneration generation;
    const Itdb_ArtworkFormat *cover_art_formats;
    const Itdb_ArtworkFormat *photo_formats;
    const Itdb_ArtworkFormat *chapter_image_formats;
};
typedef struct _ArtworkCapabilities ArtworkCapabilities;

static const ArtworkCapabilities ipod_artwork_capabilities[] = {
    { ITDB_IPOD_GENERATION_PHOTO, ipod_photo_cover_art_info, ipod_photo_photo_info, NULL },
    { ITDB_IPOD_GENERATION_VIDEO_1, ipod_video_cover_art_info, ipod_video_photo_info, NULL },
    { ITDB_IPOD_GENERATION_VIDEO_2, ipod_video_cover_art_info, ipod_video_photo_info, NULL },
    { ITDB_IPOD_GENERATION_NANO_1, ipod_nano_cover_art_info, ipod_nano_photo_info, NULL },
    { ITDB_IPOD_GENERATION_NANO_2, ipod_nano_cover_art_info, ipod_nano_photo_info, NULL },
    { ITDB_IPOD_GENERATION_NANO_3, ipod_classic_1_cover_art_info, ipod_classic_1_photo_info, ipod_classic_1_chapter_image_info },
    { ITDB_IPOD_GENERATION_NANO_4, ipod_nano4g_cover_art_info, ipod_nano4g_photo_info, ipod_nano4g_chapter_image_info },
    { ITDB_IPOD_GENERATION_NANO_5, ipod_nano5g_cover_art_info, ipod_nano5g_photo_info, NULL },
    { ITDB_IPOD_GENERATION_CLASSIC_1, ipod_classic_1_cover_art_info, ipod_classic_1_photo_info, ipod_classic_1_chapter_image_info },
    { ITDB_IPOD_GENERATION_CLASSIC_2, ipod_classic_1_cover_art_info, ipod_classic_1_photo_info, ipod_classic_1_chapter_image_info },
    { ITDB_IPOD_GENERATION_CLASSIC_3, ipod_classic_1_cover_art_info, ipod_classic_1_photo_info, ipod_classic_1_chapter_image_info },
    { ITDB_IPOD_GENERATION_TOUCH_1, ipod_touch_1_cover_art_info, ipod_touch_1_photo_info, NULL },
    { ITDB_IPOD_GENERATION_TOUCH_2, ipod_touch_1_cover_art_info, ipod_touch_1_photo_info, NULL },
    { ITDB_IPOD_GENERATION_TOUCH_3, ipod_touch_1_cover_art_info, ipod_touch_1_photo_info, NULL },
    { ITDB_IPOD_GENERATION_IPHONE_1, ipod_touch_1_cover_art_info, ipod_touch_1_photo_info, NULL },
    { ITDB_IPOD_GENERATION_IPHONE_2, ipod_touch_1_cover_art_info, ipod_touch_1_photo_info, NULL },
    { ITDB_IPOD_GENERATION_IPHONE_3, ipod_touch_1_cover_art_info, ipod_touch_1_photo_info, NULL },
    { ITDB_IPOD_GENERATION_MOBILE, ipod_mobile_1_cover_art_info, NULL, NULL },
    { ITDB_IPOD_GENERATION_UNKNOWN, NULL, NULL, NULL }
};

struct _ItdbSerialToModel {
    const char *serial;
    const char *model_number;
};
typedef struct _ItdbSerialToModel ItdbSerialToModel;

/* This table was extracted from ipod-model-table from podsleuth svn trunk
 * on 2008-06-14 (which seems to match podsleuth 0.6.2)
*/
static const ItdbSerialToModel serial_to_model_mapping[] = {
    { "LG6", "8541" },
    { "NAM", "8541" },
    { "MJ2", "8541" },
    { "ML1", "8709" },
    { "MME", "8709" },
    { "MMB", "8737" },
    { "MMC", "8738" },
    { "NGE", "8740" },
    { "NGH", "8740" },
    { "MMF", "8741" },
    { "NLW", "8946" },
    { "NRH", "8976" },
    { "QQF", "9460" },
    { "PQ5", "9244" },
    { "PNT", "9244" },
    { "NLY", "8948" },
    { "NM7", "8948" },
    { "PNU", "9245" },
    { "PS9", "9282" },
    { "Q8U", "9282" },
    { "V9V", "9787" },
    { "S2X", "9787" },
    { "PQ7", "9268" },
    { "TDU", "A079" },
    { "TDS", "A079" },
    { "TM2", "A127" },
    { "SAZ", "9830" },
    { "SB1", "9830" },
    { "SAY", "9829" },
    { "R5Q", "9585" },
    { "R5R", "9586" },
    { "R5T", "9586" },
    { "PFW", "9160" },
    { "PRC", "9160" },
    { "QKL", "9436" },
    { "QKQ", "9436" },
    { "QKK", "9435" },
    { "QKP", "9435" },
    { "QKJ", "9434" },
    { "QKN", "9434" },
    { "QKM", "9437" },
    { "QKR", "9437" },
    { "S41", "9800" },
    { "S4C", "9800" },
    { "S43", "9802" },
    { "S45", "9804" },
    { "S47", "9806" },
    { "S4J", "9806" },
    { "S42", "9801" },
    { "S44", "9803" },
    { "S48", "9807" },
    { "RS9", "9724" },
    { "QGV", "9724" },
    { "TSX", "9724" },
    { "PFV", "9724" },
    { "R80", "9724" },
    { "RSA", "9725" },
    { "TSY", "9725" },
    { "C60", "9725" },
    { "VTE", "A546" },
    { "VTF", "A546" },
    { "XQ5", "A947" },
    { "XQS", "A947" },
    { "XQV", "A949" },
    { "XQX", "A949" },
    { "YX7", "A949" },
    { "XQY", "A951" },
    { "YX8", "A951" },
    { "XR1", "A953" },
    { "YXA", "B233" }, /* 1GB Purple Shuffle 2g */
    { "YX6", "B225" }, /* 1GB Silver Shuffle 2g */
    { "YX7", "B228" }, /* 1GB Blue Shuffle 2g */
    { "YX9", "B225" }, /* 1GB Silver Shuffle 2g */
    { "8CQ", "C167" }, /* 1GB Gold Shuffle 2g */
    { "1ZH", "B518" }, /* 2GB Silver Shuffle 2g */
    { "UNA", "A350" },
    { "UNB", "A350" },
    { "UPR", "A352" },
    { "UPS", "A352" },
    { "SZB", "A004" },
    { "SZV", "A004" },
    { "SZW", "A004" },
    { "SZC", "A005" },
    { "SZT", "A005" },
    { "TJT", "A099" },
    { "TJU", "A099" },
    { "TK2", "A107" }, /* 4GB Black Nano 1g */
    { "TK3", "A107" },
    { "VQ5", "A477" },
    { "VQ6", "A477" },
    { "V8T", "A426" },
    { "V8U", "A426" },
    { "V8W", "A428" },
    { "V8X", "A428" },
    { "VQH", "A487" },
    { "VQJ", "A487" },
    { "VQK", "A489" },
    { "VKL", "A489" },
    { "WL2", "A725" },
    { "WL3", "A725" },
    { "X9A", "A726" },
    { "X9B", "A726" },
    { "VQT", "A497" },
    { "VQU", "A497" },
    { "Y0P", "A978" },
    { "Y0R", "A980" },
    { "YXR", "B249" },
    { "YXV", "B257" },
    { "YXT", "B253" },
    { "YXX", "B261" },
    { "SZ9", "A002" },
    { "WEC", "A002" },
    { "WED", "A002" },
    { "WEG", "A002" },
    { "WEH", "A002" },
    { "WEL", "A002" },
    { "TXK", "A146" },
    { "TXM", "A146" },
    { "WEE", "A146" },
    { "WEF", "A146" },
    { "WEJ", "A146" },
    { "WEK", "A146" },
    { "SZA", "A003" },
    { "SZU", "A003" },
    { "TXL", "A147" },
    { "TXN", "A147" },
    { "V9K", "A444" },
    { "V9L", "A444" },
    { "WU9", "A444" },
    { "VQM", "A446" },
    { "V9M", "A446" },
    { "V9N", "A446" },
    { "WEE", "A446" },
    { "V9P", "A448" },
    { "V9Q", "A448" },
    { "V9R", "A450" },
    { "V9S", "A450" },
    { "V95", "A450" },
    { "V96", "A450" },
    { "WUC", "A450" },
    { "W9G", "A664" }, /* 30GB iPod Video U2 5.5g */
    { "Y5N", "B029" }, /* Silver Classic 80GB */
    { "YMV", "B147" }, /* Black Classic 80GB */
    { "YMU", "B145" }, /* Silver Classic 160GB */
    { "YMX", "B150" }, /* Black Classic 160GB */
    { "2C5", "B562" }, /* Silver Classic 120GB */
    { "2C7", "B565" }, /* Black Classic 120GB */
    { "9ZS", "C293" }, /* Silver Classic 160GB (2009) */
    { "9ZU", "C297" }, /* Black Classic 160GB (2009) */

    { "37P", "B663" }, /* 4GB Green Nano 4g */
    { "37Q", "B666" }, /* 4GB Yellow Nano 4g */
    { "37H", "B654" }, /* 4GB Pink Nano 4g */
    { "1P1", "B480" }, /* 4GB Silver Nano 4g */
    { "37K", "B657" }, /* 4GB Purple Nano 4g */
    { "37L", "B660" }, /* 4GB Orange Nano 4g */
    { "2ME", "B598" }, /* 8GB Silver Nano 4g */
    { "3QS", "B732" }, /* 8GB Blue Nano 4g */
    { "3QT", "B735" }, /* 8GB Pink Nano 4g */
    { "3QU", "B739" }, /* 8GB Purple Nano 4g */
    { "3QW", "B742" }, /* 8GB Orange Nano 4g */
    { "3QX", "B745" }, /* 8GB Green Nano 4g */
    { "3QY", "B748" }, /* 8GB Yellow Nano 4g */
    { "3R0", "B754" }, /* 8GB Black Nano 4g */
    { "3QZ", "B751" }, /* 8GB Red Nano 4g */
    { "5B7", "B903" }, /* 16GB Silver Nano 4g */
    { "5B8", "B905" }, /* 16GB Blue Nano 4g */
    { "5B9", "B907" }, /* 16GB Pink Nano 4g */
    { "5BA", "B909" }, /* 16GB Purple Nano 4g */
    { "5BB", "B911" }, /* 16GB Orange Nano 4g */
    { "5BC", "B913" }, /* 16GB Green Nano 4g */
    { "5BD", "B915" }, /* 16GB Yellow Nano 4g */
    { "5BE", "B917" }, /* 16GB Red Nano 4g */
    { "5BF", "B918" }, /* 16GB Black Nano 4g */

    { "71V", "C027" }, /* 8GB Silver Nano 5g */
    { "71Y", "C031" }, /* 8GB Black Nano 5g */
    { "721", "C034" }, /* 8GB Purple Nano 5g */
    { "726", "C037" }, /* 8GB Blue Nano 5g */
    { "72A", "C040" }, /* 8GB Green Nano 5g */
    { "72F", "C046" }, /* 8GB Orange Nano 5g */
    { "72K", "C049" }, /* 8GB Red Nano 5g */
    { "72L", "C050" }, /* 8GB Pink Nano 5g */

    { "72Q", "C060" }, /* 16GB Silver Nano 5g */
    { "72R", "C062" }, /* 16GB Black Nano 5g */
    { "72S", "C064" }, /* 16GB Purple Nano 5g */
    { "72X", "C066" }, /* 16GB Blue Nano 5g */
    { "734", "C068" }, /* 16GB Green Nano 5g */
    { "738", "C070" }, /* 16GB Yellow Nano 5g */
    { "739", "C072" }, /* 16GB Orange Nano 5g */
    { "73A", "C074" }, /* 16GB Red Nano 5g */
    { "73B", "C075" }, /* 16GB Pink Nano 5g */

    { "CMN", "C525" }, /* 8GB Silver Nano 6g */
    { "DVX", "C688" }, /* 8GB Black Nano 6g */
    { "DVY", "C689" }, /* 8GB Blue Nano 6g */
    { "DW0", "C690" }, /* 8GB Green Nano 6g */
    { "DW1", "C691" }, /* 8GB Orange Nano 6g */
    { "DW2", "C692" }, /* 8GB Pink Nano 6g */
    { "DW3", "C693" }, /* 8GB Pink Nano 6g */
    { "CMP", "C526" }, /* 16GB Silver Nano 6g */
    { "DW4", "C694" }, /* 16GB Black Nano 6g */
    { "DW5", "C695" }, /* 16GB Blue Nano 6g */
    { "DW6", "C696" }, /* 16GB Green Nano 6g */
    { "DW7", "C697" }, /* 16GB Orange Nano 6g */
    { "DW8", "C698" }, /* 16GB Pink Nano 6g */
    { "DW9", "C699" }, /* 16GB Red Nano 6g */

    { "A1S", "C306" }, /* 2GB Silver Shuffle 3g */
    { "A78", "C323" }, /* 2GB Black Shuffle 3g */
    { "ALB", "C381" }, /* 2GB Green Shuffle 3g */
    { "ALD", "C384" }, /* 2GB Blue Shuffle 3g */
    { "ALG", "C387" }, /* 2GB Pink Shuffle 3g */
    { "4NZ", "B867" }, /* 4GB Silver Shuffle 3g */
    { "891", "C164" }, /* 4GB Black Shuffle 3g */
    { "A1L", "C303" }, /* 4GB Stainless Shuffle 3g */
    { "A1U", "C307" }, /* 4GB Green Shuffle 3g */
    { "A7B", "C328" }, /* 4GB Blue Shuffle 3g */
    { "A7D", "C331" }, /* 4GB Pink Shuffle 3g */

    { "CMJ", "C584" }, /* 2GB Silver Shuffle 4g */
    { "CMK", "C585" }, /* 2GB Pink Shuffle 4g */
    { "FDM", "C749" }, /* 2GB Orange Shuffle 4g */
    { "FDN", "C750" }, /* 2GB Green Shuffle 4g */
    { "FDP", "C751" }, /* 2GB Blue Shuffle 4g */

    { "W4N", "A623" }, /* 8GB Silver iPod Touch (1st gen) */
    { "W4T", "A627" }, /* 16GB Silver iPod Touch (1st gen) */
    { "0JW", "B376" }, /* 32GB Silver iPod Touch (1st gen) */
    { "201", "B528" }, /* 8GB Silver iPod Touch (2nd gen) */
    { "203", "B531" }, /* 16GB Silver iPod Touch (2nd gen) */
    { "75J", "C086" }, /* 8GB iPod Touch (3rd gen) */
    { "6K2", "C008" }, /* 32GB iPod Touch (3rd gen) */
    { "6K4", "C011" }, /* 64GB iPod Touch (3rd gen) */
    { "CP7", "C540" }, /* 8GB iPod Touch (4th gen) */
    { "CP9", "C544" }, /* 32GB iPod Touch (4th gen) */
    { "CPC", "C547" }, /* 64GB iPod Touch (4th gen) */

    { "VR0", "A501" }, /* 4GB Silver iPhone 1st gen */
    { "WH8", "A712" }, /* 8GB Silver iPhone */
    { "0KH", "B384" }, /* 16GB Silver iPhone */
    { "Y7H", "B046" }, /* 8GB Black iPhone 3G */
    { "Y7K", "B496" }, /* 16GB Black iPhone 3G */
    { "3NP", "C131" }, /* 16GB Black iPhone 3GS */
    { "3NR", "C133" }, /* 32GB Black iPhone 3GS */
    { "3NS", "C134" }, /* 32GB White iPhone 3GS */
    { "A4S", "C603" }, /* 16GB Black iPhone 4G */
    { "A4T", "C605" }, /* 32GB Black iPhone 4G */

    { "Z38", "B292" }, /* 16GB iPad with Wifi */
    { "Z39", "B293" }, /* 32GB iPad with Wifi */
    { "Z3A", "B294" }, /* 64GB iPad with Wifi */
    { NULL ,  NULL  }
};

static const Itdb_IpodInfo *get_ipod_info_from_model_number (const char *model_number);

/* Reset or create the SysInfo hash table */
static void itdb_device_reset_sysinfo (Itdb_Device *device)
{
    if (device->sysinfo)
	g_hash_table_destroy (device->sysinfo);
    device->sysinfo = g_hash_table_new_full (g_str_hash, g_str_equal,
					     g_free, g_free);
    device->sysinfo_changed = FALSE;
}


/**
 * itdb_device_new:
 * 
 * Creates a new #Itdb_Device structure
 *
 * Returns: a newly allocated #Itdb_Device which must be freed with
 * itdb_device_free() when no longer needed
 *
 * Since: 0.4.0
 */
Itdb_Device *itdb_device_new (void)
{
    Itdb_Device *dev;

    dev = g_new0 (Itdb_Device, 1);
    itdb_device_reset_sysinfo (dev);
    return dev;
}

/**
 * itdb_device_free:
 * @device: an #Itdb_Device
 *
 * Frees memory used by @device
 *
 * Since: 0.4.0
 */
void itdb_device_free (Itdb_Device *device)
{
    if (device)
    {
	g_free (device->mountpoint);
	if (device->sysinfo)
	    g_hash_table_destroy (device->sysinfo);
        if (device->sysinfo_extended)
            itdb_sysinfo_properties_free (device->sysinfo_extended);
	g_free (device);
    }
}


/**
 * itdb_device_set_mountpoint:
 * @device: an #Itdb_Device
 * @mp:     the new mount point
 *
 * Sets the mountpoint of @device to @mp and update the cached device 
 * information (in particular, re-read the SysInfo file)
 *
 * <warning><para>Calling this function invalidates all the artwork in the
 * #Itdb_iTunesDB database using this #Itdb_Device. Trying to access this
 * artwork will result in memory corruption. It's recommended to use
 * itdb_set_mountpoint() instead which will clean the invalidated artwork
 * for you.</para></warning>.
 *
 * Since: 0.4.0
 */
void itdb_device_set_mountpoint (Itdb_Device *device, const gchar *mp)
{
    g_return_if_fail (device);

    g_free (device->mountpoint);
    device->mountpoint = g_strdup (mp);
    if (mp) {
	itdb_device_read_sysinfo (device);
        itdb_device_set_timezone_info (device);
    }
}

static void itdb_device_read_sysinfo_extended (Itdb_Device *device)
{
    const gchar *p_sysinfo_ex[] = {"SysInfoExtended", NULL};
    gchar *dev_path, *sysinfo_ex_path;

    if (device->sysinfo_extended != NULL) {
        itdb_sysinfo_properties_free (device->sysinfo_extended);
        device->sysinfo_extended = NULL;
    }

    dev_path = itdb_get_device_dir (device->mountpoint);
    if (!dev_path) return;

    sysinfo_ex_path = itdb_resolve_path (dev_path, p_sysinfo_ex);
    g_free (dev_path);
    if (!sysinfo_ex_path) return;
    device->sysinfo_extended = itdb_sysinfo_extended_parse (sysinfo_ex_path, NULL);
    g_free (sysinfo_ex_path);

    if ((device->sysinfo != NULL) && (device->sysinfo_extended != NULL)) {
        const char *fwid;
        fwid = itdb_sysinfo_properties_get_firewire_id (device->sysinfo_extended);
        if (fwid != NULL) {
            g_hash_table_insert (device->sysinfo, g_strdup ("FirewireGuid"),
                                 g_strdup (fwid));
        }
    }
}

/** 
 * itdb_device_read_sysinfo:
 * @device: an #Itdb_Device
 *
 * Reads the SysInfo file and stores information in device->sysinfo for
 * later use.
 *
 * <warning><para>Calling this function invalidates all the artwork in the
 * #Itdb_iTunesDB database using this #Itdb_Device. Trying to access this
 * artwork will result in memory corruption. Directly calling this function
 * shouldn't ever be needed and it will be deprecated
 * soon.</para></warning>.
 *
 * Returns: TRUE if file could be read, FALSE otherwise 
 *
 * Since: 0.4.0
 */
gboolean itdb_device_read_sysinfo (Itdb_Device *device)
{
    const gchar *p_sysinfo[] = {"SysInfo", NULL};
    gchar *dev_path, *sysinfo_path;
    FILE *fd;
    gboolean result = FALSE;
    gchar buf[1024];

    g_return_val_if_fail (device, FALSE);
    g_return_val_if_fail (device->mountpoint, FALSE);

    itdb_device_reset_sysinfo (device);

    g_return_val_if_fail (device->sysinfo, FALSE);

    dev_path = itdb_get_device_dir (device->mountpoint);

    if (!dev_path) return FALSE;

    sysinfo_path = itdb_resolve_path (dev_path, p_sysinfo);

    if (sysinfo_path)
    {
	fd = fopen (sysinfo_path, "r");
	if (fd)
	{
	    result = TRUE;
	    while(fgets(buf, sizeof(buf), fd))
	    {
		gchar *ptr;
		gint len = strlen (buf);
		/* suppress newline at end of line */
		if ((len>0) && (buf[len-1]==0x0a))
		{
		    buf[len-1] = 0;
		    --len;
		}
		ptr = strchr (buf, ':');
		if (ptr && (ptr!=buf))
		{
		    *ptr = 0;
		    ++ptr;
		    itdb_device_set_sysinfo (device,
					     buf, g_strstrip(ptr));
		}
	    }
	    fclose (fd);
	}
	g_free (sysinfo_path);
    }
    g_free (dev_path);

    itdb_device_read_sysinfo_extended (device);

    /* indicate that sysinfo is identical to what is on the iPod */
    device->sysinfo_changed = FALSE;
    return result;
}

/* used by itdb_device_write_sysinfo() */
static void write_sysinfo_entry (const gchar *key,
				 const gchar *value,
				 FILE *file)
{
    fprintf (file, "%s: %s\n", key, value);
}



/** 
 * itdb_device_write_sysinfo:
 * @device: an #Itdb_Device
 * @error:  return location for a #GError or NULL
 *
 * Fills the SysInfo file with information in device->sysinfo. Note:
 * no directories are created if not already existent.
 *
 * Returns: TRUE if file could be read, FALSE otherwise 
 *
 * Since: 0.4.0
 */
gboolean itdb_device_write_sysinfo (Itdb_Device *device, GError **error)
{
    gchar *devicedir;
    gboolean success = FALSE;

    g_return_val_if_fail (device, FALSE);
    g_return_val_if_fail (device->mountpoint, FALSE);

    devicedir = itdb_get_device_dir (device->mountpoint);
    if (devicedir)
    {
	gchar *sysfile = g_build_filename (devicedir, "SysInfo", NULL);
	FILE *sysinfo = fopen (sysfile, "w");
	if (sysinfo)
	{
	    if (device->sysinfo)
	    {
		g_hash_table_foreach (device->sysinfo,
				      (GHFunc)write_sysinfo_entry,
				      sysinfo);
	    }
	    fclose (sysinfo);
	    success = TRUE;
	}
	else
	{
	    g_set_error (error, 0, -1,
			 _("Could not open '%s' for writing."),
			 sysfile);
	}
	g_free (sysfile);
	g_free (devicedir);
    }
    else
    {
	g_set_error (error, 0, -1,
		     _("Device directory does not exist."));
    }

    if (success)
	device->sysinfo_changed = FALSE;

    return success;
}


/**
 * itdb_device_get_sysinfo:
 * @device: an #Itdb_Device
 * @field:  field to retrive information from
 *
 * Retrieve specified field from the SysInfo file.
 *
 * Returns: the information associated with @field, or NULL if @field
 * couldn't be found. g_free() after use
 *
 * Since: 0.4.0
 */
gchar *itdb_device_get_sysinfo (const Itdb_Device *device, const gchar *field)
{
    g_return_val_if_fail (device, NULL);
    g_return_val_if_fail (device->sysinfo, NULL);
    g_return_val_if_fail (field, NULL);

    return g_strdup (g_hash_table_lookup (device->sysinfo, field));
}

/**
 * itdb_device_set_sysinfo:
 * @device: an #Itdb_Device
 * @field:  field to set
 * @value:  value to set (or NULL to remove the field).
 *
 * Set specified field. It can later be written to the iPod using
 * itdb_device_write_sysinfo()
 *
 * Since: 0.4.0
 */
void itdb_device_set_sysinfo (Itdb_Device *device,
			      const gchar *field, const gchar *value)
{
    g_return_if_fail (device);
    g_return_if_fail (device->sysinfo);
    g_return_if_fail (field);

    if (value)
    {
	g_hash_table_insert (device->sysinfo,
			     g_strdup (field), g_strdup (value));
    }
    else
    {
	g_hash_table_remove (device->sysinfo, field);
    }
    device->sysinfo_changed = TRUE;
}


/**
 * itdb_device_get_ipod_info:
 * @device: an #Itdb_Device
 *
 * Retrieve the #Itdb_IpodInfo entry for this iPod
 *
 * Returns: the #Itdb_IpodInfo entry for this iPod
 *
 * Since: 0.4.0
 */
const Itdb_IpodInfo *
itdb_device_get_ipod_info (const Itdb_Device *device)
{
    gchar *model_num;
    const Itdb_IpodInfo *info;

    if (device->sysinfo_extended != NULL) {
        const char *serial;

        serial = itdb_sysinfo_properties_get_serial_number (device->sysinfo_extended);
        info = itdb_ipod_info_from_serial (serial);
        if (info != NULL) {
            return info;
        }
    }

    model_num = itdb_device_get_sysinfo (device, "ModelNumStr");

    if (!model_num)
	return &ipod_info_table[0];

    info = get_ipod_info_from_model_number (model_num);
    g_free(model_num);
    if (info != NULL) {
        return info;
    } else {
        return &ipod_info_table[1];
    }
}


/* Return supported artwork formats supported by this iPod */
static const ArtworkCapabilities *
itdb_device_get_artwork_capabilities (const Itdb_Device *device)
{
    const Itdb_IpodInfo *info;
    const ArtworkCapabilities *caps;

    g_return_val_if_fail (device, NULL);

    info = itdb_device_get_ipod_info (device);

    g_return_val_if_fail (info, NULL);
    caps = ipod_artwork_capabilities;
    while (caps->generation != ITDB_IPOD_GENERATION_UNKNOWN) {
        if (caps->generation == info->ipod_generation) {
            break;
        }
        caps++;
    }

    if (caps->generation == ITDB_IPOD_GENERATION_UNKNOWN) {
	return NULL;
    }
 
    return caps;
}

static GList *
itdb_device_get_artwork_formats_fallback (const Itdb_Device *device, 
					  enum ArtworkType type)
{
    const ArtworkCapabilities *caps;
    const Itdb_ArtworkFormat *formats = NULL;
    const Itdb_ArtworkFormat *it;
    GList *artwork_formats = NULL;

    caps = itdb_device_get_artwork_capabilities (device);
    if (caps == NULL) {
	return NULL;
    }
    switch (type) {
	case ARTWORK_TYPE_COVER_ART:
	    formats = caps->cover_art_formats;
	    break;
	case ARTWORK_TYPE_PHOTO:
	    formats = caps->photo_formats;
	    break;
	case ARTWORK_TYPE_CHAPTER_IMAGE:
	    formats = caps->chapter_image_formats;
	    break;
    }
    if (formats == NULL) {
        return NULL;
    }

    for (it = formats; it->format_id != -1; it++) {
        artwork_formats = g_list_prepend (artwork_formats, (gpointer)it);
    }

    return artwork_formats;
}

/**
 * itdb_device_get_photo_formats:
 * @device: an #Itdb_Device
 *
 * Returns: a #GList of #Itdb_ArtworkFormat describing the photo formats 
 * supported by the iPod associated with @device. The returned list must
 * be freed with g_list_free() when no longer needed.
 *
 **/
GList *itdb_device_get_photo_formats (const Itdb_Device *device)
{
    g_return_val_if_fail (device != NULL, NULL);

    if (device->sysinfo_extended == NULL) {
        return itdb_device_get_artwork_formats_fallback (device,
							 ARTWORK_TYPE_PHOTO);
    } else {
        return g_list_copy ((GList *)itdb_sysinfo_properties_get_photo_formats (device->sysinfo_extended));
    }
    g_return_val_if_reached (NULL);
}

/**
 * itdb_device_get_cover_art_formats:
 * @device: an #Itdb_Device
 *
 * Returns: a #GList of #Itdb_ArtworkFormat describing the cover art formats 
 * supported by the iPod associated with @device. The returned list must
 * freed with g_list_free() when no longer needed.
 *
 **/
GList *itdb_device_get_cover_art_formats (const Itdb_Device *device)
{
    g_return_val_if_fail (device != NULL, NULL);

    if (device->sysinfo_extended == NULL) {
        return itdb_device_get_artwork_formats_fallback (device,
							 ARTWORK_TYPE_COVER_ART);
    } else {
        return g_list_copy ((GList *)itdb_sysinfo_properties_get_cover_art_formats (device->sysinfo_extended));
    }
    g_return_val_if_reached (NULL);
}

/**
 * itdb_device_get_chapter_image_formats:
 * @device: an #Itdb_Device
 *
 * Returns: a #GList of #Itdb_ArtworkFormat describing the chapter image 
 * formats supported by the iPod associated with @device. The returned list
 * must be freed with g_list_free() when no longer needed.
 *
 **/
GList *itdb_device_get_chapter_image_formats (const Itdb_Device *device)
{
    g_return_val_if_fail (device != NULL, NULL);

    if (device->sysinfo_extended == NULL) {
        return itdb_device_get_artwork_formats_fallback (device,
							 ARTWORK_TYPE_CHAPTER_IMAGE);
    } else {
        return g_list_copy ((GList *)itdb_sysinfo_properties_get_chapter_image_formats (device->sysinfo_extended));
    }
    g_return_val_if_reached (NULL);
}

G_GNUC_INTERNAL gboolean
itdb_device_supports_sqlite_db (const Itdb_Device *device)
{
    if (device->sysinfo_extended != NULL) {
	return itdb_sysinfo_properties_supports_sqlite (device->sysinfo_extended);
    } else {
	const Itdb_IpodInfo *info;
	info = itdb_device_get_ipod_info (device);
	switch (info->ipod_generation) {
	    case ITDB_IPOD_GENERATION_UNKNOWN:
	    case ITDB_IPOD_GENERATION_FIRST:
	    case ITDB_IPOD_GENERATION_SECOND:
	    case ITDB_IPOD_GENERATION_THIRD:
	    case ITDB_IPOD_GENERATION_FOURTH:
	    case ITDB_IPOD_GENERATION_PHOTO:
	    case ITDB_IPOD_GENERATION_MOBILE:
	    case ITDB_IPOD_GENERATION_MINI_1:
	    case ITDB_IPOD_GENERATION_MINI_2:
	    case ITDB_IPOD_GENERATION_SHUFFLE_1:
	    case ITDB_IPOD_GENERATION_SHUFFLE_2:
	    case ITDB_IPOD_GENERATION_SHUFFLE_3:
	    case ITDB_IPOD_GENERATION_SHUFFLE_4:
	    case ITDB_IPOD_GENERATION_NANO_1:
	    case ITDB_IPOD_GENERATION_NANO_2:
	    case ITDB_IPOD_GENERATION_VIDEO_1:
	    case ITDB_IPOD_GENERATION_VIDEO_2:
	    case ITDB_IPOD_GENERATION_NANO_3:
	    case ITDB_IPOD_GENERATION_NANO_4:
	    case ITDB_IPOD_GENERATION_CLASSIC_1:
	    case ITDB_IPOD_GENERATION_CLASSIC_2:
	    case ITDB_IPOD_GENERATION_CLASSIC_3:
		return FALSE;
	    case ITDB_IPOD_GENERATION_NANO_5:
	    case ITDB_IPOD_GENERATION_NANO_6:
	    case ITDB_IPOD_GENERATION_TOUCH_1:
	    case ITDB_IPOD_GENERATION_TOUCH_2:
	    case ITDB_IPOD_GENERATION_TOUCH_3:
	    case ITDB_IPOD_GENERATION_TOUCH_4:
	    case ITDB_IPOD_GENERATION_IPHONE_1:
	    case ITDB_IPOD_GENERATION_IPHONE_2:
	    case ITDB_IPOD_GENERATION_IPHONE_3:
	    case ITDB_IPOD_GENERATION_IPHONE_4:
	    case ITDB_IPOD_GENERATION_IPAD_1:
		/* FIXME: needs to check firmware version */
		return TRUE;
	}
	g_return_val_if_reached (FALSE);
    }
}

G_GNUC_INTERNAL gboolean
itdb_device_supports_compressed_itunesdb (const Itdb_Device *device)
{
    return itdb_device_supports_sqlite_db (device);
}

G_GNUC_INTERNAL gboolean 
itdb_device_supports_sparse_artwork (const Itdb_Device *device)
{
    gboolean supports_sparse_artwork = FALSE;

    g_return_val_if_fail (device != NULL, FALSE);

    if (device->sysinfo_extended != NULL) {
        supports_sparse_artwork = itdb_sysinfo_properties_supports_sparse_artwork (device->sysinfo_extended);
    }

    if (supports_sparse_artwork == FALSE) {
        const Itdb_IpodInfo *info;
        info = itdb_device_get_ipod_info (device);
        switch (info->ipod_generation) {
            case ITDB_IPOD_GENERATION_UNKNOWN:
            case ITDB_IPOD_GENERATION_FIRST:
            case ITDB_IPOD_GENERATION_SECOND:
            case ITDB_IPOD_GENERATION_THIRD:
            case ITDB_IPOD_GENERATION_FOURTH:
            case ITDB_IPOD_GENERATION_PHOTO:
            case ITDB_IPOD_GENERATION_MOBILE:
            case ITDB_IPOD_GENERATION_MINI_1:
            case ITDB_IPOD_GENERATION_MINI_2:
            case ITDB_IPOD_GENERATION_SHUFFLE_1:
            case ITDB_IPOD_GENERATION_SHUFFLE_2:
            case ITDB_IPOD_GENERATION_SHUFFLE_3:
            case ITDB_IPOD_GENERATION_SHUFFLE_4:
            case ITDB_IPOD_GENERATION_NANO_1:
            case ITDB_IPOD_GENERATION_NANO_2:
            case ITDB_IPOD_GENERATION_VIDEO_1:
            case ITDB_IPOD_GENERATION_VIDEO_2:
                supports_sparse_artwork = FALSE;
                break;
            case ITDB_IPOD_GENERATION_NANO_3:
            case ITDB_IPOD_GENERATION_NANO_4:
            case ITDB_IPOD_GENERATION_NANO_5:
            case ITDB_IPOD_GENERATION_NANO_6:
            case ITDB_IPOD_GENERATION_CLASSIC_1:
            case ITDB_IPOD_GENERATION_CLASSIC_2:
            case ITDB_IPOD_GENERATION_CLASSIC_3:
            case ITDB_IPOD_GENERATION_TOUCH_1:
            case ITDB_IPOD_GENERATION_TOUCH_2:
            case ITDB_IPOD_GENERATION_TOUCH_3:
            case ITDB_IPOD_GENERATION_TOUCH_4:
            case ITDB_IPOD_GENERATION_IPHONE_1:
            case ITDB_IPOD_GENERATION_IPHONE_2:
            case ITDB_IPOD_GENERATION_IPHONE_3:
            case ITDB_IPOD_GENERATION_IPHONE_4:
            case ITDB_IPOD_GENERATION_IPAD_1:
                supports_sparse_artwork = TRUE;
                break;
        }
    }
    return supports_sparse_artwork;
}

/* Determine the number of F.. directories in iPod_Control/Music.*/
G_GNUC_INTERNAL gint
itdb_musicdirs_number_by_mountpoint (const gchar *mountpoint)
{
    gint dir_num;
    gchar *music_dir = itdb_get_music_dir (mountpoint);

    if (!music_dir) return 0;

    /* count number of dirs */
    for (dir_num=0; ;++dir_num)
    {
	gchar *dir_filename;
	gchar dir_num_str[6];

	g_snprintf (dir_num_str, 6, "F%02d", dir_num);
  
	dir_filename = itdb_get_path (music_dir, dir_num_str);

	g_free (dir_filename);
	if (!dir_filename)  break;
    }

    g_free (music_dir);

    return dir_num;
}


/* Determine the number of F.. directories in iPod_Control/Music.

   If device->musicdirs is already set, simply return the previously
   determined number. Otherwise count the directories first and set
   itdb->musicdirs. */
G_GNUC_INTERNAL gint
itdb_device_musicdirs_number (Itdb_Device *device)
{
    g_return_val_if_fail (device, 0);

    if (device->musicdirs <= 0)
    {
	device->musicdirs = itdb_musicdirs_number_by_mountpoint (device->mountpoint);
    }
    return device->musicdirs;
}

/*
 * endianess_check_path:
 * @path:   the file to look at.
 * @hdr:    the header string (4 bytes) in case of LITTLE_ENDIAN
 *
 * Check if endianess can be determined by looking at header of @path.
 *
 * Returns: G_LITTLE_ENDIAN, G_BIG_ENDIAN or 0 if endianess could not be
 * determined.
 */
static guint endianess_check_path (const gchar *path, const gchar *hdr)
{
    guint byte_order = 0;

    if (path)
    {
	int fd = open (path, O_RDONLY);
	if (fd != -1)
	{
	    gchar buf[4];
	    if (read (fd, buf, 4) == 4)
	    {
		if (strncmp (buf, hdr, 4) == 0)
		{
		    byte_order = G_LITTLE_ENDIAN;
		}
		else
		{
		    if ((buf[0] == hdr[3]) &&
			(buf[1] == hdr[2]) &&
			(buf[2] == hdr[1]) &&
			(buf[3] == hdr[0]))
		    {
			byte_order = G_BIG_ENDIAN;
		    }
		}
	    }
	    close (fd);
	}
    }
    return byte_order;
}

/* Attempt to guess the endianess used by this iPod.
 *
 * It will overwrite the previous setting.
 *
 */
G_GNUC_INTERNAL void
itdb_device_autodetect_endianess (Itdb_Device *device)
{
    guint byte_order = 0;

    g_return_if_fail (device);

    if (device->mountpoint)
    {
	gchar *path;
	if (byte_order == 0)
	{
	    /* First try reading the iTunesDB */
	    path = itdb_get_itunesdb_path (device->mountpoint);
	    byte_order = endianess_check_path (path, "mhbd");
	    g_free (path);
	}
	if (byte_order == 0)
	{
	    /* Try reading the ArtworkDB */
	    path = itdb_get_artworkdb_path (device->mountpoint);
	    byte_order = endianess_check_path (path, "mhfd");
	    g_free (path);
	}
	if (byte_order == 0)
	{
	    /* Try reading the Photos Database */
	    path = itdb_get_photodb_path (device->mountpoint);
	    byte_order = endianess_check_path (path, "mhfd");
	    g_free (path);
	}
	if (byte_order == 0)
	{
	    gchar *control_dir = itdb_get_control_dir (device->mountpoint);
	    if (control_dir)
	    {
		gchar *cd_l = g_ascii_strdown (control_dir, -1);
		/* check if cd_l ends on "itunes/itunes_control" */
		if (strstr (cd_l, "itunes/itunes_control") ==
		    (cd_l + strlen (cd_l) - strlen ("itunes/itunes_control")))
		{
		    byte_order = G_BIG_ENDIAN;
		}
		g_free (cd_l);
		g_free (control_dir);
	    }
	}
    }

    /* default: non-reversed */
    if (byte_order == 0)
	byte_order = G_LITTLE_ENDIAN;

    device->byte_order = byte_order;
}

/*------------------------------------------------------------------*\
 *                                                                  *
 * Functions for applications to obtain general infos about various *
 * iPods.                                                           *
 *                                                                  *
\*------------------------------------------------------------------*/

/**
 * itdb_info_get_ipod_info_table:
 *
 * Return a pointer to the start of valid iPod model descriptions,
 * which is an array of #Itdb_IpodInfo entries. This can be useful if you
 * want to build a list of all iPod models known to the current libgpod.
 *
 * Returns: a pointer to the array of #Itdb_IpodInfo entries.
 *
 * Since: 0.4.0
 */
const Itdb_IpodInfo *itdb_info_get_ipod_info_table (void)
{
    return &ipod_info_table[2];
}


/**
 * itdb_info_get_ipod_model_name_string:
 * @model: an #Itdb_IpodModel
 *
 * Return the iPod's generic model name, like "Color", "Nano"...
 *
 * Returns: a pointer to the model name. This is a static string
 * and must not be g_free()d.
 *
 * Since: 0.4.0
 */
const gchar *itdb_info_get_ipod_model_name_string (Itdb_IpodModel model)
{
    gint i=0;

    /* Instead of returning ipod_model_name_table[model] directly,
       verify if @model is valid */
    while (ipod_model_name_table[i])
    {
	if (i==model)  return _(ipod_model_name_table[i]);
	++i;
    }
    return NULL;
}


/**
 * itdb_info_get_ipod_generation_string:
 * @generation: an #Itdb_IpodGeneration
 *
 * Return the iPod's generic generation name, like "First Generation",
 * "Mobile Phone"...
 *
 * Returns: a pointer to the generation name. This is a static
 * string and must not be g_free()d.
 *
 * Since: 0.4.0
 */
const gchar *itdb_info_get_ipod_generation_string (Itdb_IpodGeneration generation)
{
    gint i=0;

    /* Instead of returning ipod_generation_name_table[generation]
       directly, verify if @generation is valid */
    while (ipod_generation_name_table[i])
    {
	if (i==generation)
	    return _(ipod_generation_name_table[i]);
	++i;
    }
    return NULL;
}


/**
 * itdb_device_supports_artwork:
 * @device: an #Itdb_Device
 *
 * Indicates whether @device can display artwork or not. When dealing
 * with a non-art capable iPod, no artwork data will be written to the
 * iPod so you can spare calls to the artwork handling methods.
 *
 * Returns: true if @device can display artwork.
 *
 * Since: 0.5.0
 */
gboolean itdb_device_supports_artwork (const Itdb_Device *device)
{
    GList *formats;
    if (device == NULL) {
        return FALSE;
    }
    formats = itdb_device_get_cover_art_formats (device);
    g_list_free (formats);
    return (formats != NULL);
}


/**
 * itdb_device_supports_video:
 * @device: an #Itdb_Device
 *
 * Indicates whether @device can play videos or not.
 *
 * Returns: true if @device can play videos.
 *
 * Since: 0.7.0
 */
gboolean itdb_device_supports_video (const Itdb_Device *device)
{
    const Itdb_IpodInfo *info;
    if (device == NULL) {
        return FALSE;
    }

    info = itdb_device_get_ipod_info (device);
    switch (info->ipod_generation) {
        case ITDB_IPOD_GENERATION_UNKNOWN:
        case ITDB_IPOD_GENERATION_FIRST:
        case ITDB_IPOD_GENERATION_SECOND:
        case ITDB_IPOD_GENERATION_THIRD:
        case ITDB_IPOD_GENERATION_FOURTH:
        case ITDB_IPOD_GENERATION_PHOTO:
        case ITDB_IPOD_GENERATION_MOBILE:
        case ITDB_IPOD_GENERATION_MINI_1:
        case ITDB_IPOD_GENERATION_MINI_2:
        case ITDB_IPOD_GENERATION_SHUFFLE_1:
        case ITDB_IPOD_GENERATION_SHUFFLE_2:
        case ITDB_IPOD_GENERATION_SHUFFLE_3:
        case ITDB_IPOD_GENERATION_SHUFFLE_4:
        case ITDB_IPOD_GENERATION_NANO_1:
        case ITDB_IPOD_GENERATION_NANO_2:
        case ITDB_IPOD_GENERATION_NANO_6:
            return FALSE;
        case ITDB_IPOD_GENERATION_NANO_3:
        case ITDB_IPOD_GENERATION_NANO_4:
        case ITDB_IPOD_GENERATION_NANO_5:
        case ITDB_IPOD_GENERATION_VIDEO_1:
        case ITDB_IPOD_GENERATION_VIDEO_2:
        case ITDB_IPOD_GENERATION_CLASSIC_1:
	case ITDB_IPOD_GENERATION_CLASSIC_2:
	case ITDB_IPOD_GENERATION_CLASSIC_3:
	case ITDB_IPOD_GENERATION_TOUCH_1:
	case ITDB_IPOD_GENERATION_TOUCH_2:
	case ITDB_IPOD_GENERATION_TOUCH_3:
	case ITDB_IPOD_GENERATION_TOUCH_4:
	case ITDB_IPOD_GENERATION_IPHONE_1:
	case ITDB_IPOD_GENERATION_IPHONE_2:
	case ITDB_IPOD_GENERATION_IPHONE_3:
	case ITDB_IPOD_GENERATION_IPHONE_4:
	case ITDB_IPOD_GENERATION_IPAD_1:
            return TRUE;
    }
    g_return_val_if_reached (FALSE);
}


/**
 * itdb_device_supports_photo:
 * @device: an #Itdb_Device
 *
 * Indicates whether @device can display photos or not.
 *
 * Returns: true if @device can display photos.
 *
 * Since: 0.5.0
 */
gboolean itdb_device_supports_photo (const Itdb_Device *device)
{
    GList *formats;
    if (device == NULL) {
        return FALSE;
    }
    formats = itdb_device_get_photo_formats (device);
    g_list_free (formats);
    return (formats != NULL);
}

/**
 * itdb_device_supports_chapter_image:
 * @device: an #Itdb_Device
 *
 * Indicates whether @device can display chapter images or not.
 *
 * Return value: true if @device can display chapter images.
 *
 * Since: 0.7.2
 */
gboolean itdb_device_supports_chapter_image (const Itdb_Device *device)
{
    GList *formats;
    if (device == NULL) {
        return FALSE;
    }
    formats = itdb_device_get_chapter_image_formats (device);
    g_list_free (formats);
    return (formats != NULL);
}

/**
 * itdb_device_get_firewire_id
 * @device: an #Itdb_Device
 *
 * Returns the Firewire ID for @device, this is useful to calculate the 
 * iTunesDB checksum which is expected by newer iPod models
 * (iPod classic/fat nanos) and is needed on iPod plugged through USB contrary
 * to what the name implies.
 *
 * Returns: a string containing the firewire id in hexadecimal
 * or NULL if we don't know it.
 *
 * Since: 0.6.0
 */
const char *itdb_device_get_firewire_id (const Itdb_Device *device)
{
    if (device->sysinfo_extended != NULL) {
	return itdb_sysinfo_properties_get_firewire_id (device->sysinfo_extended);
    }

    return g_hash_table_lookup (device->sysinfo, "FirewireGuid");
}

gchar *itdb_device_get_uuid(const Itdb_Device *device)
{
    return g_hash_table_lookup (device->sysinfo, "FirewireGuid");
}

static int ord_from_hex_char(const char c)
{
  if ('0' <= c && c <= '9')
    return c - '0';
  else if ('a' <= c && c <= 'f')
    return 10 + (c - 'a');
  else if ('A' <= c && c <= 'F')
    return 10 + (c - 'A');
  else
    return -1;
}

static int
itdb_hex_from_string(unsigned char *dest, const int array_size, const char *s)
{
  /* skip optional '0x' prefix */
  if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X'))
    s += 2;

  if (strlen(s) > array_size*2)
    return -8;

  do {
    int low, high;
    if((high = ord_from_hex_char(s[0])) == -1 ||
       (low = ord_from_hex_char(s[1])) == -1)
      return -9;
    *dest++ = high<<4 | low;
  } while(*(s+=2));
  return 0;
}

gboolean itdb_device_get_hex_uuid (const Itdb_Device *device, unsigned char uuid[20])
{
    const char *uuid_str;
    int result;

    uuid_str = itdb_device_get_firewire_id (device);
    if (uuid_str == NULL) {
        return FALSE;
    }
    memset (uuid, 0, 20);
    result = itdb_hex_from_string (uuid, 20, uuid_str);

    return (result == 0);
}

ItdbChecksumType itdb_device_get_checksum_type (const Itdb_Device *device)
{

    if (device == NULL) {
        return ITDB_CHECKSUM_NONE;
    }
    
    if (device->sysinfo_extended != NULL) {
	gint db_version;
	db_version = itdb_sysinfo_properties_get_db_version (device->sysinfo_extended);
	switch (db_version) {
	    case 0:
	    case 1:
	    case 2:
		return ITDB_CHECKSUM_NONE;
	    case 3:
		return ITDB_CHECKSUM_HASH58;
	    case 4:
		return ITDB_CHECKSUM_HASH72;
	    case 5:
		return ITDB_CHECKSUM_HASHAB;
	    default:
		return ITDB_CHECKSUM_UNKNOWN;
	}
    } else {
	const Itdb_IpodInfo *info;
	info = itdb_device_get_ipod_info (device);
	if (info == NULL) {
	    return ITDB_CHECKSUM_NONE;
	}

	switch (info->ipod_generation) {
	    case ITDB_IPOD_GENERATION_CLASSIC_1: 
	    case ITDB_IPOD_GENERATION_CLASSIC_2:
	    case ITDB_IPOD_GENERATION_CLASSIC_3:
	    case ITDB_IPOD_GENERATION_NANO_3:
	    case ITDB_IPOD_GENERATION_NANO_4:
		return ITDB_CHECKSUM_HASH58;

	    case ITDB_IPOD_GENERATION_NANO_5:
	    case ITDB_IPOD_GENERATION_TOUCH_1:
	    case ITDB_IPOD_GENERATION_TOUCH_2:
	    case ITDB_IPOD_GENERATION_TOUCH_3:
	    case ITDB_IPOD_GENERATION_IPHONE_1:
	    case ITDB_IPOD_GENERATION_IPHONE_2:
	    case ITDB_IPOD_GENERATION_IPHONE_3:
		return ITDB_CHECKSUM_HASH72;

	    case ITDB_IPOD_GENERATION_IPAD_1:
	    case ITDB_IPOD_GENERATION_IPHONE_4:
	    case ITDB_IPOD_GENERATION_TOUCH_4:
	    case ITDB_IPOD_GENERATION_NANO_6:
		return ITDB_CHECKSUM_HASHAB;

	    case ITDB_IPOD_GENERATION_UNKNOWN:
	    case ITDB_IPOD_GENERATION_FIRST:
	    case ITDB_IPOD_GENERATION_SECOND:
	    case ITDB_IPOD_GENERATION_THIRD:
	    case ITDB_IPOD_GENERATION_FOURTH:
	    case ITDB_IPOD_GENERATION_PHOTO:
	    case ITDB_IPOD_GENERATION_MOBILE:
	    case ITDB_IPOD_GENERATION_MINI_1:
	    case ITDB_IPOD_GENERATION_MINI_2:
	    case ITDB_IPOD_GENERATION_SHUFFLE_1:
	    case ITDB_IPOD_GENERATION_SHUFFLE_2:
	    case ITDB_IPOD_GENERATION_SHUFFLE_3:
	    case ITDB_IPOD_GENERATION_SHUFFLE_4:
	    case ITDB_IPOD_GENERATION_NANO_1:
	    case ITDB_IPOD_GENERATION_NANO_2:
	    case ITDB_IPOD_GENERATION_VIDEO_1:
	    case ITDB_IPOD_GENERATION_VIDEO_2:
		return ITDB_CHECKSUM_NONE;
	}
    }

    return ITDB_CHECKSUM_NONE;
}

G_GNUC_INTERNAL gboolean itdb_device_write_checksum (Itdb_Device *device, 
						     unsigned char *itdb_data, 
						     gsize itdb_len,
						     GError **error)
{
    switch (itdb_device_get_checksum_type (device)) {
	case ITDB_CHECKSUM_NONE:
	    return TRUE;
	case ITDB_CHECKSUM_HASH58:
	    return itdb_hash58_write_hash (device, itdb_data, itdb_len, error);
	case ITDB_CHECKSUM_HASH72:
	    return itdb_hash72_write_hash (device, itdb_data, itdb_len, error);
        case ITDB_CHECKSUM_HASHAB:
	    return itdb_hashAB_write_hash (device, itdb_data, itdb_len, error);
	case ITDB_CHECKSUM_UNKNOWN:
            g_set_error (error, 0, -1, "Unsupported checksum type");
	    return FALSE;
    }	
    g_assert_not_reached ();
}

#ifdef WIN32
#include <windows.h>
#else
#include <sys/statvfs.h>
#endif

/**
 * itdb_device_get_storage_info:
 *
 * @device:   an #Itdb_Device
 * @capacity: returned capacity in bytes
 * @free:     returned free space in bytes
 *
 * Return the storage info for this iPod
 *
 * Returns: TRUE if storage info could be obtained, FALSE otherwise
 */
gboolean itdb_device_get_storage_info (Itdb_Device *device, guint64 *capacity, guint64 *free)
{
#ifdef WIN32
    ULARGE_INTEGER u_free, u_capacity;
#else
    struct statvfs info;
    guint64 block_size;
#endif

    g_return_val_if_fail (device, FALSE);
    g_return_val_if_fail (capacity, FALSE);
    g_return_val_if_fail (free, FALSE);

#ifdef WIN32
    if (GetDiskFreeSpaceEx(device->mountpoint, &u_free, &u_capacity, NULL) == 0) {
    	return FALSE;
    }
    *free = u_free.QuadPart;
    *capacity = u_capacity.QuadPart;
    return TRUE;
#else
    if (statvfs(device->mountpoint, &info))
	return FALSE;

    if (info.f_frsize > 0)
	block_size = info.f_frsize;
    else
	block_size = info.f_bsize;

    *capacity = info.f_blocks * block_size;
    *free = info.f_bfree * block_size;

    return TRUE;
#endif
}

struct IpodModelTable {
    GHashTable *serial_hash;
    GHashTable *model_number_hash;
};

static struct IpodModelTable *init_ipod_model_table (void)
{
    const Itdb_IpodInfo *it;
    struct IpodModelTable *model_table;
    const ItdbSerialToModel *serial_it;

    model_table = g_new0 (struct IpodModelTable, 1);
    model_table->serial_hash = g_hash_table_new (g_str_hash, g_str_equal);
    model_table->model_number_hash = g_hash_table_new (g_str_hash, g_str_equal);

    for (it = itdb_info_get_ipod_info_table (); 
         it->model_number != NULL; 
         it++) {
        g_hash_table_insert (model_table->model_number_hash, 
                             (gpointer)it->model_number, (gpointer)it);
    }

    for (serial_it = serial_to_model_mapping; 
         serial_it->serial != NULL; 
         serial_it++) {
        gpointer model_info;
        model_info = g_hash_table_lookup (model_table->model_number_hash,
                                          serial_it->model_number);
        if (model_info != NULL) {
            g_hash_table_insert (model_table->serial_hash, 
                                 (gpointer)serial_it->serial, model_info);
        } else {
            g_warning ("Inconsistent ipod model tables, model info is missing for %s (serial %s)", 
                       serial_it->model_number, serial_it->serial);
        }
    }

    return model_table;
}

static struct IpodModelTable *get_model_table (void)
{
    static GOnce my_once = G_ONCE_INIT;

    g_once (&my_once, (GThreadFunc)init_ipod_model_table, NULL);

    return my_once.retval;
}

static const Itdb_IpodInfo *
get_ipod_info_from_model_number (const char *model_number)
{
    struct IpodModelTable *model_table;

    g_return_val_if_fail (model_number != NULL, NULL);

    model_table = get_model_table ();
    if(isalpha(model_number[0])) {
	model_number++;
    }
    return g_hash_table_lookup (model_table->model_number_hash, model_number);
}

const Itdb_IpodInfo *
itdb_ipod_info_from_serial (const char *serial)
{
    struct IpodModelTable *model_table;
    glong len;

    g_return_val_if_fail (serial != NULL, NULL);

    len = strlen (serial);
    if (len < 3) {
        return NULL;
    }
    model_table = get_model_table ();
    /* The 3 last letters of the ipod serial number can be used to identify 
     * the model
     */
    return g_hash_table_lookup (model_table->serial_hash, serial+len-3);
}

GQuark itdb_device_error_quark (void)
{
    static GQuark quark = 0;
    if (!quark) {
        quark = g_quark_from_static_string ("itdb-device-error-quark");
    }
    return quark;
}

/**
 * itdb_device_supports_podcast:
 * @device: an #Itdb_Device
 *
 * Indicates whether @device can play podcasts or not.
 *
 * Returns: true if @device can play podcasts.
 *
 * Since: 0.7.2
 */
gboolean
itdb_device_supports_podcast (const Itdb_Device *device)
{
    if (device->sysinfo_extended != NULL) {
    	return itdb_sysinfo_properties_supports_podcast (device->sysinfo_extended);
    } else {
	const Itdb_IpodInfo *info;

	info = itdb_device_get_ipod_info (device);
	switch (info->ipod_generation) {
	    case ITDB_IPOD_GENERATION_UNKNOWN:
	    case ITDB_IPOD_GENERATION_FIRST:
	    case ITDB_IPOD_GENERATION_SECOND:
	    case ITDB_IPOD_GENERATION_THIRD:
	    case ITDB_IPOD_GENERATION_MOBILE:
		return FALSE;
	    case ITDB_IPOD_GENERATION_FOURTH:
	    case ITDB_IPOD_GENERATION_PHOTO:
	    case ITDB_IPOD_GENERATION_MINI_1:
	    case ITDB_IPOD_GENERATION_MINI_2:
	    case ITDB_IPOD_GENERATION_NANO_1:
	    case ITDB_IPOD_GENERATION_NANO_2:
	    case ITDB_IPOD_GENERATION_NANO_3:
	    case ITDB_IPOD_GENERATION_NANO_4:
	    case ITDB_IPOD_GENERATION_NANO_5:
	    case ITDB_IPOD_GENERATION_NANO_6:
	    case ITDB_IPOD_GENERATION_SHUFFLE_1:
	    case ITDB_IPOD_GENERATION_SHUFFLE_2:
	    case ITDB_IPOD_GENERATION_SHUFFLE_3:
	    case ITDB_IPOD_GENERATION_SHUFFLE_4:
	    case ITDB_IPOD_GENERATION_VIDEO_1:
	    case ITDB_IPOD_GENERATION_VIDEO_2:
	    case ITDB_IPOD_GENERATION_CLASSIC_1:
	    case ITDB_IPOD_GENERATION_CLASSIC_2:
	    case ITDB_IPOD_GENERATION_CLASSIC_3:
	    case ITDB_IPOD_GENERATION_TOUCH_1:
	    case ITDB_IPOD_GENERATION_TOUCH_2:
	    case ITDB_IPOD_GENERATION_TOUCH_3:
	    case ITDB_IPOD_GENERATION_TOUCH_4:
	    case ITDB_IPOD_GENERATION_IPHONE_1:
	    case ITDB_IPOD_GENERATION_IPHONE_2:
	    case ITDB_IPOD_GENERATION_IPHONE_3:
	    case ITDB_IPOD_GENERATION_IPHONE_4:
	    case ITDB_IPOD_GENERATION_IPAD_1:
		return TRUE;
	}
	g_return_val_if_reached (FALSE);
    }
}

gboolean itdb_device_is_shuffle (const Itdb_Device *device)
{
    const Itdb_IpodInfo *info;

    info = itdb_device_get_ipod_info (device);
    switch (info->ipod_generation) {
        case ITDB_IPOD_GENERATION_UNKNOWN:
        case ITDB_IPOD_GENERATION_FIRST:
        case ITDB_IPOD_GENERATION_SECOND:
        case ITDB_IPOD_GENERATION_THIRD:
        case ITDB_IPOD_GENERATION_MOBILE:
        case ITDB_IPOD_GENERATION_FOURTH:
        case ITDB_IPOD_GENERATION_PHOTO:
        case ITDB_IPOD_GENERATION_MINI_1:
        case ITDB_IPOD_GENERATION_MINI_2:
        case ITDB_IPOD_GENERATION_NANO_1:
        case ITDB_IPOD_GENERATION_NANO_2:
        case ITDB_IPOD_GENERATION_NANO_3:
        case ITDB_IPOD_GENERATION_NANO_4:
        case ITDB_IPOD_GENERATION_NANO_5:
        case ITDB_IPOD_GENERATION_NANO_6:
        case ITDB_IPOD_GENERATION_VIDEO_1:
        case ITDB_IPOD_GENERATION_VIDEO_2:
        case ITDB_IPOD_GENERATION_CLASSIC_1:
        case ITDB_IPOD_GENERATION_CLASSIC_2:
        case ITDB_IPOD_GENERATION_CLASSIC_3:
        case ITDB_IPOD_GENERATION_TOUCH_1:
        case ITDB_IPOD_GENERATION_TOUCH_2:
        case ITDB_IPOD_GENERATION_TOUCH_3:
        case ITDB_IPOD_GENERATION_TOUCH_4:
        case ITDB_IPOD_GENERATION_IPHONE_1:
        case ITDB_IPOD_GENERATION_IPHONE_2:
        case ITDB_IPOD_GENERATION_IPHONE_3:
        case ITDB_IPOD_GENERATION_IPHONE_4:
        case ITDB_IPOD_GENERATION_IPAD_1:
            return FALSE;
        case ITDB_IPOD_GENERATION_SHUFFLE_1:
        case ITDB_IPOD_GENERATION_SHUFFLE_2:
        case ITDB_IPOD_GENERATION_SHUFFLE_3:
        case ITDB_IPOD_GENERATION_SHUFFLE_4:
            return TRUE;
    }
    g_return_val_if_reached (FALSE);
}

gboolean itdb_device_is_iphone_family (const Itdb_Device *device)
{
    if (device->sysinfo_extended != NULL) {
    	return (itdb_sysinfo_properties_get_family_id (device->sysinfo_extended) >= 10000);
    } else {
	const Itdb_IpodInfo *info;

	info = itdb_device_get_ipod_info (device);
	switch (info->ipod_generation) {
	    case ITDB_IPOD_GENERATION_UNKNOWN:
	    case ITDB_IPOD_GENERATION_FIRST:
	    case ITDB_IPOD_GENERATION_SECOND:
	    case ITDB_IPOD_GENERATION_THIRD:
	    case ITDB_IPOD_GENERATION_MOBILE:
	    case ITDB_IPOD_GENERATION_FOURTH:
	    case ITDB_IPOD_GENERATION_PHOTO:
	    case ITDB_IPOD_GENERATION_MINI_1:
	    case ITDB_IPOD_GENERATION_MINI_2:
	    case ITDB_IPOD_GENERATION_NANO_1:
	    case ITDB_IPOD_GENERATION_NANO_2:
	    case ITDB_IPOD_GENERATION_NANO_3:
	    case ITDB_IPOD_GENERATION_NANO_4:
	    case ITDB_IPOD_GENERATION_NANO_5:
	    case ITDB_IPOD_GENERATION_NANO_6:
	    case ITDB_IPOD_GENERATION_SHUFFLE_1:
	    case ITDB_IPOD_GENERATION_SHUFFLE_2:
	    case ITDB_IPOD_GENERATION_SHUFFLE_3:
	    case ITDB_IPOD_GENERATION_SHUFFLE_4:
	    case ITDB_IPOD_GENERATION_VIDEO_1:
	    case ITDB_IPOD_GENERATION_VIDEO_2:
	    case ITDB_IPOD_GENERATION_CLASSIC_1:
	    case ITDB_IPOD_GENERATION_CLASSIC_2:
	    case ITDB_IPOD_GENERATION_CLASSIC_3:
		return FALSE;
	    case ITDB_IPOD_GENERATION_TOUCH_1:
	    case ITDB_IPOD_GENERATION_TOUCH_2:
	    case ITDB_IPOD_GENERATION_TOUCH_3:
	    case ITDB_IPOD_GENERATION_TOUCH_4:
	    case ITDB_IPOD_GENERATION_IPHONE_1:
	    case ITDB_IPOD_GENERATION_IPHONE_2:
	    case ITDB_IPOD_GENERATION_IPHONE_3:
	    case ITDB_IPOD_GENERATION_IPHONE_4:
	    case ITDB_IPOD_GENERATION_IPAD_1:
		return TRUE;
	}
	g_return_val_if_reached (FALSE);
    }
}

enum ItdbShadowDBVersion itdb_device_get_shadowdb_version (const Itdb_Device *device)
{
    int version;

    version = 0;
    if (device->sysinfo_extended != NULL) {
	version = itdb_sysinfo_properties_get_shadow_db_version (device->sysinfo_extended);
    }

    if (version == 0) {
	const Itdb_IpodInfo *info;

	info = itdb_device_get_ipod_info (device);
	switch (info->ipod_generation) {
	    case ITDB_IPOD_GENERATION_UNKNOWN:
	    case ITDB_IPOD_GENERATION_FIRST:
	    case ITDB_IPOD_GENERATION_SECOND:
	    case ITDB_IPOD_GENERATION_THIRD:
	    case ITDB_IPOD_GENERATION_MOBILE:
	    case ITDB_IPOD_GENERATION_FOURTH:
	    case ITDB_IPOD_GENERATION_PHOTO:
	    case ITDB_IPOD_GENERATION_MINI_1:
	    case ITDB_IPOD_GENERATION_MINI_2:
	    case ITDB_IPOD_GENERATION_NANO_1:
	    case ITDB_IPOD_GENERATION_NANO_2:
	    case ITDB_IPOD_GENERATION_NANO_3:
	    case ITDB_IPOD_GENERATION_NANO_4:
	    case ITDB_IPOD_GENERATION_NANO_5:
	    case ITDB_IPOD_GENERATION_NANO_6:
	    case ITDB_IPOD_GENERATION_VIDEO_1:
	    case ITDB_IPOD_GENERATION_VIDEO_2:
	    case ITDB_IPOD_GENERATION_CLASSIC_1:
	    case ITDB_IPOD_GENERATION_CLASSIC_2:
	    case ITDB_IPOD_GENERATION_CLASSIC_3:
	    case ITDB_IPOD_GENERATION_TOUCH_1:
	    case ITDB_IPOD_GENERATION_TOUCH_2:
	    case ITDB_IPOD_GENERATION_TOUCH_3:
	    case ITDB_IPOD_GENERATION_TOUCH_4:
	    case ITDB_IPOD_GENERATION_IPHONE_1:
	    case ITDB_IPOD_GENERATION_IPHONE_2:
	    case ITDB_IPOD_GENERATION_IPHONE_3:
	    case ITDB_IPOD_GENERATION_IPHONE_4:
	    case ITDB_IPOD_GENERATION_IPAD_1:
		version = ITDB_SHADOW_DB_UNKNOWN;
		break;
	    case ITDB_IPOD_GENERATION_SHUFFLE_1:
	    case ITDB_IPOD_GENERATION_SHUFFLE_2:
		version = ITDB_SHADOW_DB_V1;
		break;
	    case ITDB_IPOD_GENERATION_SHUFFLE_3:
	    case ITDB_IPOD_GENERATION_SHUFFLE_4:
		version = ITDB_SHADOW_DB_V2;
		break;
	}
    }

    return version;
}
