1
/* arch-tag: Implementation of itunesdb parser, based on code from gtkpod */
2
/* Time-stamp: <2003-11-29 12:09:39 jcs>
4
| Copyright (C) 2002-2003 Jorg Schuler <jcsjcs at users.sourceforge.net>
5
| Copyright (C) 2004 Christophe Fergeau <teuf@gnome.org>
6
| Part of the gtkpod project.
8
| URL: http://gtkpod.sourceforge.net/
10
| Most of the code in this file has been ported from the perl
11
| script "mktunes.pl" (part of the gnupod-tools collection) written
12
| by Adrian Ulrich <pab at blinkenlights.ch>.
14
| gnupod-tools: http://www.blinkenlights.ch/cgi-bin/fm.pl?get=ipod
16
| The code contained in this file is free software; you can redistribute
17
| it and/or modify it under the terms of the GNU Lesser General Public
18
| License as published by the Free Software Foundation; either version
19
| 2.1 of the License, or (at your option) any later version.
21
| This file is distributed in the hope that it will be useful,
22
| but WITHOUT ANY WARRANTY; without even the implied warranty of
23
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
24
| Lesser General Public License for more details.
26
| You should have received a copy of the GNU Lesser General Public
27
| License along with this code; if not, write to the Free Software
28
| Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
30
| iTunes and iPod are trademarks of Apple
32
| This product is not supported/written/published by Apple!
38
/* Some notes on how to use the functions in this file:
41
*** Reading the iTunesDB ***
43
gboolean itunesdb_parse (gchar *path); /+ path to mountpoint +/
44
will read an iTunesDB and pass the data over to your program. Your
45
programm is responsible to keep a representation of the data.
47
The information given in the "Play Counts" file is also read if
48
available and the playcounts, star rating and the time last played
51
For each track itunesdb_parse() will pass a filled out Track structure
52
to "it_add_track()", which has to be provided. The return value is
53
TRUE on success and FALSE on error. At the time being, the return
54
value is ignored, however.
56
The minimal Track structure looks like this (feel free to have
57
it_add_track() do with it as it pleases -- and yes, you are
58
responsible to free the memory):
62
gunichar2 *album_utf16; /+ album (utf16) +/
63
gunichar2 *artist_utf16; /+ artist (utf16) +/
64
gunichar2 *title_utf16; /+ title (utf16) +/
65
gunichar2 *genre_utf16; /+ genre (utf16) +/
66
gunichar2 *comment_utf16; /+ comment (utf16) +/
67
gunichar2 *composer_utf16; /+ Composer (utf16) +/
68
gunichar2 *fdesc_utf16; /+ Filetype descr (utf16)+/
69
gunichar2 *ipod_path_utf16;/+ name of file on iPod: uses ":" instead of "/" +/
70
guint32 ipod_id; /+ unique ID of track +/
71
gint32 size; /+ size of file in bytes +/
72
gint32 tracklen; /+ Length of track in ms +/
73
gint32 cd_nr; /+ CD number +/
74
gint32 cds; /+ number of CDs +/
75
gint32 track_nr; /+ track number +/
76
gint32 tracks; /+ number of tracks +/
77
gint32 year; /+ year +/
78
gint32 bitrate; /+ bitrate +/
79
guint32 time_played; /+ time of last play (Mac type) +/
80
guint32 time_modified; /+ time of last modification (Mac type) +/
81
guint32 rating; /+ star rating (stars * 20) +/
82
guint32 playcount; /+ number of times track was played +/
83
guint32 recent_playcount; /+ times track was played since last sync+/
84
gboolean transferred; /+ has file been transferred to iPod? +/
87
"transferred" will be set to TRUE because all tracks read from a
88
iTunesDB are obviously (or hopefully) already transferred to the
91
"recent_playcount" is for information only and will not be stored
94
By #defining ITUNESDB_PROVIDE_UTF8, itunesdb_parse() will also
95
provide utf8 versions of the above utf16 strings. You must then add
96
members "gchar *album"... to the Track structure.
98
For each new playlist in the iTunesDB, it_add_playlist() is
99
called with a pointer to the following Playlist struct:
103
gunichar2 *name_utf16;
104
guint32 type; /+ 1: master play list (PL_TYPE_MPL) +/
107
Again, by #defining ITUNESDB_PROVIDE_UTF8, a member "gchar *name"
108
will be initialized with a utf8 version of the playlist name.
110
it_add_playlist() must return a pointer under which it wants the
111
playlist to be referenced when it_add_track_to_playlist() is called.
113
For each track in the playlist, it_add_trackid_to_playlist() is called
114
with the above mentioned pointer to the playlist and the trackid to
117
gboolean it_add_track (Track *track);
118
Playlist *it_add_playlist (Playlist *plitem);
119
void it_add_trackid_to_playlist (Playlist *plitem, guint32 id);
122
*** Writing the iTunesDB ***
124
gboolean itunesdb_write (gchar *path), /+ path to mountpoint +/
125
will write an updated version of the iTunesDB.
127
The "Play Counts" file is renamed to "Play Counts.bak" if it exists
128
to avoid it being read multiple times.
130
It uses the following functions to retrieve the data necessary data
133
guint it_get_nr_of_tracks (void);
134
Track *it_get_track_by_nr (guint32 n);
135
guint32 it_get_nr_of_playlists (void);
136
Playlist *it_get_playlist_by_nr (guint32 n);
137
guint32 it_get_nr_of_tracks_in_playlist (Playlist *plitem);
138
Track *it_get_track_in_playlist_by_nr (Playlist *plitem, guint32 n);
140
The master playlist is expected to be "it_get_playlist_by_nr(0)". Only
141
the utf16 strings in the Playlist and Track struct are being used.
143
Please note that non-transferred tracks are not automatically
144
transferred to the iPod. A function
146
gboolean itunesdb_copy_track_to_ipod (gchar *path, Track *track, gchar *pcfile)
148
is provided to help you do that, however.
150
The following functions most likely will also come in handy:
152
gboolean itunesdb_cp (gchar *from_file, gchar *to_file);
153
guint32 itunesdb_time_get_mac_time (void);
154
time_t itunesdb_time_mac_to_host (guint32 mactime);
155
guint32 itunesdb_time_host_to_mac (time_t time);
157
Define "itunesdb_warning()" as you need (or simply use g_print and
158
change the default g_print handler with g_set_print_handler() as is
161
Jorg Schuler, 19.12.2002 */
164
/* call itunesdb_parse () to read the iTunesDB */
165
/* call itunesdb_write () to write the iTunesDB */
174
#include <libgnome/gnome-i18n.h>
180
#include "itunesdb.h"
183
/* we're being linked with gtkpod */
184
#define itunesdb_warning(...) g_print(__VA_ARGS__)
186
/* The following prints the error messages to the shell, converting
187
* UTF8 to the current locale on the fly: */
188
#define itunesdb_warning(...) do { gchar *utf8=g_strdup_printf (__VA_ARGS__); gchar *loc=g_locale_from_utf8 (utf8, -1, NULL, NULL, NULL); fprintf (stderr, "%s", loc); g_free (loc); g_free (utf8);} while (FALSE)
191
/* We instruct itunesdb_parse to provide utf8 versions of the strings */
192
#define ITUNESDB_PROVIDE_UTF8
194
#define ITUNESDB_DEBUG 0
195
#define ITUNESDB_MHIT_DEBUG 0
197
enum _iPodParserState {
200
IPOD_PARSER_PLAYLISTS,
204
typedef enum _iPodParserState iPodParserState;
209
iPodParserState state;
214
guint32 nr_playlists;
215
gboolean swapped_mhsd;
218
/* This is typedef'ed to iPodParser in itunesdb.h */
221
/* structure to hold the contents of one entry of the Play Count file */
228
static struct playcount *get_next_playcount (iPodParser *parser);
231
#define DEFAULT_MOUNT_PATH "/mnt/ipod"
232
#define GCONF_MOUNT_PATH "/apps/qahog/mount_path"
236
ipod_get_mount_path (char *mount_point)
240
path = eel_gconf_get_string (GCONF_MOUNT_PATH);
241
if (path == NULL || strcmp (path, "") == 0)
242
return g_strdup (DEFAULT_MOUNT_PATH);
249
ipod_get_itunesdb_path (const char *mount_path)
253
result = g_build_filename (G_DIR_SEPARATOR_S, mount_path,
254
"iPod_Control/iTunes/iTunesDB", NULL);
259
ipod_get_playcounts_path (const char *mount_point)
263
result = g_build_filename (G_DIR_SEPARATOR_S, mount_point,
264
"iPod_Control/iTunes/Play Counts", NULL);
269
/* Compare the two data. TRUE if identical */
270
static gboolean cmp_n_bytes (gchar *data1, gchar *data2, gint n)
276
if (data1[i] != data2[i]) return FALSE;
282
/* Seeks to position "seek", then reads "n" bytes. Returns -1 on error
283
during seek, or the number of bytes actually read */
284
static gint seek_get_n_bytes (FILE *file, gchar *data, glong seek, gint n)
289
if (fseek (file, seek, SEEK_SET) != 0) return -1;
294
if (read == EOF) return i;
295
*data++ = (gchar)read;
301
/* Get the 4-byte-number stored at position "seek" in "file"
302
(or -1 when an error occured) */
303
static guint32 get4int(FILE *file, glong seek)
308
if (seek_get_n_bytes (file, data, seek, 4) != 4) return -1;
309
n = ((guint32)data[3]) << 24;
310
n += ((guint32)data[2]) << 16;
311
n += ((guint32)data[1]) << 8;
312
n += ((guint32)data[0]);
317
#if (G_BYTE_ORDER == G_BIG_ENDIAN)
318
/* Get length of utf16 string in number of characters (words) */
319
static guint32 utf16_strlen (gunichar2 *utf16)
322
while (utf16[i] != 0) ++i;
327
/* Fix UTF16 String for BIGENDIAN machines (like PPC) */
328
static gunichar2 *fixup_utf16(gunichar2 *utf16_string) {
329
#if (G_BYTE_ORDER == G_BIG_ENDIAN)
333
for(i=0; i<utf16_strlen(utf16_string); i++)
335
utf16_string[i] = ((utf16_string[i]<<8) & 0xff00) |
336
((utf16_string[i]>>8) & 0xff);
344
/* return the length of the header *ml, the genre number *mty,
345
and a string with the entry (in UTF16?). After use you must
346
free the string with g_free (). Returns NULL in case of error. */
347
static gunichar2 *get_mhod (iPodParser *parser, gint32 *ml, gint32 *mty)
350
gunichar2 *entry_utf16 = NULL;
354
fprintf(stderr, "get_mhod seek: %x\n", (int)seek);
357
if (seek_get_n_bytes (parser->itunes, data, parser->seek, 4) != 4)
362
if (cmp_n_bytes (data, "mhod", 4) == FALSE )
367
*ml = get4int (parser->itunes, parser->seek+8); /* length */
368
*mty = get4int (parser->itunes, parser->seek+12); /* mhod_id number */
369
xl = get4int (parser->itunes, parser->seek+28); /* entry length */
372
fprintf(stderr, "ml: %x mty: %x, xl: %x\n", *ml, *mty, xl);
377
case MHOD_ID_PLAYLIST: /* do something with the "weird mhod" */
380
entry_utf16 = g_malloc (xl+2);
381
if (seek_get_n_bytes (parser->itunes, (gchar *)entry_utf16, parser->seek+40, xl) != xl) {
382
g_free (entry_utf16);
388
entry_utf16[xl/2] = 0; /* add trailing 0 */
392
return fixup_utf16(entry_utf16);
395
/* get a PL, return pos where next PL should be, name and content */
396
static iPodPlaylist * get_pl(iPodParser *parser)
398
gunichar2 *plname_utf16 = NULL, *plname_utf16_maybe;
399
#ifdef ITUNESDB_PROVIDE_UTF8
402
guint32 type, pltype, tracknum, n;
405
iPodPlaylist *plitem;
412
fprintf(stderr, "mhyp seek: %x\n", (int)parser->seek);
415
if (seek_get_n_bytes (parser->itunes, data, parser->seek, 4) != 4) return NULL;
416
if (cmp_n_bytes (data, "mhyp", 4) == FALSE) return NULL; /* not pl */
417
/* Some Playlists have added 256 to their type -- I don't know what
418
it's for, so we just ignore it for now -> & 0xff */
419
pltype = get4int (parser->itunes, parser->seek+20) & 0xff; /* Type of playlist (1= MPL) */
420
tracknum = get4int (parser->itunes, parser->seek+16); /* number of tracks in playlist */
421
nextseek = parser->seek + get4int (parser->itunes, parser->seek+8); /* possible begin of next PL */
422
zip = get4int (parser->itunes, parser->seek+4); /* length of header */
423
if (zip == 0) return NULL; /* error! */
427
if (seek_get_n_bytes (parser->itunes, data, parser->seek, 4) != 4) return NULL;
428
plname_utf16_maybe = get_mhod (parser, &zip, &type); /* PL name */
429
if (zip != -1) switch (type)
431
case MHOD_ID_PLAYLIST:
432
break; /* here we could do something about the "weird mhod" */
434
if (plname_utf16_maybe)
436
/* sometimes there seem to be two mhod TITLE headers */
437
if (plname_utf16) g_free (plname_utf16);
438
plname_utf16 = plname_utf16_maybe;
442
} while (zip != -1); /* read all MHODs */
444
{ /* we did not read a valid mhod TITLE header -> */
445
/* we simply make up our own name */
447
plname_utf16 = g_utf8_to_utf16 (_("Master-PL"),
448
-1, NULL, NULL, NULL);
449
else plname_utf16 = g_utf8_to_utf16 (_("Playlist"),
450
-1, NULL, NULL, NULL);
452
#ifdef ITUNESDB_PROVIDE_UTF8
453
plname_utf8 = g_utf16_to_utf8 (plname_utf16, -1, NULL, NULL, NULL);
458
fprintf(stderr, "pln: %s(%d Tracks) \n", plname_utf8, (int)tracknum);
461
plitem = g_malloc0 (sizeof (iPodPlaylist));
463
#ifdef ITUNESDB_PROVIDE_UTF8
464
plitem->name = plname_utf8;
466
plitem->name_utf16 = plname_utf16;
467
plitem->type = pltype;
470
fprintf(stderr, "added pl: %s", plname_utf8);
473
n = 0; /* number of tracks read */
476
/* We read the mhip headers and skip everything else. If we
477
find a mhyp header before n==tracknum, something is wrong */
478
if (seek_get_n_bytes (parser->itunes, data, parser->seek, 4) != 4) return NULL;
479
if (cmp_n_bytes (data, "mhyp", 4) == TRUE) {
480
#ifdef ITUNESDB_PROVIDE_UTF8
482
g_free (plname_utf8);
486
g_free (plname_utf16);
489
return NULL; /* Wrong!!! */
491
if (cmp_n_bytes (data, "mhip", 4) == TRUE)
493
ref = get4int(parser->itunes, parser->seek+24);
494
plitem->song_ids = g_list_append (plitem->song_ids, (gpointer)ref);
497
parser->seek += get4int (parser->itunes, parser->seek+8);
503
static iPodSong *get_mhit(iPodParser *parser)
507
#ifdef ITUNESDB_PROVIDE_UTF8
510
gunichar2 *entry_utf16;
513
struct playcount *playcount;
516
fprintf(stderr, "get_mhit seek: %x\n", (int)seek);
519
if (seek_get_n_bytes (parser->itunes, data, parser->seek, 4) != 4) {
522
if (cmp_n_bytes (data, "mhit", 4) == FALSE ) {
523
return NULL; /* we are lost! */
526
track = g_new0 (iPodSong, 1);
528
track->ipod_id = get4int(parser->itunes, parser->seek+16); /* iPod ID */
529
track->rating = get4int(parser->itunes, parser->seek+28) >> 24;/* rating */
530
track->time_modified = get4int(parser->itunes, parser->seek+32);/* modification time */
531
track->size = get4int(parser->itunes, parser->seek+36); /* file size */
532
track->tracklen = get4int(parser->itunes, parser->seek+40); /* time */
533
track->track_nr = get4int(parser->itunes, parser->seek+44); /* track number */
534
track->tracks = get4int(parser->itunes, parser->seek+48); /* nr of tracks */
535
track->year = get4int(parser->itunes, parser->seek+52); /* year */
536
track->bitrate = get4int(parser->itunes, parser->seek+56); /* bitrate */
537
track->volume = get4int(parser->itunes, parser->seek+64); /* volume adjust*/
538
track->playcount = get4int(parser->itunes, parser->seek+80); /* playcount */
539
track->time_played = get4int(parser->itunes, parser->seek+88); /* last time played */
540
track->cd_nr = get4int(parser->itunes, parser->seek+92); /* CD nr */
541
track->cds = get4int(parser->itunes, parser->seek+96); /* CD nr of.. */
542
track->transferred = TRUE; /* track is on iPod! */
544
#if ITUNESDB_MHIT_DEBUG
545
time_t time_mac_to_host (guint32 mactime);
546
gchar *time_time_to_string (time_t time);
547
#define printf_mhit(sk, str) printf ("%3d: %d (%s)\n", sk, get4int (parser->itunes, parser->seek+sk), str);
548
#define printf_mhit_time(sk, str) { gchar *buf = time_time_to_string (itunesdb_time_mac_to_host (get4int (parser->itunes, parser->seek+sk))); printf ("%3d: %s (%s)\n", sk, buf, str); g_free (buf); }
550
printf ("\nmhit: seek=%lu\n", parser->seek);
551
printf_mhit ( 4, "header size");
552
printf_mhit ( 8, "mhit size");
553
printf_mhit ( 12, "nr of mhods");
554
printf_mhit ( 16, "iPod ID");
555
printf_mhit ( 20, "?");
556
printf_mhit ( 24, "?");
557
printf (" 28: %u (type)\n", get4int (parser->itunes, parser->seek+28) & 0xffffff);
558
printf (" 28: %u (rating)\n", get4int (parser->itunes, parser->seek+28) >> 24);
559
printf_mhit ( 32, "timestamp file");
560
printf_mhit_time ( 32, "timestamp file");
561
printf_mhit ( 36, "size");
562
printf_mhit ( 40, "tracklen (ms)");
563
printf_mhit ( 44, "track_nr");
564
printf_mhit ( 48, "total tracks");
565
printf_mhit ( 52, "year");
566
printf_mhit ( 56, "bitrate");
567
printf_mhit ( 60, "sample rate");
568
printf (" 60: %u (sample rate LSB)\n", get4int (parser->itunes, parser->seek+60) & 0xffff);
569
printf (" 60: %u (sample rate HSB)\n", (get4int (parser->itunes, parser->seek+60) >> 16));
570
printf_mhit ( 64, "?");
571
printf_mhit ( 68, "?");
572
printf_mhit ( 72, "?");
573
printf_mhit ( 76, "?");
574
printf_mhit ( 80, "playcount");
575
printf_mhit ( 84, "?");
576
printf_mhit ( 88, "last played");
577
printf_mhit_time ( 88, "last played");
578
printf_mhit ( 92, "CD");
579
printf_mhit ( 96, "total CDs");
580
printf_mhit (100, "?");
581
printf_mhit (104, "?");
582
printf_mhit_time (104, "?");
583
printf_mhit (108, "?");
584
printf_mhit (112, "?");
585
printf_mhit (116, "?");
586
printf_mhit (120, "?");
587
printf_mhit (124, "?");
588
printf_mhit (128, "?");
589
printf_mhit (132, "?");
590
printf_mhit (136, "?");
591
printf_mhit (140, "?");
592
printf_mhit (144, "?");
593
printf_mhit (148, "?");
594
printf_mhit (152, "?");
596
#undef printf_mhit_time
600
parser->seek += get4int (parser->itunes, parser->seek+4); /* 1st mhod starts here! */
604
entry_utf16 = get_mhod (parser, &zip, &type);
605
if (entry_utf16 != NULL) {
606
#ifdef ITUNESDB_PROVIDE_UTF8
607
entry_utf8 = g_utf16_to_utf8 (entry_utf16, -1, NULL, NULL, NULL);
612
#ifdef ITUNESDB_PROVIDE_UTF8
613
track->album = entry_utf8;
615
track->album_utf16 = entry_utf16;
618
#ifdef ITUNESDB_PROVIDE_UTF8
619
track->artist = entry_utf8;
621
track->artist_utf16 = entry_utf16;
624
#ifdef ITUNESDB_PROVIDE_UTF8
625
track->title = entry_utf8;
627
track->title_utf16 = entry_utf16;
630
#ifdef ITUNESDB_PROVIDE_UTF8
631
track->genre = entry_utf8;
633
track->genre_utf16 = entry_utf16;
636
#ifdef ITUNESDB_PROVIDE_UTF8
637
track->ipod_path = entry_utf8;
639
track->ipod_path_utf16 = entry_utf16;
642
#ifdef ITUNESDB_PROVIDE_UTF8
643
track->fdesc = entry_utf8;
645
track->fdesc_utf16 = entry_utf16;
647
case MHOD_ID_COMMENT:
648
#ifdef ITUNESDB_PROVIDE_UTF8
649
track->comment = entry_utf8;
651
track->comment_utf16 = entry_utf16;
653
case MHOD_ID_COMPOSER:
654
#ifdef ITUNESDB_PROVIDE_UTF8
655
track->composer = entry_utf8;
657
track->composer_utf16 = entry_utf16;
659
default: /* unknown entry -- discard */
660
#ifdef ITUNESDB_PROVIDE_UTF8
663
g_free (entry_utf16);
669
playcount = get_next_playcount (parser);
672
if (playcount->rating) track->rating = playcount->rating;
673
if (playcount->time_played) track->time_played = playcount->time_played;
674
track->playcount += playcount->playcount;
675
track->recent_playcount = playcount->playcount;
678
return track; /* no more black magic */
681
/* get next playcount, that is the first entry of GList
682
* playcounts. This entry is removed from the list. You must free the
683
* return value after use */
684
static struct playcount *get_next_playcount (iPodParser *parser)
686
struct playcount *playcount;
688
if (parser->playcounts == NULL) {
691
playcount = parser->playcounts->data;
692
if (playcount) parser->playcounts = g_list_remove (parser->playcounts, playcount);
696
/* delete all entries of GList *playcounts */
697
static void reset_playcounts (iPodParser *parser)
699
g_list_foreach (parser->playcounts, (GFunc)g_free, NULL);
700
g_list_free (parser->playcounts);
701
parser->playcounts = NULL;
704
/* Read the Play Count file (formed by adding "Play Counts" to the
705
* directory contained in @filename) and set up the GList *playcounts
707
static void init_playcounts (iPodParser *parser)
709
gchar *plcname = ipod_get_playcounts_path (parser->mount_path);
710
FILE *plycts = fopen (plcname, "r");
711
gboolean error = TRUE;
713
reset_playcounts (parser);
718
guint32 header_length, entry_length, entry_num, i=0;
719
time_t tt = time (NULL);
721
localtime (&tt); /* set the ext. variable 'timezone' (see below) */
722
if (seek_get_n_bytes (plycts, data, 0, 4) != 4) break;
723
if (cmp_n_bytes (data, "mhdp", 4) == FALSE) break;
724
header_length = get4int (plycts, 4);
725
/* all the headers I know are 0x60 long -- if this one is longer
726
we can simply ignore the additional information */
727
if (header_length < 0x60) break;
728
entry_length = get4int (plycts, 8);
729
/* all the entries I know are 0x0c (firmware 1.3) or 0x10
730
* (firmware 2.0) in length */
731
if (entry_length < 0x0c) break;
732
/* number of entries */
733
entry_num = get4int (plycts, 12);
734
for (i=0; i<entry_num; ++i)
736
struct playcount *playcount = g_new0 (struct playcount, 1);
737
glong seek = header_length + i*entry_length;
739
parser->playcounts = g_list_prepend (parser->playcounts, playcount);
740
/* check if entry exists by reading its last four bytes */
741
if (seek_get_n_bytes (plycts, data,
742
seek+entry_length-4, 4) != 4) break;
743
playcount->playcount = get4int (plycts, seek);
744
playcount->time_played = get4int (plycts, seek+4);
747
* The iPod (firmware 1.3) doesn't seem to use the timezone
748
* information correctly -- no matter what you set iPod's
749
* timezone to it will always record in UTC -- we need to
750
* subtract the difference between current timezone and UTC
751
* to get a correct display. 'timezone' (initialized above)
752
* contains the difference in seconds.
754
/* FIXME: this was timezone instead of __timezone, but
755
* timezone isn't defined in libc headers. What is the appropriate
758
if (playcount->time_played)
759
playcount->time_played += __timezone;
761
/* rating only exists if the entry length is at least 0x10 */
762
if (entry_length >= 0x10)
763
playcount->rating = get4int (plycts, seek+12);
765
parser->playcounts = g_list_reverse (parser->playcounts);
766
if (i == entry_num) error = FALSE;
768
if (plycts) fclose (plycts);
769
if (error) reset_playcounts (parser);
774
/* Parse the iTunesDB and store the tracks using it_addtrack () defined
776
Returns TRUE on success, FALSE on error.
777
"path" should point to the mount point of the iPod,
779
/* Support for playlists should be added later */
782
itunesdb_parse(iPodParser *parser)
784
gboolean result = FALSE;
786
gboolean swapped_mhsd = FALSE;
788
g_assert (parser != NULL);
789
g_assert (parser->itunes != NULL);
790
g_assert (parser->state == IPOD_PARSER_INIT);
793
fprintf(stderr, "Parsing %s\nenter: %4d\n", filename, it_get_nr_of_tracks ());
797
{ /* dummy loop for easier error handling */
798
if (seek_get_n_bytes (parser->itunes, data, parser->seek, 4) != 4)
800
gchar *db_path = ipod_get_itunesdb_path (parser->mount_path);
801
itunesdb_warning (_("Error reading \"%s\".\n"), db_path);
805
/* for(i=0; i<8; ++i) printf("%02x ", data[i]); printf("\n");*/
806
if (cmp_n_bytes (data, "mhbd", 4) == FALSE)
808
gchar *db_path = ipod_get_itunesdb_path (parser->mount_path);
809
itunesdb_warning (_("\"%s\" is not a valid iPod database.\n"), db_path);
813
parser->seek = get4int (parser->itunes, 4);
814
/* all the headers I know are 0x68 long -- if this one is longer
815
we can simply ignore the additional information */
816
/* we don't need any information of the mhbd header... */
819
itunesdb_warning (_("\"%s\" is not a iTunesDB.\n"), filename);
825
if (seek_get_n_bytes (parser->itunes, data, parser->seek, 8) != 8) break;
826
if (cmp_n_bytes (data, "mhsd", 4) == TRUE)
827
{ /* mhsd header -> determine start of playlists */
828
if (get4int (parser->itunes, parser->seek + 12) == 1)
829
{ /* OK, tracklist, save start of playlists */
831
parser->pl_mhsd = parser->seek + get4int (parser->itunes, parser->seek+8);
833
else if (get4int (parser->itunes, parser->seek + 12) == 2)
834
{ /* bad: these are playlists... switch */
836
{ /* already switched once -> forget it */
841
parser->pl_mhsd = parser->seek;
842
parser->seek += get4int (parser->itunes, parser->seek+8);
847
{ /* neither playlist nor track MHSD --> skip it */
848
parser->seek += get4int (parser->itunes, parser->seek+8);
852
if (cmp_n_bytes (data, "mhlt", 4) == TRUE)
853
{ /* mhlt header -> number of tracks */
854
parser->nr_tracks = get4int (parser->itunes, parser->seek+8);
855
if (parser->nr_tracks == 0)
856
{ /* no tracks -- skip directly to next mhsd */
862
if (cmp_n_bytes (data, "mhit", 4) == TRUE)
863
{ /* mhit header -> start of tracks*/
867
zip = get4int (parser->itunes, parser->seek+4);
870
} while (result == FALSE);
871
if (result == FALSE) break; /* some error occured */
872
/* now we should be at the first MHIT */
874
/* Read Play Count file if available */
875
init_playcounts (parser);
883
ipod_parse_playlists (iPodParser *parser)
885
gboolean result = FALSE;
891
if (seek_get_n_bytes (parser->itunes, data, parser->seek, 8) != 8) break;
892
if (cmp_n_bytes (data, "mhsd", 4) == TRUE)
894
if (get4int (parser->itunes, parser->seek + 12) != 2)
895
{ /* this is not a playlist MHSD -> skip it */
896
parser->seek += get4int (parser->itunes, parser->seek+8);
899
if (cmp_n_bytes (data, "mhlp", 4) == TRUE)
900
{ /* mhlp header -> number of playlists */
901
parser->nr_playlists = get4int (parser->itunes, parser->seek+8);
903
if (cmp_n_bytes (data, "mhyp", 4) == TRUE)
904
{ /* mhyp header -> start of playlists */
908
zip = get4int (parser->itunes, parser->seek+4);
911
} while (result == FALSE);
917
ipod_item_new (iPodItemType type, gpointer data)
921
item = g_new0 (iPodItem, 1);
923
g_warning ("Couldn't allocate iPodItem\n");
932
ipod_item_destroy (iPodItem *item)
937
if (item->type == IPOD_ITEM_SONG) {
938
iPodSong *song = (iPodSong *)item->data;
939
g_assert (song != NULL);
941
/* g_free safely ignore NULL pointers */
942
g_free (song->album);
943
g_free (song->artist);
944
g_free (song->title);
945
g_free (song->genre);
946
g_free (song->comment);
947
g_free (song->composer);
948
g_free (song->fdesc);
949
g_free (song->ipod_path);
950
g_free (song->album_utf16);
951
g_free (song->artist_utf16);
952
g_free (song->title_utf16);
953
g_free (song->genre_utf16);
954
g_free (song->comment_utf16);
955
g_free (song->composer_utf16);
956
g_free (song->fdesc_utf16);
957
g_free (song->ipod_path_utf16);
958
g_free (song->pc_path_utf8);
959
g_free (song->pc_path_locale);
960
g_free (song->year_str);
961
g_free (song->hostname);
962
g_free (song->md5_hash);
963
g_free (song->charset);
965
} else if (item->type == IPOD_ITEM_PLAYLIST) {
966
iPodPlaylist *playlist = (iPodPlaylist *)item->data;
968
g_free (playlist->name);
969
g_free (playlist->name_utf16);
970
g_list_free (playlist->song_ids);
972
g_assert_not_reached ();
980
ipod_get_next_item (iPodParser *parser)
982
switch (parser->state) {
983
case IPOD_PARSER_INIT: {
985
res = itunesdb_parse (parser);
987
g_print ("Error parsing iTunesDB\n");
988
parser->state = IPOD_PARSER_ERROR;
991
if (parser->nr_tracks == 0) {
992
parser->state = IPOD_PARSER_PLAYLISTS;
994
parser->state = IPOD_PARSER_SONGS;
996
/* break statement omitted on purpose */
998
case IPOD_PARSER_SONGS: {
1000
/* get file entry */
1001
track = get_mhit (parser);
1002
if (track == NULL) {
1003
/* Skip to beginning of playlists entries */
1004
parser->seek = parser->pl_mhsd;
1005
if (ipod_parse_playlists (parser) == FALSE) {
1006
/* some error occured */
1007
parser->state = IPOD_PARSER_ERROR;
1010
parser->state = IPOD_PARSER_PLAYLISTS;
1012
return ipod_item_new (IPOD_ITEM_SONG, track);
1014
/* No break here, if we didn't return a song, we want to
1015
* parse and return the first playlist */
1017
case IPOD_PARSER_PLAYLISTS: {
1019
pl = get_pl(parser);
1021
parser->state = IPOD_PARSER_END;
1024
return ipod_item_new (IPOD_ITEM_PLAYLIST, pl);
1029
case IPOD_PARSER_END:
1032
case IPOD_PARSER_ERROR: /* Should not be reached */
1033
default: g_assert_not_reached ();
1039
ipod_parser_new (const gchar *mount_point)
1042
gchar *filename = ipod_get_itunesdb_path (mount_point);
1044
g_return_val_if_fail ((filename != NULL), NULL);
1046
parser = g_new0 (iPodParser, 1);
1047
parser->itunes = fopen (filename, "r");
1048
if (parser->itunes == NULL) {
1049
itunesdb_warning (_("Could not open \"%s\" file for reading.\n"),
1055
parser->mount_path = g_strdup (mount_point);
1056
parser->state = IPOD_PARSER_INIT;
1063
ipod_parser_destroy (iPodParser *parser)
1065
g_assert (parser != NULL);
1067
if (parser->itunes != NULL) {
1068
fclose (parser->itunes);
1070
reset_playcounts (parser);
1071
g_free (parser->mount_path);
1076
/* up to here we had the routines for reading the iTunesDB */
1077
/* ---------------------------------------------------------------------- */
1079
/* from here on we have the routines for writing the iTunesDB */
1081
#ifdef ITUNESDB_WRITE
1083
/* Name of the device in utf16 */
1084
gunichar2 ipod_name[] = { 'g', 't', 'k', 'p', 'o', 'd', 0 };
1087
/* Get length of utf16 string in number of characters (words) */
1088
static guint32 utf16_strlen (gunichar2 *utf16)
1091
while (utf16[i] != 0) ++i;
1096
/* Write 4-byte-integer "n" in correct order to "data".
1097
"data" must be sufficiently long ... */
1098
static void store4int (guint32 n, guchar *data)
1100
data[3] = (n >> 24) & 0xff;
1101
data[2] = (n >> 16) & 0xff;
1102
data[1] = (n >> 8) & 0xff;
1107
/* Write "data", "n" bytes long to current position in file.
1108
Returns TRUE on success, FALSE otherwise */
1109
static gboolean put_data_cur (FILE *file, gchar *data, gint n)
1111
if (fwrite (data, 1, n, file) != n) return FALSE;
1115
/* Write 4-byte integer "n" to "file".
1116
Returns TRUE on success, FALSE otherwise */
1117
static gboolean put_4int_cur (FILE *file, guint32 n)
1121
store4int (n, data);
1122
return put_data_cur (file, data, 4);
1126
/* Write 4-byte integer "n" to "file" at position "seek".
1127
After writing, the file position indicator is set
1128
to the end of the file.
1129
Returns TRUE on success, FALSE otherwise */
1130
static gboolean put_4int_seek (FILE *file, guint32 n, gint seek)
1134
if (fseek (file, seek, SEEK_SET) != 0) return FALSE;
1135
result = put_4int_cur (file, n);
1136
if (fseek (file, 0, SEEK_END) != 0) return FALSE;
1141
/* Write "n" times 4-byte-zero at current position
1142
Returns TRUE on success, FALSE otherwise */
1143
static gboolean put_n0_cur (FILE*file, guint32 n)
1146
gboolean result = TRUE;
1148
for (i=0; i<n; ++i) result &= put_4int_cur (file, 0);
1154
/* Write out the mhbd header. Size will be written later */
1155
static void mk_mhbd (FILE *file)
1157
put_data_cur (file, "mhbd", 4);
1158
put_4int_cur (file, 104); /* header size */
1159
put_4int_cur (file, -1); /* size of whole mhdb -- fill in later */
1160
put_4int_cur (file, 1); /* ? */
1161
put_4int_cur (file, 1); /* - changed to 2 from itunes2 to 3 ..
1162
version? We are iTunes version 1 ;) */
1163
put_4int_cur (file, 2); /* ? */
1164
put_n0_cur (file, 20); /* dummy space */
1167
/* Fill in the missing items of the mhsd header:
1168
total size and number of mhods */
1169
static void fix_mhbd (FILE *file, glong mhbd_seek, glong cur)
1171
put_4int_seek (file, cur-mhbd_seek, mhbd_seek+8); /* size of whole mhit */
1175
/* Write out the mhsd header. Size will be written later */
1176
static void mk_mhsd (FILE *file, guint32 type)
1178
put_data_cur (file, "mhsd", 4);
1179
put_4int_cur (file, 96); /* Headersize */
1180
put_4int_cur (file, -1); /* size of whole mhsd -- fill in later */
1181
put_4int_cur (file, type); /* type: 1 = track, 2 = playlist */
1182
put_n0_cur (file, 20); /* dummy space */
1186
/* Fill in the missing items of the mhsd header:
1187
total size and number of mhods */
1188
static void fix_mhsd (FILE *file, glong mhsd_seek, glong cur)
1190
put_4int_seek (file, cur-mhsd_seek, mhsd_seek+8); /* size of whole mhit */
1194
/* Write out the mhlt header. */
1195
static void mk_mhlt (FILE *file, guint32 track_num)
1197
put_data_cur (file, "mhlt", 4);
1198
put_4int_cur (file, 92); /* Headersize */
1199
put_4int_cur (file, track_num); /* tracks in this itunesdb */
1200
put_n0_cur (file, 20); /* dummy space */
1204
/* Write out the mhit header. Size will be written later */
1205
static void mk_mhit (FILE *file, Track *track)
1207
put_data_cur (file, "mhit", 4);
1208
put_4int_cur (file, 156); /* header size */
1209
put_4int_cur (file, -1); /* size of whole mhit -- fill in later */
1210
put_4int_cur (file, -1); /* nr of mhods in this mhit -- later */
1211
put_4int_cur (file, track->ipod_id); /* track index number */
1212
put_4int_cur (file, 1);
1213
put_4int_cur (file, 0);
1214
put_4int_cur (file, 257 | track->rating<<24); /* type, rating */
1215
put_4int_cur (file, track->time_modified); /* timestamp */
1216
put_4int_cur (file, track->size); /* filesize */
1217
put_4int_cur (file, track->tracklen); /* length of track in ms */
1218
put_4int_cur (file, track->track_nr);/* track number */
1219
put_4int_cur (file, track->tracks); /* number of tracks */
1220
put_4int_cur (file, track->year); /* the year */
1221
put_4int_cur (file, track->bitrate); /* bitrate */
1222
put_4int_cur (file, 0xac440000); /* ? */
1223
put_4int_cur (file, track->volume); /* volume adjust */
1224
put_n0_cur (file, 3); /* dummy space */
1225
put_4int_cur (file, track->playcount);/* playcount */
1226
put_4int_cur (file, 0); /* dummy space */
1227
put_4int_cur (file, track->time_played); /* last time played */
1228
put_4int_cur (file, track->cd_nr); /* CD number */
1229
put_4int_cur (file, track->cds); /* number of CDs */
1230
put_4int_cur (file, 0); /* hardcoded space */
1231
put_4int_cur (file, itunesdb_time_get_mac_time ()); /* current timestamp */
1232
put_n0_cur (file, 12); /* dummy space */
1236
/* Fill in the missing items of the mhit header:
1237
total size and number of mhods */
1238
static void fix_mhit (FILE *file, glong mhit_seek, glong cur, gint mhod_num)
1240
put_4int_seek (file, cur-mhit_seek, mhit_seek+8); /* size of whole mhit */
1241
put_4int_seek (file, mhod_num, mhit_seek+12); /* nr of mhods */
1245
/* Write out one mhod header.
1246
type: see enum of MHMOD_IDs;
1247
string: utf16 string to pack
1248
fqid: will be used for playlists -- use 1 for tracks */
1249
static void mk_mhod (FILE *file, guint32 type,
1250
gunichar2 *string, guint32 fqid)
1255
if (fqid == 1) mod = 40; /* normal mhod */
1256
else mod = 44; /* playlist entry */
1258
len = utf16_strlen (string); /* length of string in _words_ */
1260
put_data_cur (file, "mhod", 4); /* header */
1261
put_4int_cur (file, 24); /* size of header */
1262
put_4int_cur (file, 2*len+mod); /* size of header + body */
1263
put_4int_cur (file, type); /* type of the entry */
1264
put_n0_cur (file, 2); /* dummy space */
1265
put_4int_cur (file, fqid); /* refers to this ID if a PL item,
1266
otherwise always 1 */
1267
put_4int_cur (file, 2*len); /* size of string */
1270
put_n0_cur (file, 2); /* trash */
1271
/* FIXME: this assumes "string" is writable.
1272
However, this might not be the case,
1273
e.g. ipod_name might be in read-only mem. */
1274
string = fixup_utf16(string);
1275
put_data_cur (file, (gchar *)string, 2*len); /* the string */
1276
string = fixup_utf16(string);
1280
put_n0_cur (file, 3); /* PL mhods are different ... */
1285
/* Write out the mhlp header. Size will be written later */
1286
static void mk_mhlp (FILE *file, guint32 lists)
1288
put_data_cur (file, "mhlp", 4); /* header */
1289
put_4int_cur (file, 92); /* size of header */
1290
put_4int_cur (file, lists); /* playlists on iPod (including main!) */
1291
put_n0_cur (file, 20); /* dummy space */
1295
/* Fix the mhlp header */
1296
static void fix_mhlp (FILE *file, glong mhlp_seek, gint playlist_num)
1298
put_4int_seek (file, playlist_num, mhlp_seek+8); /* nr of playlists */
1303
/* Write out the "weird" header.
1304
This seems to be an itunespref thing.. dunno know this
1305
but if we set everything to 0, itunes doesn't show any data
1306
even if you drag an mp3 to your ipod: nothing is shown, but itunes
1308
.. so we create a hardcoded-pref.. this will change in future
1309
Seems to be a Preferences mhod, every PL has such a thing
1311
static void mk_weired (FILE *file)
1313
put_data_cur (file, "mhod", 4); /* header */
1314
put_4int_cur (file, 0x18); /* size of header ? */
1315
put_4int_cur (file, 0x0288); /* size of header + body */
1316
put_4int_cur (file, 0x64); /* type of the entry */
1317
put_n0_cur (file, 6);
1318
put_4int_cur (file, 0x010084); /* ? */
1319
put_4int_cur (file, 0x05); /* ? */
1320
put_4int_cur (file, 0x09); /* ? */
1321
put_4int_cur (file, 0x03); /* ? */
1322
put_4int_cur (file, 0x120001); /* ? */
1323
put_n0_cur (file, 3);
1324
put_4int_cur (file, 0xc80002); /* ? */
1325
put_n0_cur (file, 3);
1326
put_4int_cur (file, 0x3c000d); /* ? */
1327
put_n0_cur (file, 3);
1328
put_4int_cur (file, 0x7d0004); /* ? */
1329
put_n0_cur (file, 3);
1330
put_4int_cur (file, 0x7d0003); /* ? */
1331
put_n0_cur (file, 3);
1332
put_4int_cur (file, 0x640008); /* ? */
1333
put_n0_cur (file, 3);
1334
put_4int_cur (file, 0x640017); /* ? */
1335
put_4int_cur (file, 0x01); /* bool? (visible? / colums?) */
1336
put_n0_cur (file, 2);
1337
put_4int_cur (file, 0x500014); /* ? */
1338
put_4int_cur (file, 0x01); /* bool? (visible?) */
1339
put_n0_cur (file, 2);
1340
put_4int_cur (file, 0x7d0015); /* ? */
1341
put_4int_cur (file, 0x01); /* bool? (visible?) */
1342
put_n0_cur (file, 114);
1346
/* Write out the mhyp header. Size will be written later */
1347
static void mk_mhyp (FILE *file, gunichar2 *listname,
1348
guint32 type, guint32 track_num)
1350
put_data_cur (file, "mhyp", 4); /* header */
1351
put_4int_cur (file, 108); /* length */
1352
put_4int_cur (file, -1); /* size -> later */
1353
put_4int_cur (file, 2); /* ? */
1354
put_4int_cur (file, track_num); /* number of tracks in plist */
1355
put_4int_cur (file, type); /* 1 = main, 0 = visible */
1356
put_4int_cur (file, 0); /* ? */
1357
put_4int_cur (file, 0); /* ? */
1358
put_4int_cur (file, 0); /* ? */
1359
put_n0_cur (file, 18); /* dummy space */
1361
mk_mhod (file, MHOD_ID_TITLE, listname, 1);
1365
/* Fix the mhyp header */
1366
static void fix_mhyp (FILE *file, glong mhyp_seek, glong cur)
1368
put_4int_seek (file, cur-mhyp_seek, mhyp_seek+8);
1373
/* Header for new PL item */
1374
static void mk_mhip (FILE *file, guint32 id)
1376
put_data_cur (file, "mhip", 4);
1377
put_4int_cur (file, 76);
1378
put_4int_cur (file, 76);
1379
put_4int_cur (file, 1);
1380
put_4int_cur (file, 0);
1381
put_4int_cur (file, id); /* track id in playlist */
1382
put_4int_cur (file, id); /* ditto.. don't know the difference, but this
1383
seems to work. Maybe a special ID used for
1385
put_n0_cur (file, 12);
1389
write_mhsd_one(FILE *file, gpointer data)
1392
guint32 i, track_num, mhod_num;
1393
glong mhsd_seek, mhit_seek, mhlt_seek;
1395
track_num = it_get_nr_of_tracks(data);
1397
mhsd_seek = ftell (file); /* get position of mhsd header */
1398
mk_mhsd (file, 1); /* write header: type 1: track */
1399
mhlt_seek = ftell (file); /* get position of mhlt header */
1400
mk_mhlt (file, track_num); /* write header with nr. of tracks */
1401
for (i=0; i<track_num; ++i) /* Write each track */
1403
if((track = it_get_track_by_nr (i, data)) == 0)
1405
g_warning ("Invalid track Index!\n");
1408
mhit_seek = ftell (file);
1409
mk_mhit (file, track);
1411
if (utf16_strlen (track->title_utf16) != 0)
1413
mk_mhod (file, MHOD_ID_TITLE, track->title_utf16, 1);
1416
if (utf16_strlen (track->ipod_path_utf16) != 0)
1418
mk_mhod (file, MHOD_ID_PATH, track->ipod_path_utf16, 1);
1421
if (utf16_strlen (track->album_utf16) != 0)
1423
mk_mhod (file, MHOD_ID_ALBUM, track->album_utf16, 1);
1426
if (utf16_strlen (track->artist_utf16) != 0)
1428
mk_mhod (file, MHOD_ID_ARTIST, track->artist_utf16, 1);
1431
if (utf16_strlen (track->genre_utf16) != 0)
1433
mk_mhod (file, MHOD_ID_GENRE, track->genre_utf16, 1);
1436
if (utf16_strlen (track->fdesc_utf16) != 0)
1438
mk_mhod (file, MHOD_ID_FDESC, track->fdesc_utf16, 1);
1441
if (utf16_strlen (track->comment_utf16) != 0)
1443
mk_mhod (file, MHOD_ID_COMMENT, track->comment_utf16, 1);
1446
if (utf16_strlen (track->composer_utf16) != 0)
1448
mk_mhod (file, MHOD_ID_COMPOSER, track->composer_utf16, 1);
1451
/* Fill in the missing items of the mhit header */
1452
fix_mhit (file, mhit_seek, ftell (file), mhod_num);
1454
fix_mhsd (file, mhsd_seek, ftell (file));
1458
write_playlist(FILE *file, Playlist *pl, gpointer data)
1463
gunichar2 empty = 0;
1465
mhyp_seek = ftell(file);
1466
n = it_get_nr_of_tracks_in_playlist (pl, data);
1468
fprintf(stderr, "Playlist: %s (%d tracks)\n", pl->name, n);
1470
mk_mhyp(file, pl->name_utf16, pl->type, n);
1473
if((s = it_get_track_in_playlist_by_nr (pl, i, data)))
1475
mk_mhip(file, s->ipod_id);
1476
mk_mhod(file, MHOD_ID_PLAYLIST, &empty, s->ipod_id);
1479
fix_mhyp (file, mhyp_seek, ftell(file));
1484
/* Expects the master playlist to be (it_get_playlist_by_nr (0)) */
1486
write_mhsd_two(FILE *file, gpointer data)
1488
guint32 playlists, i;
1489
glong mhsd_seek, mhlp_seek;
1491
mhsd_seek = ftell (file); /* get position of mhsd header */
1492
mk_mhsd (file, 2); /* write header: type 2: playlists */
1493
mhlp_seek = ftell (file);
1494
playlists = it_get_nr_of_playlists(data);
1495
mk_mhlp (file, playlists);
1496
for(i = 0; i < playlists; i++)
1498
write_playlist(file, it_get_playlist_by_nr(i, data), data);
1500
fix_mhlp (file, mhlp_seek, playlists);
1501
fix_mhsd (file, mhsd_seek, ftell (file));
1505
/* Do the actual writing to the iTunesDB */
1507
write_it (FILE *file, gpointer data)
1513
write_mhsd_one(file, data); /* write tracks mhsd */
1514
write_mhsd_two(file, data); /* write playlists mhsd */
1515
fix_mhbd (file, mhbd_seek, ftell (file));
1520
/* Write out an iTunesDB.
1521
Note: only the _utf16 entries in the Track-struct are used
1522
An existing "Play Counts" file is renamed to "Play Counts.bak" if
1523
the export was successful.
1524
Returns TRUE on success, FALSE on error.
1525
"path" should point to the mount point of the
1526
iPod, e.e. "/mnt/ipod" */
1527
gboolean itunesdb_write (const gchar *path, gpointer data)
1529
gchar *filename = NULL;
1530
gboolean result = FALSE;
1532
filename = g_build_filename (path, "iPod_Control/iTunes/iTunesDB", NULL);
1533
result = itunesdb_write_to_file (filename, data);
1538
/* Same as itnuesdb_write (), but you specify the filename directly */
1539
gboolean itunesdb_write_to_file (const gchar *filename, gpointer data)
1542
gboolean result = FALSE;
1545
fprintf(stderr, "Writing to %s\n", filename);
1548
if((file = fopen (filename, "w+")))
1550
write_it (file, data);
1556
itunesdb_warning (_("Could not open \"%s\" file for writing.\n"),
1560
{ /* rename "Play Counts" to "Play Counts.bak" */
1561
gchar *dirname = g_path_get_dirname (filename);
1562
gchar *plcname_o = g_build_filename (dirname, "Play Counts", NULL);
1563
gchar *plcname_n = g_build_filename (dirname, "Play Counts.bak", NULL);
1564
if (g_file_test (plcname_o, G_FILE_TEST_EXISTS))
1566
if (rename (plcname_o, plcname_n) == -1)
1567
{ /* an error occured */
1568
itunesdb_warning (_("Error renaming '%s' to '%s' (%s).\n"),
1569
plcname_o, plcname_n, g_strerror (errno));
1579
/* Copy one track to the ipod. The PC-Filename is
1580
"pcfile" and is taken literally.
1581
"path" is assumed to be the mountpoint of the iPod.
1582
For storage, the directories "f00 ... f19" will be
1583
cycled through. The filename is constructed from
1584
"track->ipod_id": "gtkpod_id" and written to
1585
"track->ipod_path_utf8" and "track->ipod_path_utf16" */
1586
gboolean itunesdb_copy_track_to_ipod (const gchar *path,
1588
const gchar *pcfile)
1590
static gint dir_num = -1;
1591
gchar *ipod_file = NULL, *ipod_fullfile = NULL;
1592
gchar *original_suffix;
1597
if (path) pathlen = strlen (path); /* length of path in bytes */
1600
fprintf(stderr, "Entered itunesdb_copy_track_to_ipod: '%s', %p, '%s'\n", path, track, pcfile);
1602
if (dir_num == -1) dir_num = (gint) (19.0*rand()/(RAND_MAX));
1603
if(track->transferred == TRUE) return TRUE; /* nothing to do */
1606
g_warning ("Programming error: copy_track_to_ipod () called NULL-track\n");
1610
/* we may need the original suffix of pcfile to construct a correct
1612
original_suffix = strrchr (pcfile, '.');
1613
/* If there is no ".mp3", ".m4a" etc, set original_suffix to empty
1614
string. Note: the iPod will most certainly ignore this file... */
1615
if (!original_suffix) original_suffix = "";
1617
/* If track->ipod_path exists, we use that one instead. */
1618
ipod_fullfile = itunesdb_get_track_name_on_ipod (path, (iPodSong*)track);
1619
if (!ipod_fullfile) do
1620
{ /* we need to loop until we find an unused filename */
1621
if (ipod_file) g_free(ipod_file);
1622
if (ipod_fullfile) g_free(ipod_fullfile);
1623
/* The iPod seems to need the .mp3 ending to play the track.
1624
Of course the following line should be changed once gtkpod
1625
also supports other formats. */
1626
ipod_file = g_strdup_printf ("/iPod_Control/Music/F%02d/gtkpod%05d%s",
1627
dir_num, track->ipod_id + oops, original_suffix);
1628
ipod_fullfile = g_build_filename (path, ipod_file+1, NULL);
1629
/* There is a case-sensitivity problem on some systems (see note
1630
* at itunesdb_get_track_name_on_ipod (). The following code
1631
tries to work around it */
1632
if (!g_file_test (ipod_fullfile, G_FILE_TEST_EXISTS))
1633
{ /* does not exist -- let's try to create it */
1634
FILE *file = fopen (ipod_fullfile, "w+");
1636
{ /* OK -- everything's fine -- let's clean up */
1638
remove (ipod_fullfile);
1641
{ /* let's try to change the ".../Music/F..." to
1642
* ".../Music/f..." and try again */
1643
gchar *bufp = strstr (ipod_fullfile+pathlen,
1645
if (bufp) bufp[7] = 'f';
1646
/* we don't have to check if it works because if it
1647
doesn't most likely @path is wrong in the first
1648
place, or the iPod isn't mounted etc. We'll catch
1649
that curing copy anyhow. */
1652
if (oops == 0) oops += 90000;
1654
} while (g_file_test (ipod_fullfile, G_FILE_TEST_EXISTS));
1657
fprintf(stderr, "ipod_fullfile: '%s'\n", ipod_fullfile);
1660
success = itunesdb_cp (pcfile, ipod_fullfile);
1664
track->transferred = TRUE;
1666
if (dir_num == 20) dir_num = 0;
1668
{ /* need to store ipod_filename */
1669
len = strlen (ipod_file);
1670
for (i=0; i<len; ++i) /* replace '/' by ':' */
1671
if (ipod_file[i] == '/') ipod_file[i] = ':';
1672
#ifdef ITUNESDB_PROVIDE_UTF8
1673
if (track->ipod_path) g_free (track->ipod_path);
1674
track->ipod_path = g_strdup (ipod_file);
1676
if (track->ipod_path_utf16) g_free (track->ipod_path_utf16);
1677
track->ipod_path_utf16 = g_utf8_to_utf16 (ipod_file,
1678
-1, NULL, NULL, NULL);
1681
if (ipod_file ) g_free (ipod_file);
1682
if (ipod_fullfile) g_free (ipod_fullfile);
1686
/* Copy file "from_file" to "to_file".
1687
Returns TRUE on success, FALSE otherwise */
1688
gboolean itunesdb_cp (const gchar *from_file, const gchar *to_file)
1690
gchar *data=g_malloc (ITUNESDB_COPYBLK);
1691
glong bread, bwrite;
1692
gboolean success = TRUE;
1693
FILE *file_in = NULL;
1694
FILE *file_out = NULL;
1697
fprintf(stderr, "Entered itunesdb_cp: '%s', '%s'\n", from_file, to_file);
1700
do { /* dummy loop for easier error handling */
1701
file_in = fopen (from_file, "r");
1702
if (file_in == NULL)
1704
itunesdb_warning (_("Could not open \"%s\" file for reading.\n"), from_file);
1708
file_out = fopen (to_file, "w");
1709
if (file_out == NULL)
1711
itunesdb_warning (_("Could not open \"%s\" file for writing.\n"), to_file);
1716
bread = fread (data, 1, ITUNESDB_COPYBLK, file_in);
1718
fprintf(stderr, "itunesdb_cp: read %ld bytes\n", bread);
1722
if (feof (file_in) == 0)
1723
{ /* error -- not end of file! */
1724
itunesdb_warning (_("Error reading file \"%s\".\n"), from_file);
1730
bwrite = fwrite (data, 1, bread, file_out);
1732
fprintf(stderr, "itunesdb_cp: wrote %ld bytes\n", bwrite);
1734
if (bwrite != bread)
1736
itunesdb_warning (_("Error writing PC file \"%s\".\n"),to_file);
1740
} while (success && (bread != 0));
1742
if (file_in) fclose (file_in);
1747
{ /* error occured -> delete to_file */
1749
fprintf(stderr, "itunesdb_cp: copy unsuccessful, removing '%s'\n", to_file);
1758
#endif /* ITUNESDB_WRITE */
1759
/*------------------------------------------------------------------*\
1763
\*------------------------------------------------------------------*/
1765
guint32 itunesdb_time_get_mac_time (void)
1769
g_get_current_time (&time);
1770
return itunesdb_time_host_to_mac (time.tv_sec);
1774
/* convert Macintosh timestamp to host system time stamp -- modify
1775
* this function if necessary to port to host systems with different
1777
/* A "0" time will not be converted */
1778
time_t itunesdb_time_mac_to_host (guint32 mactime)
1780
if (mactime != 0) return ((time_t)mactime) - 2082844800;
1781
else return (time_t)mactime;
1785
/* convert host system timestamp to Macintosh time stamp -- modify
1786
* this function if necessary to port to host systems with different
1788
guint32 itunesdb_time_host_to_mac (time_t time)
1790
return (guint32)(time + 2082844800);
1794
/* Return the full iPod filename as stored in @s.
1796
@path: mount point of the iPod file system
1797
Return value: full filename to @s on the iPod or NULL if no
1798
filename is set in @s. NOTE: the file does not necessarily
1799
exist. NOTE: this code works around a problem on some systems (see
1800
below) and might return a filename with different case than the
1801
original filename. Don't copy it back to @s */
1802
gchar *itunesdb_get_track_name_on_ipod (const gchar *path, iPodSong *s)
1804
gchar *result = NULL;
1806
if(s && s->ipod_path && *s->ipod_path)
1808
gchar *buf = g_strdup (s->ipod_path);
1809
g_strdelimit (buf, ":", G_DIR_SEPARATOR);
1810
result = g_build_filename (path, buf, NULL);
1811
/* There seems to be a problem with some distributions
1812
(kernel versions or whatever -- even identical version
1813
numbers don't don't show identical behaviour...): even
1814
though vfat is supposed to be case insensitive, a
1815
difference is made between upper and lower case under
1816
some special circumstances. As in
1817
"/iPod_Control/Music/F00" and "/iPod_Control/Music/f00
1818
"... If the former filename does not exist, we try to
1819
access the latter. If that exists we return it,
1820
otherwise we return the first version. */
1821
if (!g_file_test (result, G_FILE_TEST_EXISTS))
1823
gchar *bufp = strstr (buf, "/Music/F");
1827
bufp[7] = 'f'; /* change the 'F' to 'f' */
1828
result2 = g_build_filename (path, buf, NULL);
1829
if (g_file_test (result2, G_FILE_TEST_EXISTS))
1834
else g_free (result2);