From 1499545555374b8eef1f94065fc2886663273903 Mon Sep 17 00:00:00 2001 From: Packit Service Date: Dec 09 2020 19:12:45 +0000 Subject: Prepare for a new update Reverting patches so we can apply the latest update and changes can be seen in the spec file and sources. --- diff --git a/bindings/python/gpod.i.in b/bindings/python/gpod.i.in index 653357f..a6628bb 100644 --- a/bindings/python/gpod.i.in +++ b/bindings/python/gpod.i.in @@ -298,11 +298,9 @@ PyObject* sw_get_photo(GList *list, gint index) { %include "gpod_doc.i" %include "@top_builddir@/config.h" -/* # be nicer to decode these utf8 strings into Unicode objects in the C # layer. Here we are leaving it to the Python side, and just giving # them utf8 encoded Strings. -*/ typedef char gchar; %typemap(in) time_t { diff --git a/bindings/python/gpod.i.in.swig b/bindings/python/gpod.i.in.swig deleted file mode 100644 index a6628bb..0000000 --- a/bindings/python/gpod.i.in.swig +++ /dev/null @@ -1,511 +0,0 @@ -/* File : gpod.i.in */ - -/* - Copyright (C) 2007 Nick Piper - Part of the gtkpod project. - - URL: http://www.gtkpod.org/ - URL: http://gtkpod.sourceforge.net/ - - 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 - - $Id$ - -Please send any fixes, improvements or suggestions to -. - -*/ - -%define DOCSTRING -"This module gives access to an iPod's content. It provides an easy to -use API to retrieve the list of files and playlists stored on an iPod, -modify them, and save them back to the iPod. - -This module implements the libgpod C API as directly as possible in -Python. See the main gpod module for a more traditional Python -interface." -%enddef - -%pythoncode %{ -version = "@PACKAGE_VERSION@" -version_info = version.split(".") -%} - -%module(docstring=DOCSTRING) gpod -%{ -#include "@top_builddir@/config.h" -#include "db-artwork-debug.h" -#include "db-artwork-parser.h" -#include "db-image-parser.h" -#include "db-itunes-parser.h" -#include "db-parse-context.h" -#include "itdb.h" -#include "itdb_device.h" -#include "itdb_private.h" -#include -#include -#ifdef HAVE_GDKPIXBUF -#ifdef HAVE_PYGOBJECT -#include -#include -#endif -#endif - - -/* include prototypes for all functions so builds using - * -Wmissing-prototypes don't fail. */ -PyObject* sw_get_tracks(Itdb_iTunesDB *itdb); -PyObject* sw_get_track(GList *list, gint index); -PyObject* sw_get_rule(GList *list, gint index); -PyObject* sw_get_playlist(GList *list, gint index); -PyObject* sw_get_list_len(GList *list); -PyObject* sw_get_playlists(Itdb_iTunesDB *itdb); -PyObject* sw_get_playlist_tracks(Itdb_Playlist *pl); -PyObject* sw_set_track_userdata(Itdb_Track *track, PyObject *data); -PyObject* sw_get_track_userdata(Itdb_Track *track); -PyObject* sw__track_extra_duplicate (PyObject *data); -PyObject* sw_get_photoalbums(Itdb_PhotoDB *db); -PyObject* sw_get_photoalbum(GList *list, gint index); -PyObject* sw_get_photos(Itdb_PhotoDB *db); -PyObject* sw_get_photo(GList *list, gint index); -PyObject* sw_get_photoalbum_members(Itdb_PhotoAlbum *album); -PyObject* sw_ipod_device_to_dict(Itdb_Device *device); -void sw__track_extra_destroy (PyObject *data); -void hash_table_to_pydict(gpointer key, gpointer value, gpointer user_data); -void SWIG_init(void); - -PyObject* sw_get_tracks(Itdb_iTunesDB *itdb) { - PyObject *list; - gint i; - GList *l; - list = PyList_New(g_list_length(itdb->tracks)); - for (l = itdb->tracks, i = 0; l; l = l->next, ++i) { - PyList_SET_ITEM(list, i, SWIG_NewPointerObj((void*)(l->data), SWIGTYPE_p__Itdb_Track, 0)); - } - return list; - } - -PyObject* sw_get_track(GList *list, gint index) { - GList *position; - if ( (index >= g_list_length(list)) || index < 0 ) { - PyErr_SetString(PyExc_IndexError, "Value out of range"); - return NULL; - } - position = g_list_nth(list, index); - return SWIG_NewPointerObj((void*)(position->data), SWIGTYPE_p__Itdb_Track, 0); - } - -PyObject* sw_get_rule(GList *list, gint index) { - GList *position; - if ( (index >= g_list_length(list)) || index < 0 ) { - PyErr_SetString(PyExc_IndexError, "Value out of range"); - return NULL; - } - position = g_list_nth(list, index); - return SWIG_NewPointerObj((void*)(position->data), SWIGTYPE_p__Itdb_SPLRule, 0); - } - -PyObject* sw_get_playlist(GList *list, gint index) { - GList *position; - if ( (index >= g_list_length(list)) || index < 0 ) { - PyErr_SetString(PyExc_IndexError, "Value out of range"); - return NULL; - } - position = g_list_nth(list, index); - return SWIG_NewPointerObj((void*)(position->data), SWIGTYPE_p__Itdb_Playlist, 0); - } - -PyObject* sw_get_list_len(GList *list) { - return PyInt_FromLong(g_list_length(list)); - } - -PyObject* sw_get_playlist_tracks(Itdb_Playlist *pl) { - PyObject *list; - gint i; - GList *l; - list = PyList_New(g_list_length(pl->members)); - for (l = pl->members, i = 0; l; l = l->next, ++i) { - PyList_SET_ITEM(list, i, SWIG_NewPointerObj((void*)(l->data), SWIGTYPE_p__Itdb_Track, 0)); - } - return list; - } - -PyObject* sw_get_playlists(Itdb_iTunesDB *itdb) { - PyObject *list; - gint i; - GList *l; - list = PyList_New(g_list_length(itdb->playlists)); - for (l = itdb->playlists, i = 0; l; l = l->next, ++i) { - PyList_SET_ITEM(list, i, SWIG_NewPointerObj((void*)(l->data), SWIGTYPE_p__Itdb_Playlist, 0)); - } - return list; - } - - void sw__track_extra_destroy (PyObject *data) { - Py_XDECREF(data); - } - - PyObject *sw__track_extra_duplicate (PyObject *data) { - if (data == Py_None) { - Py_INCREF(Py_None); - return Py_None; - } else { - return PyDict_Copy(data); - } - } - - PyObject *sw_set_track_userdata(Itdb_Track *track, PyObject *data) { - Py_INCREF(data); - if ((PyDict_Check(data)) || (data == Py_None)) { - if (track->userdata) { - Py_DECREF((PyObject *)track->userdata); - } - track->userdata = (gpointer) data; - track->userdata_duplicate = (ItdbUserDataDuplicateFunc)sw__track_extra_duplicate; - track->userdata_destroy = (ItdbUserDataDestroyFunc)sw__track_extra_destroy; - Py_INCREF(Py_None); - return Py_None; - } else { - PyErr_SetString(PyExc_TypeError, "userdata must be a Dictionary"); - return NULL; - } - } - - PyObject* sw_get_track_userdata(Itdb_Track *track) { - if (track->userdata) { - Py_INCREF((PyObject *)track->userdata); - return (PyObject *)track->userdata; - } else { - Py_INCREF(Py_None); - return Py_None; - } - } - - PyObject* sw_get_photoalbums(Itdb_PhotoDB *db) { - PyObject *list; - gint i; - GList *l; - list = PyList_New(g_list_length(db->photoalbums)); - for (l = db->photoalbums, i = 0; l; l = l->next, ++i) { - PyList_SET_ITEM(list, i, SWIG_NewPointerObj((void*)(l->data), -SWIGTYPE_p__Itdb_PhotoAlbum, 0)); - } - return list; - } - -PyObject* sw_get_photoalbum(GList *list, gint index) { - GList *position; - if ( (index >= g_list_length(list)) || index < 0 ) { - PyErr_SetString(PyExc_IndexError, "Value out of range"); - return NULL; - } - position = g_list_nth(list, index); - return SWIG_NewPointerObj((void*)(position->data), SWIGTYPE_p__Itdb_PhotoAlbum, 0); - } - - PyObject* sw_get_photoalbum_members(Itdb_PhotoAlbum *album) { - PyObject *list; - gint i; - GList *l; - list = PyList_New(g_list_length(album->members)); - for (l = album->members, i = 0; l; l = l->next, ++i) { - gint photo_id = GPOINTER_TO_INT(l->data); - PyList_SET_ITEM(list, i, PyInt_FromLong((long)photo_id)); - } - return list; - } - - PyObject* sw_get_photos(Itdb_PhotoDB *db) { - PyObject *list; - gint i; - GList *l; - list = PyList_New(g_list_length(db->photos)); - for (l = db->photos, i = 0; l; l = l->next, ++i) { - PyList_SET_ITEM(list, i, SWIG_NewPointerObj((void*)(l->data), -SWIGTYPE_p__Itdb_Artwork, 0)); - } - return list; - } - -PyObject* sw_get_photo(GList *list, gint index) { - GList *position; - if ( (index >= g_list_length(list)) || index < 0 ) { - PyErr_SetString(PyExc_IndexError, "Value out of range"); - return NULL; - } - position = g_list_nth(list, index); - return SWIG_NewPointerObj((void*)(position->data), SWIGTYPE_p__Itdb_Artwork, 0); - } - - void hash_table_to_pydict(gpointer key, - gpointer value, - gpointer user_data) { - PyDict_SetItemString((PyObject *)user_data, - (char *)key, - PyString_FromString(value)); - } - - PyObject* sw_ipod_device_to_dict(Itdb_Device *device) { - PyObject* sysinfo; - if (device == NULL) { - Py_INCREF(Py_None); - return Py_None; - } else { - sysinfo = PyDict_New(); - g_hash_table_foreach(device->sysinfo, - hash_table_to_pydict, - (gpointer) sysinfo); - return Py_BuildValue("{s:s,s:i,s:i,s:O,s:i}", - "mountpoint", - device->mountpoint, - "musicdirs", - device->musicdirs, - "byte_order", - device->byte_order, - "sysinfo", - sysinfo, - "timezone_shift", - device->timezone_shift); - } - } - -%} - -%init %{ -#ifdef HAVE_GDKPIXBUF -#ifdef HAVE_PYGOBJECT -#if !GLIB_CHECK_VERSION(2, 36, 0) - g_type_init (); -#endif - init_pygobject (); -#endif -#endif - PyDateTime_IMPORT; -%} - -%include "gpod_doc.i" -%include "@top_builddir@/config.h" - -# be nicer to decode these utf8 strings into Unicode objects in the C -# layer. Here we are leaving it to the Python side, and just giving -# them utf8 encoded Strings. -typedef char gchar; - -%typemap(in) time_t { - struct tm tmvalue; - PyObject* pydatetime = NULL; - if (PyDateTime_Check($input)) { - pydatetime = $input; - Py_INCREF(pydatetime); - } else if ((PyInt_Check($input)) || (PyLong_Check($input)) || (PyFloat_Check($input))) { - PyObject* pyargs; - Py_INCREF($input); - pyargs = PyTuple_Pack(1, $input); - if ((pydatetime = PyDateTime_FromTimestamp(pyargs)) == NULL) { - Py_DECREF(pyargs); - Py_DECREF($input); - SWIG_fail; - } - Py_DECREF(pyargs); - Py_DECREF($input); - } else { - PyErr_SetString(PyExc_ValueError, "$symname: Value must be a datetime.datetime, int or float"); - SWIG_fail; - } - tmvalue.tm_year = PyDateTime_GET_YEAR(pydatetime) - 1900; - tmvalue.tm_mon = PyDateTime_GET_MONTH(pydatetime) - 1; - tmvalue.tm_mday = PyDateTime_GET_DAY(pydatetime); - tmvalue.tm_hour = PyDateTime_DATE_GET_HOUR(pydatetime); - tmvalue.tm_min = PyDateTime_DATE_GET_MINUTE(pydatetime); - tmvalue.tm_sec = PyDateTime_DATE_GET_SECOND(pydatetime); - - Py_DECREF(pydatetime); - - $1 = mktime(&tmvalue); - if ($1 == -1) { - PyErr_SetString(PyExc_ValueError, "$symname: Failed to parse provided time"); - SWIG_fail; - } -} - -%typemap(out) time_t { -#ifdef 0 - /* for libgpod 0.6.x.. maybe ? */ - struct tm *tmvalue; - tmvalue = localtime(&$1); - $result = PyDateTime_FromDateAndTime(tmvalue->tm_year + 1900, - tmvalue->tm_mon + 1, - tmvalue->tm_mday, - tmvalue->tm_hour, - tmvalue->tm_min, - tmvalue->tm_sec, - 0); -#else - $result = PyLong_FromUnsignedLong((unsigned long) $1); -#endif -} - -%typemap(in) guint8 { - unsigned long ival; - ival = PyInt_AsUnsignedLongMask($input); - if (PyErr_Occurred()) - SWIG_fail; - if (ival > 255) { - PyErr_SetString(PyExc_ValueError, "$symname: Value must be between 0 and 255"); - SWIG_fail; - } else { - $1 = (guint8) ival; - } -} - -%typemap(in) gint8 { - long ival; - ival = PyInt_AsInt($input); - if (PyErr_Occurred()) - SWIG_fail; - if ((ival < -128) || (ival > 127)) { - PyErr_SetString(PyExc_ValueError, "$symname: Value must be between -128 and 127"); - SWIG_fail; - } else { - $1 = (gint8) ival; - } -} - -%typemap(in) guint16 { - unsigned long ival; - ival = PyInt_AsUnsignedLongMask($input); - if (PyErr_Occurred()) - SWIG_fail; - if (ival > 65535) { - PyErr_SetString(PyExc_ValueError, "$symname: Value must be between 0 and 65535"); - SWIG_fail; - } else { - $1 = (guint16) ival; - } -} - -%typemap(in) gint16 { - long ival; - ival = PyInt_AsLong($input); - if (PyErr_Occurred()) - SWIG_fail; - if ((ival < -32768) || (ival > 32767)) { - PyErr_SetString(PyExc_ValueError, "$symname: Value must be between -32,768 and 32,767"); - SWIG_fail; - } else { - $1 = (gint16) ival; - } -} - -%typemap(in) guint32 { - unsigned long ival; - ival = PyInt_AsUnsignedLongMask($input); - if (PyErr_Occurred()) - SWIG_fail; - $1 = (guint32) ival; -} - -%typemap(in) gint32 { - long ival; - ival = PyInt_AsLong($input); - if (PyErr_Occurred()) - SWIG_fail; - $1 = (gint32) ival; -} - -%typemap(in) guint64 { - if (PyInt_CheckExact($input)) - $1 = PyInt_AsUnsignedLongLongMask($input); - else - $1 = PyLong_AsUnsignedLongLong($input); - if (PyErr_Occurred()) - SWIG_fail; -} - -%typemap(in) gint64 { - if (PyInt_CheckExact($input)) - $1 = PyInt_AsLong($input); - else - $1 = PyLong_AsLongLong($input); - if (PyErr_Occurred()) - SWIG_fail; -} - -%typemap(out) guint64 { - $result = PyLong_FromUnsignedLongLong($1); -} - -%typemap(out) gint64 { - $result = PyLong_FromLongLong($1); -} - -%typemap(out) guint32 { - $result = PyLong_FromUnsignedLong($1); -} - -%typemap(out) gint32 { - $result = PyLong_FromLong($1); -} - -%typemap(out) guint16 { - $result = PyLong_FromUnsignedLong($1); -} - -%typemap(out) gint16 { - $result = PyLong_FromLong($1); -} - -%typemap(out) guint8 { - $result = PyInt_FromLong($1); -} - -typedef int gboolean; -typedef int gint; - -#ifdef HAVE_GDKPIXBUF -#ifdef HAVE_PYGOBJECT -%typemap(out) gpointer itdb_artwork_get_pixbuf { - $result = pygobject_new((GObject *)$1); - if ($1) { - g_object_unref($1); - } -} - -%typemap(in) gpointer pixbuf { - $1 = GDK_PIXBUF(pygobject_get($input)); -} -#endif -#endif - -#define G_BEGIN_DECLS -#define G_END_DECLS - -PyObject* sw_get_tracks(Itdb_iTunesDB *itdb); -PyObject* sw_get_track(GList *list, gint index); -PyObject* sw_get_rule(GList *list, gint index); -PyObject* sw_get_playlist(GList *list, gint index); -PyObject* sw_get_list_len(GList *list); -PyObject* sw_get_playlists(Itdb_iTunesDB *itdb); -PyObject* sw_get_playlist_tracks(Itdb_Playlist *pl); -PyObject* sw_set_track_userdata(Itdb_Track *track, PyObject *data); -PyObject* sw_get_track_userdata(Itdb_Track *track); -PyObject* sw_get_photoalbums(Itdb_PhotoDB *db); -PyObject* sw_get_photoalbum(GList *list, gint index); -PyObject* sw_get_photos(Itdb_PhotoDB *db); -PyObject* sw_get_photo(GList *list, gint index); -PyObject* sw_get_photoalbum_members(Itdb_PhotoAlbum *album); -PyObject* sw_ipod_device_to_dict(Itdb_Device *device); - -%include "@top_srcdir@/src/itdb.h" diff --git a/libgpod-1.0.pc.in b/libgpod-1.0.pc.in index 4438f19..cef6db4 100644 --- a/libgpod-1.0.pc.in +++ b/libgpod-1.0.pc.in @@ -6,6 +6,6 @@ includedir=@includedir@ Name: libgpod Description: A library to manipulate songs and playlists stored on an ipod Version: @VERSION@ -Requires.private: glib-2.0 >= 2.8.0 gobject-2.0 @GDKPIXBUF_REQ@ @LIBIMOBILEDEVICE_REQ@ +Requires: glib-2.0 >= 2.8.0 gobject-2.0 @GDKPIXBUF_REQ@ @LIBIMOBILEDEVICE_REQ@ Libs: -L${libdir} -lgpod Cflags: -I${includedir}/gpod-1.0 diff --git a/libgpod-1.0.pc.in.pkgconfig_overlinking b/libgpod-1.0.pc.in.pkgconfig_overlinking deleted file mode 100644 index cef6db4..0000000 --- a/libgpod-1.0.pc.in.pkgconfig_overlinking +++ /dev/null @@ -1,11 +0,0 @@ -prefix=@prefix@ -exec_prefix=@exec_prefix@ -libdir=@libdir@ -includedir=@includedir@ - -Name: libgpod -Description: A library to manipulate songs and playlists stored on an ipod -Version: @VERSION@ -Requires: glib-2.0 >= 2.8.0 gobject-2.0 @GDKPIXBUF_REQ@ @LIBIMOBILEDEVICE_REQ@ -Libs: -L${libdir} -lgpod -Cflags: -I${includedir}/gpod-1.0 diff --git a/m4/mono.m4 b/m4/mono.m4 index 3e296a9..3603108 100644 --- a/m4/mono.m4 +++ b/m4/mono.m4 @@ -34,7 +34,7 @@ AC_DEFUN([SHAMROCK_FIND_PROGRAM_OR_BAIL], AC_DEFUN([SHAMROCK_FIND_MONO_2_0_COMPILER], [ - SHAMROCK_FIND_PROGRAM_OR_BAIL(MCS, mcs) + SHAMROCK_FIND_PROGRAM_OR_BAIL(MCS, gmcs) ]) AC_DEFUN([SHAMROCK_FIND_MONO_RUNTIME], diff --git a/m4/mono.m4.mono4 b/m4/mono.m4.mono4 deleted file mode 100644 index 3603108..0000000 --- a/m4/mono.m4.mono4 +++ /dev/null @@ -1,101 +0,0 @@ -AC_DEFUN([SHAMROCK_EXPAND_LIBDIR], -[ - expanded_libdir=`( - case $prefix in - NONE) prefix=$ac_default_prefix ;; - *) ;; - esac - case $exec_prefix in - NONE) exec_prefix=$prefix ;; - *) ;; - esac - eval echo $libdir - )` - AC_SUBST(expanded_libdir) -]) - -AC_DEFUN([SHAMROCK_FIND_PROGRAM], -[ - AC_PATH_PROG($1, $2, $3) - AC_SUBST($1) -]) - -AC_DEFUN([SHAMROCK_FIND_PROGRAM_OR_BAIL], -[ - SHAMROCK_FIND_PROGRAM($1, $2, no) - if test "x$$1" = "xno"; then - if test "X$with_mono" = "Xyes"; then - AC_MSG_ERROR([You need to install '$2']) - else - mono_detected=no - fi - fi -]) - -AC_DEFUN([SHAMROCK_FIND_MONO_2_0_COMPILER], -[ - SHAMROCK_FIND_PROGRAM_OR_BAIL(MCS, gmcs) -]) - -AC_DEFUN([SHAMROCK_FIND_MONO_RUNTIME], -[ - SHAMROCK_FIND_PROGRAM_OR_BAIL(MONO, mono) -]) - -AC_DEFUN([SHAMROCK_CHECK_MONO_MODULE], -[ - PKG_CHECK_MODULES(MONO_MODULE, mono >= $1, found_mono="yes", found_mono="no") - if test "x$found_mono" = "xno"; then - if test "X$with_mono" = "Xyes"; then - AC_MSG_ERROR([You need to install mono]) - else - mono_detected=no - fi - fi -]) - -AC_DEFUN([CHECK_GLIB_GTK_SHARP], -[ - found_gtksharp="yes" - PKG_CHECK_MODULES(GDKSHARP, - gtk-sharp-2.0 >= $GTK_SHARP_MIN_VERSION, [], found_gtksharp="no") - AC_SUBST(GDKSHARP_LIBS) - - PKG_CHECK_MODULES(GLIBSHARP, - glib-sharp-2.0 >= $GTK_SHARP_MIN_VERSION, [], found_gtksharp="no") - AC_SUBST(GLIBSHARP_LIBS) - if test "X$found_gtksharp" != "Xyes"; then - if test "X$with_mono" = "Xyes"; then - AC_MSG_ERROR([You need to install gtk-sharp]) - else - mono_detected=no - fi - fi -]) - -dnl check for mono and required dependencies -AC_DEFUN([LIBGPOD_CHECK_MONO], -[ - AC_ARG_WITH(mono, - AC_HELP_STRING([--with-mono], - [build mono bindings [[default=auto]]]), - [with_mono=$withval],[with_mono=auto]) - - AC_MSG_CHECKING(whether to build mono bindings) - AC_MSG_RESULT($with_mono) - - if test "X$with_mono" != Xno; then - mono_detected="yes" - SHAMROCK_EXPAND_LIBDIR - SHAMROCK_CHECK_MONO_MODULE($MONO_MIN_VERSION) - SHAMROCK_FIND_MONO_2_0_COMPILER - SHAMROCK_FIND_MONO_RUNTIME - CHECK_GLIB_GTK_SHARP - if test "X$mono_detected" = "Xno"; then - with_mono="no" - else - with_mono="yes" - fi - fi - AM_CONDITIONAL(HAVE_MONO, test x$with_mono = xyes) -]) diff --git a/src/itdb_itunesdb.c b/src/itdb_itunesdb.c index c789cf4..a177e26 100644 --- a/src/itdb_itunesdb.c +++ b/src/itdb_itunesdb.c @@ -1156,7 +1156,6 @@ static gboolean playcounts_plist_read (FImport *fimp, GValue *plist_data) GHashTable *pc_dict, *track_dict; GValue *to_parse; GArray *array; - GValue value; gint i; guint32 mac_time; guint64 *dbid; @@ -1176,12 +1175,11 @@ static gboolean playcounts_plist_read (FImport *fimp, GValue *plist_data) array = (GArray*)g_value_get_boxed (to_parse); for (i = 0; i < array->len; i++) { - value = g_array_index (array, GValue, i); - if (!G_VALUE_HOLDS (&value, G_TYPE_HASH_TABLE)) { + if (!G_VALUE_HOLDS (g_array_index (array, GValue *, i), G_TYPE_HASH_TABLE)) { continue; } - track_dict = g_value_get_boxed (&value); + track_dict = g_value_get_boxed (g_array_index (array, GValue *, i)); if (track_dict == NULL) continue; diff --git a/src/itdb_itunesdb.c.playcounts b/src/itdb_itunesdb.c.playcounts deleted file mode 100644 index a177e26..0000000 --- a/src/itdb_itunesdb.c.playcounts +++ /dev/null @@ -1,8534 +0,0 @@ -/* -| Copyright (C) 2002-2007 Jorg Schuler -| Part of the gtkpod project. -| -| URL: http://www.gtkpod.org/ -| URL: http://gtkpod.sourceforge.net/ -| -| Much of the code in this file has originally been ported from the -| perl script "mktunes.pl" (part of the gnupod-tools collection) -| written by Adrian Ulrich : -| -| gnupod-tools: http://www.blinkenlights.ch/cgi-bin/fm.pl?get=ipod -| -| 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$ -*/ - -/* Some notes on how to use the functions in this file: - - - *** Reading the iTunesDB *** - - Itdb_iTunesDB *itunesdb_parse (gchar *path); /+ path to mountpoint +/ - will read an Itdb_iTunesDB and pass the data over to your program. - - The information given in the "Play Counts" file is also read if - available and the playcounts, star rating and the time last played - is updated. - - Itdb_iTunesDB is a structure containing a GList for the tracks and a - GList for the playlists. - - For each track these fields are set as follows: - - "transferred" will be set to TRUE because all tracks read from a - Itdb_iTunesDB are obviously (or hopefully) already transferred to the - iPod. - - "recent_playcount" is for information only (it will allow to - generate playlists with tracks played since the last time) and will - not be stored to the iPod. - - The master playlist is guaranteed to be the first playlist, and - this must not be changed by your code. - - - *** Writing the Itdb_iTunesDB *** - - gboolean itunesdb_write (gchar *path, Itdb_iTunesDB *itb) - /+ @path to mountpoint, itb to @write +/ - will write an updated version of the Itdb_iTunesDB. - - The "Play Counts" file is renamed to "Play Counts.bak" if it exists - to avoid reading it multiple times. - - Please note that non-transferred tracks are not automatically - transferred to the iPod. A function - - gboolean itunesdb_copy_track_to_ipod (gchar *path, Itdb_Track *track, gchar *pcfile) - - is provided to help you do that, however. - - The following functions most likely will be useful: - - Itdb_Track *itunesdb_new_track (void); - Use itunesdb_new_track() to get an "initialized" track structure - (the "unknowns" are initialized with reasonable values). - - gboolean itunesdb_cp (gchar *from_file, gchar *to_file); - void itunesdb_convert_filename_fs2ipod(gchar *ipod_file); - void itunesdb_convert_filename_ipod2fs(gchar *ipod_file); - - void itunesdb_rename_files (const gchar *dirname); - - (Renames/removes some files on the iPod (Playcounts, OTG - semaphore). Needs to be called if you write the Itdb_iTunesDB not - directly to the iPod but to some other location and then manually - copy the file from there to the iPod. That's much faster in the - case of using an iPod mounted in sync'ed mode.) - - Jorg Schuler, 29.12.2004 */ - - -/* call itdb_parse () to read the Itdb_iTunesDB */ -/* call itdb_write () to write the Itdb_iTunesDB */ - - -#ifdef HAVE_CONFIG_H -# include -#endif - -#include "db-artwork-parser.h" -#include "itdb_device.h" -#include "itdb_private.h" -#include "itdb_zlib.h" -#include "itdb_plist.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#ifdef HAVE_UNISTD_H -#include -#endif - -#define ITUNESDB_DEBUG 0 -#define ITUNESDB_MHIT_DEBUG 0 - -#define ITUNESDB_COPYBLK (1024*1024*4) /* blocksize for cp () */ - - -/* NOTE for developers: - - Sometimes new MHOD string fields are added by Apple in the - iTunesDB. In that case you need to modify the code in the following - places: - - itdb_itunesdb.c: - - enum MHOD_ID - - get_mhod(): inside the switch() statement. Currently no compiler - warning is given. - - get_mhod_string(): inside the switch() statement. A compiler warning - will help you find it. - - get_playlist(): inside the switch() statement. A compiler warning - will help you find it. - - get_mhit(): inside the switch() statement. A compiler warning - will help you find it. - - mk_mhod(): inside the switch() statement. A compiler warning - will help you find it. - - write_mhsd_tracks(): inside the for() loop. - - itdb_track.c: - - itdb_track_free() - - itdb_track_duplicate() - - itdb_playlists.c: - Analogous to itdb_track.c in case the string is part of the playlist - description. -*/ - -/* Note: some of the comments for the MHOD_IDs are copied verbatim - * from http://ipodlinux.org/ITunesDB */ -enum MHOD_ID { - MHOD_ID_TITLE = 1, - MHOD_ID_PATH = 2, /* file path on iPod (special format) */ - MHOD_ID_ALBUM = 3, - MHOD_ID_ARTIST = 4, - MHOD_ID_GENRE = 5, - MHOD_ID_FILETYPE = 6, -/* MHOD_ID_EQSETTING = 7, */ - MHOD_ID_COMMENT = 8, - /* Category - This is the category ("Technology", "Music", etc.) where - the podcast was located. Introduced in db version 0x0d. */ - MHOD_ID_CATEGORY = 9, - MHOD_ID_COMPOSER = 12, - MHOD_ID_GROUPING = 13, - /* Description text (such as podcast show notes). Accessible by - wselecting the center button on the iPod, where this string is - displayed along with the song title, date, and - timestamp. Introduced in db version 0x0d. */ - MHOD_ID_DESCRIPTION = 14, - /* Podcast Enclosure URL. Note: this is either a UTF-8 or ASCII - encoded string (NOT UTF-16). Also, there is no mhod::length value - for this type. Introduced in db version 0x0d. */ - MHOD_ID_PODCASTURL = 15, - /* Podcast RSS URL. Note: this is either a UTF-8 or ASCII encoded - string (NOT UTF-16). Also, there is no mhod::length value for this - type. Introduced in db version 0x0d. */ - MHOD_ID_PODCASTRSS = 16, - /* Chapter data. This is a m4a-style entry that is used to display - subsongs within a mhit. Introduced in db version 0x0d. */ - MHOD_ID_CHAPTERDATA = 17, - /* Subtitle (usually the same as Description). Introduced in db - version 0x0d. */ - MHOD_ID_SUBTITLE = 18, - /* Show (for TV Shows only). Introduced in db version 0x0d? */ - MHOD_ID_TVSHOW = 19, - /* Episode # (for TV Shows only). Introduced in db version 0x0d? */ - MHOD_ID_TVEPISODE = 20, - /* TV Network (for TV Shows only). Introduced in db version 0x0d? */ - MHOD_ID_TVNETWORK = 21, - /* Album Artist. Introduced in db version 0x13? */ - MHOD_ID_ALBUMARTIST = 22, - /* Sort key for artist. */ - MHOD_ID_SORT_ARTIST = 23, - /* Appears to be a list of keywords pertaining to a track. Introduced - in db version 0x13? */ - MHOD_ID_KEYWORDS = 24, - /* more sort keys, taking precedence over the standard entries if - present */ - MHOD_ID_SORT_TITLE = 27, - MHOD_ID_SORT_ALBUM = 28, - MHOD_ID_SORT_ALBUMARTIST = 29, - MHOD_ID_SORT_COMPOSER = 30, - MHOD_ID_SORT_TVSHOW = 31, - MHOD_ID_SPLPREF = 50, /* settings for smart playlist */ - MHOD_ID_SPLRULES = 51, /* rules for smart playlist */ - MHOD_ID_LIBPLAYLISTINDEX = 52, /* Library Playlist Index */ - MHOD_ID_LIBPLAYLISTJUMPTABLE=53, - MHOD_ID_PLAYLIST = 100, - MHOD_ID_ALBUM_ALBUM = 200, /* MHODs for the MHIAs */ - MHOD_ID_ALBUM_ARTIST = 201, - MHOD_ID_ALBUM_SORT_ARTIST = 202, - MHOD_ID_ALBUM_ARTIST_MHII = 300, -}; - - -/* Used as first iPod ID when renumbering (re-assigning) the iPod - (track) IDs before writing out the iTunesDB. */ -static const gint FIRST_IPOD_ID=52; - - -enum MHOD52_SORTTYPE { - MHOD52_SORTTYPE_TITLE = 0x03, - MHOD52_SORTTYPE_ALBUM = 0x04, - MHOD52_SORTTYPE_ARTIST = 0x05, - MHOD52_SORTTYPE_GENRE = 0x07, - MHOD52_SORTTYPE_COMPOSER = 0x12/*, - MHOD52_SORTTYPE_TVSHOW = 0x1d, - MHOD52_SORTTYPE_TVSEASON = 0x1e, - MHOD52_SORTTYPE_TVEPISODE= 0x1f*/ -}; - -struct mhod52track -{ - gchar *album; - gchar *title; - gchar *artist; - gchar *genre; - gchar *composer; - gint track_nr; - gint cd_nr; - gint index; - gint numtracks; - gunichar2 letter_album; - gunichar2 letter_title; - gunichar2 letter_artist; - gunichar2 letter_genre; - gunichar2 letter_composer; -}; - -struct mhod53_entry -{ - gunichar2 letter; - gint32 start; - gint32 count; -}; - -struct _MHODData -{ - gboolean valid; - gint32 type; - union - { - gint32 track_pos; - gchar *string; - Itdb_Chapterdata *chapterdata; - Itdb_SPLPref *splpref; - Itdb_SPLRules *splrules; - GList *mhod52coltracks; - } data; - enum MHOD52_SORTTYPE mhod52sorttype; - GList *mhod53_list; -}; - -typedef struct _MHODData MHODData; - -struct _PosEntry { - guint32 trackid; - gint32 track_pos; -}; - -typedef struct _PosEntry PosEntry; - -/* Declarations */ -static gboolean itdb_create_directories (Itdb_Device *device, GError **error); - -/* ID for error domain */ -GQuark itdb_file_error_quark (void) -{ - static GQuark q = 0; - if (q == 0) - q = g_quark_from_static_string ("itdb-file-error-quark"); - return q; -} - - -static guint16 raw_get16lint (FContents *cts, glong seek); -static guint32 raw_get24lint (FContents *cts, glong seek); -static guint32 raw_get32lint (FContents *cts, glong seek); -static guint64 raw_get64lint (FContents *cts, glong seek); -static float raw_get32lfloat (FContents *cts, glong seek); -static guint16 raw_get16bint (FContents *cts, glong seek); -static guint32 raw_get24bint (FContents *cts, glong seek); -static guint32 raw_get32bint (FContents *cts, glong seek); -static guint64 raw_get64bint (FContents *cts, glong seek); -static float raw_get32bfloat (FContents *cts, glong seek); - -static gboolean is_shuffle_2g (Itdb_Device *device); - -static const ByteReader LITTLE_ENDIAN_READER = -{ - raw_get16lint, raw_get24lint, raw_get32lint, raw_get64lint, raw_get32lfloat -}; -static const ByteReader BIG_ENDIAN_READER = -{ - raw_get16bint, raw_get24bint, raw_get32bint, raw_get64bint, raw_get32bfloat -}; - -static void fcontents_set_reversed(FContents *cts, gboolean reversed) -{ - cts->reversed = reversed; - if (!reversed) - { - memcpy(&cts->le_reader, &LITTLE_ENDIAN_READER, sizeof (ByteReader)); - memcpy(&cts->be_reader, &BIG_ENDIAN_READER, sizeof (ByteReader)); - } - else - { - memcpy(&cts->le_reader, &BIG_ENDIAN_READER, sizeof (ByteReader)); - memcpy(&cts->be_reader, &LITTLE_ENDIAN_READER, sizeof (ByteReader)); - } -} - -/* Read the contents of @filename and return a FContents - struct. Returns NULL in case of error and @error is set - accordingly */ -static FContents *fcontents_read (const gchar *fname, GError **error) -{ - FContents *cts; - - g_return_val_if_fail (fname, NULL); - - cts = g_new0 (FContents, 1); - fcontents_set_reversed (cts, FALSE); - - if (g_file_get_contents (fname, &cts->contents, &cts->length, error)) - { - cts->filename = g_strdup (fname); - } - else - { - g_free (cts); - cts = NULL; - } - return cts; -} - - -/* Frees the memory taken by a FContents structure. NULL pointer will - * be ignored */ -static void fcontents_free (FContents *cts) -{ - if (cts) - { - g_free (cts->filename); - g_free (cts->contents); - /* must not g_error_free (cts->error) because the error was - propagated -> might free the error twice */ - g_free (cts); - } -} - -static void itdb_fsync (void) -{ -#ifndef WIN32 - sync(); -#endif -} - -/* There seems to be a problem with some distributions (kernel - versions or whatever -- even identical version numbers don't don't - show identical behaviour...): even though vfat is supposed to be - case insensitive, a difference is made between upper and lower case - under some special circumstances. As in "/iPod_Control/Music/F00" - and "/iPod_Control/Music/f00 "... If the former filename does not - exist, we try to find an existing case insensitive match for each - component of the filename. If we can find such a match, we return - it. Otherwise, we return NULL.*/ - -/** - * itdb_resolve_path: - * @root: in local encoding - * @components: in utf8 - * - * Resolve the path to a track on the iPod - * - * We start by assuming that the iPod mount point exists. Then, for - * each component c of @track->ipod_path, we try to find an entry d in - * good_path that is case-insensitively equal to c. If we find d, we - * append d to good_path and make the result the new good_path. - * Otherwise, we quit and return NULL. - * - * Returns: path to track on the iPod or NULL. - */ -gchar * itdb_resolve_path (const gchar *root, - const gchar * const * components) -{ - gchar *good_path; - guint32 i; - - if (!root) return NULL; - good_path = g_strdup (root); - - for(i = 0 ; components[i] ; i++) { - GDir *cur_dir; - gchar *component_as_filename; - gchar *test_path; - gchar *component_stdcase; - const gchar *dir_file=NULL; - - /* skip empty components */ - if (strlen (components[i]) == 0) continue; - component_as_filename = - g_filename_from_utf8(components[i],-1,NULL,NULL,NULL); - test_path = g_build_filename(good_path,component_as_filename,NULL); - g_free(component_as_filename); - if(g_file_test(test_path,G_FILE_TEST_EXISTS)) { - /* This component does not require fixup */ - g_free(good_path); - good_path = test_path; - continue; - } - g_free(test_path); - component_stdcase = g_utf8_casefold(components[i],-1); - /* Case insensitively compare the current component with each entry - * in the current directory. */ - - cur_dir = g_dir_open(good_path,0,NULL); - if (cur_dir) while ((dir_file = g_dir_read_name(cur_dir))) - { - gchar *file_utf8; - gchar *file_stdcase; - gboolean found; - gchar *new_good_path; - - file_utf8 = g_filename_to_utf8(dir_file,-1,NULL,NULL,NULL); - if (file_utf8 == NULL) { - continue; - } - file_stdcase = g_utf8_casefold(file_utf8,-1); - g_free(file_utf8); - found = !g_utf8_collate(file_stdcase,component_stdcase); - g_free(file_stdcase); - if(!found) - { - /* This is not the matching entry */ - continue; - } - - new_good_path = g_build_filename(good_path,dir_file,NULL); - g_free(good_path); - good_path= new_good_path; - /* This is the matching entry, so we can stop searching */ - break; - } - - if(!dir_file) { - /* We never found a matching entry */ - g_free(good_path); - good_path = NULL; - } - - g_free(component_stdcase); - if (cur_dir) g_dir_close(cur_dir); - if(!good_path || !g_file_test(good_path,G_FILE_TEST_EXISTS)) - break; /* We couldn't fix this component, so don't try later ones */ - } - - if(good_path && g_file_test(good_path,G_FILE_TEST_EXISTS)) - return good_path; - - g_free (good_path); - return NULL; -} - - -/* Check if the @seek with length @len is legal or out of - * range. Returns TRUE if legal and FALSE when it is out of range, in - * which case cts->error is set as well. */ -static gboolean check_seek (FContents *cts, glong seek, glong len) -{ - g_return_val_if_fail (cts, FALSE); - g_return_val_if_fail (cts->contents, FALSE); - - if ((seek+len <= cts->length) && (seek >=0)) - { - return TRUE; - } - else - { - g_return_val_if_fail (cts->filename, FALSE); - g_set_error (&cts->error, - ITDB_FILE_ERROR, - ITDB_FILE_ERROR_SEEK, - _("Illegal seek to offset %ld (length %ld) in file '%s'."), - seek, len, cts->filename); - return FALSE; - } -} - - -/* Copies @len bytes from position @seek in @cts->contents to - @data. Returns FALSE on error and sets cts->error accordingly. */ -static gboolean seek_get_n_bytes (FContents *cts, gchar *data, - glong seek, glong len) -{ - if (check_seek (cts, seek, len)) - { - memcpy (data, &cts->contents[seek], len); - return TRUE; - } - return FALSE; -} - -/* Compare @n bytes of @cts->contents starting at @seek and - * @data. Returns TRUE if equal, FALSE if not. Also returns FALSE on - * error, so you must check cts->error */ -static gboolean cmp_n_bytes_seek (FContents *cts, const gchar *data, - glong seek, glong len) -{ - if (check_seek (cts, seek, len)) - { - gint i; - for (i=0; icontents[seek+i] != data[i]) return FALSE; - } - return TRUE; - } - return FALSE; -} - - -/* Compare 4 bytes of @header with 4 bytes at @seek taking into - * consideration the status of cts->reversed */ -static gboolean check_header_seek (FContents *cts, const gchar *data, - glong seek) -{ - gchar rdata[4]; - gint i, offset, sign; - - g_return_val_if_fail (cts, FALSE); - g_return_val_if_fail (data, FALSE); - /* reverse data for compare if necessary */ - if (cts->reversed) - { - offset = 3; - sign = -1; - } - else - { - offset = 0; - sign = 1; - } - for (i=0; i<4; ++i) - { - rdata[i] = data[offset + sign*i]; - } - - return cmp_n_bytes_seek (cts, rdata, seek, 4); -} - - -/* ------------------------------------------------------------ - Not Endian Dependent - ------------------------------------------------------------ */ - -/* Returns the 1-byte number stored at position @seek. On error the - * GError in @cts is set. */ -static inline guint8 get8int (FContents *cts, glong seek) -{ - guint8 n=0; - - if (check_seek (cts, seek, 1)) - { - n = cts->contents[seek]; - } - return n; -} - - -/* ------------------------------------------------------------ - Little Endian - ------------------------------------------------------------ */ - -/* Get the 2-byte-number stored at position "seek" in little endian - encoding. On error the GError in @cts is set. */ -static guint16 raw_get16lint (FContents *cts, glong seek) -{ - guint16 n=0; - - if (check_seek (cts, seek, 2)) - { - memcpy (&n, &cts->contents[seek], 2); - n = GUINT16_FROM_LE (n); - } - return n; -} - -/* Get the 3-byte-number stored at position "seek" in little endian - encoding. On error the GError in @cts is set. */ -static guint32 raw_get24lint (FContents *cts, glong seek) -{ - guint32 n=0; - - if (check_seek (cts, seek, 3)) - { - n = ((guint32)get8int (cts, seek+0)) + - (((guint32)get8int (cts, seek+1)) >> 8) + - (((guint32)get8int (cts, seek+2)) >> 16); - } - return n; -} - -/* Get the 4-byte-number stored at position "seek" in little endian - encoding. On error the GError in @cts is set. */ -static guint32 raw_get32lint (FContents *cts, glong seek) -{ - guint32 n=0; - - if (check_seek (cts, seek, 4)) - { - memcpy (&n, &cts->contents[seek], 4); - n = GUINT32_FROM_LE (n); - } - return n; -} - -/* Get 4 byte floating number */ -static float raw_get32lfloat (FContents *cts, glong seek) -{ - union - { - guint32 i; - float f; - } flt; - - g_return_val_if_fail (sizeof (float) == 4, 0); - - flt.i = raw_get32lint (cts, seek); - - return flt.f; -} - - -/* Get the 8-byte-number stored at position "seek" in little endian - encoding. On error the GError in @cts is set. */ -static guint64 raw_get64lint (FContents *cts, glong seek) -{ - guint64 n=0; - - if (check_seek (cts, seek, 8)) - { - memcpy (&n, &cts->contents[seek], 8); - n = GUINT64_FROM_LE (n); - } - return n; -} - - -/* ------------------------------------------------------------ - Big Endian - ------------------------------------------------------------ */ - -/* Get the 2-byte-number stored at position "seek" in little endian - encoding. On error the GError in @cts is set. */ -static guint16 raw_get16bint (FContents *cts, glong seek) -{ - guint16 n=0; - - if (check_seek (cts, seek, 2)) - { - memcpy (&n, &cts->contents[seek], 2); - n = GUINT16_FROM_BE (n); - } - return n; -} - -/* Get the 3-byte-number stored at position "seek" in big endian - encoding. On error the GError in @cts is set. */ -static guint32 raw_get24bint (FContents *cts, glong seek) -{ - guint32 n=0; - - if (check_seek (cts, seek, 3)) - { - n = ((guint32)get8int (cts, seek+2)) + - (((guint32)get8int (cts, seek+1)) >> 8) + - (((guint32)get8int (cts, seek+0)) >> 16); - } - return n; -} - -/* Get the 4-byte-number stored at position "seek" in big endian - encoding. On error the GError in @cts is set. */ -static guint32 raw_get32bint (FContents *cts, glong seek) -{ - guint32 n=0; - - if (check_seek (cts, seek, 4)) - { - memcpy (&n, &cts->contents[seek], 4); - n = GUINT32_FROM_BE (n); - } - return n; -} - -/* Get 4 byte floating number */ -static float raw_get32bfloat (FContents *cts, glong seek) -{ - union - { - guint32 i; - float f; - } flt; - - g_return_val_if_fail (sizeof (float) == 4, 0); - - flt.i = raw_get32bint (cts, seek); - - return flt.f; -} - -/* Get the 8-byte-number stored at position "seek" in big endian - encoding. On error the GError in @cts is set. */ -static guint64 raw_get64bint (FContents *cts, glong seek) -{ - guint64 n=0; - - if (check_seek (cts, seek, 8)) - { - memcpy (&n, &cts->contents[seek], 8); - n = GUINT64_FROM_BE (n); - } - return n; -} - - -/* ------------------------------------------------------------ - Little Endian - ------------------------------------------------------------ */ - -static inline guint16 get16lint (FContents *cts, glong seek) -{ - return cts->le_reader.get16int (cts, seek); -} - -static inline guint32 get24lint (FContents *cts, glong seek) -{ - return cts->le_reader.get24int (cts, seek); -} -#if 0 -static inline guint32 get24bint (FContents *cts, glong seek) -{ - return cts->be_reader.get24int (cts, seek); -} -#endif -static inline guint32 get32lint (FContents *cts, glong seek) -{ - return cts->le_reader.get32int (cts, seek); -} - -static inline float get32lfloat (FContents *cts, glong seek) -{ - return cts->le_reader.get32float (cts, seek); -} - -static inline guint64 get64lint (FContents *cts, glong seek) -{ - return cts->le_reader.get64int (cts, seek); -} - - - -/* ------------------------------------------------------------ - Big Endian - ------------------------------------------------------------ */ - -static inline guint16 get16bint (FContents *cts, glong seek) -{ - return cts->be_reader.get16int (cts, seek); -} - -static inline guint32 get32bint (FContents *cts, glong seek) -{ - return cts->be_reader.get32int (cts, seek); -} - -#if 0 -static inline float get32bfloat (FContents *cts, glong seek) -{ - return cts->be_reader.get32float (cts, seek); -} -#endif - -static inline guint64 get64bint (FContents *cts, glong seek) -{ - return cts->be_reader.get64int (cts, seek); -} - -/* Try to convert from UTF-16 to UTF-8 and handle partial characters - * at the end of the string */ -static char *utf16_to_utf8_with_partial (gunichar2 *entry_utf16) -{ - GError *error = NULL; - char *entry_utf8; - glong items_read; - - entry_utf8 = g_utf16_to_utf8 (entry_utf16, -1, &items_read, NULL, &error); - if (entry_utf8 == NULL) { - if (g_error_matches (error, G_CONVERT_ERROR, G_CONVERT_ERROR_PARTIAL_INPUT)) { - entry_utf8 = g_utf16_to_utf8 (entry_utf16, items_read, NULL, NULL, NULL); - } - g_error_free (error); - } - - return entry_utf8; -} - -/* Fix little endian UTF16 String to correct byteorder if necessary - * (all strings in the Itdb_iTunesDB are little endian except for the ones - * in smart playlists). */ -static gunichar2 *fixup_little_utf16 (gunichar2 *utf16_string) -{ -# if (G_BYTE_ORDER == G_BIG_ENDIAN) - gint32 i; - if (utf16_string) - { - for(i=0; utf16_string[i]; i++) - { - utf16_string[i] = GUINT16_SWAP_LE_BE (utf16_string[i]); - } - } -# endif - return utf16_string; -} - -/* Fix big endian UTF16 String to correct byteorder if necessary (only - * strings in smart playlists and chapter data are big endian) */ -static gunichar2 *fixup_big_utf16 (gunichar2 *utf16_string) -{ -# if (G_BYTE_ORDER == G_LITTLE_ENDIAN) - gint32 i; - if (utf16_string) - { - for(i=0; utf16_string[i]; i++) - { - utf16_string[i] = GUINT16_SWAP_LE_BE (utf16_string[i]); - } - } -# endif - return utf16_string; -} - - -#define CHECK_ERROR(imp, val) if (cts->error) { g_propagate_error (&imp->error, cts->error); return (val); } - - -/* get next playcount, that is the first entry of GList - * playcounts. This entry is removed from the list. You must free the - * return value after use */ -static struct playcount *playcount_take_next (FImport *fimp) -{ - struct playcount *playcount; - g_return_val_if_fail (fimp, NULL); - - playcount = g_list_nth_data (fimp->playcounts, 0); - - if (playcount) - fimp->playcounts = g_list_remove (fimp->playcounts, playcount); - return playcount; -} - -/* delete all entries of GList *playcounts */ -static void playcounts_free (FImport *fimp) -{ - struct playcount *playcount; - - g_return_if_fail (fimp); - - while ((playcount=playcount_take_next (fimp))) g_free (playcount); -} - - -/* called by playcounts_init */ -static gboolean playcounts_read (FImport *fimp, FContents *cts) -{ - GList* playcounts = NULL; - guint32 header_length, entry_length, entry_num, i=0; - - g_return_val_if_fail (fimp, FALSE); - g_return_val_if_fail (cts, FALSE); - - if (!check_header_seek (cts, "mhdp", 0)) - { - if (cts->error) - { - g_propagate_error (&fimp->error, cts->error); - return FALSE; - } - fcontents_set_reversed (cts, TRUE); - if (!check_header_seek (cts, "mhdp", 0)) - { - if (cts->error) - { - g_propagate_error (&fimp->error, cts->error); - return FALSE; - } - else - { /* set error */ - g_return_val_if_fail (cts->filename, FALSE); - g_set_error (&fimp->error, - ITDB_FILE_ERROR, - ITDB_FILE_ERROR_CORRUPT, - _("Not a Play Counts file: '%s' (missing mhdp header)."), - cts->filename); - return FALSE; - } - } - } - header_length = get32lint (cts, 4); - CHECK_ERROR (fimp, FALSE); - /* all the headers I know are 0x60 long -- if this one is longer - we can simply ignore the additional information */ - if (header_length < 0x60) - { - g_set_error (&fimp->error, - ITDB_FILE_ERROR, - ITDB_FILE_ERROR_CORRUPT, - _("Play Counts file ('%s'): header length smaller than expected (%d<96)."), - cts->filename, header_length); - return FALSE; - } - entry_length = get32lint (cts, 8); - CHECK_ERROR (fimp, FALSE); - /* all the entries I know are 0x0c (firmware 1.3) or 0x10 - * (firmware 2.0), 0x14 (iTunesDB version 0x0d) or 0x1c (iTunesDB - * version 0x13) in length */ - if (entry_length < 0x0c) - { - g_set_error (&fimp->error, - ITDB_FILE_ERROR, - ITDB_FILE_ERROR_CORRUPT, - _("Play Counts file ('%s'): entry length smaller than expected (%d<12)."), - cts->filename, entry_length); - return FALSE; - } - /* number of entries */ - entry_num = get32lint (cts, 12); - CHECK_ERROR (fimp, FALSE); - for (i=0; iplaycount = get32lint (cts, seek); - mac_time = get32lint (cts, seek+4); - playcount->time_played = device_time_mac_to_time_t (fimp->itdb->device, mac_time); - playcount->bookmark_time = get32lint (cts, seek+8); - - /* rating only exists if the entry length is at least 0x10 */ - if (entry_length >= 0x10) - { - playcount->rating = get32lint (cts, seek+12); - } - else - { - playcount->rating = NO_PLAYCOUNT; - } - /* unk16 only exists if the entry length is at least 0x14 */ - if (entry_length >= 0x14) - { - playcount->pc_unk16 = get32lint (cts, seek+16); - } - /* skip_count and last_skipped only exists if the entry length - is at least 0x1c */ - if (entry_length >= 0x1c) - { - playcount->skipcount = get32lint (cts, seek+20); - mac_time = get32lint (cts, seek+24); - playcount->last_skipped = device_time_mac_to_time_t (fimp->itdb->device, - mac_time); - - } - } - fimp->playcounts = g_list_reverse(playcounts); - return TRUE; -} - - -/* called by playcounts_init */ -static gboolean itunesstats_read (FImport *fimp, FContents *cts) -{ - GList* playcounts = NULL; - guint32 entry_num, entry_length, i=0; - struct playcount *playcount; - glong seek = 0; - - g_return_val_if_fail (fimp, FALSE); - g_return_val_if_fail (cts, FALSE); - g_return_val_if_fail (fimp->itdb, FALSE); - g_return_val_if_fail (fimp->itdb->device, FALSE); - - if ( is_shuffle_2g (fimp->itdb->device)) - { - /* Old iTunesStats format */ - - /* number of entries */ - entry_num = get24lint (cts, seek); - CHECK_ERROR (fimp, FALSE); - seek = 6; - for (i=0; ierror, - ITDB_FILE_ERROR, - ITDB_FILE_ERROR_CORRUPT, - _("iTunesStats file ('%s'): entry length smaller than expected (%d<18)."), - cts->filename, entry_length); - return FALSE; - } - playcount = g_new0 (struct playcount, 1); - playcounts = g_list_prepend (playcounts, playcount); - /* NOTE: - * - * The iPod (firmware 1.3, 2.0, ...?) doesn't seem to use the - * timezone information correctly -- no matter what you set - * iPod's timezone to, it will always record as if it were set - * to UTC -- we need to subtract the difference between - * current timezone and UTC to get a correct - * display. -- this should be done by the application were - * necessary */ - playcount->bookmark_time = get24lint (cts, seek+3); - CHECK_ERROR (fimp, FALSE); - playcount->st_unk06 = get24lint (cts, seek+6); - CHECK_ERROR (fimp, FALSE); - playcount->st_unk09 = get24lint (cts, seek+9); - CHECK_ERROR (fimp, FALSE); - playcount->playcount = get24lint (cts, seek+12); - CHECK_ERROR (fimp, FALSE); - playcount->skipped = get24lint (cts, seek+15); - CHECK_ERROR (fimp, FALSE); - - playcount->rating = NO_PLAYCOUNT; - - seek += entry_length; - } - } else { - /* New iTunesStats format */ - entry_num = get32lint (cts, seek); /* Number of entries */ - CHECK_ERROR (fimp, FALSE); - - seek = 8; - for (i=0; ierror, - ITDB_FILE_ERROR, - ITDB_FILE_ERROR_CORRUPT, - _("iTunesStats file ('%s'): entry length smaller than expected (%d<32)."), - cts->filename, entry_length); - return FALSE; - } - playcount = g_new0 (struct playcount, 1); - playcounts = g_list_prepend (playcounts, playcount); - - playcount->bookmark_time = get32lint (cts, seek+4); - CHECK_ERROR (fimp, FALSE); - playcount->playcount = get32lint (cts, seek+8); - CHECK_ERROR (fimp, FALSE); - playcount->time_played = get32lint (cts, seek+12); - CHECK_ERROR (fimp, FALSE); - playcount->skipped = get32lint (cts, seek+16); - CHECK_ERROR (fimp, FALSE); - playcount->last_skipped = get32lint (cts, seek+20); - CHECK_ERROR (fimp, FALSE); - /* 8 unknown bytes follow but it is unlikely they are - * the same as the unknown fields in the old format. - * I have only seen zeros in those spots so far so I'm - * ignoring them for now. */ - } - } - fimp->playcounts = g_list_reverse(playcounts); - return TRUE; -} - -static gint64 playcounts_plist_get_gint64 (GHashTable *track_dict, - const char *key) -{ - GValue *value; - - value = g_hash_table_lookup (track_dict, key); - if (value && G_VALUE_HOLDS (value, G_TYPE_INT64)) { - return g_value_get_int64 (value); - } - - return 0; -} - -#ifndef HAVE_G_INT64_EQUAL -static gboolean g_int64_equal (gconstpointer v1, gconstpointer v2) -{ - return *((const gint64*) v1) == *((const gint64*) v2); -} -#endif - -#ifndef HAVE_G_INT64_HASH -static guint g_int64_hash (gconstpointer v) -{ - return (guint) *(const gint64*) v; -} -#endif - -/* called by playcounts_init */ -static gboolean playcounts_plist_read (FImport *fimp, GValue *plist_data) -{ - GHashTable *playcounts; - struct playcount *playcount; - GHashTable *pc_dict, *track_dict; - GValue *to_parse; - GArray *array; - gint i; - guint32 mac_time; - guint64 *dbid; - - g_return_val_if_fail (G_VALUE_HOLDS (plist_data, G_TYPE_HASH_TABLE), FALSE); - pc_dict = g_value_get_boxed (plist_data); - - to_parse = g_hash_table_lookup (pc_dict, "tracks"); - if (to_parse == NULL) { - return FALSE; - } - if (!G_VALUE_HOLDS (to_parse, G_TYPE_ARRAY)) { - return FALSE; - } - - playcounts = g_hash_table_new_full (g_int64_hash, g_int64_equal, g_free, g_free); - - array = (GArray*)g_value_get_boxed (to_parse); - for (i = 0; i < array->len; i++) { - if (!G_VALUE_HOLDS (g_array_index (array, GValue *, i), G_TYPE_HASH_TABLE)) { - continue; - } - - track_dict = g_value_get_boxed (g_array_index (array, GValue *, i)); - if (track_dict == NULL) - continue; - - to_parse = g_hash_table_lookup (track_dict, "persistentID"); - if (!to_parse) - continue; - - dbid = g_new0 (guint64, 1); - if (!G_VALUE_HOLDS (to_parse, G_TYPE_INT64)) - continue; - *dbid = g_value_get_int64 (to_parse); - playcount = g_new0 (struct playcount, 1); - g_hash_table_insert (playcounts, dbid, playcount); - - playcount->bookmark_time = playcounts_plist_get_gint64 (track_dict, "bookmarkTimeInMS"); - playcount->playcount = playcounts_plist_get_gint64 (track_dict, "playCount"); - mac_time = playcounts_plist_get_gint64 (track_dict, "playMacOSDate"); - playcount->time_played = device_time_mac_to_time_t (fimp->itdb->device, mac_time); - playcount->skipcount = playcounts_plist_get_gint64 (track_dict, "skipCount"); - mac_time = playcounts_plist_get_gint64 (track_dict, "skipMacOSDate"); - playcount->last_skipped = device_time_mac_to_time_t (fimp->itdb->device, mac_time); - playcount->rating = playcounts_plist_get_gint64 (track_dict, "userRating"); - if (!playcount->rating) - playcount->rating = NO_PLAYCOUNT; - - to_parse = g_hash_table_lookup (track_dict, "playedState"); - if (to_parse && G_VALUE_HOLDS (to_parse, G_TYPE_BOOLEAN)) { - ; /* What do we do with this? */ - } - } - fimp->pcounts2 = playcounts; - return TRUE; -} - -/* Delete the file containing the playcounts, once we saved the database, - * the recent playcounts will have been added to the database, so this - * file is no longer useful. */ -static void playcounts_reset (const char *mountpoint) -{ - const gchar *dirs[] = { "Play Counts", "iTunesStats", - "PlayCounts.plist", NULL }; - const gchar **it; - - for (it = dirs; *it != NULL; it++) { - char *playcounts_path; - const gchar *components[] = { *it, NULL }; - playcounts_path = itdb_resolve_path (mountpoint, components); - if (playcounts_path != NULL) { - g_unlink (playcounts_path); - g_free (playcounts_path); - } - } -} - -/* Read the Play Count file (formed by adding "Play Counts" to the - * directory component of fimp->itdb->itdb_filename) and set up the - * GList *playcounts. If no Play Count file is present, attempt to - * read the iTunesStats file instead, which is used on the Shuffle for - * the same purpose. - * - * Returns TRUE on success (also when no Play Count - * file is found as this is not an error) and FALSE otherwise, in - * which case fimp->error is set accordingly. */ -static gboolean playcounts_init (FImport *fimp) -{ - const gchar *plc[] = {"Play Counts", NULL}; - const gchar *ist[] = {"iTunesStats", NULL}; - const gchar *plcpl[] = {"PlayCounts.plist", NULL}; - gchar *plcname, *dirname, *istname, *plcplname; - gboolean result=TRUE; - struct stat filestat; - FContents *cts; - GValue *plist_data; - - g_return_val_if_fail (fimp, FALSE); - g_return_val_if_fail (!fimp->error, FALSE); - g_return_val_if_fail (!fimp->playcounts, FALSE); - g_return_val_if_fail (fimp->itdb, FALSE); - g_return_val_if_fail (fimp->itdb->filename, FALSE); - - dirname = g_path_get_dirname (fimp->itdb->filename); - - plcname = itdb_resolve_path (dirname, plc); - istname = itdb_resolve_path (dirname, ist); - plcplname = itdb_resolve_path (dirname, plcpl); - - g_free (dirname); - - /* skip if no playcounts file is present */ - if (plcname) - { - /* skip if playcounts file has zero-length (often happens after - * dosfsck) */ - stat (plcname, &filestat); - if (filestat.st_size >= 0x60) - { - cts = fcontents_read (plcname, &fimp->error); - if (cts) - { - result = playcounts_read (fimp, cts); - fcontents_free (cts); - } - else - { - result = FALSE; - } - } - } - else if (istname) - { - /* skip if iTunesStats file has zero-length (often happens after - * dosfsck) */ - stat (istname, &filestat); - if (filestat.st_size >= 0x06) - { - cts = fcontents_read (istname, &fimp->error); - if (cts) - { - result = itunesstats_read (fimp, cts); - fcontents_free (cts); - } - else - { - result = FALSE; - } - } - } - else if (plcplname) - { - /* skip if PlayCounts.plist file has zero-length */ - stat (plcplname, &filestat); - if (filestat.st_size > 0) - { - plist_data = itdb_plist_parse_from_file (plcplname, &fimp->error); - if (plist_data) - { - result = playcounts_plist_read (fimp, plist_data); - g_value_unset (plist_data); - } - else - { - result = FALSE; - } - } - } - - g_free (plcname); - g_free (istname); - g_free (plcplname); - - return result; -} - - -/* Free the memory taken by @fimp. fimp->itdb must be freed separately - * before calling this function */ -static void itdb_free_fimp (FImport *fimp) -{ - if (fimp) - { - if (fimp->fcontents) fcontents_free (fimp->fcontents); - g_list_free (fimp->pos_glist); - g_list_free (fimp->tracks); - playcounts_free (fimp); - if (fimp->pcounts2 != NULL) { - g_hash_table_destroy (fimp->pcounts2); - } - g_free (fimp); - } -} - -/** - * itdb_free: - * @itdb: an #Itdb_iTunesDB - * - * Free the memory taken by @itdb. - */ -void itdb_free (Itdb_iTunesDB *itdb) -{ - if (itdb) - { - g_list_foreach (itdb->playlists, - (GFunc)(itdb_playlist_free), NULL); - - if (itdb->priv) { - if (itdb->priv->mhsd5_playlists) - { - g_list_foreach (itdb->priv->mhsd5_playlists, - (GFunc)(itdb_playlist_free), NULL); - } - - g_free(itdb->priv->genius_cuid); - } - - g_list_free (itdb->playlists); - g_list_foreach (itdb->tracks, - (GFunc)(itdb_track_free), NULL); - g_list_free (itdb->tracks); - g_free (itdb->filename); - itdb_device_free (itdb->device); - if (itdb->userdata && itdb->userdata_destroy) - (*itdb->userdata_destroy) (itdb->userdata); - g_free (itdb->priv); - g_free (itdb); - } -} - -/** - * itdb_duplicate: - * @itdb: an #Itdb_iTunesDB - * - * Duplicate @itdb - * FIXME: not implemented yet - * - * Returns: always return NULL since it's unimplemented - */ -Itdb_iTunesDB *itdb_duplicate (Itdb_iTunesDB *itdb) -{ - g_return_val_if_fail (itdb, NULL); - /* FIXME: not yet implemented */ - g_return_val_if_reached (NULL); -} - -/** - * itdb_playlists_number: - * @itdb: an #Itdb_iTunesDB - * - * Counts the number of playlists stored in @itdb - * - * Returns: the number of playlists in @itdb (including the master - * playlist) - */ -guint32 itdb_playlists_number (Itdb_iTunesDB *itdb) -{ - g_return_val_if_fail (itdb, 0); - - return g_list_length (itdb->playlists); -} - -/** - * itdb_tracks_number: - * @itdb: an #Itdb_iTunesDB - * - * Counts the number of tracks stored in @itdb - * - * Returns: the number of tracks in @itdb - */ -guint32 itdb_tracks_number (Itdb_iTunesDB *itdb) -{ - g_return_val_if_fail (itdb, 0); - - return g_list_length (itdb->tracks); -} - -/** - * itdb_tracks_number_nontransferred: - * @itdb: an #Itdb_iTunesDB - * - * Counts the number of non-transferred tracks in @itdb - * - * Returns: the number of tracks in @itdb that haven't been transferred - * to the iPod yet (ie the number of #Itdb_Track in which the transferred field - * is false) - */ -guint32 itdb_tracks_number_nontransferred (Itdb_iTunesDB *itdb) -{ - guint n = 0; - GList *gl; - g_return_val_if_fail (itdb, 0); - - for (gl=itdb->tracks; gl; gl=gl->next) - { - Itdb_Track *track = gl->data; - g_return_val_if_fail (track, 0); - if (!track->transferred) ++n; - } - return n; -} - -/** - * itdb_new: - * - * Creates a new Itdb_iTunesDB with the unknowns filled in to reasonable - * values. - * - * Returns: a newly created Itdb_iTunesDB to be freed with itdb_free() - * when it's no longer needed - */ -Itdb_iTunesDB *itdb_new (void) -{ - Itdb_iTunesDB *itdb; - -#if !GLIB_CHECK_VERSION(2, 36, 0) - /* g_type_init() is obsolete with newer glib versions */ - static GOnce g_type_init_once = G_ONCE_INIT; - g_once (&g_type_init_once, (GThreadFunc)g_type_init, NULL); -#endif - - itdb = g_new0 (Itdb_iTunesDB, 1); - itdb->priv = g_new0 (Itdb_iTunesDB_Private, 1); - itdb->device = itdb_device_new (); - itdb->version = 0x13; - itdb->id = ((guint64)g_random_int () << 32) | - ((guint64)g_random_int ()); - itdb->priv->pid = ((guint64)g_random_int () << 32) | - ((guint64)g_random_int ()); - itdb->priv->lang = 0x656e; - itdb->priv->platform = 1; /* Mac */ - return itdb; -} - -/* Returns the type of the mhod and the length *ml. *ml is set to -1 - * on error (e.g. because there's no mhod at @seek). */ -/* A return value of -1 and no error set means that no mhod was found - at @seek */ -static gint32 get_mhod_type (FContents *cts, glong seek, guint32 *ml) -{ - gint32 type = -1; - -#if ITUNESDB_DEBUG - fprintf(stderr, "get_mhod_type seek: %x\n", (int)seek); -#endif - - if (ml) *ml = -1; - - if (check_header_seek (cts, "mhod", seek)) - { - guint32 len = get32lint (cts, seek+8); /* total length */ - if (cts->error) return -1; - if (ml) *ml = len; - type = get32lint (cts, seek+12); /* mhod_id */ - if (cts->error) return -1; - } - return type; -} - -static char *extract_mhod_string (FContents *cts, glong seek) -{ - gunichar2 *entry_utf16; - char *entry_utf8; - gint string_type; - gsize len; - - /* type of string: 0x02: UTF8, 0x01 or 0x00: UTF16 LE */ - string_type = get32lint (cts, seek); - len = get32lint (cts, seek+4); /* length of string */ - g_return_val_if_fail (len < G_MAXUINT - 2, NULL); - if (string_type != 0x02) { - /* UTF-16 string */ - entry_utf16 = g_new0 (gunichar2, (len+2)/2); - if (seek_get_n_bytes (cts, (gchar *)entry_utf16, seek+16, len)) { - fixup_little_utf16 (entry_utf16); - entry_utf8 = utf16_to_utf8_with_partial (entry_utf16); - g_free (entry_utf16); - } else { - g_free (entry_utf16); - return NULL; - } - } else { - /* UTF-8 string */ - entry_utf8 = g_new0 (gchar, len+1); - if (!seek_get_n_bytes (cts, entry_utf8, seek+16, len)) { - g_free (entry_utf8); - return NULL; - } - } - - if ((entry_utf8 != NULL) && g_utf8_validate (entry_utf8, -1, NULL)) { - return entry_utf8; - } else { - g_free (entry_utf8); - return NULL; - } -} - -/* Returns the contents of the mhod at position @mhod_seek. This can - be a simple string or something more complicated as in the case for - Itdb_SPLPREF OR Itdb_SPLRULES. - - *mhod_len is set to the total length of the mhod (-1 in case an - *error occured). - - MHODData.valid is set to FALSE in case of any error. cts->error - will be set accordingly. - - MHODData.type is set to the type of the mhod. The data (or a - pointer to the data) will be stored in - .playlist_id/.string/.chapterdata/.splp/.splrs -*/ - -static MHODData get_mhod (FImport *fimp, glong mhod_seek, guint32 *ml) -{ - MHODData result; - gint32 xl; - guint32 mhod_len; - gint32 header_length; - gulong seek; - FContents *cts; - - cts = fimp->fcontents; - - result.valid = FALSE; - result.type = -1; - g_return_val_if_fail (ml, result); - *ml = -1; - - g_return_val_if_fail (cts, result); - g_return_val_if_fail (!cts->error, result); - -#if ITUNESDB_DEBUG - fprintf(stderr, "get_mhod seek: %ld\n", mhod_seek); -#endif - - result.type = get_mhod_type (cts, mhod_seek, &mhod_len); - - if (mhod_len == -1) - { - if (!cts->error) - { /* set error */ - g_set_error (&cts->error, - ITDB_FILE_ERROR, - ITDB_FILE_ERROR_CORRUPT, - _("iTunesDB corrupt: no MHOD at offset %ld in file '%s'."), - mhod_seek, cts->filename); - } - return result; - } - - if (!check_seek (cts, mhod_seek, mhod_len)) - return result; - - - header_length = get32lint (cts, mhod_seek+4); /* header length */ - - seek = mhod_seek + header_length; - -#if ITUNESDB_DEBUG - fprintf(stderr, "ml: %x type: %x\n", *ml, result.type); -#endif - - switch ((enum MHOD_ID)result.type) - { - case MHOD_ID_LIBPLAYLISTINDEX: - /* this is not yet supported */ - case MHOD_ID_PLAYLIST: - /* return the position indicator */ - result.data.track_pos = get32lint (cts, mhod_seek+24); - break; - case MHOD_ID_TITLE: - case MHOD_ID_PATH: - case MHOD_ID_ALBUM: - case MHOD_ID_ARTIST: - case MHOD_ID_GENRE: - case MHOD_ID_FILETYPE: - case MHOD_ID_COMMENT: - case MHOD_ID_CATEGORY: - case MHOD_ID_COMPOSER: - case MHOD_ID_GROUPING: - case MHOD_ID_DESCRIPTION: - case MHOD_ID_SUBTITLE: - case MHOD_ID_TVSHOW: - case MHOD_ID_TVEPISODE: - case MHOD_ID_TVNETWORK: - case MHOD_ID_ALBUMARTIST: - case MHOD_ID_KEYWORDS: - case MHOD_ID_SORT_ARTIST: - case MHOD_ID_SORT_TITLE: - case MHOD_ID_SORT_ALBUM: - case MHOD_ID_SORT_ALBUMARTIST: - case MHOD_ID_SORT_COMPOSER: - case MHOD_ID_SORT_TVSHOW: - result.data.string = extract_mhod_string (cts, seek); - if (result.data.string == NULL) { - *ml = mhod_len; - return result; - } - break; - case MHOD_ID_PODCASTURL: - case MHOD_ID_PODCASTRSS: - /* length of string */ - xl = mhod_len - header_length; - g_return_val_if_fail (xl < G_MAXUINT - 1, result); - result.data.string = g_new0 (gchar, xl+1); - if (!seek_get_n_bytes (cts, result.data.string, seek, xl)) - { - g_free (result.data.string); - return result; /* *ml==-1, result.valid==FALSE */ - } - break; - case MHOD_ID_CHAPTERDATA: - result.data.chapterdata = itdb_chapterdata_new(); - result.data.chapterdata->unk024 = get32lint (cts, seek); - result.data.chapterdata->unk028 = get32lint (cts, seek+4); - result.data.chapterdata->unk032 = get32lint (cts, seek+8); - seek += 12; /* get past unks */ - if (check_header_seek (cts, "sean", seek+4)) - { - gint i; - guint32 numchapters; - numchapters = get32bint (cts, seek+12) - 1; /* minus 1 for hedr atom */ - seek += 20; /* move to atom data */ - for (i=0; iliveupdate = get8int (cts, seek); - result.data.splpref->checkrules = get8int (cts, seek+1); - result.data.splpref->checklimits = get8int (cts, seek+2); - result.data.splpref->limittype = get8int (cts, seek+3); - result.data.splpref->limitsort = get8int (cts, seek+4); - result.data.splpref->limitvalue = get32lint (cts, seek+8); - result.data.splpref->matchcheckedonly = get8int (cts, seek+12); - /* if the opposite flag is on (seek+13), set limitsort's high - bit -- see note in itunesdb.h for more info */ - if (get8int (cts, seek+13)) - result.data.splpref->limitsort |= 0x80000000; - break; - case MHOD_ID_SPLRULES: /* Rules for smart playlist */ - if (check_header_seek (cts, "SLst", seek)) - { - /* !!! for some reason the SLst part is the only part of the - iTunesDB with big-endian encoding, including UTF16 - strings */ - gint i; - guint32 numrules; - if (!check_seek (cts, seek, 136)) - return result; /* *ml==-1, result.valid==FALSE */ - result.data.splrules = g_new0 (Itdb_SPLRules, 1); - result.data.splrules->unk004 = get32bint (cts, seek+4); - numrules = get32bint (cts, seek+8); - result.data.splrules->match_operator = get32bint (cts, seek+12); - seek += 136; /* I can't find this value stored in the - iTunesDB :-( */ - for (i=0; irules = g_list_append ( - result.data.splrules->rules, splr); - if (!check_seek (cts, seek, 56)) - goto splrules_error; - splr->field = get32bint (cts, seek); - splr->action = get32bint (cts, seek+4); - - if (!itdb_spl_action_known (splr->action)) - { - g_warning (_("Unknown smart rule action at %ld: %x. Trying to continue.\n"), seek, splr->action); - } - - seek += 52; - length = get32bint (cts, seek); - g_return_val_if_fail (length < G_MAXUINT-2, result); - - ft = itdb_splr_get_field_type (splr); - switch (ft) - { - case ITDB_SPLFT_STRING: - string_utf16 = g_new0 (gunichar2, (length+2)/2); - if (!seek_get_n_bytes (cts, (gchar *)string_utf16, - seek+4, length)) - { - g_free (string_utf16); - goto splrules_error; - } - fixup_big_utf16 (string_utf16); - splr->string = g_utf16_to_utf8 ( - string_utf16, -1, NULL, NULL, NULL); - g_free (string_utf16); - break; - case ITDB_SPLFT_INT: - case ITDB_SPLFT_DATE: - case ITDB_SPLFT_BOOLEAN: - case ITDB_SPLFT_PLAYLIST: - case ITDB_SPLFT_UNKNOWN: - case ITDB_SPLFT_BINARY_AND: - if (length != 0x44) - { - g_warning (_("Length of smart playlist rule field (%d) not as expected. Trying to continue anyhow.\n"), length); - } - if (!check_seek (cts, seek, 72)) - goto splrules_error; - splr->fromvalue = get64bint (cts, seek+4); - splr->fromdate = get64bint (cts, seek+12); - splr->fromunits = get64bint (cts, seek+20); - splr->tovalue = get64bint (cts, seek+28); - splr->todate = get64bint (cts, seek+36); - splr->tounits = get64bint (cts, seek+44); - /* ITDB_SPLFIELD_PLAYLIST seems to use these unknowns*/ - splr->unk052 = get32bint (cts, seek+52); - splr->unk056 = get32bint (cts, seek+56); - splr->unk060 = get32bint (cts, seek+60); - splr->unk064 = get32bint (cts, seek+64); - splr->unk068 = get32bint (cts, seek+68); - - if (ft == ITDB_SPLFT_DATE) { - ItdbSPLActionType at; - at = itdb_splr_get_action_type (splr); - if ((at == ITDB_SPLAT_RANGE_DATE) || - (at == ITDB_SPLAT_DATE)) - { - Itdb_iTunesDB *itdb = fimp->itdb; - splr->fromvalue = device_time_mac_to_time_t (itdb->device, - splr->fromvalue); - splr->tovalue = device_time_mac_to_time_t (itdb->device, - splr->tovalue); - } - } - - break; - } - seek += length+4; - } - } - else - { - if (!cts->error) - { /* set error */ - g_set_error (&cts->error, - ITDB_FILE_ERROR, - ITDB_FILE_ERROR_CORRUPT, - _("iTunesDB corrupt: no SLst at offset %ld in file '%s'."), - seek, cts->filename); - } - return result; /* *ml==-1, result.valid==FALSE */ - } - break; - splrules_error: - g_list_foreach (result.data.splrules->rules, - (GFunc)(itdb_splr_free), NULL); - g_list_free (result.data.splrules->rules); - g_free (result.data.splrules); - return result; /* *ml==-1, result.valid==FALSE */ - default: - g_warning (_("Encountered unknown MHOD type (%d) while parsing the iTunesDB. Ignoring.\n\n"), result.type); - *ml = mhod_len; - return result; - } - - *ml = mhod_len; - result.valid = TRUE; - return result; -} - -/* Returns the value of a string type mhod. return the length of the - mhod *ml, the mhod type *mty, and a string with the entry (in - UTF8). After use you must free the string with g_free(). Returns - NULL if no string is avaible. *ml is set to -1 in case of error and - cts->error is set appropriately. */ -static gchar *get_mhod_string (FImport *fimp, glong seek, guint32 *ml, gint32 *mty) -{ - MHODData mhoddata; - FContents *cts; - - cts = fimp->fcontents; - - *mty = get_mhod_type (cts, seek, ml); - if (cts->error) return NULL; - - if (*ml != -1) switch ((enum MHOD_ID)*mty) - { - case MHOD_ID_TITLE: - case MHOD_ID_PATH: - case MHOD_ID_ALBUM: - case MHOD_ID_ARTIST: - case MHOD_ID_GENRE: - case MHOD_ID_FILETYPE: - case MHOD_ID_COMMENT: - case MHOD_ID_CATEGORY: - case MHOD_ID_COMPOSER: - case MHOD_ID_GROUPING: - case MHOD_ID_DESCRIPTION: - case MHOD_ID_PODCASTURL: - case MHOD_ID_PODCASTRSS: - case MHOD_ID_SUBTITLE: - case MHOD_ID_TVSHOW: - case MHOD_ID_TVEPISODE: - case MHOD_ID_TVNETWORK: - case MHOD_ID_ALBUMARTIST: - case MHOD_ID_KEYWORDS: - case MHOD_ID_SORT_ARTIST: - case MHOD_ID_SORT_TITLE: - case MHOD_ID_SORT_ALBUM: - case MHOD_ID_SORT_ALBUMARTIST: - case MHOD_ID_SORT_COMPOSER: - case MHOD_ID_SORT_TVSHOW: - case MHOD_ID_ALBUM_ALBUM: - case MHOD_ID_ALBUM_ARTIST: - case MHOD_ID_ALBUM_ARTIST_MHII: - case MHOD_ID_ALBUM_SORT_ARTIST: - mhoddata = get_mhod (fimp, seek, ml); - if ((*ml != -1) && mhoddata.valid) - return mhoddata.data.string; - else - return NULL; - case MHOD_ID_SPLPREF: - case MHOD_ID_SPLRULES: - case MHOD_ID_LIBPLAYLISTINDEX: - case MHOD_ID_PLAYLIST: - case MHOD_ID_CHAPTERDATA: - case MHOD_ID_LIBPLAYLISTJUMPTABLE: - /* these do not have a string entry */ - return NULL; - } -#if ITUNESDB_MHIT_DEBUG - fprintf (stderr, "Ignoring unknown MHOD of type %d at offset %ld\n", *mty, seek); -#endif - return NULL; -} - - -/* convenience function: set error for zero length hunk */ -static void set_error_zero_length_hunk (GError **error, glong seek, - const gchar *filename) -{ - g_set_error (error, - ITDB_FILE_ERROR, - ITDB_FILE_ERROR_CORRUPT, - _("iTunesDB corrupt: hunk length 0 for hunk at %ld in file '%s'."), - seek, filename); -} - -/* convenience function: set error for missing hunk */ -static void set_error_a_not_found_in_b (GError **error, - const gchar *a, - const gchar *b, - glong b_seek) -{ - g_set_error (error, - ITDB_FILE_ERROR, - ITDB_FILE_ERROR_CORRUPT, - _("iTunesDB corrupt: no section '%s' found in section '%s' starting at %ld."), - a, b, b_seek); -} - -/* convenience function: set error if header is smaller than expected */ -static void set_error_a_header_smaller_than_b (GError **error, - const gchar *a, - guint32 b, guint32 len, - glong a_seek, - const gchar *filename) -{ - g_set_error (error, - ITDB_FILE_ERROR, - ITDB_FILE_ERROR_CORRUPT, - _("header length of '%s' smaller than expected (%d < %d) at offset %ld in file '%s'."), - a, b, len, a_seek, filename); -} - - -/* finds next occurence of section @a in section b (@b_seek) starting - at @start_seek -*/ -/* Return value: - -1 and cts->error not set: section @a could not be found - -1 and cts->error set: some error occured - >=0: start of next occurence of section @a -*/ -static glong find_next_a_in_b (FContents *cts, - const gchar *a, - glong b_seek, glong start_seek) -{ - glong b_len; - glong offset, len; - - g_return_val_if_fail (a, -1); - g_return_val_if_fail (cts, -1); - g_return_val_if_fail (strlen (a) == 4, -1); - g_return_val_if_fail (b_seek>=0, -1); - g_return_val_if_fail (start_seek >= b_seek, -1); - -/* printf ("%s: b_seek: %lx, start_seek: %lx\n", a, b_seek, start_seek); */ - - b_len = get32lint (cts, b_seek+8); - if (cts->error) return -1; - - offset = start_seek - b_seek; - do - { /* skip headers inside the b hunk (b_len) until we find header - @a */ - len = get32lint (cts, b_seek+offset+4); - if (cts->error) return -1; - if (len == 0) - { /* This needs to be checked, otherwise we might hang */ - set_error_zero_length_hunk (&cts->error, b_seek+offset, - cts->filename); - return -1; - } - offset += len; -/* printf ("offset: %lx, b_len: %lx, bseek+offset: %lx\n", */ -/* offset, b_len, b_seek+offset); */ - } while ((offset < b_len-4) && - !check_header_seek (cts, a, b_seek+offset)); - if (cts->error) return -1; - - if (offset >= b_len) return -1; - -/* printf ("%s found at %lx\n", a, b_seek+offset); */ - - return b_seek+offset; -} - - - - -/* return the position of mhsd with type @type */ -/* Return value: - -1 if mhsd cannot be found. cts->error will not be set - - 0 and cts->error is set if some other error occurs. - Since the mhsd can never be at position 0 (a mhbd must be there), - a return value of 0 always indicates an error. -*/ -static glong find_mhsd (FContents *cts, guint32 type) -{ - guint32 i, len, mhsd_num; - glong seek; - - len = get32lint (cts, 4); - if (cts->error) return 0; - /* all the headers I know are 0x68 long -- if this one is longer - we can could simply ignore the additional information */ - /* Since 'we' (parse_fimp()) only need data from the first 32 - bytes, don't complain unless it's smaller than that */ - if (len < 32) - { - g_set_error (&cts->error, - ITDB_FILE_ERROR, - ITDB_FILE_ERROR_CORRUPT, - _("iTunesDB ('%s'): header length of mhsd hunk smaller than expected (%d<32). Aborting."), - cts->filename, len); - return FALSE; - } - - mhsd_num = get32lint (cts, 20); - if (cts->error) return 0; - - seek = 0; - for (i=0; ierror) - { /* set error */ - g_set_error (&cts->error, - ITDB_FILE_ERROR, - ITDB_FILE_ERROR_CORRUPT, - _("iTunesDB '%s' corrupt: mhsd expected at %ld."), - cts->filename, seek); - } - return 0; - } - len = get32lint (cts, seek+8); - if (cts->error) return 0; - - mhsd_type = get32lint (cts, seek+12); - if (cts->error) return 0; - - if (mhsd_type == type) return seek; - } - return -1; -} - - -/* sort in reverse order */ -static gint pos_comp (gconstpointer a, gconstpointer b) -{ - const PosEntry *pa = (const PosEntry*)a; - const PosEntry *pb = (const PosEntry*)b; - - return pb->track_pos - pa->track_pos; -} - - -/* Read and process the mhip at @seek. Return a pointer to the next - possible mhip. */ -/* Return value: -1 if no mhip is present at @seek */ -static glong get_mhip (FImport *fimp, glong mhip_seek) -{ - gboolean first_entry = TRUE; - FContents *cts; - guint32 mhip_hlen, mhip_len, mhod_num, mhod_seek; - gint32 i; - gint32 mhod_type; - guint32 trackid; - - - g_return_val_if_fail (fimp, -1); - - cts = fimp->fcontents; - - if (!check_header_seek (cts, "mhip", mhip_seek)) - { - CHECK_ERROR (fimp, -1); - return -1; - } - - mhip_hlen = get32lint (cts, mhip_seek+4); - CHECK_ERROR (fimp, -1); - - if (mhip_hlen < 36) - { - set_error_a_header_smaller_than_b (&fimp->error, - "mhip", - mhip_hlen, 36, - mhip_seek, cts->filename); - return -1; - } - - /* Check if entire mhip header can be read -- that way we won't - have to check for read errors every time we access a single - byte */ - - check_seek (cts, mhip_seek, mhip_hlen); - CHECK_ERROR (fimp, -1); - - mhip_len = get32lint (cts, mhip_seek+8); - mhod_num = get32lint (cts, mhip_seek+12); - trackid = get32lint(cts, mhip_seek+24); - - mhod_seek = mhip_seek + mhip_hlen; - - /* the mhod that follows gives us the position in the - playlist (type 100). Just for flexibility, we scan all - following mhods and pick the type 100 */ - for (i=0; itrackid = trackid; - entry->track_pos = mhod.data.track_pos; - fimp->pos_glist = g_list_prepend (fimp->pos_glist, entry); - /* don't call this section more than once (it never - should happen except in the case of corrupted - iTunesDBs...) */ - first_entry = FALSE; - } - } - else - { - if (mhod_len == -1) - { - g_warning (_("Number of MHODs in mhip at %ld inconsistent in file '%s'."), - mhip_seek, cts->filename); - break; - } - } - mhod_seek += mhod_len; - } - - /* Up to iTunesd V4.7 or so the mhip_len was set incorrectly - (mhip_len == mhip_hlen). In that case we need to find the seek - to the next mhip by going through all mhods. - */ - if ((mhip_len == mhip_hlen) && (mhod_num > 0)) - return mhod_seek; - else - return mhip_seek+mhip_len; -} - - - - -/* Get a playlist. Returns the position where the next playlist should - be. On error -1 is returned and fimp->error is set - appropriately. */ -/* get_mhyp */ -static glong get_playlist (FImport *fimp, guint mhsd_type, glong mhyp_seek) -{ - guint32 i, mhipnum, mhod_num; - glong nextseek, mhod_seek, mhip_seek; - guint32 header_len; - Itdb_Playlist *plitem = NULL; - FContents *cts; - GList *gl; - -#if ITUNESDB_DEBUG - fprintf(stderr, "mhyp seek: %x\n", (int)mhyp_seek); -#endif - g_return_val_if_fail (fimp, -1); - g_return_val_if_fail (fimp->idtree, -1); - g_return_val_if_fail (fimp->pos_glist == NULL, -1); - - cts = fimp->fcontents; - - if (!check_header_seek (cts, "mhyp", mhyp_seek)) - { - if (cts->error) - g_propagate_error (&fimp->error, cts->error); - return -1; - } - header_len = get32lint (cts, mhyp_seek+4); /* length of header */ - CHECK_ERROR (fimp, -1); - - if (header_len < 48) - { - set_error_a_header_smaller_than_b (&fimp->error, - "mhyp", - header_len, 48, - mhyp_seek, cts->filename); - return -1; - } - - /* Check if entire mhyp can be read -- that way we won't have to - * check for read errors every time we access a single byte */ - - check_seek (cts, mhyp_seek, header_len); - CHECK_ERROR (fimp, -1); - - nextseek = mhyp_seek + get32lint (cts, mhyp_seek+8);/* possible begin of next PL */ - mhod_num = get32lint (cts, mhyp_seek+12); /* number of MHODs we expect */ - mhipnum = get32lint (cts, mhyp_seek+16); /* number of tracks - (mhips) in playlist */ - - plitem = itdb_playlist_new (NULL, FALSE); - - plitem->num = mhipnum; - /* Some Playlists have added 256 to their type -- I don't know what - it's for, so we just ignore it for now -> & 0xff */ - plitem->type = get8int (cts, mhyp_seek+20); - plitem->flag1 = get8int (cts, mhyp_seek+21); - plitem->flag2 = get8int (cts, mhyp_seek+22); - plitem->flag3 = get8int (cts, mhyp_seek+23); - plitem->timestamp = get32lint (cts, mhyp_seek+24); - plitem->timestamp = device_time_mac_to_time_t (fimp->itdb->device, plitem->timestamp); - plitem->id = get64lint (cts, mhyp_seek+28); -/* plitem->mhodcount = get32lint (cts, mhyp_seek+36); */ -/* plitem->libmhodcount = get16lint (cts, mhyp_seek+40);*/ - plitem->podcastflag = get16lint (cts, mhyp_seek+42); - plitem->sortorder = get32lint (cts, mhyp_seek+44); - if (header_len >= 0x6C) { - plitem->priv->mhsd5_type = get16lint (cts, mhyp_seek+0x50); - } - - mhod_seek = mhyp_seek + header_len; - - for (i=0; i < mhod_num; ++i) - { - gint32 type; - MHODData mhod; - - type = get_mhod_type (cts, mhod_seek, &header_len); - CHECK_ERROR (fimp, -1); - if (header_len != -1) - { - switch ((enum MHOD_ID)type) - { - case MHOD_ID_PLAYLIST: - /* here we could do something about the playlist settings */ - break; - case MHOD_ID_TITLE: - mhod = get_mhod (fimp, mhod_seek, &header_len); - CHECK_ERROR (fimp, -1); - if (mhod.valid && mhod.data.string) - { - /* sometimes there seem to be two mhod TITLE headers */ - g_free (plitem->name); - plitem->name = mhod.data.string; - mhod.valid = FALSE; - } - break; - case MHOD_ID_SPLPREF: - mhod = get_mhod (fimp, mhod_seek, &header_len); - CHECK_ERROR (fimp, -1); - if (mhod.valid && mhod.data.splpref) - { - plitem->is_spl = TRUE; - memcpy (&plitem->splpref, mhod.data.splpref, - sizeof (Itdb_SPLPref)); - g_free (mhod.data.splpref); - mhod.valid = FALSE; - } - break; - case MHOD_ID_SPLRULES: - mhod = get_mhod (fimp, mhod_seek, &header_len); - CHECK_ERROR (fimp, -1); - if (mhod.valid && mhod.data.splrules) - { - plitem->is_spl = TRUE; - memcpy (&plitem->splrules, mhod.data.splrules, - sizeof (Itdb_SPLRules)); - g_free (mhod.data.splrules); - mhod.valid = FALSE; - } - break; - case MHOD_ID_PATH: - case MHOD_ID_ALBUM: - case MHOD_ID_ARTIST: - case MHOD_ID_GENRE: - case MHOD_ID_FILETYPE: - case MHOD_ID_COMMENT: - case MHOD_ID_CATEGORY: - case MHOD_ID_COMPOSER: - case MHOD_ID_GROUPING: - case MHOD_ID_DESCRIPTION: - case MHOD_ID_PODCASTURL: - case MHOD_ID_PODCASTRSS: - case MHOD_ID_SUBTITLE: - case MHOD_ID_TVSHOW: - case MHOD_ID_TVEPISODE: - case MHOD_ID_TVNETWORK: - case MHOD_ID_ALBUMARTIST: - case MHOD_ID_KEYWORDS: - case MHOD_ID_CHAPTERDATA: - case MHOD_ID_SORT_ARTIST: - case MHOD_ID_SORT_TITLE: - case MHOD_ID_SORT_ALBUM: - case MHOD_ID_SORT_ALBUMARTIST: - case MHOD_ID_SORT_COMPOSER: - case MHOD_ID_SORT_TVSHOW: - case MHOD_ID_ALBUM_ALBUM: - case MHOD_ID_ALBUM_ARTIST: - case MHOD_ID_ALBUM_ARTIST_MHII: - case MHOD_ID_ALBUM_SORT_ARTIST: - case MHOD_ID_LIBPLAYLISTJUMPTABLE: - /* these are not expected here */ - break; - case MHOD_ID_LIBPLAYLISTINDEX: - /* this I don't know how to handle */ - break; - } - mhod_seek += header_len; - } - else - { - g_warning (_("Number of MHODs in mhyp at %ld inconsistent in file '%s'."), - mhyp_seek, cts->filename); - break; - } - } - - if (!plitem->name) - { /* we did not read a valid mhod TITLE header -> */ - /* we simply make up our own name */ - if (itdb_playlist_is_mpl (plitem)) - plitem->name = g_strdup (_("Master-PL")); - else - { - if (itdb_playlist_is_podcasts (plitem)) - plitem->name = g_strdup (_("Podcasts")); - else - plitem->name = g_strdup (_("Playlist")); - } - } - -#if ITUNESDB_DEBUG - fprintf(stderr, "pln: %s(%d Itdb_Tracks) \n", plitem->name, (int)plitem->num); -#endif - - /* add new playlist */ - if (mhsd_type == 5) { - itdb_playlist_add_mhsd5_playlist (fimp->itdb, plitem, -1); - } else { - itdb_playlist_add (fimp->itdb, plitem, -1); - } - - mhip_seek = mhod_seek; - - /* tracks read */ - for (i=0; i < mhipnum; ++i) - { - mhip_seek = get_mhip (fimp, mhip_seek); - if (mhip_seek == -1) - { - g_set_error (&fimp->error, - ITDB_FILE_ERROR, - ITDB_FILE_ERROR_CORRUPT, - _("iTunesDB corrupt: number of mhip sections inconsistent in mhyp starting at %ld in file '%s'."), - mhyp_seek, cts->filename); - return -1; - } - } - - /* sort in reverse order */ - fimp->pos_glist = g_list_sort (fimp->pos_glist, pos_comp); - for (gl = fimp->pos_glist; gl; gl = g_list_next (gl)) - { - PosEntry* entry = (PosEntry*)gl->data; - Itdb_Track *tr = itdb_track_id_tree_by_id (fimp->idtree, entry->trackid); - if (tr) - { - /* preprend because we sorted in reverse order */ - itdb_playlist_add_track (plitem, tr, 0); - } - else - { - if (plitem->podcastflag == ITDB_PL_FLAG_NORM) - { - g_warning (_("Itdb_Track ID '%d' not found.\n"), entry->trackid); - } - } - g_free(entry); - } - - g_list_free (fimp->pos_glist); - fimp->pos_glist = NULL; - return nextseek; -} - - -/* returns a pointer to the next header or -1 on error. fimp->error is - set appropriately. If no "mhit" header is found at the location - specified, -1 is returned but no error is set. */ -static glong get_mhit (FImport *fimp, glong mhit_seek) -{ - Itdb_Track *track; - gchar *entry_utf8; - gint32 type; - guint32 header_len; - guint32 zip; - struct playcount *playcount; - guint32 i, mhod_nums; - FContents *cts; - glong seek = mhit_seek; - gboolean free_playcount; - -#if ITUNESDB_DEBUG - fprintf(stderr, "get_mhit seek: %x\n", (int)seek); -#endif - - g_return_val_if_fail (fimp, -1); - - cts = fimp->fcontents; - - if (!check_header_seek (cts, "mhit", seek)) - { - if (cts->error) - g_propagate_error (&fimp->error, cts->error); - return -1; - } - - header_len = get32lint (cts, seek+4); - CHECK_ERROR (fimp, -1); - - /* size of the mhit header: For dbversion <= 0x0b (iTunes 4.7 and - earlier), the length is 0x9c. As of dbversion 0x0c and 0x0d - (iTunes 4.7.1 - iTunes 4.9), the size is 0xf4. */ - if (header_len < 0x9c) - { - set_error_a_header_smaller_than_b (&fimp->error, - "mhit", - header_len, 0x9c, - seek, cts->filename); - return -1; - } - - /* Check if entire mhit can be read -- that way we won't have to - * check for read errors every time we access a single byte */ - - check_seek (cts, seek, header_len); - - mhod_nums = get32lint (cts, seek+12); - CHECK_ERROR (fimp, -1); - - - track = itdb_track_new (); - - if (header_len >= 0x9c) - { - guint32 val32; - track->id = get32lint(cts, seek+16); /* iPod ID */ - track->visible = get32lint (cts, seek+20); - track->filetype_marker = get32lint (cts, seek+24); - track->type1 = get8int (cts, seek+28); - track->type2 = get8int (cts, seek+29); - track->compilation = get8int (cts, seek+30); - track->rating = get8int (cts, seek+31); - track->time_modified = get32lint(cts, seek+32); /* time added */ - track->time_modified = device_time_mac_to_time_t (fimp->itdb->device, - track->time_modified); - track->size = get32lint(cts, seek+36); /* file size */ - track->tracklen = get32lint(cts, seek+40); /* time */ - track->track_nr = get32lint(cts, seek+44); /* track number */ - track->tracks = get32lint(cts, seek+48); /* nr of tracks */ - track->year = get32lint(cts, seek+52); /* year */ - track->bitrate = get32lint(cts, seek+56); /* bitrate */ - val32 = get32lint (cts, seek+60); - track->samplerate = val32 >> 16; /* sample rate */ - track->samplerate_low = val32 & 0xffff; /* remaining bits */ - track->volume = get32lint(cts, seek+64); /* volume adjust */ - track->starttime = get32lint (cts, seek+68); - track->stoptime = get32lint (cts, seek+72); - track->soundcheck = get32lint (cts, seek+76);/* soundcheck */ - track->playcount = get32lint (cts, seek+80); /* playcount */ - track->playcount2 = get32lint (cts, seek+84); - track->time_played = get32lint(cts, seek+88);/* last time played */ - track->time_played = device_time_mac_to_time_t (fimp->itdb->device, - track->time_played); - track->cd_nr = get32lint(cts, seek+92); /* CD nr */ - track->cds = get32lint(cts, seek+96); /* CD nr of.. */ - /* Apple Store/Audible User ID (for DRM'ed files only, set to 0 - otherwise). */ - track->drm_userid = get32lint (cts, seek+100); - track->time_added = get32lint(cts, seek+104);/* last mod. time */ - track->time_added = device_time_mac_to_time_t (fimp->itdb->device, - track->time_added); - track->bookmark_time = get32lint (cts, seek+108);/*time bookmarked*/ - track->dbid = get64lint (cts, seek+112); - track->checked = get8int (cts, seek+120); /*Checked/Unchecked: 0/1*/ - /* The rating set by the application, as opposed to the rating - set on the iPod itself */ - track->app_rating = get8int (cts, seek+121); - track->BPM = get16lint (cts, seek+122); - track->artwork_count = get16lint (cts, seek+124); - track->unk126 = get16lint (cts, seek+126); - track->artwork_size = get32lint (cts, seek+128); - track->unk132 = get32lint (cts, seek+132); - track->samplerate2 = get32lfloat (cts, seek+136); - track->time_released = get32lint (cts, seek+140); - track->time_released = device_time_mac_to_time_t (fimp->itdb->device, - track->time_released); - track->unk144 = get16lint (cts, seek+144); - track->explicit_flag = get16lint (cts, seek+146); - track->unk148 = get32lint (cts, seek+148); - track->unk152 = get32lint (cts, seek+152); - } - if (header_len >= 0xf4) - { - track->skipcount = get32lint (cts, seek+156); - track->last_skipped = get32lint (cts, seek+160); - track->last_skipped = device_time_mac_to_time_t (fimp->itdb->device, - track->last_skipped); - track->has_artwork = get8int (cts, seek+164); - track->skip_when_shuffling = get8int (cts, seek+165); - track->remember_playback_position = get8int (cts, seek+166); - track->flag4 = get8int (cts, seek+167); - track->dbid2 = get64lint (cts, seek+168); - track->lyrics_flag = get8int (cts, seek+176); - track->movie_flag = get8int (cts, seek+177); - track->mark_unplayed = get8int (cts, seek+178); - track->unk179 = get8int (cts, seek+179); - track->unk180 = get32lint (cts, seek+180); - track->pregap = get32lint (cts, seek+184); - track->samplecount = get64lint (cts, seek+188); - track->unk196 = get32lint (cts, seek+196); - track->postgap = get32lint (cts, seek+200); - track->unk204 = get32lint (cts, seek+204); - track->mediatype = get32lint (cts, seek+208); - track->season_nr = get32lint (cts, seek+212); - track->episode_nr = get32lint (cts, seek+216); - track->unk220 = get32lint (cts, seek+220); - track->unk224 = get32lint (cts, seek+224); - track->unk228 = get32lint (cts, seek+228); - track->unk232 = get32lint (cts, seek+232); - track->unk236 = get32lint (cts, seek+236); - track->unk240 = get32lint (cts, seek+240); - } - if (header_len >= 0x148) - { - track->unk244 = get32lint (cts, seek+244); - track->gapless_data = get32lint (cts, seek+248); - track->unk252 = get32lint (cts, seek+252); - track->gapless_track_flag = get16lint (cts, seek+256); - track->gapless_album_flag = get16lint (cts, seek+258); - } - if (header_len >= 0x184) - { - track->mhii_link = get32lint (cts, seek+352); - } - - track->transferred = TRUE; /* track is on iPod! */ - - seek += get32lint (cts, seek+4); /* 1st mhod starts here! */ - CHECK_ERROR (fimp, -1); - - for (i=0; ititle = entry_utf8; - break; - case MHOD_ID_PATH: - track->ipod_path = entry_utf8; - break; - case MHOD_ID_ALBUM: - track->album = entry_utf8; - break; - case MHOD_ID_ARTIST: - track->artist = entry_utf8; - break; - case MHOD_ID_GENRE: - track->genre = entry_utf8; - break; - case MHOD_ID_FILETYPE: - track->filetype = entry_utf8; - break; - case MHOD_ID_COMMENT: - track->comment = entry_utf8; - break; - case MHOD_ID_CATEGORY: - track->category = entry_utf8; - break; - case MHOD_ID_COMPOSER: - track->composer = entry_utf8; - break; - case MHOD_ID_GROUPING: - track->grouping = entry_utf8; - break; - case MHOD_ID_DESCRIPTION: - track->description = entry_utf8; - break; - case MHOD_ID_PODCASTURL: - track->podcasturl = entry_utf8; - break; - case MHOD_ID_PODCASTRSS: - track->podcastrss = entry_utf8; - break; - case MHOD_ID_SUBTITLE: - track->subtitle = entry_utf8; - break; - case MHOD_ID_TVSHOW: - track->tvshow = entry_utf8; - break; - case MHOD_ID_TVEPISODE: - track->tvepisode = entry_utf8; - break; - case MHOD_ID_TVNETWORK: - track->tvnetwork = entry_utf8; - break; - case MHOD_ID_ALBUMARTIST: - track->albumartist = entry_utf8; - break; - case MHOD_ID_KEYWORDS: - track->keywords = entry_utf8; - break; - case MHOD_ID_SORT_ARTIST: - track->sort_artist = entry_utf8; - break; - case MHOD_ID_SORT_TITLE: - track->sort_title = entry_utf8; - break; - case MHOD_ID_SORT_ALBUM: - track->sort_album = entry_utf8; - break; - case MHOD_ID_SORT_ALBUMARTIST: - track->sort_albumartist = entry_utf8; - break; - case MHOD_ID_SORT_COMPOSER: - track->sort_composer = entry_utf8; - break; - case MHOD_ID_SORT_TVSHOW: - track->sort_tvshow = entry_utf8; - break; - case MHOD_ID_SPLPREF: - case MHOD_ID_SPLRULES: - case MHOD_ID_LIBPLAYLISTINDEX: - case MHOD_ID_LIBPLAYLISTJUMPTABLE: - case MHOD_ID_PLAYLIST: - case MHOD_ID_CHAPTERDATA: - case MHOD_ID_ALBUM_ALBUM: - case MHOD_ID_ALBUM_ARTIST: - case MHOD_ID_ALBUM_ARTIST_MHII: - case MHOD_ID_ALBUM_SORT_ARTIST: - g_free (entry_utf8); - break; - } - } - else - { - MHODData mhod; - switch (type) - { - case MHOD_ID_CHAPTERDATA: - mhod = get_mhod (fimp, seek, &zip); - if (mhod.valid && mhod.data.chapterdata) - { - track->chapterdata = mhod.data.chapterdata; - mhod.valid = FALSE; - } - break; - default: -/* - printf ("found mhod type %d at %lx inside mhit starting at %lx\n", - type, seek, mhit_seek);*/ - break; - } - } - seek += zip; - } - - playcount = playcount_take_next (fimp); - free_playcount = TRUE; - if (!playcount && fimp->pcounts2) - { - playcount = g_hash_table_lookup (fimp->pcounts2, &track->dbid); - free_playcount = FALSE; - } - if (playcount) - { - if (playcount->rating != NO_PLAYCOUNT) - { - if (track->rating != playcount->rating) - { - /* backup original rating to app_rating */ - track->app_rating = track->rating; - track->rating = playcount->rating; - } - } - if (playcount->time_played) - track->time_played = playcount->time_played; - - if (playcount->bookmark_time) - track->bookmark_time = playcount->bookmark_time; - - track->playcount += playcount->playcount; - if (playcount->playcount != 0) - { /* unmark the 'unplayed' flag */ - track->mark_unplayed = 0x01; - } - track->recent_playcount = playcount->playcount; - - track->skipcount += playcount->skipcount; - track->recent_skipcount = playcount->skipcount; - if (free_playcount) - g_free (playcount); - } - fimp->tracks = g_list_prepend(fimp->tracks, track); - return seek; -} - - -/* Called by read_OTG_playlists(): OTG playlist stored in @cts by - * adding a new playlist (named @plname) with the tracks specified in - * @cts. If @plname is NULL, a standard name will be substituted */ -/* Returns FALSE on error, TRUE on success. On error @fimp->error will - * be set apropriately. */ -static gboolean process_OTG_file (FImport *fimp, FContents *cts, - const gchar *plname) -{ - guint32 header_length, entry_length, entry_num; - - g_return_val_if_fail (fimp && cts, FALSE); - g_return_val_if_fail (fimp->itdb, FALSE); - - if (!plname) plname = _("OTG Playlist"); - - if (!check_header_seek (cts, "mhpo", 0)) - { - if (cts->error) - { - g_propagate_error (&fimp->error, cts->error); - return FALSE; - } - fcontents_set_reversed (cts, TRUE); - if (!check_header_seek (cts, "mhpo", 0)) - { - /* cts->error can't be set as already checked above */ - /* set error */ - g_return_val_if_fail (cts->filename, FALSE); - g_set_error (&fimp->error, - ITDB_FILE_ERROR, - ITDB_FILE_ERROR_CORRUPT, - _("Not a OTG playlist file: '%s' (missing mhpo header)."), - cts->filename); - return FALSE; - } - } - header_length = get32lint (cts, 4); - CHECK_ERROR (fimp, FALSE); - /* all the headers I know are 0x14 long -- if this one is - longer we can simply ignore the additional information */ - if (header_length < 0x14) - { - g_set_error (&fimp->error, - ITDB_FILE_ERROR, - ITDB_FILE_ERROR_CORRUPT, - _("OTG playlist file ('%s'): header length smaller than expected (%d<20)."), - cts->filename, header_length); - return FALSE; - } - entry_length = get32lint (cts, 8); - CHECK_ERROR (fimp, FALSE); - /* all the entries I know are 0x04 long */ - if (entry_length < 0x04) - { - g_set_error (&fimp->error, - ITDB_FILE_ERROR, - ITDB_FILE_ERROR_CORRUPT, - _("OTG playlist file ('%s'): entry length smaller than expected (%d<4)."), - cts->filename, entry_length); - return FALSE; - } - /* number of entries */ - entry_num = get32lint (cts, 12); - CHECK_ERROR (fimp, FALSE); - - if (entry_num > 0) - { - gint i; - Itdb_Playlist *pl; - - pl = itdb_playlist_new (plname, FALSE); - /* Add new playlist */ - itdb_playlist_add (fimp->itdb, pl, -1); - - /* Add items */ - for (i=0; iitdb->tracks, num); - if (track) - { - itdb_playlist_add_track (pl, track, -1); - } - else - { - g_set_error (&fimp->error, - ITDB_FILE_ERROR, - ITDB_FILE_ERROR_CORRUPT, - _("OTG playlist file '%s': reference to non-existent track (%d)."), - cts->filename, num); - return FALSE; - } - } - } - return TRUE; -} - - - - -/* Add the On-The-Go Playlist(s) to the database */ -/* The OTG-Files are located in the directory given by - fimp->itdb->itdb_filename. - On error FALSE is returned and fimp->error is set accordingly. */ -static gboolean read_OTG_playlists (FImport *fimp) -{ - gchar *db[] = {"OTGPlaylistInfo", NULL}; - gchar *dirname, *otgname; - gint i=1; - - g_return_val_if_fail (fimp, FALSE); - g_return_val_if_fail (fimp->itdb, FALSE); - g_return_val_if_fail (fimp->itdb->filename, FALSE); - - dirname = g_path_get_dirname (fimp->itdb->filename); - otgname = itdb_resolve_path (dirname, (const gchar **)db); - - /* only parse if "OTGPlaylistInfo" exists */ - while (otgname) - { - FContents *cts = fcontents_read (otgname, &fimp->error); - if (cts) - { - gchar *plname = g_strdup_printf (_("OTG Playlist %d"), i); - process_OTG_file (fimp, cts, plname); - g_free (plname); - fcontents_free (cts); - } - g_free (otgname); - if (fimp->error) break; - db[0] = g_strdup_printf ("OTGPlaylistInfo_%d", i); - otgname = itdb_resolve_path (dirname, (const gchar **)db); - g_free (db[0]); - ++i; - } - g_free (dirname); - return TRUE; -} - - -/* Read the tracklist (mhlt). mhsd_seek must point to type 1 mhsd - (this is treated as a programming error) */ -/* Return value: - TRUE: import successful - FALSE: error occured, fimp->error is set */ -static gboolean parse_tracks (FImport *fimp, glong mhsd_seek) -{ - FContents *cts; - GList* gl; - glong mhlt_seek, seek; - guint32 nr_tracks, i; - - g_return_val_if_fail (fimp, FALSE); - g_return_val_if_fail (fimp->itdb, FALSE); - g_return_val_if_fail (fimp->fcontents, FALSE); - g_return_val_if_fail (fimp->fcontents->filename, FALSE); - g_return_val_if_fail (mhsd_seek >= 0, FALSE); - - cts = fimp->fcontents; - - g_return_val_if_fail (check_header_seek (cts, "mhsd", mhsd_seek), - FALSE); - - /* The mhlt header should be the next after the mhsd header. In - order to allow slight changes in the format, we skip headers - until we find an mhlt inside the given mhsd */ - - mhlt_seek = find_next_a_in_b (cts, "mhlt", mhsd_seek, mhsd_seek); - CHECK_ERROR (fimp, FALSE); - - if (mhlt_seek == -1) - { - set_error_a_not_found_in_b (&fimp->error, - "mhlt", "mhsd", mhsd_seek); - return FALSE; - } - - /* Now we are at the mhlt */ - nr_tracks = get32lint (cts, mhlt_seek+8); - CHECK_ERROR (fimp, FALSE); - - seek = find_next_a_in_b (cts, "mhit", mhsd_seek, mhlt_seek); - CHECK_ERROR (fimp, FALSE); - /* seek should now point to the first mhit */ - for (i=0; ierror) return FALSE; - if (seek == -1) - { /* this should not be -- issue warning */ - g_warning (_("iTunesDB corrupt: number of tracks (mhit hunks) inconsistent. Trying to continue.\n")); - break; - } - } - for (gl=fimp->tracks; gl; gl=g_list_next(gl)) { - Itdb_Track *track = (Itdb_Track *)gl->data; - itdb_track_add (fimp->itdb, track, 0); - } - return TRUE; -} - - - -/* Read the playlists (mhlp). mhsd_seek must point to type 2 or type 3 - mhsd (this is treated as a programming error) */ -/* Return value: - TRUE: import successful - FALSE: error occured, fimp->error is set */ -static gboolean parse_playlists (FImport *fimp, glong mhsd_seek) -{ - FContents *cts; - glong seek, mhlp_seek; - guint32 nr_playlists, i; - guint mhsd_type; - - g_return_val_if_fail (fimp, FALSE); - g_return_val_if_fail (fimp->itdb, FALSE); - g_return_val_if_fail (fimp->fcontents, FALSE); - g_return_val_if_fail (fimp->fcontents->filename, FALSE); - g_return_val_if_fail (mhsd_seek >= 0, FALSE); - - cts = fimp->fcontents; - - g_return_val_if_fail (check_header_seek (cts, "mhsd", mhsd_seek), - FALSE); - mhsd_type = get32lint (cts, mhsd_seek+12); - - /* The mhlp header should be the next after the mhsd header. In - order to allow slight changes in the format, we skip headers - until we find an mhlp inside the given mhsd */ - mhlp_seek = find_next_a_in_b (cts, "mhlp", mhsd_seek, mhsd_seek); - CHECK_ERROR (fimp, FALSE); - - if (mhlp_seek == -1) - { - set_error_a_not_found_in_b (&fimp->error, - "mhlp", "mhsd", mhsd_seek); - return FALSE; - } - /* Now we are at the mhlp */ - - nr_playlists = get32lint (cts, mhlp_seek+8); - CHECK_ERROR (fimp, FALSE); - - /* Create track-id tree for quicker track lookup */ - fimp->idtree = itdb_track_id_tree_create (fimp->itdb); - - seek = find_next_a_in_b (cts, "mhyp", mhsd_seek, mhlp_seek); - CHECK_ERROR (fimp, FALSE); - /* seek should now point to the first mhit */ - for (i=0; ierror) return FALSE; - if (seek == -1) - { /* this should not be -- issue warning */ - g_warning (_("iTunesDB possibly corrupt: number of playlists (mhyp hunks) inconsistent. Trying to continue.\n")); - break; - } - } - - itdb_track_id_tree_destroy (fimp->idtree); - fimp->idtree = NULL; - - return TRUE; -} - -static gboolean parse_genius_mhsd(FImport *fimp, glong mhsd_seek) -{ - FContents *cts; - guint32 hdrlen, mhsdlen; - gint32 len; - gchar *genius_cuid; - - g_return_val_if_fail (fimp, FALSE); - g_return_val_if_fail (fimp->itdb, FALSE); - g_return_val_if_fail (fimp->fcontents, FALSE); - g_return_val_if_fail (fimp->fcontents->filename, FALSE); - g_return_val_if_fail (mhsd_seek >= 0, FALSE); - - cts = fimp->fcontents; - - g_return_val_if_fail (check_header_seek (cts, "mhsd", mhsd_seek), - FALSE); - - hdrlen = get32lint (cts, mhsd_seek+4); - mhsdlen = get32lint (cts, mhsd_seek+8); - - len = mhsdlen - hdrlen; - if (len < 0) { - return FALSE; - } - - if (len != 32) { - g_warning(_("%s: Unexpected length %d for genius_cuid!\n"), __func__, len); - } - - genius_cuid = g_new0(gchar, len+1); - if (!seek_get_n_bytes (cts, genius_cuid, mhsd_seek+hdrlen, len)) { - g_free (genius_cuid); - return FALSE; - } - - fimp->itdb->priv->genius_cuid = genius_cuid; - - return TRUE; -} - -static gboolean looks_like_itunesdb (FImport *fimp) -{ - FContents *cts; - - cts = fimp->fcontents; - - /* Check if it looks like an iTunesDB */ - if (!check_header_seek (cts, "mhbd", 0)) - { - fcontents_set_reversed (cts, TRUE); - if (cts->error) return 0; - if (!check_header_seek (cts, "mhbd", 0)) - { - if (!cts->error) - { /* set error */ - g_set_error (&cts->error, - ITDB_FILE_ERROR, - ITDB_FILE_ERROR_CORRUPT, - _("Not a iTunesDB: '%s' (missing mhdb header)."), - cts->filename); - } - return FALSE; - } - } - return TRUE; -} - -static gboolean parse_fimp (FImport *fimp, gboolean compressed) -{ - glong seek=0; - FContents *cts; - glong mhsd_1, mhsd_2, mhsd_3, mhsd_5, mhsd_9; - - g_return_val_if_fail (fimp, FALSE); - g_return_val_if_fail (fimp->itdb, FALSE); - g_return_val_if_fail (fimp->fcontents, FALSE); - g_return_val_if_fail (fimp->fcontents->filename, FALSE); - - if (!looks_like_itunesdb (fimp)) { - return FALSE; - } - - if (compressed) { - itdb_zlib_check_decompress_fimp (fimp); - } - - cts = fimp->fcontents; - - /* get the positions of the various mhsd */ - /* type 1: track list */ - mhsd_1 = find_mhsd (cts, 1); - CHECK_ERROR (fimp, FALSE); - /* type 2: standard playlist section -- Podcasts playlist will be - just an ordinary playlist */ - mhsd_2 = find_mhsd (cts, 2); - CHECK_ERROR (fimp, FALSE); - /* type 3: playlist section with special version of Podcasts - playlist (optional) */ - mhsd_3 = find_mhsd (cts, 3); - CHECK_ERROR (fimp, FALSE); - - /* read smart playlists */ - mhsd_5 = find_mhsd (cts, 5); - CHECK_ERROR (fimp, FALSE); - - /* read genius cuid mhsd */ - mhsd_9 = find_mhsd (cts, 9); - CHECK_ERROR (fimp, FALSE); - - fimp->itdb->version = get32lint (cts, seek+0x10); - CHECK_ERROR (fimp, FALSE); - fimp->itdb->id = get64lint (cts, seek+0x18); - CHECK_ERROR (fimp, FALSE); - fimp->itdb->priv->platform = get16lint (cts, seek+0x20); - CHECK_ERROR (fimp, FALSE); - fimp->itdb->priv->unk_0x22 = get16lint (cts, seek+0x22); - CHECK_ERROR (fimp, FALSE); - fimp->itdb->priv->id_0x24 = get64lint (cts, seek+0x24); - CHECK_ERROR (fimp, FALSE); - fimp->itdb->priv->lang = get16lint (cts, seek+0x46); - CHECK_ERROR (fimp, FALSE); - fimp->itdb->priv->pid = get64lint (cts, seek+0x48); - CHECK_ERROR (fimp, FALSE); - fimp->itdb->priv->unk_0x50 = get32lint (cts, seek+0x50); - CHECK_ERROR (fimp, FALSE); - fimp->itdb->priv->unk_0x54 = get32lint (cts, seek+0x54); - CHECK_ERROR (fimp, FALSE); - fimp->itdb->tzoffset = get32lint (cts, seek+0x6c); - CHECK_ERROR (fimp, FALSE); - fimp->itdb->priv->audio_language = get16lint (cts, seek+0xA0); - CHECK_ERROR (fimp, FALSE); - fimp->itdb->priv->subtitle_language = get16lint (cts, seek+0xA2); - CHECK_ERROR (fimp, FALSE); - fimp->itdb->priv->unk_0xa4 = get16lint (cts, seek+0xA4); - CHECK_ERROR (fimp, FALSE); - fimp->itdb->priv->unk_0xa6 = get16lint (cts, seek+0xA6); - CHECK_ERROR (fimp, FALSE); - fimp->itdb->priv->unk_0xa8 = get16lint (cts, seek+0xA8); - CHECK_ERROR (fimp, FALSE); - if(fimp->itdb->priv->unk_0xa8 != 0) { - g_warning ("Unknown value for 0xa8 in header: should be 0 for uncompressed, is %d.\n", fimp->itdb->priv->unk_0xa8); - } - - if (mhsd_1 == -1) - { /* Very bad: no type 1 mhsd which should hold the tracklist */ - g_set_error (&fimp->error, - ITDB_FILE_ERROR, - ITDB_FILE_ERROR_CORRUPT, - _("iTunesDB '%s' corrupt: Could not find tracklist (no mhsd type 1 section found)"), - cts->filename); - return FALSE; - } - - /* copy the 'reversed endian flag' */ - if (cts->reversed) { - fimp->itdb->device->byte_order = G_BIG_ENDIAN; - } else { - fimp->itdb->device->byte_order = G_LITTLE_ENDIAN; - } -#if 0 - fimp->itdb->device->endianess_set = TRUE; - fimp->itdb->device->endianess_reversed = cts->reversed; -#endif - - parse_tracks (fimp, mhsd_1); - if (fimp->error) return FALSE; - - if (mhsd_3 != -1) - parse_playlists (fimp, mhsd_3); - else if (mhsd_2 != -1) - parse_playlists (fimp, mhsd_2); - else - { /* Very bad: no type 2 or type 3 mhsd which should hold the - playlists */ - g_set_error (&fimp->error, - ITDB_FILE_ERROR, - ITDB_FILE_ERROR_CORRUPT, - _("iTunesDB '%s' corrupt: Could not find playlists (no mhsd type 2 or type 3 sections found)"), - cts->filename); - return FALSE; - } - - if (mhsd_5 != -1) { - parse_playlists (fimp, mhsd_5); - } - - if (mhsd_9 != -1) { - parse_genius_mhsd (fimp, mhsd_9); - } - - return TRUE; -} - - - -/* Set @error with standard error message */ -static void error_no_itunes_dir (const gchar *mp, GError **error) -{ - gchar *str; - - g_return_if_fail (mp); - g_return_if_fail (error); - - str = g_build_filename (mp, "iPod_Control", "iTunes", NULL); - g_set_error (error, - ITDB_FILE_ERROR, - ITDB_FILE_ERROR_NOTFOUND, - _("iTunes directory not found: '%s' (or similar)."), - str); - g_free (str); -} - -/* Set @error with standard error message */ -static void error_no_music_dir (const gchar *mp, GError **error) -{ - gchar *str; - - g_return_if_fail (mp); - g_return_if_fail (error); - - str = g_build_filename (mp, "iPod_Control", "Music", NULL); - g_set_error (error, - ITDB_FILE_ERROR, - ITDB_FILE_ERROR_NOTFOUND, - _("Music directory not found: '%s' (or similar)."), - str); - g_free (str); -} - -#if 0 -/* Set @error with standard error message */ -static void error_no_control_dir (const gchar *mp, GError **error) -{ - gchar *str; - - g_return_if_fail (mp); - g_return_if_fail (error); - - str = g_build_filename (mp, "iPod_Control", NULL); - g_set_error (error, - ITDB_FILE_ERROR, - ITDB_FILE_ERROR_NOTFOUND, - _("Control directory not found: '%s' (or similar)."), - str); - g_free (str); -} -#endif - - -static gboolean -itdb_parse_internal (Itdb_iTunesDB *itdb, gboolean compressed, GError **error) -{ - FImport *fimp; - gboolean success = FALSE; - - g_return_val_if_fail (itdb->filename != NULL, FALSE); - - fimp = g_new0 (FImport, 1); - fimp->itdb = itdb; - - fimp->fcontents = fcontents_read (itdb->filename, error); - - if (fimp->fcontents) - { - itdb_hash72_extract_hash_info (fimp->itdb->device, - (guchar *)fimp->fcontents->contents, - fimp->fcontents->length); - if (!playcounts_init (fimp)) - { - g_warning ("Error parsing recent playcounts"); - } - if (parse_fimp (fimp, compressed)) - { - if (read_OTG_playlists (fimp)) - { - success = TRUE; - } - } - } - - if (fimp->error) - g_propagate_error (error, fimp->error); - - itdb_free_fimp (fimp); - - return success; -} - -/** - * itdb_parse: - * @mp: mount point of the iPod (eg "/mnt/ipod") in local encoding - * @error: return location for a #GError or NULL - * - * Parse the Itdb_iTunesDB of the iPod located at @mp - * - * Returns: a newly allocated #Itdb_iTunesDB struct holding the tracks and - * the playlists present on the iPod at @mp, NULL if @mp isn't an iPod mount - * point. If non-NULL, the #Itdb_iTunesDB is to be freed with itdb_free() when - * it's no longer needed - */ -Itdb_iTunesDB *itdb_parse (const gchar *mp, GError **error) -{ - gchar *filename; - Itdb_iTunesDB *itdb = NULL; - gboolean compressed = FALSE; - - filename = itdb_get_itunescdb_path (mp); - if (!filename) { - filename = itdb_get_itunesdb_path (mp); - } else { - compressed = TRUE; - } - if (filename) - { - itdb = itdb_new (); - - if (itdb) - { - gboolean success; - - itdb_set_mountpoint (itdb, mp); - itdb->filename = g_strdup (filename); - success = itdb_parse_internal (itdb, compressed, error); - if (success) - { - /* We don't test the return value of ipod_parse_artwork_db - * since the database content will be consistent even if - * we fail to get the various thumbnails, we ignore the - * error since older ipods don't have thumbnails. - - * FIXME: this probably should go into itdb_parse_file, - * but I don't understand its purpose, and - * ipod_parse_artwork_db needs the mountpoint field from - * the itdb, which may not be available in the other - * function - - * JCS: itdb_parse_file is used to read local repositories - * (usually repositories stored in - * ~/.gtkpod). ipod_parse_artwork_db (and the - * corresponding artbook write function) should probably - * be expanded to look for (write) the required files into - * the same directory as itdb->filename in case - * itdb->mountpoint does not exist. Because several local - * repositories may exist in the same directory, the names - * should be modified by the repository name. - */ - ipod_parse_artwork_db (itdb); - } - else - { - itdb_free (itdb); - itdb = NULL; - } - } - } - else - { - g_set_error (error, - ITDB_FILE_ERROR, - ITDB_FILE_ERROR_NOTFOUND, - _("Couldn't find an iPod database on %s."), - mp); - } - g_free (filename); - return itdb; -} - -/** - * itdb_parse_file: - * @filename: path to a file in iTunesDB format - * @error: return location for a #GError or NULL - * - * Same as itunesdb_parse(), but filename is specified directly. - * - * Returns: a newly allocated #Itdb_iTunesDB struct holding the tracks and - * the playlists present in @filename, NULL if @filename isn't a parsable - * iTunesDB file. If non-NULL, the #Itdb_iTunesDB is to be freed with - * itdb_free() when it's no longer needed - */ -Itdb_iTunesDB *itdb_parse_file (const gchar *filename, GError **error) -{ - Itdb_iTunesDB *itdb; - gboolean success; - - g_return_val_if_fail (filename, NULL); - - itdb = itdb_new (); - itdb->filename = g_strdup (filename); - - success = itdb_parse_internal (itdb, FALSE, error); - if (!success) - { - itdb_free (itdb); - itdb = NULL; - } - - return itdb; -} - - -/* up to here we had the functions for reading the iTunesDB */ -/* ---------------------------------------------------------------------- */ -/* from here on we have the functions for writing the iTunesDB */ - -/* will expand @cts when necessary in order to accomodate @len bytes - starting at @seek */ -static void wcontents_maybe_expand (WContents *cts, gulong len, - gulong seek) -{ - g_return_if_fail (cts); - - while (cts->pos+len > cts->total) - { - cts->total += WCONTENTS_STEPSIZE; - cts->contents = g_realloc (cts->contents, cts->total); - } -} - - -/* Write @data, @n bytes long to position @seek. Will always be - * successful because glib terminates when out of memory */ -static void put_data_seek (WContents *cts, gchar *data, - gulong len, gulong seek) -{ - g_return_if_fail (cts); - - if (len != 0) - { - g_return_if_fail (data); - wcontents_maybe_expand (cts, len, seek); - - memcpy (&cts->contents[seek], data, len); - /* adjust end position if necessary */ - if (seek+len > cts->pos) - cts->pos = seek+len; - } -} - - - -/* Write @data, @n bytes long to end of @cts. Will always be - * successful because glib terminates when out of memory */ -static void put_data (WContents *cts, gchar *data, gulong len) -{ - g_return_if_fail (cts); - - put_data_seek (cts, data, len, cts->pos); -} - - -/* Write @string without trailing Null to end of @cts. Will always be - * successful because glib terminates when out of memory */ -static void put_string (WContents *cts, gchar *string) -{ - g_return_if_fail (cts); - g_return_if_fail (string); - - put_data (cts, string, strlen(string)); -} - -/* Write 4-byte long @header identifcation taking into account - * possible reversed endianess */ -static void put_header (WContents *cts, gchar *header) -{ - gchar rdata[4]; - gint i, offset, sign; - - - g_return_if_fail (cts); - g_return_if_fail (header); - g_return_if_fail (strlen (header) == 4); - - /* reverse data for write if necessary */ - if (cts->reversed) - { - offset = 3; - sign = -1; - } - else - { - offset = 0; - sign = 1; - } - for (i=0; i<4; ++i) - { - rdata[i] = header[offset + sign*i]; - } - - put_data (cts, rdata, 4); -} - - - -/* ------------------------------------------------------------ - Little Endian - ------------------------------------------------------------ */ - -/* Write 2-byte integer @n to @cts in little endian order. */ -static void raw_put16lint (WContents *cts, guint16 n) -{ - n = GUINT16_TO_LE (n); - put_data (cts, (gchar *)&n, 2); -} - -/* Write 3-byte integer @n to @cts in big endian order. */ -static void raw_put24lint (WContents *cts, guint32 n) -{ - gchar buf[3] ; - buf[2] = (n >> 16) & 0xff ; - buf[1] = (n >> 8) & 0xff ; - buf[0] = (n >> 0) & 0xff ; - put_data (cts, buf, 3); -} - - -/* Write 4-byte integer @n to @cts in little endian order. */ -static void raw_put32lint (WContents *cts, guint32 n) -{ - n = GUINT32_TO_LE (n); - put_data (cts, (gchar *)&n, 4); -} - -/* Write 4 byte floating number */ -static void raw_put32lfloat (WContents *cts, float f) -{ - union - { - guint32 i; - float f; - } flt; - - if (sizeof (float) != 4) - { - raw_put32lint (cts, 0); - g_return_if_reached (); - } - - flt.f = f; - raw_put32lint (cts, flt.i); -} - - -/* Write 4-byte integer @n to @cts at position @seek in little - endian order. */ -static void raw_put32lint_seek (WContents *cts, guint32 n, gulong seek) -{ - n = GUINT32_TO_LE (n); - put_data_seek (cts, (gchar *)&n, 4, seek); -} - - -/* Write 8-byte integer @n to @cts in big little order. */ -static void raw_put64lint (WContents *cts, guint64 n) -{ - n = GUINT64_TO_LE (n); - put_data (cts, (gchar *)&n, 8); -} - - -/* ------------------------------------------------------------ - Big Endian - ------------------------------------------------------------ */ - -/* Write 2-byte integer @n to @cts in big endian order. */ -static void raw_put16bint (WContents *cts, guint16 n) -{ - n = GUINT16_TO_BE (n); - put_data (cts, (gchar *)&n, 2); -} - -/* Write 3-byte integer @n to @cts in big endian order. */ -static void raw_put24bint (WContents *cts, guint32 n) -{ - gchar buf[3] ; - buf[0] = (n >> 16) & 0xff ; - buf[1] = (n >> 8) & 0xff ; - buf[2] = (n >> 0) & 0xff ; - put_data (cts, buf, 3); -} - -/* Write 4-byte integer @n to @cts in big endian order. */ -static void raw_put32bint (WContents *cts, guint32 n) -{ - n = GUINT32_TO_BE (n); - put_data (cts, (gchar *)&n, 4); -} - - -/* Write 4-byte integer @n to @cts at position @seek in big - endian order. */ -static void raw_put32bint_seek (WContents *cts, guint32 n, gulong seek) -{ - n = GUINT32_TO_BE (n); - put_data_seek (cts, (gchar *)&n, 4, seek); -} - - -/* Write 4 byte floating number */ -static void raw_put32bfloat (WContents *cts, float f) -{ - union - { - guint32 i; - float f; - } flt; - - if (sizeof (float) != 4) - { - raw_put32bint (cts, 0); - g_return_if_reached (); - } - - flt.f = f; - raw_put32bint (cts, flt.i); -} - - -/* Write 8-byte integer @n to cts in big endian order. */ -static void raw_put64bint (WContents *cts, guint64 n) -{ - n = GUINT64_TO_BE (n); - put_data (cts, (gchar *)&n, 8); -} - - -/* ------------------------------------------------------------ - Not Endian Dependent - ------------------------------------------------------------ */ - -/* Write 1-byte integer @n to @cts */ -static void put8int (WContents *cts, guint8 n) -{ - put_data (cts, (gchar *)&n, 1); -} - - -/* Append @n times 2-byte-long zeros */ -static void put16_n0 (WContents *cts, gulong n) -{ - g_return_if_fail (cts); - - if (n>0) - { - wcontents_maybe_expand (cts, 2*n, cts->pos); - memset (&cts->contents[cts->pos], 0, 2*n); - cts->pos += 2*n; - } -} - -/* Append @n times 4-byte-long zeros */ -static void put32_n0 (WContents *cts, gulong n) -{ - g_return_if_fail (cts); - - if (n>0) - { - wcontents_maybe_expand (cts, 4*n, cts->pos); - memset (&cts->contents[cts->pos], 0, 4*n); - cts->pos += 4*n; - } -} - - -/* ------------------------------------------------------------ - Reversed Endian Sensitive (little endian) - ------------------------------------------------------------ */ - -static void put16lint (WContents *cts, guint16 n) -{ - if (!cts->reversed) - raw_put16lint (cts, n); - else - raw_put16bint (cts, n); -} - -#if 0 -static void put24lint (WContents *cts, guint32 n) -{ - if (!cts->reversed) - raw_put24lint (cts, n); - else - raw_put24bint (cts, n); -} -#endif -static void put32lint (WContents *cts, guint32 n) -{ - if (!cts->reversed) - raw_put32lint (cts, n); - else - raw_put32bint (cts, n); -} - -static void put32lfloat (WContents *cts, float f) -{ - if (!cts->reversed) - raw_put32lfloat (cts, f); - else - raw_put32bfloat (cts, f); -} - -static void put32lint_seek (WContents *cts, guint32 n, gulong seek) -{ - if (!cts->reversed) - raw_put32lint_seek (cts, n, seek); - else - raw_put32bint_seek (cts, n, seek); -} - - -static void put64lint (WContents *cts, guint64 n) -{ - if (!cts->reversed) - raw_put64lint (cts, n); - else - raw_put64bint (cts, n); -} - -/* ------------------------------------------------------------ - Reversed Endian Sensitive (big endian) - ------------------------------------------------------------ */ -static void put16bint (WContents *cts, guint16 n) -{ - if (cts->reversed) - raw_put16lint (cts, n); - else - raw_put16bint (cts, n); -} - -#if 0 -static void put16bint_seek (WContents *cts, guint16 n, gulong seek) -{ - if (cts->reversed) - raw_put16lint_seek (cts, n, seek); - else - raw_put16bint_seek (cts, n, seek); -} -#endif -static void put24bint (WContents *cts, guint32 n) -{ - if (cts->reversed) - raw_put24lint (cts, n); - else - raw_put24bint (cts, n); -} - -static void put32bint (WContents *cts, guint32 n) -{ - if (cts->reversed) - raw_put32lint (cts, n); - else - raw_put32bint (cts, n); -} -#if 0 -static void put32bfloat (WContents *cts, float f) -{ - if (cts->reversed) - raw_put32lfloat (cts, f); - else - raw_put32bfloat (cts, f); -} -#endif - -static void put32bint_seek (WContents *cts, guint32 n, gulong seek) -{ - if (cts->reversed) - raw_put32lint_seek (cts, n, seek); - else - raw_put32bint_seek (cts, n, seek); -} - -static void put64bint (WContents *cts, guint64 n) -{ - if (cts->reversed) - raw_put64lint (cts, n); - else - raw_put64bint (cts, n); -} - - - -/* Write out the mhbd header. Size will be written later */ -static void mk_mhbd (FExport *fexp, guint32 children) -{ - WContents *cts; - - g_return_if_fail (fexp); - g_return_if_fail (fexp->itdb); - g_return_if_fail (fexp->wcontents); - - cts = fexp->wcontents; - - put_header (cts, "mhbd"); - put32lint (cts, 244); /* header size */ - put32lint (cts, -1); /* size of whole mhdb -- fill in later */ - if (itdb_device_supports_compressed_itunesdb (fexp->itdb->device)) { - /* 2 on iPhone 3.0 and Nano 5G, 1 on iPod Color and iPod Classic - the iPod Classic 3G won't work if set to 2. Tying it to compressed - database support but that's a long shot. */ - put32lint (cts, 2); - } else { - put32lint (cts, 1); - } - - /* Version number: 0x01: iTunes 2 - 0x02: iTunes 3 - 0x09: iTunes 4.2 - 0x0a: iTunes 4.5 - 0x0b: iTunes 4.7 - 0x0c: iTunes 4.71/4.8 (required for shuffle) - 0x0d: iTunes 4.9 - 0x0e: iTunes 5 - 0x0f: iTunes 6 - 0x10: iTunes 6.0.1(?) - 0x11: iTunes 6.0.2 - 0x12 = iTunes 6.0.5. - 0x13 = iTunes 7 - 0x14 = iTunes 7.1 - 0x15 = iTunes 7.2 - 0x19 = iTunes 7.4 - 0x28 = iTunes 8.2.1 - 0x2a = iTunes 9.0.1 - 0x2e = iTunes 9.1 - 0x30 = iTunes 9.2 - Be aware that newer ipods won't work if the library version number is too - old - */ - fexp->itdb->version = 0x30; - put32lint (cts, fexp->itdb->version); - put32lint (cts, children); - put64lint (cts, fexp->itdb->id); - /* 0x20 */ - put16lint (cts, fexp->itdb->priv->platform); /* OS type, 1 = MacOS X, 2 = Windows */ - - /* 0x22 */ - put16lint (cts, fexp->itdb->priv->unk_0x22); /* unknown */ - put64lint (cts, fexp->itdb->priv->id_0x24); /* unkown id */ - put32lint (cts, 0); /* unknown */ - /* 0x30 */ - put16lint (cts, 0); /* set hashing scheme to 0 for now, will be set - * to the appropriate value in - * itdb_device_write_checksum */ - put16_n0 (cts, 10); /* unknown */ - /* 0x46 */ - put16lint (cts, fexp->itdb->priv->lang); /* language (e.g. 'de' for German) */ - put64lint (cts, fexp->itdb->priv->pid); /* library persistent ID */ - /* 0x50 */ - put32lint (cts, fexp->itdb->priv->unk_0x50); /* unknown: seen: 0x05 for nano 3G */ - /* seen: 0x01 for iPod Color */ - put32lint (cts, fexp->itdb->priv->unk_0x54); /* unknown: seen: 0x4d for nano 3G */ - /* seen: 0x0f for iPod Color */ - put32_n0 (cts, 5); /* 20 bytes hash58 */ - put32lint (cts, fexp->itdb->tzoffset); /* timezone offset in seconds */ - /* 0x70 */ - switch (itdb_device_get_checksum_type(fexp->itdb->device)) { - case ITDB_CHECKSUM_HASHAB: - put16lint (cts, 4); /* new on 4th gen iOS devices */ - break; - case ITDB_CHECKSUM_HASH72: - put16lint (cts, 2); - break; - default: - put16lint (cts, 0); - break; - } - put16lint (cts, 0); /* hash72 */ - put32_n0 (cts, 11); /* hash72 */ - /* 0xa0 */ - put16lint (cts, fexp->itdb->priv->audio_language); /* audio_language */ - put16lint (cts, fexp->itdb->priv->subtitle_language); /* subtitle_language */ - put16lint (cts, fexp->itdb->priv->unk_0xa4); /* unknown */ - put16lint (cts, fexp->itdb->priv->unk_0xa6); /* unknown */ - put16lint (cts, fexp->itdb->priv->unk_0xa8); /* unknown */ - put8int (cts, 0); - /* 0xab */ - put8int (cts, 0); /* hashAB */ - put32_n0 (cts, 14); /* hashAB */ - put32_n0 (cts, 4); /* dummy space */ -} - -/* Fill in the length of a standard header */ -static void fix_header (WContents *cts, gulong header_seek) -{ - put32lint_seek (cts, cts->pos-header_seek, header_seek+8); -} - -/* Fill in the length of a short header which are common in the itunesSD */ -static void fix_short_header (WContents *cts, gulong header_seek) -{ - put32lint_seek (cts, cts->pos-header_seek, header_seek+4); -} - -/* Write out the mhsd header. Size will be written later */ -static void mk_mhsd (FExport *fexp, guint32 type) -{ - WContents *cts; - - g_return_if_fail (fexp); - g_return_if_fail (fexp->itdb); - g_return_if_fail (fexp->wcontents); - - cts = fexp->wcontents; - - put_header (cts, "mhsd"); - put32lint (cts, 96); /* Headersize */ - put32lint (cts, -1); /* size of whole mhsd -- fill in later */ - put32lint (cts, type); /* type: 1 = track, 2 = playlist */ - put32_n0 (cts, 20); /* dummy space */ -} - - -/* Write out the mhlt header. */ -static void mk_mhlt (FExport *fexp, guint32 num) -{ - WContents *cts; - - g_return_if_fail (fexp); - g_return_if_fail (fexp->itdb); - g_return_if_fail (fexp->wcontents); - - cts = fexp->wcontents; - - put_header (cts, "mhlt"); - put32lint (cts, 92); /* Headersize */ - put32lint (cts, num); /* tracks in this itunesdb */ - put32_n0 (cts, 20); /* dummy space */ -} - - -/* Write out the mhit header. Size will be written later */ -static void mk_mhit (WContents *cts, Itdb_Track *track) -{ - guint32 mac_time; - g_return_if_fail (cts); - g_return_if_fail (track); - - put_header (cts, "mhit"); - put32lint (cts, 0x248);/* header size */ - put32lint (cts, -1); /* size of whole mhit -- fill in later */ - put32lint (cts, -1); /* nr of mhods in this mhit -- later */ - /* +0x10 */ - put32lint (cts, track->id); /* track index number */ - - put32lint (cts, track->visible); - put32lint (cts, track->filetype_marker); - put8int (cts, track->type1); - put8int (cts, track->type2); - put8int (cts, track->compilation); - put8int (cts, track->rating); - /* +0x20 */ - mac_time = device_time_time_t_to_mac (track->itdb->device, track->time_modified); - put32lint (cts, mac_time); /* timestamp */ - put32lint (cts, track->size); /* filesize */ - put32lint (cts, track->tracklen);/* length of track in ms */ - put32lint (cts, track->track_nr);/* track number */ - /* +0x30 */ - put32lint (cts, track->tracks); /* number of tracks */ - put32lint (cts, track->year); /* the year */ - put32lint (cts, track->bitrate); /* bitrate */ - put32lint (cts, (((guint32)track->samplerate)<<16) | - ((guint32)track->samplerate_low)); - /* +0x40 */ - put32lint (cts, track->volume); /* volume adjust */ - put32lint (cts, track->starttime); - put32lint (cts, track->stoptime); - put32lint (cts, track->soundcheck); - /* +0x50 */ - put32lint (cts, track->playcount);/* playcount */ - /* track->playcount2 = track->playcount; */ - put32lint (cts, track->playcount2); /* playcount2, keep original value */ - mac_time = device_time_time_t_to_mac (track->itdb->device, track->time_played); - put32lint (cts, mac_time); /* last time played */ - put32lint (cts, track->cd_nr); /* CD number */ - /* +0x60 */ - put32lint (cts, track->cds); /* number of CDs */ - put32lint (cts, track->drm_userid); - mac_time = device_time_time_t_to_mac (track->itdb->device, track->time_added); - put32lint (cts, mac_time); /* timestamp */ - put32lint (cts, track->bookmark_time); - /* +0x70 */ - put64lint (cts, track->dbid); - if (track->checked) put8int (cts, 1); - else put8int (cts, 0); - put8int (cts, track->app_rating); - put16lint (cts, track->BPM); - put16lint (cts, track->artwork_count); - put16lint (cts, track->unk126); - /* +0x80 */ - put32lint (cts, track->artwork_size); - put32lint (cts, track->unk132); - put32lfloat (cts, track->samplerate2); - mac_time = device_time_time_t_to_mac (track->itdb->device, track->time_released); - put32lint (cts, mac_time); - /* +0x90 */ - put16lint (cts, track->unk144); - put16lint (cts, track->explicit_flag); - put32lint (cts, track->unk148); - put32lint (cts, track->unk152); - /* since iTunesDB version 0x0c */ - put32lint (cts, track->skipcount); - /* +0xA0 */ - mac_time = device_time_time_t_to_mac (track->itdb->device, track->last_skipped); - put32lint (cts, mac_time); - put8int (cts, track->has_artwork); - put8int (cts, track->skip_when_shuffling); - put8int (cts, track->remember_playback_position); - put8int (cts, track->flag4); - put64lint (cts, track->dbid2); - /* +0xB0 */ - put8int (cts, track->lyrics_flag); - put8int (cts, track->movie_flag); - put8int (cts, track->mark_unplayed); - put8int (cts, track->unk179); - put32lint (cts, track->unk180); - put32lint (cts, track->pregap); - put64lint (cts, track->samplecount); - put32lint (cts, track->unk196); - put32lint (cts, track->postgap); - put32lint (cts, track->unk204); - /* +0xD0 */ - put32lint (cts, track->mediatype); - put32lint (cts, track->season_nr); - put32lint (cts, track->episode_nr); - put32lint (cts, track->unk220); - /* +0xE0 */ - put32lint (cts, track->unk224); - put32lint (cts, track->unk228); - put32lint (cts, track->unk232); - put32lint (cts, track->unk236); - /* +0xF0 */ - put32lint (cts, track->unk240); - put32lint (cts, track->unk244); - put32lint (cts, track->gapless_data); - put32lint (cts, track->unk252); - /* +0x100 */ - put16lint (cts, track->gapless_track_flag); - put16lint (cts, track->gapless_album_flag); - put32_n0 (cts, 7); - /* +0x120 */ - put32lint (cts, track->priv->album_id); - put64lint (cts, track->itdb->priv->id_0x24); /* same as mhbd+0x24, purpose unknown */ - put32lint (cts, track->size); /* seems to be filesize again */ - /* +0x130 */ - put32lint (cts, 0); - put64lint (cts, 0x808080808080LL); /* what the heck is this?! */ - put32lint (cts, 0); - /* +0x140 */ - put32_n0 (cts, 2); - if ((track->mediatype & ITDB_MEDIATYPE_EPUB_BOOK) - || (track->mediatype & ITDB_MEDIATYPE_PDF_BOOK)) { - put16lint (cts, 1); - put16lint (cts, 1); - } else { - put32lint (cts, 0); - } - put32_n0 (cts, 5); - /* +0x160 */ - /* mhii_link is needed on fat nanos/ipod classic to get artwork - * in the right sidepane. This matches mhii::song_id in the ArtworkDB */ - put32lint (cts, track->mhii_link); - put32lint (cts, 0); - put32lint (cts, 1); /* unknown */ - put32lint (cts, 0); - /* +0x170 */ - put32_n0 (cts, 28); - /* +0x1E0 */ - put32lint (cts, track->priv->artist_id); - put32_n0 (cts, 4); - /* +0x1F4 */ - put32lint (cts, track->priv->composer_id); /* FIXME: this is another ID for something, composer?! */ - put32_n0 (cts, 20); /* padding */ -} - - -/* Fill in the missing items of the mhit header: - total size and number of mhods */ -static void fix_mhit (WContents *cts, gulong mhit_seek, guint32 mhod_num) -{ - g_return_if_fail (cts); - - /* size of whole mhit */ - put32lint_seek (cts, cts->pos-mhit_seek, mhit_seek+8); - /* nr of mhods */ - put32lint_seek (cts, mhod_num, mhit_seek+12); -} - -/* Returns: - - track->sort_artist if track->sort_artist is not NULL - - - 'Artist, The' if track->sort_artist is NULL and track->artist of - of type 'The Artist'. - - - NULL if track->sort_artist is NULL and track->artist is not of - type 'The Artist'. - - You must g_free() the returned string */ -static gchar *get_sort_artist (Itdb_Track *track) -{ - g_return_val_if_fail (track, NULL); - - if (track->sort_artist && *track->sort_artist) - { - return g_strdup (track->sort_artist); - } - - if (!(track->artist && *track->artist)) - { - return NULL; - } - - /* check if artist is of type 'The Artist' */ - if (g_ascii_strncasecmp ("The ", track->artist, 4) == 0) - { /* return 'Artist, The', followed by five 0x01 chars - (analogous to iTunes) */ - return g_strdup_printf ("%s, The%c%c%c%c%c", - track->artist+4, - 0x01, 0x01, 0x01, 0x01, 0x01); - } - - return NULL; -} - - - - -static gint mhod52_sort_title (const struct mhod52track *a, const struct mhod52track *b) -{ - return strcmp (a->title, b->title); -} - - -static gint mhod52_sort_album (const struct mhod52track *a, const struct mhod52track *b) -{ - gint result; - - result = strcmp (a->album, b->album); - if (result == 0) - result = a->cd_nr - b->cd_nr; - if (result == 0) - result = a->track_nr - b->track_nr; - if (result == 0) - result = strcmp (a->title, b->title); - return result; -} - -static gint mhod52_sort_artist (struct mhod52track *a, struct mhod52track *b) -{ - gint result; - - result = strcmp (a->artist, b->artist); - if (result == 0) - result = strcmp (a->album, b->album); - if (result == 0) - result = a->cd_nr - b->cd_nr; - if (result == 0) - result = a->track_nr - b->track_nr; - if (result == 0) - result = strcmp (a->title, b->title); - return result; -} - -static gint mhod52_sort_genre (struct mhod52track *a, struct mhod52track *b) -{ - gint result; - - result = strcmp (a->genre, b->genre); - if (result == 0) - result = strcmp (a->artist, b->artist); - if (result == 0) - result = strcmp (a->album, b->album); - if (result == 0) - result = a->cd_nr - b->cd_nr; - if (result == 0) - result = a->track_nr - b->track_nr; - if (result == 0) - result = strcmp (a->title, b->title); - return result; -} - -static gint mhod52_sort_composer (struct mhod52track *a, struct mhod52track *b) -{ - gint result; - - result = strcmp (a->composer, b->composer); - if (result == 0) - result = strcmp (a->album, b->album); - if (result == 0) - result = a->cd_nr - b->cd_nr; - if (result == 0) - result = a->track_nr - b->track_nr; - if (result == 0) - result = strcmp (a->title, b->title); - return result; -} - - -/* Return the first alpha-numeric character in the string - Return upper-case for alpha, return 0 for all numbers */ - -static gunichar2 jump_table_letter (gchar *p) -{ - gunichar chr = 0; - gboolean found_alnum_chars = FALSE; - - g_return_val_if_fail (p != NULL, '0'); - g_return_val_if_fail (g_utf8_validate (p, -1, NULL), '0'); - - while (*p != '\0') { - chr = g_utf8_get_char (p); - if (g_unichar_isalnum (chr)) - { - found_alnum_chars = TRUE; - break; - } - p = g_utf8_find_next_char (p, NULL); - } - - if (!found_alnum_chars) { - /* Return number 0 if no alphanumerics in string */ - return '0'; - } - - if (g_unichar_isalpha(chr)) - { - gunichar2 utf16chr; - gunichar upperchr; - gunichar2 *str; - GError *err = NULL; - - upperchr = g_unichar_toupper (chr); - str = g_ucs4_to_utf16 (&upperchr, 1, NULL, NULL, &err); - if (err != NULL) { - fprintf (stderr, "Error in UCS4 to UTF16 conversion: %s, original unichar: %x, toupper unichar: %x\n", err->message, chr, upperchr); - g_error_free (err); - return '0'; - } - else - { - utf16chr = str[0]; - g_free (str); - return utf16chr; - } - } - - return '0'; /* Return number 0 for any digit */ -} - - -/* Create a new list containing all tracks but with collate keys - instead of the actual strings (title, artist, album, genre, - composer) */ -static GList *mhod52_make_collate_keys (GList *tracks) -{ - gint numtracks = g_list_length (tracks); - GList *gl, *coltracks = NULL; - gint index=0; - - for (gl=tracks; gl; gl=gl->next) - { - gchar *str; - struct mhod52track *ct; - Itdb_Track *tr = gl->data; - g_return_val_if_fail (tr, NULL); - - ct = g_new0 (struct mhod52track, 1); - coltracks = g_list_prepend (coltracks, ct); - - /* album */ - if (tr->sort_album && *tr->sort_album) - { - ct->album = g_utf8_collate_key (tr->sort_album, -1); - ct->letter_album = jump_table_letter (tr->sort_album); - } - else if (tr->album) - { - ct->album = g_utf8_collate_key (tr->album, -1); - ct->letter_album = jump_table_letter (tr->album); - } - else - { - ct->album = g_strdup (""); - ct->letter_album = '0'; - } - - /* title */ - if (tr->sort_title && *tr->sort_title) - { - ct->title = g_utf8_collate_key (tr->sort_title, -1); - ct->letter_title = jump_table_letter (tr->sort_title); - } - else if (tr->title) - { - ct->title = g_utf8_collate_key (tr->title, -1); - ct->letter_title = jump_table_letter (tr->title); - } - else - { - ct->title = g_strdup (""); - ct->letter_title = '0'; - } - - /* artist */ - str = get_sort_artist (tr); - if (str) - { - ct->artist = g_utf8_collate_key (str, -1); - ct->letter_artist = jump_table_letter (str); - g_free (str); - } - else if (tr->artist) - { - ct->artist = g_utf8_collate_key (tr->artist, -1); - ct->letter_artist = jump_table_letter (tr->artist); - } - else - { - ct->artist = g_strdup (""); - ct->letter_artist = '0'; - } - - /* genre */ - if (tr->genre) - { - ct->genre = g_utf8_collate_key (tr->genre, -1); - ct->letter_genre = jump_table_letter (tr->genre); - } - else - { - ct->genre = g_strdup (""); - ct->letter_genre = '0'; - } - - /* composer */ - if (tr->sort_composer && *tr->sort_composer) - { - ct->composer = g_utf8_collate_key (tr->sort_composer, -1); - ct->letter_composer = jump_table_letter (tr->sort_composer); - } - else if (tr->composer) - { - ct->composer = g_utf8_collate_key (tr->composer, -1); - ct->letter_composer = jump_table_letter (tr->composer); - } - else - { - ct->composer = g_strdup (""); - ct->letter_composer = '0'; - } - - ct->track_nr = tr->track_nr; - ct->cd_nr = tr->cd_nr; - ct->numtracks = numtracks; - ct->index = index++; - } - return coltracks; -} - - -/* Free all memory used up by the collate keys */ -static void mhod52_free_collate_keys (GList *coltracks) -{ - GList *gl; - - for (gl=coltracks; gl; gl=gl->next) - { - struct mhod52track *ct = gl->data; - g_return_if_fail (ct); - g_free (ct->album); - g_free (ct->title); - g_free (ct->artist); - g_free (ct->genre); - g_free (ct->composer); - g_free (ct); - } - g_list_free (coltracks); -} - -static void -itdb_chapterdata_build_chapter_blob_internal (WContents *cts, - Itdb_Chapterdata *chapterdata) -{ - gulong atom_len_seek; - gint numchapters; - GList *ch_gl = NULL; - /* printf("[%s] -- inserting into \"chapter\"\n", __func__); */ - - numchapters = g_list_length (chapterdata->chapters); - - put32lint (cts, chapterdata->unk024); /* unknown */ - put32lint (cts, chapterdata->unk028); /* unknown */ - put32lint (cts, chapterdata->unk032); /* unknown */ - atom_len_seek = cts->pos; /* needed to fix length */ - put32bint (cts, -1); /* total length of sean atom, fix later */ - put_header (cts, "sean"); - put32bint (cts, 1); /* unknown */ - put32bint (cts, numchapters+1); /* children */ - put32bint (cts, 0); /* unknown */ - for (ch_gl=chapterdata->chapters; ch_gl; ch_gl=ch_gl->next) - { - gunichar2 *title_utf16; - Itdb_Chapter *chapter = ch_gl->data; - glong len; - title_utf16 = g_utf8_to_utf16 (chapter->chaptertitle, -1, NULL, &len, NULL); - fixup_big_utf16 (title_utf16); - put32bint (cts, 42+2*len); /* total length */ - put_header (cts, "chap"); - put32bint (cts, chapter->startpos); /* should we check if startpos=0 here? */ - put32bint (cts, 1); /* children */ - put32bint (cts, 0); /* unknown */ - put32bint (cts, 22+2*len); /* length */ - put_header (cts, "name"); - put32bint (cts, 1); /* unknown */ - put32_n0 (cts, 2); /* unknown */ - put16bint (cts, len); - put_data (cts, (gchar *)title_utf16, 2*len); - g_free (title_utf16); - } - put32bint (cts, 28); /* size */ - put_header (cts, "hedr"); - put32bint (cts, 1); /* unknown */ - put32bint (cts, 0); /* children */ - put32_n0 (cts, 2); /* unknown */ - put32bint (cts, 1); /* unknown */ - - put32bint_seek (cts, cts->pos-atom_len_seek, atom_len_seek); /* size */ -} - -/* Write out one mhod header. - type: see enum of MHMOD_IDs; - data: utf8 string for text items - position indicator for MHOD_ID_PLAYLIST - Itdb_SPLPref for MHOD_ID_SPLPREF - Itdb_SPLRules for MHOD_ID_SPLRULES */ -static void mk_mhod (FExport *fexp, MHODData *mhod) -{ - WContents *cts = fexp->wcontents; - - g_return_if_fail (cts); - g_return_if_fail (mhod->valid); - - switch ((enum MHOD_ID)mhod->type) - { - case MHOD_ID_TITLE: - case MHOD_ID_PATH: - case MHOD_ID_ALBUM: - case MHOD_ID_ARTIST: - case MHOD_ID_GENRE: - case MHOD_ID_FILETYPE: - case MHOD_ID_COMMENT: - case MHOD_ID_CATEGORY: - case MHOD_ID_COMPOSER: - case MHOD_ID_GROUPING: - case MHOD_ID_DESCRIPTION: - case MHOD_ID_SUBTITLE: - case MHOD_ID_TVSHOW: - case MHOD_ID_TVEPISODE: - case MHOD_ID_TVNETWORK: - case MHOD_ID_ALBUMARTIST: - case MHOD_ID_KEYWORDS: - case MHOD_ID_SORT_ARTIST: - case MHOD_ID_SORT_TITLE: - case MHOD_ID_SORT_ALBUM: - case MHOD_ID_SORT_ALBUMARTIST: - case MHOD_ID_SORT_COMPOSER: - case MHOD_ID_SORT_TVSHOW: - case MHOD_ID_ALBUM_ALBUM: - case MHOD_ID_ALBUM_ARTIST: - case MHOD_ID_ALBUM_SORT_ARTIST: - case MHOD_ID_ALBUM_ARTIST_MHII: - g_return_if_fail (mhod->data.string); - /* normal iTunesDBs seem to take utf16 strings), endian-inversed - iTunesDBs seem to take utf8 strings */ - if (!cts->reversed) - { - /* convert to utf16 */ - glong len; - gunichar2 *entry_utf16 = g_utf8_to_utf16 (mhod->data.string, -1, - NULL, &len, NULL); - fixup_little_utf16 (entry_utf16); - put_header (cts, "mhod"); /* header */ - put32lint (cts, 24); /* size of header */ - put32lint (cts, sizeof (gunichar2)*len+40); /* size of header + body */ - put32lint (cts, mhod->type);/* type of the mhod */ - put32_n0 (cts, 2); /* unknown */ - /* end of header, start of data */ - put32lint (cts, 1); /* string type UTF16 */ - put32lint (cts, sizeof (gunichar2)*len); /* size of string */ - put32lint (cts, 1); /* unknown, but is set to 1 */ - put32lint (cts, 0); - put_data (cts, (gchar *)entry_utf16, sizeof (gunichar2)*len);/* the string */ - g_free (entry_utf16); - } - else - { - guint32 len = strlen (mhod->data.string); - put_header (cts, "mhod"); /* header */ - put32lint (cts, 24); /* size of header */ - put32lint (cts, len+40); /* size of header + body */ - put32lint (cts, mhod->type);/* type of the mhod */ - put32_n0 (cts, 2); /* unknown */ - /* end of header, start of data */ - put32lint (cts, 2); /* string type UTF 8 */ - put32lint (cts, len); /* size of string */ - put8int (cts, 1); /* unknown */ - put8int (cts, 0); /* unknown */ - put8int (cts, 0); /* unknown */ - put8int (cts, 0); /* unknown */ - put32lint (cts, 0); /* unknown */ - put_string (cts, mhod->data.string);/* the string */ - } - break; - case MHOD_ID_PODCASTURL: - case MHOD_ID_PODCASTRSS: - g_return_if_fail (mhod->data.string); - { - guint32 len = strlen (mhod->data.string); - put_header (cts, "mhod"); /* header */ - put32lint (cts, 24); /* size of header */ - put32lint (cts, 24+len); /* size of header + data */ - put32lint (cts, mhod->type);/* type of the mhod */ - put32_n0 (cts, 2); /* unknown */ - put_string (cts, mhod->data.string);/* the string */ - } - break; - case MHOD_ID_PLAYLIST: - put_header (cts, "mhod"); /* header */ - put32lint (cts, 24); /* size of header */ - put32lint (cts, 44); /* size of header + body */ - put32lint (cts, mhod->type); /* type of the entry */ - put32_n0 (cts, 2); /* unknown */ - /* end of header, start of data */ - put32lint (cts, mhod->data.track_pos);/* position of track in playlist */ - put32_n0 (cts, 4); /* unknown */ - break; - case MHOD_ID_CHAPTERDATA: - g_return_if_fail (mhod->data.chapterdata); - { - gulong header_seek = cts->pos; /* needed to fix length */ - put_header (cts, "mhod"); /* header */ - put32lint (cts, 24); /* header size */ - put32lint (cts, -1); /* total length, fix later */ - put32lint (cts, mhod->type); /* entry type */ - put32_n0 (cts, 2); /* unknown */ - itdb_chapterdata_build_chapter_blob_internal (cts, mhod->data.chapterdata); - fix_header (cts, header_seek); - } - break; - case MHOD_ID_SPLPREF: - g_return_if_fail (mhod->data.splpref); - put_header (cts, "mhod"); /* header */ - put32lint (cts, 24); /* size of header */ - put32lint (cts, 96); /* size of header + body */ - put32lint (cts, mhod->type);/* type of the entry */ - put32_n0 (cts, 2); /* unknown */ - /* end of header, start of data */ - put8int (cts, mhod->data.splpref->liveupdate); - put8int (cts, mhod->data.splpref->checkrules? 1:0); - put8int (cts, mhod->data.splpref->checklimits); - put8int (cts, mhod->data.splpref->limittype); - put8int (cts, mhod->data.splpref->limitsort & 0xff); - put8int (cts, 0); /* unknown */ - put8int (cts, 0); /* unknown */ - put8int (cts, 0); /* unknown */ - put32lint (cts, mhod->data.splpref->limitvalue); - put8int (cts, mhod->data.splpref->matchcheckedonly); - /* for the following see note at definitions of limitsort - types in itunesdb.h */ - put8int (cts, (mhod->data.splpref->limitsort & 0x80000000) ? 1:0); - put8int (cts, 0); /* unknown */ - put8int (cts, 0); /* unknown */ - put32_n0 (cts, 14); /* unknown */ - break; - case MHOD_ID_SPLRULES: - g_return_if_fail (mhod->data.splrules); - { - gulong header_seek = cts->pos; /* needed to fix length */ - GList *gl; - gint numrules = g_list_length (mhod->data.splrules->rules); - - put_header (cts, "mhod"); /* header */ - put32lint (cts, 24); /* size of header */ - put32lint (cts, -1); /* total length, fix later */ - put32lint (cts, mhod->type);/* type of the entry */ - put32_n0 (cts, 2); /* unknown */ - /* end of header, start of data */ - /* For some reason this is the only part of the iTunesDB - that uses big endian */ - put_header (cts, "SLst"); /* header */ - put32bint (cts, mhod->data.splrules->unk004); /* unknown*/ - put32bint (cts, numrules); - put32bint (cts, mhod->data.splrules->match_operator); - put32_n0 (cts, 30); /* unknown */ - /* end of header, now follow the rules */ - for (gl=mhod->data.splrules->rules; gl; gl=gl->next) - { - Itdb_SPLRule *splr = gl->data; - ItdbSPLFieldType ft; - glong len; - gunichar2 *entry_utf16; - g_return_if_fail (splr); - ft = itdb_splr_get_field_type (splr); -/* printf ("%p: field: %d ft: %d\n", splr, splr->field, ft);*/ - itdb_splr_validate (splr); - put32bint (cts, splr->field); - put32bint (cts, splr->action); - put32_n0 (cts, 11); /* unknown */ - switch (ft) - { - case ITDB_SPLFT_STRING: - /* write string-type rule */ - len = 0; - entry_utf16 = NULL; - /* splr->string may be NULL */ - if (splr->string) - entry_utf16 = g_utf8_to_utf16 (splr->string, - -1,NULL,&len,NULL); - fixup_big_utf16 (entry_utf16); - put32bint (cts, sizeof (gunichar2)*len); /* length of string */ - put_data (cts, (gchar *)entry_utf16, sizeof (gunichar2)*len); - g_free (entry_utf16); - break; - case ITDB_SPLFT_DATE: - case ITDB_SPLFT_INT: - case ITDB_SPLFT_BOOLEAN: - case ITDB_SPLFT_PLAYLIST: - case ITDB_SPLFT_UNKNOWN: - case ITDB_SPLFT_BINARY_AND: { - guint64 fromvalue; - guint64 tovalue; - - fromvalue = splr->fromvalue; - tovalue = splr->tovalue; - - if (ft == ITDB_SPLFT_DATE) { - ItdbSPLActionType at; - at = itdb_splr_get_action_type (splr); - if ((at == ITDB_SPLAT_RANGE_DATE) || - (at == ITDB_SPLAT_DATE)) - { - Itdb_iTunesDB *itdb = fexp->itdb; - fromvalue = device_time_time_t_to_mac (itdb->device, fromvalue); - tovalue = device_time_time_t_to_mac (itdb->device, tovalue); - } - } - - /* write non-string-type rule */ - put32bint (cts, 0x44); /* length of data */ - /* data */ - put64bint (cts, fromvalue); - put64bint (cts, splr->fromdate); - put64bint (cts, splr->fromunits); - put64bint (cts, tovalue); - put64bint (cts, splr->todate); - put64bint (cts, splr->tounits); - put32bint (cts, splr->unk052); - put32bint (cts, splr->unk056); - put32bint (cts, splr->unk060); - put32bint (cts, splr->unk064); - put32bint (cts, splr->unk068); - break; - } - } - } - /* insert length of mhod junk */ - fix_header (cts, header_seek); - } - break; - case MHOD_ID_LIBPLAYLISTINDEX: - g_return_if_fail (mhod->data.mhod52coltracks); - g_return_if_fail (mhod->data.mhod52coltracks->data); - { - struct mhod52track *ct = mhod->data.mhod52coltracks->data; - gint numtracks = ct->numtracks; - GList *gl; - gpointer compfunc = NULL; - gunichar2 sortkey = 0; - gunichar2 lastsortkey = 0; - guint32 mhod53index = 0; - struct mhod53_entry *m53 = NULL; - - /* sort list */ - switch (mhod->mhod52sorttype) - { - case MHOD52_SORTTYPE_TITLE: - compfunc = mhod52_sort_title; - break; - case MHOD52_SORTTYPE_ALBUM: - compfunc = mhod52_sort_album; - break; - case MHOD52_SORTTYPE_ARTIST: - compfunc = mhod52_sort_artist; - break; - case MHOD52_SORTTYPE_GENRE: - compfunc = mhod52_sort_genre; - break; - case MHOD52_SORTTYPE_COMPOSER: - compfunc = mhod52_sort_composer; - break; - } - g_return_if_fail (compfunc); - - /* sort the tracks */ - mhod->data.mhod52coltracks = g_list_sort (mhod->data.mhod52coltracks, - compfunc); - /* Write the MHOD */ - put_header (cts, "mhod"); /* header */ - put32lint (cts, 24); /* size of header */ - put32lint (cts, 4*numtracks+72); /* size of header + body */ - put32lint (cts, mhod->type); /* type of the mhod */ - put32_n0 (cts, 2); /* unknown */ - /* end of header, start of data */ - put32lint (cts, mhod->mhod52sorttype); /* sort type */ - put32lint (cts, numtracks); /* number of entries */ - put32_n0 (cts, 10); /* unknown */ - for (gl=mhod->data.mhod52coltracks; gl; gl=gl->next) - { - ct = gl->data; - g_return_if_fail (ct); - put32lint (cts, ct->index); - - /* This is for the type 53s */ - switch (mhod->mhod52sorttype) - { - case MHOD52_SORTTYPE_TITLE: - sortkey = ct->letter_title; - break; - case MHOD52_SORTTYPE_ALBUM: - sortkey = ct->letter_album; - break; - case MHOD52_SORTTYPE_ARTIST: - sortkey = ct->letter_artist; - break; - case MHOD52_SORTTYPE_GENRE: - sortkey = ct->letter_genre; - break; - case MHOD52_SORTTYPE_COMPOSER: - sortkey = ct->letter_composer; - break; - } - - if (sortkey != lastsortkey) { - m53 = g_new0 (struct mhod53_entry, 1); - m53->letter = sortkey; - m53->start = mhod53index; - m53->count = 0; - mhod->mhod53_list = g_list_append (mhod->mhod53_list, m53); - lastsortkey = sortkey; - } - mhod53index++; - g_assert (m53 != NULL); - m53->count++; - } - } - break; - case MHOD_ID_LIBPLAYLISTJUMPTABLE: - { - GList *gl; - guint32 numentries; - struct mhod53_entry *m53; - - numentries = g_list_length (mhod->mhod53_list); - put_header (cts, "mhod"); /* header */ - put32lint (cts, 24); /* size of header */ - put32lint (cts, 12*numentries+40); /* size of header + body */ - put32lint (cts, mhod->type); /* type of the mhod */ - put32_n0 (cts, 2); /* unknown */ - put32lint (cts, mhod->mhod52sorttype); /* sort type */ - put32lint (cts, numentries); /* number of entries */ - put32_n0 (cts, 2); /* unknown */ - - for (gl=mhod->mhod53_list; gl; gl=gl->next) - { - m53 = gl->data; - put16lint (cts, m53->letter); - put16lint (cts, 0); - put32lint (cts, m53->start); - put32lint (cts, m53->count); - } - } - break; - } -} - -static void mk_mhia (gpointer key, gpointer value, gpointer user_data) -{ - FExport *fexp; - WContents *cts; - Itdb_Track *track; - Itdb_Item_Id *id; - MHODData mhod; - guint mhod_num; - gulong mhia_seek; - - track = (Itdb_Track *)key; - g_return_if_fail (track != NULL); - - id = (Itdb_Item_Id *)value; - g_return_if_fail (id != NULL); - - fexp = (FExport *)user_data; - g_return_if_fail (fexp); - g_return_if_fail (fexp->wcontents); - cts = fexp->wcontents; - mhia_seek = cts->pos; - - put_header (cts, "mhia"); /* header */ - put32lint (cts, 88); /* size of header */ - put32lint (cts, -1); /* total size -> later */ - put32lint (cts, 2); /* number of children mhods */ - put32lint (cts, id->id); /* album id */ - put64lint (cts, id->sql_id); /* id used in the sqlite DB */ - put32lint (cts, 2); /* unknown */ - put32_n0 (cts, 14); /* padding */ - - mhod.valid = TRUE; - mhod_num = 0; - if (track->album && *track->album) { - mhod.type = MHOD_ID_ALBUM_ALBUM; - mhod.data.string = track->album; - mk_mhod (fexp, &mhod); - ++mhod_num; - } - - if (track->albumartist && *track->albumartist) { - mhod.type = MHOD_ID_ALBUM_ARTIST; - mhod.data.string = track->albumartist; - mk_mhod (fexp, &mhod); - ++mhod_num; - } else if (track->artist && *track->artist) { - mhod.type = MHOD_ID_ALBUM_ARTIST; - mhod.data.string = track->artist; - mk_mhod (fexp, &mhod); - ++mhod_num; - } - - if (track->sort_albumartist && *track->sort_albumartist) { - mhod.type = MHOD_ID_ALBUM_SORT_ARTIST; - mhod.data.string = track->sort_albumartist; - mk_mhod (fexp, &mhod); - ++mhod_num; - } else if (track->sort_artist && *track->sort_artist) { - mhod.type = MHOD_ID_ALBUM_SORT_ARTIST; - mhod.data.string = track->sort_artist; - mk_mhod (fexp, &mhod); - ++mhod_num; - } - fix_mhit (cts, mhia_seek, mhod_num); -} - -static void mk_mhla (FExport *fexp) -{ - WContents *cts; - - g_return_if_fail (fexp); - g_return_if_fail (fexp->wcontents); - g_return_if_fail (fexp->albums); - - cts = fexp->wcontents; - - put_header (cts, "mhla"); /* header */ - put32lint (cts, 92); /* size of header */ - /* albums on iPod */ - put32lint (cts, g_hash_table_size (fexp->albums)); - put32_n0 (cts, 20); /* dummy space */ - g_hash_table_foreach (fexp->albums, mk_mhia, fexp); -} - -/* Write out the mhlp header. Size will be written later */ -static void mk_mhlp (FExport *fexp) -{ - WContents *cts; - - g_return_if_fail (fexp); - g_return_if_fail (fexp->wcontents); - - cts = fexp->wcontents; - - put_header (cts, "mhlp"); /* header */ - put32lint (cts, 92); /* size of header */ - /* playlists on iPod (including main!) */ - put32lint (cts, -1); /* filled in later */ - put32_n0 (cts, 20); /* dummy space */ -} - -static void mk_mhii (gpointer key, gpointer value, gpointer user_data) -{ - FExport *fexp; - WContents *cts; - Itdb_Track *track; - Itdb_Item_Id *id; - MHODData mhod; - guint mhod_num; - gulong mhii_seek; - - track = (Itdb_Track *)key; - g_return_if_fail (track != NULL); - - id = (Itdb_Item_Id *)value; - g_return_if_fail (id != NULL); - - fexp = (FExport *)user_data; - g_return_if_fail (fexp); - g_return_if_fail (fexp->wcontents); - cts = fexp->wcontents; - mhii_seek = cts->pos; - - put_header (cts, "mhii"); /* header */ - put32lint (cts, 80); /* size of header */ - put32lint (cts, -1); /* total size -> later */ - put32lint (cts, 1); /* number of children mhods */ - put32lint (cts, id->id); /* artist id */ - put64lint (cts, id->sql_id); /* artist SQL id */ - put32lint (cts, 2); /* unknown */ - put32_n0 (cts, 12); /* padding */ - - mhod.valid = TRUE; - mhod_num = 0; - - if (track->artist && *track->artist) { - mhod.type = MHOD_ID_ALBUM_ARTIST_MHII; - mhod.data.string = track->artist; - mk_mhod (fexp, &mhod); - ++mhod_num; - } - - fix_mhit (cts, mhii_seek, mhod_num); -} - -static void mk_mhli (FExport *fexp) -{ - WContents *cts; - - g_return_if_fail (fexp); - g_return_if_fail (fexp->wcontents); - g_return_if_fail (fexp->artists); - - cts = fexp->wcontents; - - put_header (cts, "mhli"); /* header */ - put32lint (cts, 92); /* size of header */ - /* albums on iPod (including main!) */ - put32lint (cts, g_hash_table_size (fexp->artists)); - put32_n0 (cts, 20); /* dummy space */ - g_hash_table_foreach (fexp->artists, mk_mhii, fexp); -} - -/* Write out the long MHOD_ID_PLAYLIST mhod header. - This seems to be an itunespref thing.. dunno know this - but if we set everything to 0, itunes doesn't show any data - even if you drag an mp3 to your ipod: nothing is shown, but itunes - will copy the file! - .. so we create a hardcoded-pref.. this will change in future - Seems to be a Preferences mhod, every PL has such a thing - FIXME !!! */ -static void mk_long_mhod_id_playlist (FExport *fexp, Itdb_Playlist *pl) -{ - WContents *cts; - - g_return_if_fail (fexp); - g_return_if_fail (fexp->wcontents); - g_return_if_fail (pl); - - cts = fexp->wcontents; - - put_header (cts, "mhod"); /* header */ - put32lint (cts, 0x18); /* size of header ? */ - put32lint (cts, 0x0288); /* size of header + body */ - put32lint (cts, MHOD_ID_PLAYLIST); /* type of the entry */ - put32_n0 (cts, 6); - put32lint (cts, 0x010084); /* ? */ - put32lint (cts, 0x05); /* ? */ - put32lint (cts, 0x09); /* ? */ - put32lint (cts, 0x03); /* ? */ - put32lint (cts, 0x120001); /* ? */ - put32_n0 (cts, 3); - put32lint (cts, 0xc80002); /* ? */ - put32_n0 (cts, 3); - put32lint (cts, 0x3c000d); /* ? */ - put32_n0 (cts, 3); - put32lint (cts, 0x7d0004); /* ? */ - put32_n0 (cts, 3); - put32lint (cts, 0x7d0003); /* ? */ - put32_n0 (cts, 3); - put32lint (cts, 0x640008); /* ? */ - put32_n0 (cts, 3); - put32lint (cts, 0x640017); /* ? */ - put32lint (cts, 0x01); /* bool? (visible? / colums?) */ - put32_n0 (cts, 2); - put32lint (cts, 0x500014); /* ? */ - put32lint (cts, 0x01); /* bool? (visible?) */ - put32_n0 (cts, 2); - put32lint (cts, 0x7d0015); /* ? */ - put32lint (cts, 0x01); /* bool? (visible?) */ - put32_n0 (cts, 114); -} - - - -/* Header for new PL item */ -/* @pos: position in playlist */ -static void mk_mhip (FExport *fexp, - guint32 childcount, - guint32 podcastgroupflag, - guint32 podcastgroupid, - guint32 trackid, - guint32 timestamp, - guint32 podcastgroupref) -{ - WContents *cts; - - g_return_if_fail (fexp); - g_return_if_fail (fexp->wcontents); - - cts = fexp->wcontents; - - put_header (cts, "mhip"); - put32lint (cts, 76); /* 4 */ - put32lint (cts, -1); /* fill in later */ /* 8 */ - put32lint (cts, childcount); /* 12 */ - put32lint (cts, podcastgroupflag); /* 16 */ - put32lint (cts, podcastgroupid); /* 20 */ - put32lint (cts, trackid); /* 24 */ - timestamp = device_time_time_t_to_mac (fexp->itdb->device, timestamp); - put32lint (cts, timestamp); /* 28 */ - put32lint (cts, podcastgroupref); /* 32 */ - put32_n0 (cts, 10); /* 36 */ -} - - -/* Write first mhsd hunk. Return FALSE in case of error and set - * fexp->error */ -static gboolean write_mhsd_tracks (FExport *fexp) -{ - GList *gl; - gulong mhsd_seek; - WContents *cts; - - g_return_val_if_fail (fexp, FALSE); - g_return_val_if_fail (fexp->itdb, FALSE); - g_return_val_if_fail (fexp->wcontents, FALSE); - - cts = fexp->wcontents; - - mhsd_seek = cts->pos; /* get position of mhsd header */ - mk_mhsd (fexp, 1); /* write header: type 1: tracks */ - /* write header with nr. of tracks */ - mk_mhlt (fexp, g_list_length (fexp->itdb->tracks)); - for (gl=fexp->itdb->tracks; gl; gl=gl->next) /* Write each track */ - { - Itdb_Track *track = gl->data; - guint32 mhod_num = 0; - gulong mhit_seek = cts->pos; - MHODData mhod; - - g_return_val_if_fail (track, FALSE); - - mhod.valid = TRUE; - - mk_mhit (cts, track); - if (track->title && *track->title) - { - mhod.type = MHOD_ID_TITLE; - mhod.data.string = track->title; - mk_mhod (fexp, &mhod); - ++mhod_num; - } - if (track->artist && *track->artist) - { - mhod.type = MHOD_ID_ARTIST; - mhod.data.string = track->artist; - mk_mhod (fexp, &mhod); - ++mhod_num; - } - if (track->album && *track->album) - { - mhod.type = MHOD_ID_ALBUM; - mhod.data.string = track->album; - mk_mhod (fexp, &mhod); - ++mhod_num; - } - if (track->filetype && *track->filetype) - { - mhod.type = MHOD_ID_FILETYPE; - mhod.data.string = track->filetype; - mk_mhod (fexp, &mhod); - ++mhod_num; - } - if (track->comment && *track->comment) - { - mhod.type = MHOD_ID_COMMENT; - mhod.data.string = track->comment; - mk_mhod (fexp, &mhod); - ++mhod_num; - } - if (track->ipod_path && *track->ipod_path) - { - mhod.type = MHOD_ID_PATH; - mhod.data.string = track->ipod_path; - mk_mhod (fexp, &mhod); - ++mhod_num; - } - if (track->genre && *track->genre) - { - mhod.type = MHOD_ID_GENRE; - mhod.data.string = track->genre; - mk_mhod (fexp, &mhod); - ++mhod_num; - } - if (track->category && *track->category) - { - mhod.type = MHOD_ID_CATEGORY; - mhod.data.string = track->category; - mk_mhod (fexp, &mhod); - ++mhod_num; - } - if (track->composer && *track->composer) - { - mhod.type = MHOD_ID_COMPOSER; - mhod.data.string = track->composer; - mk_mhod (fexp, &mhod); - ++mhod_num; - } - if (track->grouping && *track->grouping) - { - mhod.type = MHOD_ID_GROUPING; - mhod.data.string = track->grouping; - mk_mhod (fexp, &mhod); - ++mhod_num; - } - if (track->description && *track->description) - { - mhod.type = MHOD_ID_DESCRIPTION; - mhod.data.string = track->description; - mk_mhod (fexp, &mhod); - ++mhod_num; - } - if (track->subtitle && *track->subtitle) - { - mhod.type = MHOD_ID_SUBTITLE; - mhod.data.string = track->subtitle; - mk_mhod (fexp, &mhod); - ++mhod_num; - } - if (track->tvshow && *track->tvshow) - { - mhod.type = MHOD_ID_TVSHOW; - mhod.data.string = track->tvshow; - mk_mhod (fexp, &mhod); - ++mhod_num; - } - if (track->tvepisode && *track->tvepisode) - { - mhod.type = MHOD_ID_TVEPISODE; - mhod.data.string = track->tvepisode; - mk_mhod (fexp, &mhod); - ++mhod_num; - } - if (track->tvnetwork && *track->tvnetwork) - { - mhod.type = MHOD_ID_TVNETWORK; - mhod.data.string = track->tvnetwork; - mk_mhod (fexp, &mhod); - ++mhod_num; - } - if (track->albumartist && *track->albumartist) - { - mhod.type = MHOD_ID_ALBUMARTIST; - mhod.data.string = track->albumartist; - mk_mhod (fexp, &mhod); - ++mhod_num; - } - if (track->keywords && *track->keywords) - { - mhod.type = MHOD_ID_KEYWORDS; - mhod.data.string = track->keywords; - mk_mhod (fexp, &mhod); - ++mhod_num; - } - if (track->podcasturl && *track->podcasturl) - { - mhod.type = MHOD_ID_PODCASTURL; - mhod.data.string = track->podcasturl; - mk_mhod (fexp, &mhod); - ++mhod_num; - } - if (track->podcastrss && *track->podcastrss) - { - mhod.type = MHOD_ID_PODCASTRSS; - mhod.data.string = track->podcastrss; - mk_mhod (fexp, &mhod); - ++mhod_num; - } - if (track->sort_artist && *track->sort_artist) - { - mhod.type = MHOD_ID_SORT_ARTIST; - mhod.data.string = track->sort_artist; - mk_mhod (fexp, &mhod); - ++mhod_num; - } - if (track->sort_title && *track->sort_title) - { - mhod.type = MHOD_ID_SORT_TITLE; - mhod.data.string = track->sort_title; - mk_mhod (fexp, &mhod); - ++mhod_num; - } - if (track->sort_album && *track->sort_album) - { - mhod.type = MHOD_ID_SORT_ALBUM; - mhod.data.string = track->sort_album; - mk_mhod (fexp, &mhod); - ++mhod_num; - } - if (track->sort_albumartist && *track->sort_albumartist) - { - mhod.type = MHOD_ID_SORT_ALBUMARTIST; - mhod.data.string = track->sort_albumartist; - mk_mhod (fexp, &mhod); - ++mhod_num; - } - if (track->sort_composer && *track->sort_composer) - { - mhod.type = MHOD_ID_SORT_COMPOSER; - mhod.data.string = track->sort_composer; - mk_mhod (fexp, &mhod); - ++mhod_num; - } - if (track->sort_tvshow && *track->sort_tvshow) - { - mhod.type = MHOD_ID_SORT_TVSHOW; - mhod.data.string = track->sort_tvshow; - mk_mhod (fexp, &mhod); - ++mhod_num; - } - if (track->chapterdata && track->chapterdata->chapters) - { - mhod.type = MHOD_ID_CHAPTERDATA; - mhod.data.chapterdata = track->chapterdata; - mk_mhod (fexp, &mhod); - ++mhod_num; - } - /* Fill in the missing items of the mhit header */ - fix_mhit (cts, mhit_seek, mhod_num); - } - fix_header (cts, mhsd_seek); - return TRUE; -} - - -/* write out the mhip/mhod pairs for playlist @pl. @mhyp_seek points - to the start of the corresponding mhyp, but is not used yet. */ -static gboolean write_playlist_mhips (FExport *fexp, - Itdb_Playlist *pl, - glong mhyp_seek) -{ - GList *gl; - WContents *cts; - guint32 i=0; - guint32 mhip_num; - - g_return_val_if_fail (fexp, FALSE); - g_return_val_if_fail (fexp->itdb, FALSE); - g_return_val_if_fail (fexp->wcontents, FALSE); - g_return_val_if_fail (pl, FALSE); - - cts = fexp->wcontents; - - for (gl=pl->members; gl; gl=gl->next) - { - Itdb_Track *track = gl->data; - glong mhip_seek = cts->pos; - MHODData mhod; - - g_return_val_if_fail (track, FALSE); - - mk_mhip (fexp, 1, 0, 0, track->id, 0, 0); - mhod.valid = TRUE; - mhod.type = MHOD_ID_PLAYLIST; - mhod.data.track_pos = i; - mk_mhod (fexp, &mhod); - /* note: with iTunes 4.9 the mhod is counted as a child to - mhip, so we have put the total length of the mhip and mhod - into the mhip header */ - fix_header (cts, mhip_seek); - ++i; - } - - /* set number of mhips */ - mhip_num = g_list_length (pl->members); - put32lint_seek (cts, mhip_num, mhyp_seek+16); - - return TRUE; -} - - -static void free_memberlist (gpointer data); -static void write_one_podcast_group (gpointer key, gpointer value, gpointer userdata); -static void free_memberlist (gpointer data) -{ - GList **memberlist = data; - if (memberlist) - { - g_list_free (*memberlist); - g_free (memberlist); - } -} -static void write_one_podcast_group (gpointer key, gpointer value, - gpointer userdata) -{ - gchar *album = key; - GList **memberlist = value; - FExport *fexp = userdata; - GList *gl; - WContents *cts; - glong mhip_seek; - guint32 groupid; - MHODData mhod; - - g_return_if_fail (album); - g_return_if_fail (memberlist); - g_return_if_fail (fexp); - g_return_if_fail (fexp->itdb); - g_return_if_fail (fexp->wcontents); - - cts = fexp->wcontents; - mhip_seek = cts->pos; - - groupid = fexp->next_id++; - mk_mhip (fexp, 1, 256, groupid, 0, 0, 0); - - mhod.valid = TRUE; - mhod.type = MHOD_ID_TITLE; - mhod.data.string = album; - mk_mhod (fexp, &mhod); - fix_header (cts, mhip_seek); - - /* write members */ - for (gl=*memberlist; gl; gl=gl->next) - { - Itdb_Track *track = gl->data; - guint32 mhip_id; - - g_return_if_fail (track); - - mhip_seek = cts->pos; - mhip_id = fexp->next_id++; - mk_mhip (fexp, 1, 0, mhip_id, track->id, 0, groupid); - mhod.type = MHOD_ID_PLAYLIST; - mhod.data.track_pos = mhip_id; - mk_mhod (fexp, &mhod); - fix_header (cts, mhip_seek); - } -} - - - -/* write out the mhip/mhod pairs for the podcast playlist - @pl. @mhyp_seek points to the start of the corresponding mhyp, but - is not used yet. */ -static gboolean write_podcast_mhips (FExport *fexp, - Itdb_Playlist *pl, - glong mhyp_seek) -{ - GList *gl; - WContents *cts; - guint32 mhip_num; - GHashTable *album_hash; - - g_return_val_if_fail (fexp, FALSE); - g_return_val_if_fail (fexp->itdb, FALSE); - g_return_val_if_fail (fexp->wcontents, FALSE); - g_return_val_if_fail (pl, FALSE); - - cts = fexp->wcontents; - - /* Create a list with all available album names because we have to - group the podcasts according to albums */ - album_hash = g_hash_table_new_full (g_str_hash, g_str_equal, - NULL, free_memberlist); - for (gl=pl->members; gl; gl=gl->next) - { - GList **memberlist; - gchar *album; - Itdb_Track *track = gl->data; - g_return_val_if_fail (track, FALSE); - - if (track->album) - album = track->album; - else - album = ""; - - memberlist = g_hash_table_lookup (album_hash, album); - - if (!memberlist) - { - memberlist = g_new0 (GList *, 1); - g_hash_table_insert (album_hash, album, memberlist); - } - *memberlist = g_list_append (*memberlist, track); - } - - g_hash_table_foreach (album_hash, write_one_podcast_group, fexp); - - /* set number of mhips */ - mhip_num = g_list_length (pl->members)+g_hash_table_size (album_hash); - put32lint_seek (cts, mhip_num, mhyp_seek+16); - - g_hash_table_destroy (album_hash); - - return TRUE; -} - -/* Write one type 52 and the corresponding type 53 mhod */ -static void mk_mhod52 (enum MHOD52_SORTTYPE mhod52sorttype, FExport *fexp, - MHODData *mhod) -{ - mhod->mhod52sorttype = mhod52sorttype; - mhod->type = MHOD_ID_LIBPLAYLISTINDEX; - mk_mhod (fexp, mhod); -} - -static void mk_mhod53 (enum MHOD52_SORTTYPE mhod52sorttype, FExport *fexp, - MHODData *mhod) -{ - mhod->type = MHOD_ID_LIBPLAYLISTJUMPTABLE; - mk_mhod (fexp, mhod); - g_list_foreach (mhod->mhod53_list, (GFunc)g_free, NULL); - g_list_free (mhod->mhod53_list); - mhod->mhod53_list = NULL; -} - - -/* corresponds to mk_mhyp */ -/* mhsd_type: 2: write normal playlists - 3: write podcast playlist in special format */ -/* Return FALSE in case of error and set fexp->error */ -static gboolean write_playlist (FExport *fexp, - Itdb_Playlist *pl, guint32 mhsd_type) -{ - gulong mhyp_seek; - WContents *cts; - gboolean result = TRUE; - MHODData mhod; - gint mhodnum; - guint32 mac_time; - - g_return_val_if_fail (fexp, FALSE); - g_return_val_if_fail (fexp->itdb, FALSE); - g_return_val_if_fail (fexp->wcontents, FALSE); - g_return_val_if_fail (pl, FALSE); - - cts = fexp->wcontents; - mhyp_seek = cts->pos; - -#if ITUNESDB_DEBUG - fprintf(stderr, "Playlist: %s (%d tracks)\n", pl->name, g_list_length (pl->members)); -#endif - - put_header (cts, "mhyp"); /* header */ - put32lint (cts, 108); /* length */ - put32lint (cts, -1); /* size -> later */ - mhodnum = 2; - if (pl->is_spl) - { /* 2 more SPL MHODs */ - mhodnum += 2; - } - else - { - if ((pl->type == ITDB_PL_TYPE_MPL) && pl->members) - { /* 5 more MHOD 52 lists and 5 more MHOD 53 lists*/ - mhodnum += 10; - } - } - put32lint (cts, mhodnum); /* nr of mhods */ - /* number of tracks in plist */ - put32lint (cts, -1); /* number of mhips -> later */ - put8int (cts, pl->type); /* 1 = main, 0 = visible */ - put8int (cts, pl->flag1); /* unknown */ - put8int (cts, pl->flag2); /* unknown */ - put8int (cts, pl->flag3); /* unknown */ - mac_time = device_time_time_t_to_mac (fexp->itdb->device, pl->timestamp); - put32lint (cts, mac_time); /* some timestamp */ - put64lint (cts, pl->id); /* 64 bit ID */ - put32lint (cts, 0); /* unknown, always 0? */ - put16lint (cts, 1); /* string mhod count (1) */ - put16lint (cts, pl->podcastflag); - put32lint (cts, pl->sortorder); - if (mhsd_type == 5) { - put32_n0 (cts, 8); /* ? */ - /* +0x50 */ - put16lint (cts, pl->priv->mhsd5_type); - put16lint (cts, pl->priv->mhsd5_type); /* same as 0x50 ? */ - /* +0x54 */ - if ((pl->priv->mhsd5_type == ITDB_PLAYLIST_MHSD5_MOVIE_RENTALS) - || (pl->priv->mhsd5_type == ITDB_PLAYLIST_MHSD5_RINGTONES)){ - put32lint (cts, 1); /* unknown, 1 for Movie rentals + Ringtones */ - } else { - put32lint (cts, 0); /* 0 otherwise */ - } - put32_n0 (cts, 5); /* ? */ - - - } else { - put32_n0 (cts, 15); /* ? */ - } - - mhod.valid = TRUE; - mhod.type = MHOD_ID_TITLE; - mhod.data.string = pl->name; - mk_mhod (fexp, &mhod); - mk_long_mhod_id_playlist (fexp, pl); - - if ((pl->type == ITDB_PL_TYPE_MPL) && pl->members) - { /* write out the MHOD 52 and MHOD 53 lists */ - /* We have to sort all tracks five times. To speed this up, - translate the utf8 keys into collate_keys and use the - faster strcmp() for comparison */ - mhod.valid = TRUE; - mhod.data.mhod52coltracks = mhod52_make_collate_keys (pl->members); - mhod.mhod53_list = NULL; - - mk_mhod52 (MHOD52_SORTTYPE_TITLE, fexp, &mhod); - mk_mhod53 (MHOD52_SORTTYPE_TITLE, fexp, &mhod); - mk_mhod52 (MHOD52_SORTTYPE_ARTIST, fexp, &mhod); - mk_mhod53 (MHOD52_SORTTYPE_ARTIST, fexp, &mhod); - mk_mhod52 (MHOD52_SORTTYPE_ALBUM, fexp, &mhod); - mk_mhod53 (MHOD52_SORTTYPE_ALBUM, fexp, &mhod); - mk_mhod52 (MHOD52_SORTTYPE_GENRE, fexp, &mhod); - mk_mhod53 (MHOD52_SORTTYPE_GENRE, fexp, &mhod); - mk_mhod52 (MHOD52_SORTTYPE_COMPOSER, fexp, &mhod); - mk_mhod53 (MHOD52_SORTTYPE_COMPOSER, fexp, &mhod); - - mhod52_free_collate_keys (mhod.data.mhod52coltracks); - } - else if (pl->is_spl) - { /* write the smart rules */ - mhod.type = MHOD_ID_SPLPREF; - mhod.data.splpref = &pl->splpref; - mk_mhod (fexp, &mhod); - - mhod.type = MHOD_ID_SPLRULES; - mhod.data.splrules = &pl->splrules; - mk_mhod (fexp, &mhod); - } - - if (itdb_playlist_is_podcasts(pl) && (mhsd_type == 3)) - { - /* write special podcast playlist */ - result = write_podcast_mhips (fexp, pl, mhyp_seek); - } - else if (mhsd_type == 5) - { - /* set number of mhips to 0 for mhsd_type 5 */ - put32lint_seek (cts, 0, mhyp_seek+16); - } - else - { - /* write standard playlist hard-coded tracks */ - result = write_playlist_mhips (fexp, pl, mhyp_seek); - } - fix_header (cts, mhyp_seek); - return result; -} - - -/* Expects the master playlist to be the first in the list */ -/* Return FALSE in case of error and set fexp->error */ -static gboolean write_mhsd_playlists (FExport *fexp, guint32 mhsd_type) -{ - GList *gl; - glong mhsd_seek; - glong mhlp_seek; - WContents *cts; - int plcnt = 0; - GList *playlists; - - g_return_val_if_fail (fexp, FALSE); - g_return_val_if_fail (fexp->itdb, FALSE); - g_return_val_if_fail (fexp->wcontents, FALSE); - g_return_val_if_fail ((mhsd_type == 2) || (mhsd_type == 3) || (mhsd_type == 5), FALSE); - - cts = fexp->wcontents; - mhsd_seek = cts->pos; /* get position of mhsd header */ - mk_mhsd (fexp, mhsd_type); /* write header */ - /* write header with nr. of playlists */ - mhlp_seek = cts->pos; - mk_mhlp (fexp); - if (mhsd_type == 5) { - playlists = fexp->itdb->priv->mhsd5_playlists; - } else { - playlists = fexp->itdb->playlists; - } - for (gl=playlists; gl; gl=gl->next) - { - Itdb_Playlist *pl = gl->data; - g_return_val_if_fail (pl, FALSE); - - write_playlist (fexp, pl, mhsd_type); - plcnt++; - if (fexp->error) return FALSE; - } - - put32lint_seek (cts, plcnt, mhlp_seek+8); - - fix_header (cts, mhsd_seek); - return TRUE; -} - -static gboolean write_mhsd_albums (FExport *fexp) -{ - gulong mhsd_seek; - WContents *cts; - - g_return_val_if_fail (fexp, FALSE); - g_return_val_if_fail (fexp->itdb, FALSE); - g_return_val_if_fail (fexp->wcontents, FALSE); - - cts = fexp->wcontents; - mhsd_seek = cts->pos; /* get position of mhsd header */ - mk_mhsd (fexp, 4); /* write header */ - mk_mhla (fexp); - fix_header (cts, mhsd_seek); - return TRUE; -} - -static gboolean write_mhsd_artists (FExport *fexp) -{ - gulong mhsd_seek; - WContents *cts; - - g_return_val_if_fail (fexp, FALSE); - g_return_val_if_fail (fexp->itdb, FALSE); - g_return_val_if_fail (fexp->wcontents, FALSE); - - cts = fexp->wcontents; - mhsd_seek = cts->pos; /* get position of mhsd header */ - mk_mhsd (fexp, 8); /* write header */ - mk_mhli (fexp); - fix_header (cts, mhsd_seek); - return TRUE; -} - -static gboolean write_mhsd_type6 (FExport *fexp) -{ - gulong mhsd_seek; - WContents *cts; - - g_return_val_if_fail (fexp, FALSE); - g_return_val_if_fail (fexp->itdb, FALSE); - g_return_val_if_fail (fexp->wcontents, FALSE); - - cts = fexp->wcontents; - mhsd_seek = cts->pos; /* get position of mhsd header */ - mk_mhsd (fexp, 6); /* write header */ - mk_mhlt (fexp, 0); /* for now, produce an empty set */ - fix_header (cts, mhsd_seek); - return TRUE; -} - -static gboolean write_genius_mhsd (FExport *fexp) -{ - gulong mhsd_seek; - WContents *cts; - - g_return_val_if_fail (fexp, FALSE); - g_return_val_if_fail (fexp->itdb, FALSE); - g_return_val_if_fail (fexp->wcontents, FALSE); - - if (!fexp->itdb->priv->genius_cuid) { - return TRUE; - } - cts = fexp->wcontents; - mhsd_seek = cts->pos; /* get position of mhsd header */ - mk_mhsd (fexp, 9); /* write header */ - put_string (cts, fexp->itdb->priv->genius_cuid); - fix_header (cts, mhsd_seek); - return TRUE; -} - -static gboolean write_mhsd_type10 (FExport *fexp) -{ - gulong mhsd_seek; - WContents *cts; - - g_return_val_if_fail (fexp, FALSE); - g_return_val_if_fail (fexp->itdb, FALSE); - g_return_val_if_fail (fexp->wcontents, FALSE); - - cts = fexp->wcontents; - mhsd_seek = cts->pos; /* get position of mhsd header */ - mk_mhsd (fexp, 10); /* write header */ - mk_mhlt (fexp, 0); /* for now, produce an empty set */ - fix_header (cts, mhsd_seek); - return TRUE; -} - -/* create a WContents structure */ -static WContents *wcontents_new (const gchar *filename) -{ - WContents *cts; - - g_return_val_if_fail (filename, NULL); - - cts = g_new0 (WContents, 1); - cts->filename = g_strdup (filename); - - return cts; -} - - -/* write the contents of WContents. Return FALSE on error and set - * cts->error accordingly. */ -static gboolean wcontents_write (WContents *cts) -{ - g_return_val_if_fail (cts, FALSE); - g_return_val_if_fail (cts->filename, FALSE); - - cts->error = NULL; - return g_file_set_contents (cts->filename, cts->contents, - cts->pos, &cts->error); -} - - -/* Free memory associated with WContents @cts */ -static void wcontents_free (WContents *cts) -{ - if (cts) - { - g_free (cts->filename); - g_free (cts->contents); - /* must not g_error_free (cts->error) because the error was - propagated -> might free the error twice */ - g_free (cts); - } -} - -static gboolean safe_str_equal (gconstpointer v1, gconstpointer v2) -{ - if ((v1 == NULL) || (v2 == NULL)) { - return (v1 == v2); - } else { - return g_str_equal (v1, v2); - } -} - -static guint itdb_album_hash (gconstpointer v) -{ - Itdb_Track *track = (Itdb_Track *)v; - if (track->album != NULL) { - return g_str_hash (track->album); - } else { - return 0; - } - g_assert_not_reached (); -} - -static gboolean itdb_album_equal (gconstpointer v1, gconstpointer v2) -{ - Itdb_Track *track1 = (Itdb_Track *)v1; - Itdb_Track *track2 = (Itdb_Track *)v2; - gboolean same_show; - gboolean same_album; - - /*album is the same if: - * tvshow is the same, and - * album and albumartist are the same, or - * albumartist is null and artist and album are the same - */ - same_show = safe_str_equal (track1->tvshow, track2->tvshow); - if (!same_show) { - return FALSE; - } - - same_album = safe_str_equal (track1->album, track2->album); - if (!same_album) { - return FALSE; - } - - if ((track1->albumartist != NULL) && (track2->albumartist != NULL)) { - return safe_str_equal (track1->albumartist, track2->albumartist); - } else { - return safe_str_equal (track1->artist, track2->artist); - } -} - -static void add_new_id (GHashTable *ids, Itdb_Track *track, guint new_id) -{ - Itdb_Item_Id *id; - - id = g_new0 (Itdb_Item_Id, 1); - id->id = new_id; - id->sql_id = ((guint64)g_random_int () << 32) | ((guint64)g_random_int ()); - - g_hash_table_insert (ids, track, id); -} - -static guint itdb_artist_hash (gconstpointer v) -{ - Itdb_Track *track = (Itdb_Track *)v; - if (track->artist != NULL) { - return g_str_hash (track->artist); - } else { - return 0; - } -} - -static gboolean itdb_artist_equal (gconstpointer v1, gconstpointer v2) -{ - Itdb_Track *track1 = (Itdb_Track *)v1; - Itdb_Track *track2 = (Itdb_Track *)v2; - - return safe_str_equal (track1->artist, track2->artist); -} - -static guint itdb_composer_hash (gconstpointer v) -{ - Itdb_Track *track = (Itdb_Track *)v; - if (track->composer != NULL) { - return g_str_hash (track->composer); - } else { - return 0; - } -} - -static gboolean itdb_composer_equal (gconstpointer v1, gconstpointer v2) -{ - Itdb_Track *track1 = (Itdb_Track *)v1; - Itdb_Track *track2 = (Itdb_Track *)v2; - - return safe_str_equal (track1->composer, track2->composer); -} - -/* - reassign the iPod IDs - - make sure the itdb->tracks are in the same order as the mpl - - assign album IDs to write the MHLA -*/ -static void prepare_itdb_for_write (FExport *fexp) -{ - GList *gl; - GList *pl; - Itdb_iTunesDB *itdb; - Itdb_Playlist *mpl; - Itdb_Playlist *playlist; - guint album_id = 1; - guint artist_id = 1; - guint composer_id = 1; - - g_return_if_fail (fexp); - itdb = fexp->itdb; - g_return_if_fail (itdb); - - /* Arrange itdb->tracks in the same order as mpl->members - (otherwise On-The-Go Playlists will not show the correct - content. Unfortunately we cannot just copy mpl->members to - itdb->tracks because podcasts do not show up in the MPL. */ - - mpl = itdb_playlist_mpl (itdb); - g_return_if_fail (mpl); - - for (gl=g_list_last(mpl->members); gl; gl=gl->prev) - { - GList *link; - Itdb_Track *track = gl->data; - g_return_if_fail (track); - link = g_list_find (itdb->tracks, track); - g_return_if_fail (link); - - /* move this track to the beginning of itdb_tracks */ - itdb->tracks = g_list_delete_link (itdb->tracks, link); - itdb->tracks = g_list_prepend (itdb->tracks, track); - } - - fexp->next_id = FIRST_IPOD_ID; - - g_assert (fexp->albums == NULL); - fexp->albums = g_hash_table_new_full (itdb_album_hash, itdb_album_equal, - NULL, g_free); - - g_assert (fexp->artists == NULL); - fexp->artists = g_hash_table_new_full (itdb_artist_hash, itdb_artist_equal, - NULL, g_free); - - g_assert (fexp->composers == NULL); - fexp->composers = g_hash_table_new_full (itdb_composer_hash, itdb_composer_equal, - NULL, g_free); - - /* assign unique IDs and create sort keys */ - for (gl=itdb->tracks; gl; gl=gl->next) - { - Itdb_Track *track = gl->data; - Itdb_Item_Id *id; - - g_return_if_fail (track); - track->id = fexp->next_id++; - - if (track->album != NULL) { - /* album ids are used when writing the mhla header */ - id = g_hash_table_lookup (fexp->albums, track); - if (id != NULL) { - track->priv->album_id = id->id; - } else { - add_new_id (fexp->albums, track, album_id); - track->priv->album_id = album_id; - album_id++; - } - } - - if (track->artist != NULL) { - /* artist ids are used when writing the mhli header */ - id = g_hash_table_lookup (fexp->artists, track); - if (id != NULL) { - track->priv->artist_id = id->id; - } else { - add_new_id (fexp->artists, track, artist_id); - track->priv->artist_id = artist_id; - artist_id++; - } - } - - if (track->composer != NULL) { - id = g_hash_table_lookup (fexp->composers, track); - if (id != NULL) { - track->priv->composer_id = id->id; - } else { - add_new_id (fexp->composers, track, composer_id); - track->priv->composer_id = composer_id; - composer_id++; - } - } - } - - /* Make sure playlist->num is correct */ - for (pl = itdb->playlists; pl; pl = pl->next) { - playlist = pl->data; - g_return_if_fail (playlist); - playlist->num = itdb_playlist_tracks_number (playlist); - } -} - -static gboolean itdb_write_file_internal (Itdb_iTunesDB *itdb, - const gchar *filename, - GError **error) -{ - FExport *fexp; - gulong mhbd_seek = 0; - WContents *cts; - gboolean result = TRUE; - guint32 num_mhsds; - - g_return_val_if_fail (itdb, FALSE); - g_return_val_if_fail (itdb->device, FALSE); - g_return_val_if_fail (filename || itdb->filename, FALSE); - - if (!filename) filename = itdb->filename; - - /* set endianess flag */ - if (!itdb->device->byte_order) - itdb_device_autodetect_endianess (itdb->device); - - fexp = g_new0 (FExport, 1); - fexp->itdb = itdb; - fexp->wcontents = wcontents_new (filename); - cts = fexp->wcontents; - - cts->reversed = (itdb->device->byte_order == G_BIG_ENDIAN); - - prepare_itdb_for_write (fexp); - -#if HAVE_GDKPIXBUF - /* only write ArtworkDB if we deal with an iPod - FIXME: figure out a way to store the artwork data when storing - to local directories. At the moment it's the application's task - to handle this. */ - /* The ArtworkDB must be written after the call to - * prepare_itdb_for_write since it needs Itdb_Track::id to be set - * to its final value to write properly on nano video/ipod classics - */ - if (itdb_device_supports_artwork (itdb->device)) { - ipod_write_artwork_db (itdb); - } -#endif - - /* default mhsd count */ - num_mhsds = 8; /* eight mhsds */ - - /* if genius_cuid present, we have one more */ - if (fexp->itdb->priv->genius_cuid) { - num_mhsds++; - } - - mk_mhbd (fexp, num_mhsds); - - /* write tracklist (mhsd type 1) */ - if (!fexp->error && !write_mhsd_tracks (fexp)) { - g_set_error (&fexp->error, - ITDB_FILE_ERROR, - ITDB_FILE_ERROR_ITDB_CORRUPT, - _("Error writing list of tracks (mhsd type 1)")); - goto err; - } - - /* write special podcast version mhsd (mhsd type 3) */ - if (!fexp->error && !write_mhsd_playlists (fexp, 3)) { - g_set_error (&fexp->error, - ITDB_FILE_ERROR, - ITDB_FILE_ERROR_ITDB_CORRUPT, - _("Error writing special podcast playlists (mhsd type 3)")); - goto err; - } - /* write standard playlist mhsd (mhsd type 2) */ - if (!fexp->error && !write_mhsd_playlists (fexp, 2)) { - g_set_error (&fexp->error, - ITDB_FILE_ERROR, - ITDB_FILE_ERROR_ITDB_CORRUPT, - _("Error writing standard playlists (mhsd type 2)")); - goto err; - } - - /* write albums (mhsd type 4) */ - if (!write_mhsd_albums (fexp)) { - g_set_error (&fexp->error, - ITDB_FILE_ERROR, - ITDB_FILE_ERROR_ITDB_CORRUPT, - _("Error writing list of albums (mhsd type 4)")); - goto err; - } - /* write artists (mhsd type 8) */ - if (!fexp->error && !write_mhsd_artists (fexp)) { - g_set_error (&fexp->error, - ITDB_FILE_ERROR, - ITDB_FILE_ERROR_ITDB_CORRUPT, - _("Error writing list of artists (mhsd type 8)")); - goto err; - } - - /* write empty mhsd type 6, whatever it is */ - if (!fexp->error && !write_mhsd_type6 (fexp)) { - g_set_error (&fexp->error, - ITDB_FILE_ERROR, - ITDB_FILE_ERROR_ITDB_CORRUPT, - _("Error writing mhsd type 6")); - goto err; - } - - /* write empty mhsd type 10, whatever it is */ - if (!fexp->error && !write_mhsd_type10 (fexp)) { - g_set_error (&fexp->error, - ITDB_FILE_ERROR, - ITDB_FILE_ERROR_ITDB_CORRUPT, - _("Error writing mhsd type 10")); - goto err; - } - - /* write mhsd5 playlists */ - if (!fexp->error && !write_mhsd_playlists (fexp, 5)) { - g_set_error (&fexp->error, - ITDB_FILE_ERROR, - ITDB_FILE_ERROR_ITDB_CORRUPT, - _("Error writing mhsd5 playlists")); - goto err; - } - - if (fexp->itdb->priv->genius_cuid) { - /* write genius cuid (mhsd type 9) */ - if (!fexp->error && !write_genius_mhsd (fexp)) { - g_set_error (&fexp->error, - ITDB_FILE_ERROR, - ITDB_FILE_ERROR_ITDB_CORRUPT, - _("Error writing mhsd type 9")); - goto err; - } - } - - fix_header (cts, mhbd_seek); - - if (itdb_device_supports_compressed_itunesdb (itdb->device)) { - if (!itdb_zlib_check_compress_fexp (fexp)) { - goto err; - } - } - - /* Set checksum (ipods require it starting from Classic and Nano Video) */ - itdb_device_write_checksum (itdb->device, - (unsigned char *)fexp->wcontents->contents, - fexp->wcontents->pos, - &fexp->error); - if (fexp->error) { - goto err; - } - - if (itdb_device_supports_sqlite_db (itdb->device)) { - if (itdb_sqlite_generate_itdbs(fexp) != 0) { - goto err; - } - } - - if (itdb_device_is_shuffle (itdb->device)) { - /* iPod Shuffle uses a simplified database in addition to the - * iTunesDB */ - if (!itdb_shuffle_write (itdb, &fexp->error)) { - goto err; - } - } - - if (!fexp->error) - { - if (wcontents_write (cts)) { - playcounts_reset (itdb_get_mountpoint (itdb)); - } else { - g_propagate_error (&fexp->error, cts->error); - } - } -err: - if (fexp->error) - { - g_propagate_error (error, fexp->error); - result = FALSE; - } - wcontents_free (cts); - if (fexp->albums != NULL) { - g_hash_table_destroy (fexp->albums); - } - if (fexp->artists != NULL) { - g_hash_table_destroy (fexp->artists); - } - g_free (fexp); - if (result == TRUE) - { - gchar *fn = g_strdup (filename); - g_free (itdb->filename); - itdb->filename = fn; - } - - /* make sure all buffers are flushed as some people tend to - disconnect as soon as gtkpod returns */ - itdb_fsync (); - - return result; -} - -/** - * itdb_write_file: - * @itdb: the #Itdb_iTunesDB to save - * @filename: filename to save @itdb to - * @error: return location for a #GError or NULL - * - * Write the content of @itdb to @filename. If @filename is NULL, it attempts - * to write to @itdb->filename. - * - * Returns: TRUE if all went well, FALSE otherwise - */ - -gboolean itdb_write_file (Itdb_iTunesDB *itdb, const gchar *filename, - GError **error) -{ - return itdb_write_file_internal (itdb, filename, error); -} - -/** - * itdb_write: - * @itdb: the #Itdb_iTunesDB to write to disk - * @error: return location for a #GError or NULL - * - * Write out an iTunesDB. It reassigns unique IDs to all tracks. - * An existing "Play Counts" file is renamed to "Play Counts.bak" if - * the export was successful. - * An existing "OTGPlaylistInfo" file is removed if the export was - * successful. - * - * Returns: TRUE on success, FALSE on error, in which case @error is - * set accordingly. - */ -gboolean itdb_write (Itdb_iTunesDB *itdb, GError **error) -{ - gchar *itunes_filename, *itunes_path; - gboolean result = FALSE; - - g_return_val_if_fail (itdb, FALSE); - g_return_val_if_fail (itdb_get_mountpoint (itdb), FALSE); - - /* Now handling regular iPod or iPhone/iPod Touch */ - - /* First, let's try to write the .ithmb files containing the artwork data - * since this operation modifies the 'artwork_count' and 'artwork_size' - * field in the Itdb_Track contained in the database. - * Errors happening during that operation are considered non fatal since - * they shouldn't corrupt the main database. - */ - itunes_path = itdb_get_itunes_dir (itdb_get_mountpoint (itdb)); - - if(!itunes_path) - { - error_no_itunes_dir (itdb_get_mountpoint (itdb), error); - return FALSE; - } - - if (itdb_device_supports_compressed_itunesdb (itdb->device)) { - itunes_filename = g_build_filename (itunes_path, "iTunesCDB", NULL); - } else { - itunes_filename = g_build_filename (itunes_path, "iTunesDB", NULL); - } - - itdb_start_sync (itdb); - - result = itdb_write_file_internal (itdb, itunes_filename, error); - g_free (itunes_filename); - - if (result && itdb_device_supports_compressed_itunesdb (itdb->device)) { - /* the iPhone gets confused when it has both an iTunesCDB and a non - * empty iTunesDB - */ - itunes_filename = g_build_filename (itunes_path, "iTunesDB", NULL); - g_file_set_contents(itunes_filename, NULL, 0, NULL); - g_free (itunes_filename); - } - - g_free (itunes_path); - - if (result != FALSE) - { - /* Write SysInfo file if it has changed */ - if (itdb->device->sysinfo_changed) - { - itdb_device_write_sysinfo (itdb->device, error); - } - result = itdb_rename_files (itdb_get_mountpoint (itdb), error); - } - - /* make sure all buffers are flushed as some people tend to - disconnect as soon as gtkpod returns */ - itdb_fsync (); - - itdb_stop_sync (itdb); - - return result; -} - -/** - * itdb_start_sync: - * @itdb: the #Itdb_iTunesDB that is being sync'ed - * - * Hints libgpod that a series of file copies/database saves/... is about - * to start. On regular iPods, this does nothing (but is safe to be used), - * but on iPhones and iPod Touch this makes sure the "Sync in progress" screen - * is shown for the whole duration of the writing process. - * - * Calls to itdb_start_sync must be paired with calls to itdb_stop_sync. Nesting - * is allowed. - * - * Returns: TRUE on success, FALSE on error - */ -gboolean itdb_start_sync (Itdb_iTunesDB *itdb) -{ - g_return_val_if_fail (itdb != NULL, FALSE); - g_return_val_if_fail (itdb->device != NULL, FALSE); - -#ifdef HAVE_LIBIMOBILEDEVICE - if (itdb->device->iphone_sync_context != NULL) { - itdb->device->iphone_sync_nest_level++; - return TRUE; - } - if (itdb_device_is_iphone_family (itdb->device)) { - int sync_status; - sync_status = itdb_iphone_start_sync (itdb->device, - &itdb->device->iphone_sync_context); - if (sync_status == 0) { - return TRUE; - } else { - return FALSE; - } - } -#endif - - return TRUE; -} - -/** - * itdb_stop_sync: - * @itdb: the #Itdb_iTunesDB that is being sync'ed - * - * Hints libgpod that the series of file copies/database saves/... started - * with itdb_start_sync is finished. On regular iPods, this does nothing - * (but is safe to be used). On iPhones and iPod Touch this will hide the - * "Sync in progress" screen. - * - * Calls to itdb_stop_sync must be paired with calls to itdb_start_sync. Nesting - * is allowed, and only the final itdb_stop_sync will actually stop the sync. - * - * Returns: TRUE on success, FALSE on error - */ -gboolean itdb_stop_sync (Itdb_iTunesDB *itdb) -{ - g_return_val_if_fail (itdb != NULL, FALSE); - g_return_val_if_fail (itdb->device != NULL, FALSE); - -#ifdef HAVE_LIBIMOBILEDEVICE - if (itdb->device->iphone_sync_nest_level) { - itdb->device->iphone_sync_nest_level--; - return TRUE; - } - if (itdb_device_is_iphone_family (itdb->device)) { - int sync_status; - if (itdb->device->iphone_sync_context == NULL) { - g_warning ("Trying to unlock an already unlocked device"); - return FALSE; - } - sync_status = itdb_iphone_stop_sync (itdb->device->iphone_sync_context); - itdb->device->iphone_sync_context = NULL; - if (sync_status != 0) { - return FALSE; - } - } -#endif - return TRUE; -} - -/* from here on we have the functions for writing the iTunesDB */ -/* -------------------------------------------------------------------- */ -/* up to here we had the functions for writing the iTunesSD */ - -/* -| Copyright (C) 2005 Jorg Schuler -| Part of the gtkpod project. -| -| Based on itunessd.c written by Steve Wahl for gtkpod-0.88: -| -| Copyright 2005 Steve Wahl -| -| This file contains routines to create the iTunesSD file, as -| used by the ipod shuffle. -| -| Like itunesdb.c, it is derived from the perl script "mktunes.pl" -| (part of the gnupod-tools collection) written by Adrian -| Ulrich . -| -| Small(?) portions derived from itunesdb.c, so Jorg Schuler probably -| has some copyright ownership in this file as well. -| -| 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! -| -*/ - -/* notes: - - All software currently seems to write iTunesDB as well as iTunesSD - on the iPod shuffle. I assume that reading from the iTunesSD file - is not necessary. The iTunesStats file is different, but I leave - that for another day. - - The iTunesSD file format is as follows (taken from WikiPodLinux, feb - '05): - - Offset Field Bytes Value - (hex) (dec) - - iTunesSD header (occurs once, at beginning of file): - - 00 num_songs 3 number of song entries in the file - - 03 unknown 3 always(?) 0x010600 - 06 header size 3 size of the header (0x12, 18 bytes) - 09 unknown 3 possibly zero padding - - iTunesSD song entry format (occurs once for each song) - - 000 size of entry 3 always(?) 0x00022e (558 bytes) - 003 unk1 3 unknown, always(?) 0x5aa501 - 006 starttime 3 Start Time, in 256 ms increments - e.g. 60s = 0xea (234 dec) - 009 unk2 3 unknown (always 0?) - 00C unk3 3 unknown, some relationship to starttime - 00F stoptime 3 Stop Time, also in 256 ms increments. - Zero means play to end of file. - 012 unk4 3 Unknown. - 015 unk5 3 Unknown, but associated with stoptime? - 018 volume 3 Volume - ranges from 0x00 (-100%) to 0x64 - (0%) to 0xc8 (100%) - 01B file_type 3 0x01 = MP3, 0x02 = AAC, 0x04=WAV - 01E unk6 3 unknown (always 0x200?) - 021 filename 522 filename of the song, padded at the end - with 0's. Note: forward slashes are used - here, not colons like in the iTunesDB -- - for example, - "/iPod_Control/Music/F00/Song.mp3" - 22B shuffleflag 1 If this value is 0x00, the song will be - skipped while the player is in shuffle - mode. Any other value will allow it to be - played in both normal and shuffle modes. - iTunes 4.7.1 sets this to 0 for audio books. - 22C bookmarkflag 1 If this flag is 0x00, the song will not be - bookmarkable (i.e. its playback position - won't be saved when switching to a different - song). Any other value wil make it - Bookmarkable. Unlike hard drive based iPods, - all songs can be marked as bookmarkable, - not just .m4b and .aa - 22D unknownflag 1 unknown, always? 0x00. - -All integers in the iTunesSD file are in BIG endian form... - -*/ - -/** - * itdb_shuffle_write: - * @itdb: the #Itdb_iTunesDB to write to disk - * @error: return location for a #GError or NULL - * - * Write out an iTunesSD for the Shuffle. - * - * First reassigns unique IDs to all tracks. An existing "Play - * Counts" file is renamed to "Play Counts.bak" if the export was - * successful. An existing "OTGPlaylistInfo" file is removed if the - * export was successful. @itdb->mountpoint must point to the mount - * point of the iPod, e.g. "/mnt/ipod" and be in local encoding. - * - * Returns: TRUE on success, FALSE on error, in which case @error is - * set accordingly. - */ -gboolean itdb_shuffle_write (Itdb_iTunesDB *itdb, GError **error) -{ - gchar *itunes_filename, *itunes_path; - gboolean result = FALSE; - - g_return_val_if_fail (itdb, FALSE); - g_return_val_if_fail (itdb_get_mountpoint (itdb), FALSE); - - itunes_path = itdb_get_itunes_dir (itdb_get_mountpoint (itdb)); - - if(!itunes_path) - { - gchar *str = g_build_filename (itdb_get_mountpoint (itdb), - "iPod_Control", "iTunes", NULL); - g_set_error (error, - ITDB_FILE_ERROR, - ITDB_FILE_ERROR_NOTFOUND, - _("Path not found: '%s' (or similar)."), - str); - g_free (str); - return FALSE; - } - - itunes_filename = g_build_filename (itunes_path, "iTunesSD", NULL); - - result = itdb_shuffle_write_file (itdb, itunes_filename, error); - - g_free(itunes_filename); - g_free(itunes_path); - - if (result == TRUE) - result = itdb_rename_files (itdb_get_mountpoint (itdb), error); - - /* make sure all buffers are flushed as some people tend to - disconnect as soon as gtkpod returns */ - itdb_fsync (); - - return result; -} - -/* helper function */ -static gboolean haystack (gchar *filetype, gchar **desclist) -{ - gchar **dlp; - if (!filetype || !desclist) return FALSE; - for (dlp=desclist; *dlp; ++dlp) - { - if (strstr (filetype, *dlp)) return TRUE; - } - return FALSE; -} - -/* Converts a standard itunesDB filetype to the one used in itunesSD */ -/* TODO: Find the values for other filetypes */ -static guint convert_filetype (gchar *filetype) -{ - guint32 stype; - /* Type 0x01 Default value */ - /* gchar *mp3_desc[] = {"MPEG", "MP3", "mpeg", "mp3", NULL}; */ - /* Type 0x02 */ - gchar *mp4_desc[] = {"AAC", "MP4", "M4A", "aac", "mp4", "m4a", NULL}; - /* Type 0x04 */ - gchar *wav_desc[] = {"WAV", "wav", NULL}; - - /* Default to mp3 */ - stype=0x01; - - if (haystack (filetype, mp4_desc)) - stype=0x02; - else if (haystack (filetype, wav_desc)) - stype=0x04; - - return stype; -} - -/* Write out the rths header, the iTunesSD track header */ -static gboolean write_rths (WContents *cts, Itdb_Track *track) -{ - gulong rths_seek; - gint padlength; - gchar *path; - - g_return_val_if_fail (cts, FALSE); - g_return_val_if_fail (track, FALSE); - - rths_seek=cts->pos; - - /* Prep the path */ - /* Correct this if the path field changes length */ - path = g_strndup (track->ipod_path, 256); - g_strdelimit (path, ":", '/'); - - put_header (cts, "rths"); - put32lint (cts, -1); /* Length of header to be added later */ - put32lint (cts, track->starttime); /* Start pos in ms */ - put32lint (cts, track->stoptime); /* Stop pos in ms */ - put32lint (cts, track->volume); /* Volume gain */ - put32lint (cts, convert_filetype (track->filetype)); /* Filetype see - convert filetype*/ - /* If the length of this field changes make sure to correct the - g_strndup above */ - put_string (cts, path); /* Path to the song */ - for (padlength = 256-strlen (path); padlength > 0; padlength--) - put8int (cts, 0); - - put32lint (cts, track->bookmark_time); /* Bookmark time */ - /* Don't Skip on shuffle */ - /* This field has the exact opposite value as in the iTunesDB */ - /* 1 when you want to skip, 0 when you don't want to skip. Causes a - * playlist to be skipped if all songs in that playlist have this set */ - put8int (cts, !track->skip_when_shuffling); - put8int (cts, track->remember_playback_position); /* Remember playing pos */ - /* put8int (cts, ); In uninterruptable album */ - put8int (cts, 0); /* Best guess its gapless album */ - put8int (cts, 0); /* Unknown */ - put32lint (cts, track->pregap); /* Pregap */ - put32lint (cts, track->postgap); /* Postgap */ - put32lint (cts, (guint32) track->samplecount); /* Number of Samples */ - put32_n0 (cts, 1); /* Unknown */ - put32lint (cts, track->gapless_data); /* Gapless Data */ - put32_n0 (cts, 1); /* Unknown */ - put32lint (cts, track->priv->album_id); /* Album ID */ - put16lint (cts, track->track_nr); /* Track number */ - put16lint (cts, track->cd_nr); /* Disk number */ - put32_n0 (cts, 2); /* Unknown */ - put64lint (cts, track->dbid); /* Voiceover Filename also dbid*/ - put32lint (cts, track->priv->artist_id); /* Artist ID */ - put32_n0 (cts, 8); /* Unknown */ - - fix_short_header (cts, rths_seek); - - g_free (path); - - return TRUE; -} - -/* Write out the hths header, the iTunesSD track list header*/ -static gboolean write_hths (FExport *fexp) -{ - gulong hths_seek, track_seek; - WContents *cts; - GList *gl; - guint32 trackcnt; - guint32 nonstdtrackcnt; - - g_return_val_if_fail (fexp, FALSE); - g_return_val_if_fail (fexp->itdb, FALSE); - g_return_val_if_fail (fexp->wcontents, FALSE); - - cts = fexp->wcontents; - hths_seek = cts->pos; - trackcnt = itdb_tracks_number(fexp->itdb); - nonstdtrackcnt = 0; /* Counts the number of audiobook and podcast tracks */ - - /* Add Track list offset to bdhs header */ - /* If the bdhs header changes make sure the seek value is still correct */ - put32lint_seek(cts, cts->pos, 36); - - put_header (cts, "hths"); - put32lint (cts, -1); /* Length of header to be added later */ - put32lint (cts, trackcnt); /* Number of tracks */ - put32_n0 (cts, 2); /* Unknown */ - track_seek=cts->pos; /* Put the offset of the first track here */ - put32_n0 (cts, trackcnt); /* The offset for each track to be added later */ - - fix_short_header (cts, hths_seek); - - /* Add each tracks header */ - for(gl=fexp->itdb->tracks;gl;gl=gl->next) - { - Itdb_Track *track = gl->data; - - /* Add this tracks offset then add the track */ - put32lint_seek(cts, cts->pos, track_seek); - g_return_val_if_fail (write_rths(cts, track), FALSE); - - if ((track->mediatype & ITDB_MEDIATYPE_AUDIOBOOK) || - (track->mediatype & ITDB_MEDIATYPE_PODCAST)) - nonstdtrackcnt++; - - /* Go to the offset for the next track */ - track_seek += 4; - } - /* Add the number of nonpodcasts to bdhs header */ - /* If the bdhs header changes make sure the seek value is still correct */ - put32lint_seek(cts, trackcnt-nonstdtrackcnt, 32); - return TRUE; -} - -/* Write out the lphs header, the iTunesSD playlist header */ -static gboolean write_lphs (WContents *cts, Itdb_Playlist *pl) -{ - gulong lphs_seek; - GList *tl, *tracks, *current_track; - Itdb_Track *tr, *ctr; - guint64 id; - guint32 stype; - guint32 tracknum; - guint32 nonstdtrackcnt; - - g_return_val_if_fail (pl, FALSE); - g_return_val_if_fail (pl->itdb, FALSE); - g_return_val_if_fail (cts, FALSE); - - lphs_seek = cts->pos; - tracks = pl->itdb->tracks; - nonstdtrackcnt = 0; /* The number of audiobook and podcast tracks*/ - /* Change the playlist itunesDB type into a itunesSD type */ - /* 1 is for the master playlist - 2 is for a normal playlist - 3 is for the podcast playlist - 4 is for an audiobook playlist */ - if (itdb_playlist_is_mpl (pl)) - stype = 1; - else if (itdb_playlist_is_podcasts (pl)) - stype = 3; - else if (itdb_playlist_is_audiobooks (pl)) - stype = 4; - else /* Everything else we treat as normal */ - stype = 2; - - put_header (cts, "lphs"); - put32lint (cts, -1); /* Length of header to be written later */ - put32lint (cts, pl->num); /* Number of tracks in this playlist */ - put32lint (cts, -1); /* The number of non podcasts or audiobooks to be added later */ - - if (stype == 1) - put32_n0 (cts, 2); /* The voiceover for the master is at 0 */ - else - put64lint (cts, pl->id); /* Voiceover filename also Playlist ID */ - - put32lint (cts, stype); /* Type of playlist */ - put32_n0 (cts, 4); /* Unknown */ - if (tracks) /* It makes no sense to do the following if there is no tracks */ - { - /* Walk the playlist and find and write out the track number - of each track in it */ - for( tl = pl -> members; tl; tl = tl->next) - { - tracknum = 0; - current_track= tracks; - tr = tl->data; - id = tr->dbid; - ctr = current_track->data; - /* Count the number of podcasts and audiobooks for later use */ - if ((tr->mediatype & ITDB_MEDIATYPE_AUDIOBOOK) || - (tr->mediatype & ITDB_MEDIATYPE_PODCAST)) - nonstdtrackcnt++; - - while( id != ctr->dbid) - { - tracknum ++; - current_track = current_track->next; - g_return_val_if_fail (current_track, FALSE); - ctr = current_track->data; - } - put32lint (cts, tracknum); - } - } - - put32lint_seek (cts, pl->num - nonstdtrackcnt, lphs_seek+12); - fix_short_header (cts, lphs_seek); - return TRUE; - -} - -/* Write out the hphs header, the iTunesSD playlist list header */ -static gboolean write_hphs (FExport *fexp) -{ - gulong hphs_seek, playlist_seek; - WContents *cts; - GList *gl; - guint16 playlistcnt; - guint16 podcastscnt; - guint16 mastercnt; - guint16 audiobookscnt; - - g_return_val_if_fail (fexp, FALSE); - g_return_val_if_fail (fexp->itdb, FALSE); - g_return_val_if_fail (fexp->wcontents, FALSE); - - cts = fexp->wcontents; - hphs_seek = cts->pos; - playlistcnt = 0; /* Number of nonempty playlists, this may not be the same - as what itdb_playlists_number returns*/ - podcastscnt = 0; /* Number of podcast playlists should be 1 if one exists*/ - mastercnt = 0; /* Number of master playlists should be 1 */ - audiobookscnt = 0; /* Number of audiobook playlists */ - - /* We have to walk the playlist list before we can write the needed counts */ - for (gl = fexp->itdb->playlists; gl; gl = gl->next) { - Itdb_Playlist *pl = gl->data; - - if (!pl->members) { - /* If the playlist has no members skip it */ - continue; - } - /* Otherwise count it and count its type */ - playlistcnt++; - - if (itdb_playlist_is_mpl (pl)){ - mastercnt++; - } - else if (itdb_playlist_is_podcasts (pl)) { - podcastscnt++; - } - else if (itdb_playlist_is_audiobooks (pl)) { - audiobookscnt++; - } - } - - /* Add nonempty playlist count to bdhs */ - put32lint_seek (cts, playlistcnt, 16); - - /* Add the Playlist Header Offset */ - put32lint_seek (cts, cts->pos, 40); - - put_header (cts, "hphs"); - put32lint (cts, -1); /* Length of header to be added later */ - put16lint (cts, playlistcnt); /* Number of playlists */ - put16_n0 (cts, 1); /* Unknown */ - put16lint (cts, playlistcnt-podcastscnt); /* Number of non podcast playlists - there should be a maximum of 1 - podcast playlist. */ - put16lint (cts, mastercnt); /* Number of master playlists there should be a - maximum of 1 master playlist */ - put16lint (cts, playlistcnt-audiobookscnt); /* Number of non audiobook playlists */ - put16_n0 (cts, 1); - - playlist_seek = cts->pos; - put32_n0 (cts, playlistcnt); /* Offsets for each playlist */ - - fix_short_header (cts, hphs_seek); - - for (gl = fexp->itdb->playlists; gl; gl = gl->next) - { - Itdb_Playlist *pl = gl->data; - - if (!pl->members) { - continue; - } - - /* Write this playlist's header offset */ - put32lint_seek (cts, cts->pos, playlist_seek); - - g_return_val_if_fail (write_lphs (cts, pl),FALSE); - - /* Move to the field for the next header */ - playlist_seek += 4; - } - return TRUE; -} - -/* Write out the bdhs header, the main iTunesSD header */ -static gboolean write_bdhs (FExport *fexp) -{ - WContents *cts; - gulong bdhs_seek; - guint32 trackcnt; - - g_return_val_if_fail (fexp, FALSE); - g_return_val_if_fail (fexp->itdb, FALSE); - g_return_val_if_fail (fexp->wcontents, FALSE); - - cts = fexp->wcontents; - bdhs_seek = cts->pos; - trackcnt = itdb_tracks_number (fexp->itdb); - - put_header (cts, "bdhs"); - put32lint (cts, 0x02000003); /* Unknown */ - put32lint (cts, -1); /* Length of header to be added later*/ - put32lint (cts, trackcnt); /* Number of tracks */ - put32_n0 (cts, 1); /* Number of nonempty playlists to be written later - in write_hphs see the note below! */ - put32_n0 (cts, 2); /* Unknown */ - /* TODO: Parse the max volume */ - put8int (cts, 0); /* Limit max volume currently ignored and set to off */ - /* TODO: Find another source of the voiceover option */ - put8int (cts, 1); /* Voiceover currently ignored and set to on */ - put16_n0 (cts, 1); /* Unknown */ - - /* NOTE: If the bdhs header changes the offsets of these fields may change - make sure you correct the field offset in their respective tags */ - - put32lint (cts, -1); /* Number of tracks excluding podcasts and audiobooks - added in write_hths */ - put32lint (cts, -1); /* Track Header Offset added in write_hths */ - put32lint (cts, -1); /* Playlist Header Offset added in write_hphs */ - - put32_n0 (cts, 5); /* Unknown */ - - fix_header(cts, bdhs_seek); - - return TRUE; -} - -static gboolean is_shuffle_2g (Itdb_Device *device) -{ - return (itdb_device_get_shadowdb_version (device) == ITDB_SHADOW_DB_V1); -} - -/** - * itdb_shuffle_write_file: - * @itdb: the #Itdb_iTunesDB to write to disk - * @filename: file to write to, cannot be NULL - * @error: return location for a #GError or NULL - * - * Do the actual writing to the iTunesSD - * - * Returns: TRUE on success, FALSE on error, in which case @error is - * set accordingly. - */ -gboolean itdb_shuffle_write_file (Itdb_iTunesDB *itdb, - const gchar *filename, GError **error) -{ - FExport *fexp; - GList *gl; - WContents *cts; - gboolean result = TRUE;; - - g_return_val_if_fail (itdb, FALSE); - g_return_val_if_fail (filename, FALSE); - g_return_val_if_fail (itdb->device, FALSE); - - /* Set endianess flag just in case */ - if (!itdb->device->byte_order) - itdb_device_autodetect_endianess (itdb->device); - - fexp = g_new0 (FExport, 1); - fexp->itdb = itdb; - fexp->wcontents = wcontents_new (filename); - cts = fexp->wcontents; - - cts->reversed = (itdb->device->byte_order == G_BIG_ENDIAN); - - prepare_itdb_for_write (fexp); - - if (is_shuffle_2g (itdb->device)) { - /* Older iTunesSD format */ - put24bint (cts, itdb_tracks_number (itdb)); - put24bint (cts, 0x010600); - put24bint (cts, 0x12); /* size of header */ - put24bint (cts, 0x0); /* padding? */ - put24bint (cts, 0x0); - put24bint (cts, 0x0); - - for (gl=itdb->tracks; gl; gl=gl->next) - { - Itdb_Track *tr = gl->data; - gchar *path; - gunichar2 *path_utf16; - glong pathlen; - - g_return_val_if_fail (tr, FALSE); - - put24bint (cts, 0x00022e); - put24bint (cts, 0x5aa501); - /* starttime is in 256 ms incr. for shuffle */ - put24bint (cts, tr->starttime / 256); - put24bint (cts, 0); - put24bint (cts, 0); - put24bint (cts, tr->stoptime / 256); - put24bint (cts, 0); - put24bint (cts, 0); - /* track->volume ranges from -255 to +255 */ - /* we want 0 - 200 */ - put24bint (cts, ((tr->volume + 255) * 201) / 511); - - /* The next one should be 0x01 for MP3, - ** 0x02 for AAC, and 0x04 for WAV, but I can't find - ** a suitable indicator within the track structure? */ - /* JCS: let's do heuristic on tr->filetype which would contain - "MPEG audio file", "AAC audio file", "Protected AAC audio - file", "AAC audio book file", "WAV audio file" (or similar - if not written by gtkpod) */ - - put24bint (cts, convert_filetype (tr->filetype)); - - put24bint (cts, 0x200); - - path = g_strdup (tr->ipod_path); - /* shuffle uses forward slash separator, not colon */ - g_strdelimit (path, ":", '/'); - path_utf16 = g_utf8_to_utf16 (path, -1, NULL, &pathlen, NULL); - if (pathlen > 261) pathlen = 261; - fixup_little_utf16 (path_utf16); - put_data (cts, (gchar *)path_utf16, sizeof (gunichar2)*pathlen); - /* pad to 522 bytes */ - put16_n0 (cts, 261-pathlen); - g_free(path); - g_free(path_utf16); - - put8int (cts, tr->skip_when_shuffling); /* Is the song used in shuffle mode */ - put8int (cts, tr->remember_playback_position); /* Is the song bookmarkable */ - put8int (cts, 0); - } - }else { - /* Newer iTunesSD format */ - /* Add the main Header */ - write_bdhs(fexp); - - /* Add the Tracks Header */ - if (!write_hths(fexp)){ - g_set_error(&fexp->error, - ITDB_FILE_ERROR, - ITDB_FILE_ERROR_CORRUPT, - _("Error writing list of tracks (hths)")); - goto serr; - } - - /* Add the Playlist Header */ - if(!fexp->error && !write_hphs(fexp)){ - g_set_error(&fexp->error, - ITDB_FILE_ERROR, - ITDB_FILE_ERROR_CORRUPT, - _("Error writing playlists (hphs)")); - goto serr; - } - } - if (!fexp->error) - { - if (!wcontents_write (cts)) - g_propagate_error (&fexp->error, cts->error); - } -serr: - if (fexp->error) - { - g_propagate_error (error, fexp->error); - result = FALSE; - } - wcontents_free (cts); - g_free (fexp); - - /* make sure all buffers are flushed as some people tend to - disconnect as soon as gtkpod returns */ - itdb_fsync (); - - return result; -} - -/*------------------------------------------------------------------*\ - * * - * Other file/filename stuff * - * * -\*------------------------------------------------------------------*/ - -/** - * itdb_rename_files: - * @mp: mount point of the iPod - * @error: return location for a #GError or NULL - * - * Renames/removes some files on the iPod (Playcounts, OTG - * semaphore). May have to be called if you write the iTunesDB not - * directly to the iPod but to some other location and then manually - * copy the file from there to the iPod. - * - * Returns: FALSE on error and sets @error accordingly - */ -gboolean itdb_rename_files (const gchar *mp, GError **error) -{ - const gchar *db_plc_o[] = {"Play Counts", NULL}; - const gchar *db_otg[] = {"OTGPlaylistInfo", NULL}; - const gchar *db_shu[] = {"iTunesShuffle", NULL}; - const gchar *db_ist[] = {"iTunesStats", NULL}; - gchar *itunesdir; - gchar *plcname_o; - gchar *plcname_n; - gchar *otgname; - gchar *shuname; - gchar *istname; - gboolean result = TRUE; - - g_return_val_if_fail (mp, FALSE); - - itunesdir = itdb_get_itunes_dir (mp); - - if(!itunesdir) - { - error_no_itunes_dir (mp, error); - return FALSE; - } - - plcname_o = itdb_resolve_path (itunesdir, db_plc_o); - plcname_n = g_build_filename (itunesdir, - "Play Counts.bak", NULL); - otgname = itdb_resolve_path (itunesdir, db_otg); - shuname = itdb_resolve_path (itunesdir, db_shu); - istname = itdb_resolve_path (itunesdir, db_ist); - - /* rename "Play Counts" to "Play Counts.bak" */ - if (plcname_o) - { - if (rename (plcname_o, plcname_n) == -1) - { /* an error occured */ - g_set_error (error, - G_FILE_ERROR, - g_file_error_from_errno (errno), - _("Error renaming '%s' to '%s' (%s)."), - plcname_o, plcname_n, g_strerror (errno)); - result = FALSE; - } - } - - /* remove "OTGPlaylistInfo" (the iPod will remove the remaining - * files */ - if (otgname) - { - if (unlink (otgname) == -1) - { - if (error && !*error) - { /* don't overwrite previous error */ - g_set_error (error, - G_FILE_ERROR, - g_file_error_from_errno (errno), - _("Error removing '%s' (%s)."), - otgname, g_strerror (errno)); - } - result = FALSE; - } - } - - /* remove some Shuffle files */ - if (shuname) - { - if (unlink (shuname) == -1) - { - if (error && !*error) - { /* don't overwrite previous error */ - g_set_error (error, - G_FILE_ERROR, - g_file_error_from_errno (errno), - _("Error removing '%s' (%s)."), - shuname, g_strerror (errno)); - } - result = FALSE; - } - } - - /* remove some Shuffle files */ - if (istname) - { - if (unlink (istname) == -1) - { - if (error && !*error) - { /* don't overwrite previous error */ - g_set_error (error, - G_FILE_ERROR, - g_file_error_from_errno (errno), - _("Error removing '%s' (%s)."), - istname, g_strerror (errno)); - } - result = FALSE; - } - } - - g_free (plcname_o); - g_free (plcname_n); - g_free (otgname); - g_free (shuname); - g_free (istname); - g_free (itunesdir); - - return result; -} - - -/** - * itdb_filename_fs2ipod: - * @filename: a 'PC-style' filename (eg /iPod_Control/Music/f00/test.mp3) - * - * Convert string from casual PC file name to iPod iTunesDB format - * using ':' instead of G_DIR_SEPARATOR_S (i.e. slashes on Unix-like - * systems). @ipod_file is modified in place. - */ -void itdb_filename_fs2ipod (gchar *ipod_file) -{ - g_strdelimit (ipod_file, G_DIR_SEPARATOR_S, ':'); -} - -/** - * itdb_filename_ipod2fs: - * @ipod_file: a 'PC-style' filename (eg /iPod_Control/Music/f00/test.mp3) - * - * Convert string from from iPod iTunesDB format to casual PC file - * name using G_DIR_SEPARATOR (ie slashes on Unix-like systems) - * instead of ':'. @ipod_file is modified in place. - */ -void itdb_filename_ipod2fs (gchar *ipod_file) -{ - g_strdelimit (ipod_file, ":", G_DIR_SEPARATOR); -} - -/** - * itdb_set_mountpoint: - * @itdb: an #Itdb_iTunesDB - * @mp: new mount point - * - * Sets the mountpoint of @itdb. Always use this function to set the - * mountpoint of an #Itdb_iTunesDB as it will reset the number of - * available /iPod_Control/Music/F.. dirs. It doesn't attempt to parse - * an iPod database that may be present on the iPod at @mp. - * - * Calling this function removes the artwork in the - * #Itdb_iTunesDB database using this #Itdb_Device which was read from the - * iPod.. - * - * Since: 0.1.3 - */ -void itdb_set_mountpoint (Itdb_iTunesDB *itdb, const gchar *mp) -{ - g_return_if_fail (itdb); - g_return_if_fail (itdb->device); - - itdb_device_set_mountpoint (itdb->device, mp); - itdb->device->musicdirs = 0; -} - -/** - * itdb_get_mountpoint: - * @itdb: an #Itdb_iTunesDB - * - * Retrieve a reference to the mountpoint of @itdb - * - * Returns: the @itdb mountpoint, this string shouldn't be freed - * nor modified - * - * Since: 0.4.0 - */ -const gchar *itdb_get_mountpoint (Itdb_iTunesDB *itdb) -{ - g_return_val_if_fail (itdb, NULL); - g_return_val_if_fail (itdb->device, NULL); - return itdb->device->mountpoint; -} - -/* Retrieve a reference to the mountpoint */ -const gchar *itdb_photodb_get_mountpoint (Itdb_PhotoDB *photodb) -{ - g_return_val_if_fail (photodb, NULL); - g_return_val_if_fail (photodb->device, NULL); - return photodb->device->mountpoint; -} - -/** - * itdb_musicdirs_number: - * @itdb: an #Itdb_iTunesDB - * - * Determine the number of F.. directories in iPod_Control/Music. - * - * If @itdb->musicdirs is already set, simply return the previously - * determined number. Otherwise count the directories first and set - * @itdb->musicdirs. - * - * Returns: max number of directories in iPod_Control/Music - * - * Since: 0.1.3 - */ -gint itdb_musicdirs_number (Itdb_iTunesDB *itdb) -{ - g_return_val_if_fail (itdb, 0); - g_return_val_if_fail (itdb->device, 0); - - return itdb_device_musicdirs_number (itdb->device); -} - -/** - * itdb_cp_get_dest_filename: - * @track: track to transfer or NULL - * @mountpoint: mountpoint of your iPod or NULL - * @filename: the source file - * @error: return location for a #GError or NULL - * - * Creates a valid filename on the iPod where @filename can be copied. - * - * You must provide either @track or @mountpoint. Providing @track is - * not thread-safe (accesses track->itdb->device and may even write to - * track->itdb->device). Providing @mountpoint is thread-safe but - * slightly slower because the number of music directories is counted - * each time the function is called. - * - * You can use itdb_cp() to copy the track to the iPod or implement - * your own copy function. After the file was copied you have to call - * itdb_cp_finalize() to obtain relevant update information for - * #Itdb_Track. - * - * Returns: a valid filename on the iPod where @filename can be - * copied or NULL in case of an error. In that case @error is set - * accordingly. You must free the filename when it is no longer - * needed. - * - * Since: 0.5.0 - */ -gchar *itdb_cp_get_dest_filename (Itdb_Track *track, - const gchar *mountpoint, - const gchar *filename, - GError **error) -{ - gchar *ipod_fullfile = NULL; - - /* either supply mountpoint or track */ - g_return_val_if_fail (mountpoint || track, NULL); - /* if mountpoint is not set, track->itdb is required */ - g_return_val_if_fail (mountpoint || track->itdb, NULL); - g_return_val_if_fail (filename, NULL); - - if (!mountpoint) - { - mountpoint = itdb_get_mountpoint (track->itdb); - } - - if (!mountpoint) - { - g_set_error (error, - ITDB_FILE_ERROR, - ITDB_FILE_ERROR_NOTFOUND, - _("Mountpoint not set.")); - return NULL; - } - - /* If track->ipod_path exists, we use that one instead. */ - if (track) - { - ipod_fullfile = itdb_filename_on_ipod (track); - } - - if (!ipod_fullfile) - { - gint dir_num, musicdirs_number; - gchar *dest_components[] = {NULL, NULL, NULL}; - gchar *parent_dir_filename, *music_dir; - gchar *original_suffix; - gchar dir_num_str[6]; - gint32 oops = 0; - gint32 rand = g_random_int_range (0, 899999); /* 0 to 900000 */ - - music_dir = itdb_get_music_dir (mountpoint); - if (!music_dir) - { - error_no_music_dir (mountpoint, error); - return NULL; - } - - if (track) - { - musicdirs_number = itdb_musicdirs_number (track->itdb); - } - else - { - musicdirs_number = itdb_musicdirs_number_by_mountpoint (mountpoint); - } - if (musicdirs_number <= 0) - { - g_set_error (error, - ITDB_FILE_ERROR, - ITDB_FILE_ERROR_NOTFOUND, - _("No 'F..' directories found in '%s'."), - music_dir); - g_free (music_dir); - return NULL; - } - - dir_num = g_random_int_range (0, musicdirs_number); - - g_snprintf (dir_num_str, 6, "F%02d", dir_num); - dest_components[0] = dir_num_str; - - parent_dir_filename = - itdb_resolve_path (music_dir, (const gchar **)dest_components); - if(parent_dir_filename == NULL) - { - /* Can't find the F%02d directory */ - gchar *str = g_build_filename (music_dir, - dest_components[0], NULL); - g_set_error (error, - ITDB_FILE_ERROR, - ITDB_FILE_ERROR_NOTFOUND, - _("Path not found: '%s'."), - str); - g_free (str); - g_free (music_dir); - return NULL; - } - - /* we need the original suffix of pcfile to construct a correct ipod - filename */ - original_suffix = strrchr (filename, '.'); - /* If there is no ".mp3", ".m4a" etc, set original_suffix to empty - string. Note: the iPod will most certainly ignore this file... */ - if (!original_suffix) original_suffix = ""; - - /* use lower-case version of extension as some iPods seem to - choke on upper-case extension. */ - original_suffix = g_ascii_strdown (original_suffix, -1); - - do - { /* we need to loop until we find an unused filename */ - dest_components[1] = - g_strdup_printf("libgpod%06d%s", - rand + oops, original_suffix); - ipod_fullfile = itdb_resolve_path ( - parent_dir_filename, - (const gchar **)&dest_components[1]); - if(ipod_fullfile) - { /* already exists -- try next */ - g_free(ipod_fullfile); - ipod_fullfile = NULL; - } - else - { /* found unused file -- build filename */ - ipod_fullfile = g_build_filename (parent_dir_filename, - dest_components[1], NULL); - } - g_free (dest_components[1]); - ++oops; - } while (!ipod_fullfile); - g_free(parent_dir_filename); - g_free (music_dir); - g_free (original_suffix); - } - - return ipod_fullfile; -} - -/** - * itdb_cp_finalize: - * @track: track to update or NULL - * @mountpoint: mountpoint of your iPod or NULL - * @dest_filename: the name of the file on the iPod copied to - * @error: return location for a #GError or NULL - * - * Updates information in @track necessary for the iPod. - * - * You must supply either @track or @mountpoint. If @track == NULL, a - * new track structure is created that must be freed with - * itdb_track_free() when it is no longer needed. - * - * The following @track fields are updated: - * - * - * - * ipod_path - * - * - * filetype_marker - * - * - * transferred - * - * - * size - * - * - * - * Returns: on success a pointer to the #Itdb_Track item passed - * or a new #Itdb_Track item if @track was NULL. In the latter case - * you must free the memory using itdb_track_free() when the item is - * no longer used. If an error occurs NULL is returned and @error is - * set accordingly. Errors occur when @dest_filename cannot be - * accessed or the mountpoint is not set. - * - * Since: 0.5.0 - */ -Itdb_Track *itdb_cp_finalize (Itdb_Track *track, - const gchar *mountpoint, - const gchar *dest_filename, - GError **error) -{ - const gchar *suffix; - Itdb_Track *use_track; - gint i, mplen; - struct stat statbuf; - - /* either supply mountpoint or track */ - g_return_val_if_fail (mountpoint || track, NULL); - /* if mountpoint is not set, track->itdb is required */ - g_return_val_if_fail (mountpoint || track->itdb, NULL); - g_return_val_if_fail (dest_filename, NULL); - - if (!mountpoint) - { - mountpoint = itdb_get_mountpoint (track->itdb); - } - - if (!mountpoint) - { - g_set_error (error, - ITDB_FILE_ERROR, - ITDB_FILE_ERROR_NOTFOUND, - _("Mountpoint not set.")); - return NULL; - } - - if (stat (dest_filename, &statbuf) == -1) - { - g_set_error (error, - G_FILE_ERROR, - g_file_error_from_errno (errno), - _("'%s' could not be accessed (%s)."), - dest_filename, g_strerror (errno)); - return NULL; - } - - if (strlen (mountpoint) >= strlen (dest_filename)) - { - g_set_error (error, - ITDB_FILE_ERROR, - ITDB_FILE_ERROR_CORRUPT, - _("Destination file '%s' does not appear to be on the iPod mounted at '%s'."), - dest_filename, mountpoint); - return NULL; - } - - if (!track) - { - use_track = itdb_track_new (); - } - else - { - use_track = track; - } - - use_track->transferred = TRUE; - use_track->size = statbuf.st_size; - - /* we need the original suffix of pcfile to construct a correct ipod - filename */ - suffix = strrchr (dest_filename, '.'); - /* If there is no ".mp3", ".m4a" etc, set original_suffix to empty - string. Note: the iPod will most certainly ignore this file... */ - if (!suffix) suffix = "."; - - /* set filetype from the suffix, e.g. '.mp3' -> 'MP3 ' */ - use_track->filetype_marker = 0; - for (i=1; i<=4; ++i) /* start with i=1 to skip the '.' */ - { - use_track->filetype_marker = use_track->filetype_marker << 8; - if (strlen (suffix) > i) - use_track->filetype_marker |= g_ascii_toupper (suffix[i]); - else - use_track->filetype_marker |= ' '; - } - - /* now extract filepath for use_track->ipod_path from ipod_fullfile */ - /* ipod_path must begin with a '/' */ - g_free (use_track->ipod_path); - mplen = strlen (mountpoint); /* length of mountpoint in bytes */ - if (dest_filename[mplen] == G_DIR_SEPARATOR) - { - use_track->ipod_path = g_strdup (&dest_filename[mplen]); - } - else - { - use_track->ipod_path = g_strdup_printf ("%c%s", G_DIR_SEPARATOR, - &dest_filename[mplen]); - } - /* convert to iPod type */ - itdb_filename_fs2ipod (use_track->ipod_path); - - return use_track; -} - -/** - * itdb_cp_track_to_ipod: - * @track: the #Itdb_Track to copy (containing @filename metadata) - * @filename: the source file - * @error: return location for a #GError or NULL - * - * Copy one track to the iPod. The PC filename is @filename - * and is taken literally. - * - * The mountpoint of the iPod (in local encoding) must have been set - * with itdb_set_mountpoint() (done automatically when reading an - * iTunesDB). - * - * If @track->transferred is set to TRUE, nothing is done. Upon - * successful transfer @track->transferred is set to TRUE. - * - * For storage, the directories "F00 ... Fnn" will be used randomly. - * - * The filename is constructed as "libgpod@random_number" and copied - * to @track->ipod_path. If this file already exists, @random_number - * is adjusted until an unused filename is found. - * - * If @track->ipod_path is already set, this one will be used - * instead. If a file with this name already exists, it will be - * overwritten. - * - * @track->filetype_marker is set according to the filename extension - * - * Returns: TRUE on success, FALSE on error, in which case @error is - * set accordingly. - */ -gboolean itdb_cp_track_to_ipod (Itdb_Track *track, - const gchar *filename, GError **error) -{ - gchar *dest_filename; - gboolean result = FALSE; - - g_return_val_if_fail (track, FALSE); - g_return_val_if_fail (track->itdb, FALSE); - g_return_val_if_fail (itdb_get_mountpoint (track->itdb), FALSE); - g_return_val_if_fail (filename, FALSE); - - if(track->transferred) return TRUE; /* nothing to do */ - - dest_filename = itdb_cp_get_dest_filename (track, NULL, filename, error); - - if (dest_filename) - { - if (itdb_cp (filename, dest_filename, error)) - { - if (itdb_cp_finalize (track, NULL, dest_filename, error)) - { - result = TRUE; - } - } - g_free (dest_filename); - } - - return result; -} - -/** - * itdb_filename_on_ipod: - * @track: an #Itdb_Track - * - * Get the full iPod filename as stored in @track. - * - * - * NULL is returned when the file does not exist. - * - * - * - * This code works around a problem on some systems (see - * itdb_resolve_path()) and might return a filename with different - * case than the original filename. Don't copy it back to @track if - * you can avoid it. - * - * - * Returns: full filename to @track on the iPod or NULL if no - * filename is set in @track. Must be freed with g_free() after use. - */ -gchar *itdb_filename_on_ipod (Itdb_Track *track) -{ - gchar *result = NULL; - gchar *buf; - const gchar *mp; - - g_return_val_if_fail (track, NULL); - - if (!track->ipod_path || !*track->ipod_path) - { /* No filename set */ - return NULL; - } - - g_return_val_if_fail (track->itdb, NULL); - - if (!itdb_get_mountpoint (track->itdb)) return NULL; - - mp = itdb_get_mountpoint (track->itdb); - - buf = g_strdup (track->ipod_path); - itdb_filename_ipod2fs (buf); - result = g_build_filename (mp, buf, NULL); - g_free (buf); - - if (!g_file_test (result, G_FILE_TEST_EXISTS)) - { - gchar **components = g_strsplit (track->ipod_path,":",10); - g_free (result); - result = itdb_resolve_path (mp, (const gchar **)components); - g_strfreev (components); - } - - return result; -} - -/* Use open instead of fopen. fwrite is really slow on the Mac. */ -/** - * itdb_cp: - * @from_file: source file - * @to_file: destination file - * @error: return location for a #GError or NULL - * - * Copy file @from_file to @to_file. - * - * Returns: TRUE on success, FALSE on error, in which case @error is - * set accordingly. - */ -gboolean itdb_cp (const gchar *from_file, const gchar *to_file, - GError **error) -{ -#ifndef O_BINARY -#define O_BINARY 0 -#endif - gchar *data; - glong bread, bwrite; - int file_in = -1; - int file_out = -1; - -#if ITUNESDB_DEBUG - fprintf(stderr, "Entered itunesdb_cp: '%s', '%s'\n", from_file, to_file); -#endif - - g_return_val_if_fail (from_file, FALSE); - g_return_val_if_fail (to_file, FALSE); - - data = g_malloc (ITUNESDB_COPYBLK); - - file_in = g_open (from_file, O_RDONLY | O_BINARY, 0); - if (file_in < 0) - { - g_set_error (error, - G_FILE_ERROR, - g_file_error_from_errno (errno), - _("Error opening '%s' for reading (%s)."), - from_file, g_strerror (errno)); - goto err_out; - } - - file_out = g_open (to_file, O_CREAT|O_WRONLY|O_TRUNC|O_BINARY, 0777); - if (file_out < 0) - { - g_set_error (error, - G_FILE_ERROR, - g_file_error_from_errno (errno), - _("Error opening '%s' for writing (%s)."), - to_file, g_strerror (errno)); - goto err_out; - } - - do { - bread = read (file_in, data, ITUNESDB_COPYBLK); -#if ITUNESDB_DEBUG - fprintf(stderr, "itunesdb_cp: read %ld bytes\n", bread); -#endif - if (bread < 0) - { - /* error -- not end of file! */ - g_set_error (error, - G_FILE_ERROR, - g_file_error_from_errno (errno), - _("Error while reading from '%s' (%s)."), - from_file, g_strerror (errno)); - goto err_out; - } - else - { - bwrite = write (file_out, data, bread); -#if ITUNESDB_DEBUG - fprintf(stderr, "itunesdb_cp: wrote %ld bytes\n", bwrite); -#endif - if (bwrite != bread) - { - g_set_error (error, - G_FILE_ERROR, - g_file_error_from_errno (errno), - _("Error while writing to '%s' (%s)."), - to_file, g_strerror (errno)); - goto err_out; - } - } - } while (bread != 0); - - if (close (file_in) != 0) - { - file_in = -1; - g_set_error (error, - G_FILE_ERROR, - g_file_error_from_errno (errno), - _("Error when closing '%s' (%s)."), - from_file, g_strerror (errno)); - goto err_out; - } - if (close (file_out) != 0) - { - file_out = -1; - g_set_error (error, - G_FILE_ERROR, - g_file_error_from_errno (errno), - _("Error when closing '%s' (%s)."), - to_file, g_strerror (errno)); - goto err_out; - } - g_free (data); - return TRUE; - - err_out: - if (file_in >= 0) close (file_in); - if (file_out >= 0) close (file_out); - g_unlink (to_file); - g_free (data); - return FALSE; -} - -/** - * itdb_get_control_dir: - * @mountpoint: the iPod mountpoint - * - * Get the i*_Control directory. Observed values are 'iPod_Control' - * for standard iPods and 'iTunes/iTunes_Control' for mobile - * applications. - * - * Returns: path to the control dir or NULL if non-existent. Must - * g_free() after use. - * - * Since: 0.4.0 - */ -gchar *itdb_get_control_dir (const gchar *mountpoint) -{ - gchar *p_iphone[] = {"iTunes_Control", NULL}; - gchar *p_ipod[] = {"iPod_Control", NULL}; - gchar *p_mobile[] = {"iTunes", "iTunes_Control", NULL}; - /* Use an array with all possibilities, so further extension will - be easy. It is important that checking for the iTunes_Control - directory be done before the iPod_Control directory because - some devices actually have both directories. The iTunes_Control - directory is correct in these cases. This happens for devices - that Apple shipped with the iPod_Control as the control directory - and later switched to iTunes_Control instead. iTunes appears to - remove files from the old iPod_Control directory but leave the - directory structure intact. Finding this empty directory structure - first will result in a failure to find files. */ - gchar **paths[] = {p_iphone, p_ipod, p_mobile, NULL}; - gchar ***ptr; - gchar *result = NULL; - - g_return_val_if_fail (mountpoint, NULL); - - for (ptr=paths; *ptr && !result; ++ptr) - { - result = itdb_resolve_path (mountpoint, (const gchar **)*ptr); - } - return result; -} - -/** - * itdb_get_dir: - * @mountpoint: the iPod mountpoint - * @dir: a directory - * - * Retrieve the directory @dir by first calling itdb_get_control_dir() - * and then adding @dir - * - * Returns: path to @dir or NULL if non-existent. Must g_free() - * after use. - */ -static gchar *itdb_get_dir (const gchar *mountpoint, const gchar *dir) -{ - gchar *control_dir; - gchar *result = NULL; - - g_return_val_if_fail (mountpoint, NULL); - g_return_val_if_fail (dir, NULL); - - control_dir = itdb_get_control_dir (mountpoint); - if (control_dir) - { - const gchar *p_dir[] = {NULL, NULL}; - p_dir[0] = dir; - result = itdb_resolve_path (control_dir, p_dir); - g_free (control_dir); - } - return result; -} - -/** - * itdb_get_path: - * @dir: a directory - * @file: a file - * - * Retrieve a path to the @file in @dir - * - * Returns: path to the @file or NULL if non-existent. Must g_free() - * after use. - * - * Since: 0.4.0 - */ -gchar *itdb_get_path (const gchar *dir, const gchar *file) -{ - const gchar *p_file[] = {NULL, NULL}; - - g_return_val_if_fail (dir, NULL); - - p_file[0] = file; - - return itdb_resolve_path (dir, p_file); -} - -/** - * itdb_get_itunes_dir: - * @mountpoint: the iPod mountpoint - * - * Retrieve the iTunes directory (containing the iTunesDB) by first - * calling itdb_get_control_dir() and then adding 'iTunes' - * - * Returns: path to the iTunes directory or NULL if non-existent. - * Must g_free() after use. - * - * Since: 0.4.0 - */ -gchar *itdb_get_itunes_dir (const gchar *mountpoint) -{ - g_return_val_if_fail (mountpoint, NULL); - - return itdb_get_dir (mountpoint, "iTunes"); -} - -/** - * itdb_get_music_dir: - * @mountpoint: the iPod mountpoint - * - * Retrieve the Music directory (containing the Fnn dirs) by first - * calling itdb_get_control_dir() and then adding 'Music' - * - * Returns: path to the Music directory or NULL if - * non-existent. Must g_free() after use. - */ -gchar *itdb_get_music_dir (const gchar *mountpoint) -{ - g_return_val_if_fail (mountpoint, NULL); - - return itdb_get_dir (mountpoint, "Music"); -} - -/** - * itdb_get_device_dir: - * @mountpoint: the iPod mountpoint - * - * Retrieve the Device directory (containing the SysInfo file) by - * first calling itdb_get_control_dir() and then adding 'Device' - * - * Returns: path to the Device directory or NULL if - * non-existent. Must g_free() after use. - * - * Since: 0.4.0 - */ -gchar *itdb_get_device_dir (const gchar *mountpoint) -{ - g_return_val_if_fail (mountpoint, NULL); - - return itdb_get_dir (mountpoint, "Device"); -} - -/** - * itdb_get_artwork_dir: - * @mountpoint: the iPod mountpoint - * - * Retrieve the Artwork directory (containing the ArtworDB) by - * first calling itdb_get_control_dir() and then adding 'Artwork' - * - * Returns: path to the Artwork directory or NULL if - * non-existent. Must g_free() after use. - * - * Since: 0.4.0 - */ -gchar *itdb_get_artwork_dir (const gchar *mountpoint) -{ - g_return_val_if_fail (mountpoint, NULL); - - return itdb_get_dir (mountpoint, "Artwork"); -} - -/** - * itdb_get_itunesdb_path: - * @mountpoint: the iPod mountpoint - * - * Retrieve a path to the iTunesDB - * - * Returns: path to the iTunesDB or NULL if non-existent. Must g_free() - * after use. - * - * Since: 0.4.0 - */ -gchar *itdb_get_itunesdb_path (const gchar *mountpoint) -{ - gchar *itunes_dir, *path=NULL; - - g_return_val_if_fail (mountpoint, NULL); - - itunes_dir = itdb_get_itunes_dir (mountpoint); - - if (itunes_dir) - { - path = itdb_get_path (itunes_dir, "iTunesCDB"); - if(!path) { - path = itdb_get_path (itunes_dir, "iTunesDB"); - } - g_free (itunes_dir); - } - - return path; -} - -/** - * itdb_get_itunescdb_path: - * @mountpoint: the iPod mountpoint - * - * Retrieve a path to the iTunesCDB. The iTunesCDB is a compressed version - * of the iTunesDB which can be found on iPhones/iPod Touch with firmware 3.0 - * - * - * Returns: path to the iTunesCDB or NULL if non-existent. Must g_free() - * after use. - * - * Since: 0.4.0 - */ -gchar *itdb_get_itunescdb_path (const gchar *mountpoint) -{ - gchar *itunes_dir, *path=NULL; - - g_return_val_if_fail (mountpoint, NULL); - - itunes_dir = itdb_get_itunes_dir (mountpoint); - - if (itunes_dir) - { - path = itdb_get_path (itunes_dir, "iTunesCDB"); - g_free (itunes_dir); - } - - return path; -} - -/** - * itdb_get_itunessd_path: - * @mountpoint: the iPod mountpoint - * - * Retrieve a path to the iTunesSD - * - * Returns: path to the iTunesSD or NULL if non-existent. Must g_free() - * after use. - * - * Since: 0.4.0 - */ -gchar *itdb_get_itunessd_path (const gchar *mountpoint) -{ - gchar *itunes_dir, *path=NULL; - - g_return_val_if_fail (mountpoint, NULL); - - itunes_dir = itdb_get_itunes_dir (mountpoint); - - if (itunes_dir) - { - path = itdb_get_path (itunes_dir, "iTunesSD"); - g_free (itunes_dir); - } - - return path; -} - -/** - * itdb_get_artworkdb_path: - * @mountpoint: the iPod mountpoint - * - * Retrieve a path to the ArtworkDB - * - * Returns: path to the ArtworkDB or NULL if non-existent. Must g_free() - * after use. - * - * Since: 0.4.0 - */ -gchar *itdb_get_artworkdb_path (const gchar *mountpoint) -{ - gchar *itunes_dir, *path=NULL; - - g_return_val_if_fail (mountpoint, NULL); - - itunes_dir = itdb_get_artwork_dir (mountpoint); - - if (itunes_dir) - { - path = itdb_get_path (itunes_dir, "ArtworkDB"); - g_free (itunes_dir); - } - - return path; -} - - -/*------------------------------------------------------------------*\ - * * - * Timestamp stuff * - * * -\*------------------------------------------------------------------*/ - -/** - * itdb_time_get_mac_time: - * - * Gets the current time in a format appropriate for storing in the libgpod - * data structures - * - * Returns: current time - * - * Deprecated: kept for compatibility with older code, directly use - * g_get_current_time() or time(NULL) instead - */ -time_t itdb_time_get_mac_time (void) -{ - GTimeVal time; - - g_get_current_time (&time); - - return time.tv_sec; -} - -/** - * itdb_time_mac_to_host: - * @time: time expressed in libgpod format - * - * Converts a timestamp from libgpod format to host system timestamp. - * - * Returns: timestamp for the host system - * - * Deprecated: It's been kept for compatibility with older code, but this - * function is now a no-op - */ -time_t itdb_time_mac_to_host (time_t time) -{ - return time; -} - -/** - * itdb_time_host_to_mac: - * @time: time expressed in host unit - * - * Convert host system timestamp to libgpod format timestamp - * - * Returns: a libgpod timestamp - * - * Deprecated: It's been kept for compatibility with older code, but this - * function is now a no-op - */ -time_t itdb_time_host_to_mac (time_t time) -{ - return time; -} - -/** - * itdb_init_ipod: - * @mountpoint: the iPod mountpoint - * @model_number: the iPod model number, can be NULL - * @ipod_name: the name to give to the iPod. Will be displayed in - * gtkpod or itunes - * @error: return location for a #GError or NULL - * - * Initialise an iPod device from scratch. The function attempts to - * create a blank database, complete with master playlist and device - * information as well as the directory structure required for the - * type of iPod. - * @model_number is used to tell libgpod about the exact iPod - * model, which is needed for proper artwork writing. @model_number can be - * found from the table returned by itdb_device_get_ipod_info_table (for - * example). On recent distros with iPods released - * in the last few years (starting with the iPod Color), it should be fine - * to pass in a NULL @model_number while still getting artwork writing. - * - * Returns: TRUE when successful, FALSE if a failure has occurred. - * - * Since: 0.4.0 - */ -gboolean itdb_init_ipod (const gchar *mountpoint, - const gchar *model_number, - const gchar *ipod_name, - GError **error) -{ - gboolean writeok; - Itdb_iTunesDB *itdb = NULL; - Itdb_Playlist *mpl = NULL; - gchar *path; - - g_return_val_if_fail (mountpoint, FALSE); - - /* Create new blank itdb database for writing to iPod */ - itdb = itdb_new(); - - /* Assign iPod device reference to new database */ - itdb_set_mountpoint(itdb, mountpoint); - - /* Insert model_number into sysinfo file if present - * The model number can be extracted in a couple of ways: - * - use the read_sysinfo_file function - * - use libipoddevice and hal to get the model - * (as far as I know, libipoddevice will also - * read the sysinfo file, complemented by some - * guessing). - */ - if (model_number) - { - itdb_device_set_sysinfo (itdb->device, - "ModelNumStr", model_number); - } - - /* Create the remaining directories resident on blank ipod */ - writeok = itdb_create_directories(itdb->device, error); - if(! writeok) - { - return FALSE; - } - - /* Create a new playlist with the desired name of the ipod - * and set it as the mpl */ - if (ipod_name == NULL) - { - mpl = itdb_playlist_new(_("iPod"), FALSE); - } - else - { - mpl = itdb_playlist_new(ipod_name, FALSE); - } - itdb_playlist_set_mpl(mpl); - itdb_playlist_add(itdb, mpl, -1); - - /* Write both the iTunesDB and iTunesSD files to the new ipod, - * unless they already exist */ - path = itdb_get_itunesdb_path (mountpoint); - if (!path) - { - writeok = itdb_write(itdb, error); - if(! writeok) - { - itdb_free (itdb); - return FALSE; - } - } - g_free (path); - - /* If model is a shuffle or the model is undetermined, - * ie. @model_number is NULL, then create the itunesSD database - */ - if(!model_number || itdb_device_is_shuffle (itdb->device)) - { - path = itdb_get_itunessd_path (mountpoint); - if (!path) - { - writeok = itdb_shuffle_write(itdb, error); - if(! writeok) - { - itdb_free (itdb); - return FALSE; - } - } - g_free (path); - } - itdb_free (itdb); - return TRUE; -} - -/** - * itdb_chapterdata_build_chapter_blob: - * @chapterdata: Itdb_Chapterdata pointer of chapter data to be encoded - * - * Creates an iTunesDB binary blob of chapter data from @chapterdata. - * This helper function is used by both mk_mhod() in itdb_itunesdb.c - * and mk_Extras() in itdb_sqlite.c, so take care when updating to - * maintain compatibility with both chapterdata blobs. - * - * NOTE: Caller must call g_byte_array_free(chapter_blob, TRUE) on the - * returned chapter_blob - * - * Returns: a binary itdb blob of chapter data (to be freed by the caller) - * - * Since: 0.7.95 - */ -G_GNUC_INTERNAL GByteArray *itdb_chapterdata_build_chapter_blob(Itdb_Chapterdata *chapterdata, gboolean reversed) -{ - WContents *cts; - GByteArray * chapter_blob = NULL; - - cts = wcontents_new(""); - cts->reversed = reversed; - cts->pos = 0; - - itdb_chapterdata_build_chapter_blob_internal (cts, chapterdata); - - chapter_blob = g_byte_array_sized_new(cts->pos); - g_byte_array_append(chapter_blob, (guint8 *)cts->contents, cts->pos); - wcontents_free(cts); - return chapter_blob; -} - - -/*------------------------------------------------------------------*\ - * * - * Some functions to access Itdb_DB safely * - * * -\*------------------------------------------------------------------*/ -G_GNUC_INTERNAL -Itdb_iTunesDB *db_get_itunesdb (Itdb_DB *db) -{ - g_return_val_if_fail (db, NULL); - g_return_val_if_fail (db->db_type == DB_TYPE_ITUNES, NULL); - - return db->db.itdb; -} - -G_GNUC_INTERNAL -Itdb_PhotoDB *db_get_photodb (Itdb_DB *db) -{ - g_return_val_if_fail (db, NULL); - g_return_val_if_fail (db->db_type == DB_TYPE_PHOTO, NULL); - - return db->db.photodb; -} - -G_GNUC_INTERNAL -Itdb_Device *db_get_device(Itdb_DB *db) -{ - g_return_val_if_fail (db, NULL); - - switch (db->db_type) { - case DB_TYPE_ITUNES: - g_return_val_if_fail (db_get_itunesdb(db), NULL); - return db_get_itunesdb(db)->device; - case DB_TYPE_PHOTO: - g_return_val_if_fail (db_get_photodb(db), NULL); - return db_get_photodb(db)->device; - } - g_return_val_if_reached (NULL); -} - -G_GNUC_INTERNAL -gchar *db_get_mountpoint(Itdb_DB *db) -{ - Itdb_Device *device; - g_return_val_if_fail (db, NULL); - - device = db_get_device (db); - g_return_val_if_fail (device, NULL); - - return device->mountpoint; -} - -static gchar *itdb_device_get_control_dir (const Itdb_Device *device) -{ - Itdb_IpodInfo const *info = NULL; - char *podpath; - - podpath = itdb_get_control_dir (device->mountpoint); - if (podpath != NULL) { - return podpath; - } - - /* The device doesn't already have an iPod_Control directory, let's try - * to get which one is appropriate to use - */ - info = itdb_device_get_ipod_info (device); - - if (itdb_device_is_shuffle (device)) { - podpath = g_build_filename (device->mountpoint, "iPod_Control", NULL); - } else if (itdb_device_is_iphone_family (device)) { - podpath = g_build_filename (device->mountpoint,"iTunes_Control", NULL); - } else if (info->ipod_model == ITDB_IPOD_MODEL_MOBILE_1) { - podpath = g_build_filename (device->mountpoint, - "iTunes", "iTunes_Control", - NULL); - } else { - podpath = g_build_filename (device->mountpoint, "iPod_Control", NULL); - } - - return podpath; -} - - -/*------------------------------------------------------------------*\ - * * - * Create iPod directory hierarchy * - * * -\*------------------------------------------------------------------*/ -static gboolean itdb_create_directories (Itdb_Device *device, GError **error) -{ - const gchar *mp; - gboolean result; - gchar *pbuf; - gint i, dirnum; - gchar *podpath = NULL; - gchar *model_number; - Itdb_IpodInfo const *info = NULL; - - g_return_val_if_fail (device, FALSE); - - mp = device->mountpoint; - info = itdb_device_get_ipod_info (device); - - g_return_val_if_fail (mp, FALSE); - - /* Construct the Control directory */ - pbuf = itdb_device_get_control_dir (device); - if (!g_file_test (pbuf, G_FILE_TEST_EXISTS)) - { - if (g_mkdir_with_parents(pbuf, 0777) != 0) - { - goto error_dir; - } - } - g_free (pbuf); - podpath = itdb_get_control_dir (mp); - if (!podpath) { - goto error_dir; - } - - /* Construct the Music directory inside the Control directory */ - pbuf = g_build_filename (podpath, "Music", NULL); - if (!g_file_test (pbuf, G_FILE_TEST_EXISTS)) - { - if((g_mkdir(pbuf, 0777) != 0)) - { - goto error_dir; - } - } - g_free (pbuf); - - /* Construct the iTunes directory inside the Control directory */ - pbuf = g_build_filename (podpath, "iTunes", NULL); - if (!g_file_test (pbuf, G_FILE_TEST_EXISTS)) - { - if((g_mkdir(pbuf, 0777) != 0)) - { - goto error_dir; - } - } - g_free (pbuf); - - /* Build Artwork directory only for devices requiring artwork - * (assume that 'unknown models' are new and will support - * artwork) */ - if (itdb_device_supports_artwork(device) || - (info->ipod_model == ITDB_IPOD_MODEL_UNKNOWN)) - { - pbuf = g_build_filename (podpath, "Artwork", NULL); - if (!g_file_test (pbuf, G_FILE_TEST_EXISTS)) - { - if((g_mkdir(pbuf, 0777) != 0)) { - goto error_dir; - } - } - g_free (pbuf); - } - - /* Build Photo directory only for devices requiring it */ - if (itdb_device_supports_photo(device) || - (info->ipod_model == ITDB_IPOD_MODEL_UNKNOWN)) - { - pbuf = g_build_filename (mp, "Photos", "Thumbs", NULL); - if (!g_file_test (pbuf, G_FILE_TEST_EXISTS)) - { - if (g_mkdir_with_parents(pbuf, 0777) != 0) - { - goto error_dir; - } - } - g_free (pbuf); - } - - /* Build the directories that hold the music files */ - dirnum = info->musicdirs; - if (dirnum == 0) - { - guint64 capacity, free_space; - if (itdb_device_get_storage_info(device, &capacity, &free_space)) { - gdouble size = ((gdouble)capacity) / 1073741824; - if (size < 20) dirnum = 20; - else dirnum = 50; - } else { - dirnum = 20; - } - } - - for(i = 0; i < dirnum; i++) - { - gchar *num = g_strdup_printf ("F%02d", i); - pbuf = g_build_filename (podpath, "Music", num, NULL); - g_free (num); - if (!g_file_test (pbuf, G_FILE_TEST_EXISTS)) - { - if((g_mkdir(pbuf, 0777) != 0)) - { - goto error_dir; - } - } - g_free (pbuf); - } - - /* Build Calendar directory for models requiring it */ - if (!itdb_device_is_iphone_family (device) - && !itdb_device_is_shuffle (device)) - { - pbuf = g_build_filename (mp, "Calendars", NULL); - if (!g_file_test (pbuf, G_FILE_TEST_EXISTS)) - { - if((g_mkdir(pbuf, 0777) != 0)) - { - goto error_dir; - } - } - g_free (pbuf); - - /* Build Contacts directory for models requiring it */ - pbuf = g_build_filename (mp, "Contacts", NULL); - if (!g_file_test (pbuf, G_FILE_TEST_EXISTS)) - { - if((g_mkdir(pbuf, 0777) != 0)) - { - goto error_dir; - } - } - g_free (pbuf); - - pbuf = g_build_filename (mp, "Notes", NULL); - if (!g_file_test (pbuf, G_FILE_TEST_EXISTS)) - { - if((g_mkdir(pbuf, 0777) != 0)) - { - goto error_dir; - } - } - g_free (pbuf); - } - - /* Construct a Device directory file for special models */ - pbuf = g_build_filename (podpath, "Device", NULL); - if (!g_file_test (pbuf, G_FILE_TEST_EXISTS)) - { - if((g_mkdir(pbuf, 0777) != 0)) - { - goto error_dir; - } - } - g_free (pbuf); - - model_number = itdb_device_get_sysinfo (device, "ModelNumStr"); - /* Construct a SysInfo file */ - if (model_number && (strlen (model_number) != 0)) - { - pbuf = NULL; - if (!itdb_device_write_sysinfo (device, error)) - { - g_free (model_number); - goto error_dir; - } - } - g_free (model_number); - pbuf = NULL; - - error_dir: - if (pbuf) - { - g_set_error (error, 0, -1, - _("Problem creating iPod directory or file: '%s'."), - pbuf); - result = FALSE; - } else - { - result = TRUE; - } - g_free (pbuf); - g_free (podpath); - return result; -}