~ubuntu-branches/ubuntu/utopic/rhythmbox/utopic-proposed

« back to all changes in this revision

Viewing changes to sources/itunesdb.c

Tags: upstream-0.9.2
ImportĀ upstreamĀ versionĀ 0.9.2

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/*  arch-tag: Implementation of itunesdb parser, based on code from gtkpod */
2
 
/* Time-stamp: <2003-11-29 12:09:39 jcs>
3
 
|
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.
7
 
8
 
|  URL: http://gtkpod.sourceforge.net/
9
 
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>.
13
 
|
14
 
|  gnupod-tools: http://www.blinkenlights.ch/cgi-bin/fm.pl?get=ipod
15
 
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.
20
 
|  
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.
25
 
|  
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
29
 
30
 
|  iTunes and iPod are trademarks of Apple
31
 
32
 
|  This product is not supported/written/published by Apple!
33
 
*/
34
 
 
35
 
 
36
 
 
37
 
 
38
 
/* Some notes on how to use the functions in this file:
39
 
 
40
 
 
41
 
   *** Reading the iTunesDB ***
42
 
 
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.
46
 
 
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
49
 
   is updated.
50
 
 
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.
55
 
 
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):
59
 
 
60
 
   typedef struct
61
 
   {
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?    +/
85
 
   } Track;
86
 
 
87
 
   "transferred" will be set to TRUE because all tracks read from a
88
 
   iTunesDB are obviously (or hopefully) already transferred to the
89
 
   iPod.
90
 
 
91
 
   "recent_playcount" is for information only and will not be stored
92
 
   to the iPod.
93
 
 
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.
97
 
 
98
 
   For each new playlist in the iTunesDB, it_add_playlist() is
99
 
   called with a pointer to the following Playlist struct:
100
 
 
101
 
   typedef struct
102
 
   {
103
 
     gunichar2 *name_utf16;
104
 
     guint32 type;         /+ 1: master play list (PL_TYPE_MPL) +/
105
 
   } Playlist;
106
 
 
107
 
   Again, by #defining ITUNESDB_PROVIDE_UTF8, a member "gchar *name"
108
 
   will be initialized with a utf8 version of the playlist name.
109
 
 
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.
112
 
 
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
115
 
   be added.
116
 
 
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);
120
 
 
121
 
 
122
 
   *** Writing the iTunesDB ***
123
 
 
124
 
   gboolean itunesdb_write (gchar *path), /+ path to mountpoint +/
125
 
   will write an updated version of the iTunesDB.
126
 
 
127
 
   The "Play Counts" file is renamed to "Play Counts.bak" if it exists
128
 
   to avoid it being read multiple times.
129
 
 
130
 
   It uses the following functions to retrieve the data necessary data
131
 
   from memory:
132
 
 
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);
139
 
 
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.
142
 
 
143
 
   Please note that non-transferred tracks are not automatically
144
 
   transferred to the iPod. A function
145
 
 
146
 
   gboolean itunesdb_copy_track_to_ipod (gchar *path, Track *track, gchar *pcfile)
147
 
 
148
 
   is provided to help you do that, however.
149
 
 
150
 
   The following functions most likely will also come in handy:
151
 
 
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);
156
 
 
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
159
 
   done in gtkpod).
160
 
 
161
 
   Jorg Schuler, 19.12.2002 */
162
 
 
163
 
 
164
 
/* call itunesdb_parse () to read the iTunesDB  */
165
 
/* call itunesdb_write () to write the iTunesDB */
166
 
 
167
 
 
168
 
 
169
 
#ifdef HAVE_CONFIG_H
170
 
#  include <config.h>
171
 
#endif
172
 
 
173
 
#include <glib.h>
174
 
#include <libgnome/gnome-i18n.h>
175
 
#include <stdio.h>
176
 
#include <errno.h>
177
 
#include <stdlib.h>
178
 
#include <string.h>
179
 
#include <time.h>
180
 
#include "itunesdb.h"
181
 
 
182
 
#ifdef IS_GTKPOD
183
 
/* we're being linked with gtkpod */
184
 
#define itunesdb_warning(...) g_print(__VA_ARGS__)
185
 
#else
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)
189
 
#endif
190
 
 
191
 
/* We instruct itunesdb_parse to provide utf8 versions of the strings */
192
 
#define ITUNESDB_PROVIDE_UTF8
193
 
 
194
 
#define ITUNESDB_DEBUG 0
195
 
#define ITUNESDB_MHIT_DEBUG 0
196
 
 
197
 
enum _iPodParserState {
198
 
        IPOD_PARSER_INIT,
199
 
        IPOD_PARSER_SONGS,
200
 
        IPOD_PARSER_PLAYLISTS,
201
 
        IPOD_PARSER_END,
202
 
        IPOD_PARSER_ERROR
203
 
};
204
 
typedef enum _iPodParserState iPodParserState;
205
 
 
206
 
 
207
 
struct _iPodParser {
208
 
        FILE *itunes;
209
 
        iPodParserState state;
210
 
        gchar *mount_path;
211
 
        gsize seek;
212
 
        gsize pl_mhsd;
213
 
        guint32 nr_tracks;
214
 
        guint32 nr_playlists;
215
 
        gboolean swapped_mhsd;
216
 
        GList *playcounts;
217
 
};
218
 
/* This is typedef'ed to iPodParser in itunesdb.h */
219
 
 
220
 
 
221
 
/* structure to hold the contents of one entry of the Play Count file */
222
 
struct playcount {
223
 
    guint32 playcount;
224
 
    guint32 time_played;
225
 
    guint32 rating;
226
 
};
227
 
 
228
 
static struct playcount *get_next_playcount (iPodParser *parser);
229
 
 
230
 
#if 0
231
 
#define DEFAULT_MOUNT_PATH "/mnt/ipod"
232
 
#define GCONF_MOUNT_PATH "/apps/qahog/mount_path"
233
 
 
234
 
 
235
 
static char *
236
 
ipod_get_mount_path (char *mount_point)
237
 
{
238
 
        gchar *path;
239
 
 
240
 
        path = eel_gconf_get_string (GCONF_MOUNT_PATH);
241
 
        if (path == NULL || strcmp (path, "") == 0)
242
 
                return g_strdup (DEFAULT_MOUNT_PATH);
243
 
        else
244
 
                return path;
245
 
}
246
 
#endif
247
 
 
248
 
static char *
249
 
ipod_get_itunesdb_path (const char *mount_path)
250
 
{
251
 
        gchar *result;
252
 
 
253
 
        result = g_build_filename (G_DIR_SEPARATOR_S, mount_path,
254
 
                                   "iPod_Control/iTunes/iTunesDB", NULL);
255
 
        return result;
256
 
}
257
 
 
258
 
static char *
259
 
ipod_get_playcounts_path (const char *mount_point)
260
 
{
261
 
        gchar *result;
262
 
 
263
 
        result = g_build_filename (G_DIR_SEPARATOR_S, mount_point,
264
 
                                   "iPod_Control/iTunes/Play Counts", NULL);
265
 
        return result;
266
 
}
267
 
 
268
 
 
269
 
/* Compare the two data. TRUE if identical */
270
 
static gboolean cmp_n_bytes (gchar *data1, gchar *data2, gint n)
271
 
{
272
 
  gint i;
273
 
 
274
 
  for(i=0; i<n; ++i)
275
 
    {
276
 
      if (data1[i] != data2[i]) return FALSE;
277
 
    }
278
 
  return TRUE;
279
 
}
280
 
 
281
 
 
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)
285
 
{
286
 
  gint i;
287
 
  gint read;
288
 
 
289
 
  if (fseek (file, seek, SEEK_SET) != 0) return -1;
290
 
 
291
 
  for(i=0; i<n; ++i)
292
 
    {
293
 
      read = fgetc (file);
294
 
      if (read == EOF) return i;
295
 
      *data++ = (gchar)read;
296
 
    }
297
 
  return i;
298
 
}
299
 
 
300
 
 
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)
304
 
{
305
 
  guchar data[4];
306
 
  guint32 n;
307
 
 
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]);
313
 
  return n;
314
 
}
315
 
 
316
 
 
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)
320
 
{
321
 
  guint32 i=0;
322
 
  while (utf16[i] != 0) ++i;
323
 
  return i;
324
 
}
325
 
#endif
326
 
 
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)
330
 
gint32 i;
331
 
 if (utf16_string)
332
 
 {
333
 
     for(i=0; i<utf16_strlen(utf16_string); i++)
334
 
     {
335
 
         utf16_string[i] = ((utf16_string[i]<<8) & 0xff00) | 
336
 
             ((utf16_string[i]>>8) & 0xff);
337
 
     }
338
 
 }
339
 
#endif
340
 
return utf16_string;
341
 
}
342
 
 
343
 
 
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)
348
 
{
349
 
  gchar data[4];
350
 
  gunichar2 *entry_utf16 = NULL;
351
 
  gint32 xl;
352
 
 
353
 
#if ITUNESDB_DEBUG
354
 
  fprintf(stderr, "get_mhod seek: %x\n", (int)seek);
355
 
#endif
356
 
 
357
 
  if (seek_get_n_bytes (parser->itunes, data, parser->seek, 4) != 4) 
358
 
    {
359
 
      *ml = -1;
360
 
      return NULL;
361
 
    }
362
 
  if (cmp_n_bytes (data, "mhod", 4) == FALSE )
363
 
    {
364
 
      *ml = -1;
365
 
      return NULL;
366
 
    }
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   */
370
 
 
371
 
#if ITUNESDB_DEBUG
372
 
  fprintf(stderr, "ml: %x mty: %x, xl: %x\n", *ml, *mty, xl);
373
 
#endif
374
 
 
375
 
  switch (*mty)
376
 
  {
377
 
  case MHOD_ID_PLAYLIST: /* do something with the "weird mhod" */
378
 
      break;
379
 
  default:
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);
383
 
          entry_utf16 = NULL;
384
 
          *ml = -1;
385
 
      }
386
 
      else
387
 
      {
388
 
          entry_utf16[xl/2] = 0; /* add trailing 0 */
389
 
      }
390
 
      break;
391
 
  }
392
 
  return fixup_utf16(entry_utf16);
393
 
}
394
 
 
395
 
/* get a PL, return pos where next PL should be, name and content */
396
 
static iPodPlaylist * get_pl(iPodParser *parser) 
397
 
{
398
 
  gunichar2 *plname_utf16 = NULL, *plname_utf16_maybe;
399
 
#ifdef ITUNESDB_PROVIDE_UTF8
400
 
  gchar *plname_utf8;
401
 
#endif
402
 
  guint32 type, pltype, tracknum, n;
403
 
  guint32 nextseek;
404
 
  gint32 zip;
405
 
  iPodPlaylist *plitem;
406
 
  guint32 ref;
407
 
 
408
 
  gchar data[4];
409
 
 
410
 
 
411
 
#if ITUNESDB_DEBUG
412
 
  fprintf(stderr, "mhyp seek: %x\n", (int)parser->seek);
413
 
#endif
414
 
 
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! */
424
 
  do
425
 
  {
426
 
      parser->seek += zip;
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)
430
 
      {
431
 
      case MHOD_ID_PLAYLIST:
432
 
          break; /* here we could do something about the "weird mhod" */
433
 
      case MHOD_ID_TITLE:
434
 
          if (plname_utf16_maybe)
435
 
          {
436
 
              /* sometimes there seem to be two mhod TITLE headers */
437
 
              if (plname_utf16) g_free (plname_utf16);
438
 
              plname_utf16 = plname_utf16_maybe;
439
 
          }
440
 
          break;
441
 
      }
442
 
  } while (zip != -1); /* read all MHODs */
443
 
  if (!plname_utf16)
444
 
  {   /* we did not read a valid mhod TITLE header -> */
445
 
      /* we simply make up our own name */
446
 
        if (pltype == 1)
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);
451
 
  }
452
 
#ifdef ITUNESDB_PROVIDE_UTF8
453
 
  plname_utf8 = g_utf16_to_utf8 (plname_utf16, -1, NULL, NULL, NULL);
454
 
#endif 
455
 
 
456
 
 
457
 
#if ITUNESDB_DEBUG
458
 
  fprintf(stderr, "pln: %s(%d Tracks) \n", plname_utf8, (int)tracknum);
459
 
#endif
460
 
 
461
 
  plitem = g_malloc0 (sizeof (iPodPlaylist));
462
 
 
463
 
#ifdef ITUNESDB_PROVIDE_UTF8
464
 
  plitem->name = plname_utf8;
465
 
#endif
466
 
  plitem->name_utf16 = plname_utf16;
467
 
  plitem->type = pltype;
468
 
 
469
 
#if ITUNESDB_DEBUG
470
 
  fprintf(stderr, "added pl: %s", plname_utf8);
471
 
#endif
472
 
 
473
 
  n = 0;  /* number of tracks read */
474
 
  while (n < tracknum)
475
 
    {
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
481
 
          if (plname_utf8) {
482
 
              g_free (plname_utf8);
483
 
          }
484
 
#endif
485
 
          if (plname_utf16) {
486
 
              g_free (plname_utf16);
487
 
          }
488
 
          g_free (plitem);
489
 
          return NULL; /* Wrong!!! */
490
 
      }
491
 
      if (cmp_n_bytes (data, "mhip", 4) == TRUE)
492
 
        {
493
 
          ref = get4int(parser->itunes, parser->seek+24);
494
 
          plitem->song_ids = g_list_append (plitem->song_ids, (gpointer)ref);
495
 
          ++n;
496
 
        }
497
 
      parser->seek += get4int (parser->itunes, parser->seek+8);
498
 
    }
499
 
  return plitem;
500
 
}
501
 
 
502
 
 
503
 
static iPodSong *get_mhit(iPodParser *parser)
504
 
{
505
 
  iPodSong *track;
506
 
  gchar data[4];
507
 
#ifdef ITUNESDB_PROVIDE_UTF8
508
 
  gchar *entry_utf8;
509
 
#endif 
510
 
  gunichar2 *entry_utf16;
511
 
  gint type;
512
 
  gint zip = 0;
513
 
  struct playcount *playcount;
514
 
 
515
 
#if ITUNESDB_DEBUG
516
 
  fprintf(stderr, "get_mhit seek: %x\n", (int)seek);
517
 
#endif
518
 
 
519
 
  if (seek_get_n_bytes (parser->itunes, data, parser->seek, 4) != 4) {
520
 
    return NULL;
521
 
  }
522
 
  if (cmp_n_bytes (data, "mhit", 4) == FALSE ) {
523
 
    return NULL; /* we are lost! */
524
 
  }
525
 
 
526
 
  track = g_new0 (iPodSong, 1);
527
 
 
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! */
543
 
 
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); }
549
 
  {
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, "?");
595
 
  }
596
 
#undef printf_mhit_time
597
 
#undef printf_mhit
598
 
#endif
599
 
 
600
 
  parser->seek += get4int (parser->itunes, parser->seek+4); /* 1st mhod starts here! */
601
 
  while(zip != -1)
602
 
    {
603
 
     parser->seek += zip;
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);
608
 
#endif 
609
 
       switch (type)
610
 
         {
611
 
         case MHOD_ID_ALBUM:
612
 
#ifdef ITUNESDB_PROVIDE_UTF8
613
 
           track->album = entry_utf8;
614
 
#endif 
615
 
           track->album_utf16 = entry_utf16;
616
 
           break;
617
 
         case MHOD_ID_ARTIST:
618
 
#ifdef ITUNESDB_PROVIDE_UTF8
619
 
           track->artist = entry_utf8;
620
 
#endif
621
 
           track->artist_utf16 = entry_utf16;
622
 
           break;
623
 
         case MHOD_ID_TITLE:
624
 
#ifdef ITUNESDB_PROVIDE_UTF8
625
 
           track->title = entry_utf8;
626
 
#endif
627
 
           track->title_utf16 = entry_utf16;
628
 
           break;
629
 
         case MHOD_ID_GENRE:
630
 
#ifdef ITUNESDB_PROVIDE_UTF8
631
 
           track->genre = entry_utf8;
632
 
#endif
633
 
           track->genre_utf16 = entry_utf16;
634
 
           break;
635
 
         case MHOD_ID_PATH:
636
 
#ifdef ITUNESDB_PROVIDE_UTF8
637
 
           track->ipod_path = entry_utf8;
638
 
#endif
639
 
           track->ipod_path_utf16 = entry_utf16;
640
 
           break;
641
 
         case MHOD_ID_FDESC:
642
 
#ifdef ITUNESDB_PROVIDE_UTF8
643
 
           track->fdesc = entry_utf8;
644
 
#endif
645
 
           track->fdesc_utf16 = entry_utf16;
646
 
           break;
647
 
         case MHOD_ID_COMMENT:
648
 
#ifdef ITUNESDB_PROVIDE_UTF8
649
 
           track->comment = entry_utf8;
650
 
#endif
651
 
           track->comment_utf16 = entry_utf16;
652
 
           break;
653
 
         case MHOD_ID_COMPOSER:
654
 
#ifdef ITUNESDB_PROVIDE_UTF8
655
 
           track->composer = entry_utf8;
656
 
#endif
657
 
           track->composer_utf16 = entry_utf16;
658
 
           break;
659
 
         default: /* unknown entry -- discard */
660
 
#ifdef ITUNESDB_PROVIDE_UTF8
661
 
           g_free (entry_utf8);
662
 
#endif
663
 
           g_free (entry_utf16);
664
 
           break;
665
 
         }
666
 
     }
667
 
    }
668
 
 
669
 
  playcount = get_next_playcount (parser);
670
 
  if (playcount)
671
 
  {
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;
676
 
      g_free (playcount);
677
 
  }
678
 
  return track;   /* no more black magic */
679
 
}
680
 
 
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)
685
 
{
686
 
    struct playcount *playcount;
687
 
    
688
 
    if (parser->playcounts == NULL) {
689
 
        return NULL;
690
 
    }
691
 
    playcount = parser->playcounts->data;
692
 
    if (playcount)  parser->playcounts = g_list_remove (parser->playcounts, playcount);
693
 
    return playcount;
694
 
}
695
 
 
696
 
/* delete all entries of GList *playcounts */
697
 
static void reset_playcounts (iPodParser *parser)
698
 
{
699
 
    g_list_foreach (parser->playcounts, (GFunc)g_free, NULL);
700
 
    g_list_free (parser->playcounts);
701
 
    parser->playcounts = NULL;
702
 
}
703
 
 
704
 
/* Read the Play Count file (formed by adding "Play Counts" to the
705
 
 * directory contained in @filename) and set up the GList *playcounts
706
 
 * */
707
 
static void init_playcounts (iPodParser *parser)
708
 
{
709
 
  gchar *plcname = ipod_get_playcounts_path (parser->mount_path);
710
 
  FILE *plycts = fopen (plcname, "r");
711
 
  gboolean error = TRUE;
712
 
 
713
 
  reset_playcounts (parser);
714
 
 
715
 
  if (plycts) do
716
 
  {
717
 
      gchar data[4];
718
 
      guint32 header_length, entry_length, entry_num, i=0;
719
 
      time_t tt = time (NULL);
720
 
 
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)
735
 
      {
736
 
          struct playcount *playcount = g_new0 (struct playcount, 1);
737
 
          glong seek = header_length + i*entry_length;
738
 
 
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);
745
 
          /* NOTE:
746
 
           *
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.
753
 
           */
754
 
          /* FIXME: this was timezone instead of __timezone, but 
755
 
           * timezone isn't defined in libc headers. What is the appropriate
756
 
           * way to do that ?
757
 
           */
758
 
          if (playcount->time_played)
759
 
              playcount->time_played += __timezone;
760
 
 
761
 
          /* rating only exists if the entry length is at least 0x10 */
762
 
          if (entry_length >= 0x10)
763
 
              playcount->rating = get4int (plycts, seek+12);
764
 
      }
765
 
      parser->playcounts = g_list_reverse (parser->playcounts);
766
 
      if (i == entry_num)  error = FALSE;
767
 
  } while (FALSE);
768
 
  if (plycts)  fclose (plycts);
769
 
  if (error)   reset_playcounts (parser);
770
 
  g_free (plcname);
771
 
}
772
 
 
773
 
 
774
 
/* Parse the iTunesDB and store the tracks using it_addtrack () defined
775
 
   in track.c.
776
 
   Returns TRUE on success, FALSE on error.
777
 
   "path" should point to the mount point of the iPod,
778
 
   e.e. "/mnt/ipod" */
779
 
/* Support for playlists should be added later */
780
 
 
781
 
static gboolean 
782
 
itunesdb_parse(iPodParser *parser)
783
 
{
784
 
  gboolean result = FALSE;
785
 
  gchar data[8];
786
 
  gboolean swapped_mhsd = FALSE;
787
 
 
788
 
  g_assert (parser != NULL);
789
 
  g_assert (parser->itunes != NULL);
790
 
  g_assert (parser->state == IPOD_PARSER_INIT);
791
 
 
792
 
#if ITUNESDB_DEBUG
793
 
  fprintf(stderr, "Parsing %s\nenter: %4d\n", filename, it_get_nr_of_tracks ());
794
 
#endif
795
 
 
796
 
  do
797
 
  { /* dummy loop for easier error handling */
798
 
      if (seek_get_n_bytes (parser->itunes, data, parser->seek, 4) != 4)
799
 
      {
800
 
          gchar *db_path = ipod_get_itunesdb_path (parser->mount_path);
801
 
          itunesdb_warning (_("Error reading \"%s\".\n"), db_path);
802
 
          g_free (db_path);
803
 
          break;
804
 
      }
805
 
      /* for(i=0; i<8; ++i)  printf("%02x ", data[i]); printf("\n");*/
806
 
      if (cmp_n_bytes (data, "mhbd", 4) == FALSE) 
807
 
      {  
808
 
          gchar *db_path = ipod_get_itunesdb_path (parser->mount_path);
809
 
          itunesdb_warning (_("\"%s\" is not a valid iPod database.\n"), db_path);
810
 
          g_free (db_path);
811
 
          break;
812
 
      }
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... */
817
 
      /*      if (seek < 0x68)
818
 
      {
819
 
          itunesdb_warning (_("\"%s\" is not a iTunesDB.\n"), filename);
820
 
          break;
821
 
          }*/
822
 
      do
823
 
      {
824
 
          guint32 zip;
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 */
830
 
                  if (!swapped_mhsd)
831
 
                      parser->pl_mhsd = parser->seek + get4int (parser->itunes, parser->seek+8);
832
 
              }
833
 
              else if (get4int (parser->itunes, parser->seek + 12) == 2)
834
 
              { /* bad: these are playlists... switch */
835
 
                  if (swapped_mhsd)
836
 
                  { /* already switched once -> forget it */
837
 
                      break;
838
 
                  }
839
 
                  else
840
 
                  {
841
 
                      parser->pl_mhsd = parser->seek;
842
 
                      parser->seek += get4int (parser->itunes, parser->seek+8);
843
 
                      swapped_mhsd = TRUE;
844
 
                  }
845
 
              }
846
 
              else
847
 
              { /* neither playlist nor track MHSD --> skip it */
848
 
                  parser->seek += get4int (parser->itunes, parser->seek+8);
849
 
              }
850
 
          }
851
 
 
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 */
857
 
                  result = TRUE;
858
 
                  break;
859
 
              }
860
 
          }
861
 
 
862
 
          if (cmp_n_bytes (data, "mhit", 4) == TRUE)
863
 
          { /* mhit header -> start of tracks*/
864
 
              result = TRUE;
865
 
              break;
866
 
          }
867
 
          zip = get4int (parser->itunes, parser->seek+4);
868
 
          if (zip == 0)  break;
869
 
          parser->seek += zip;
870
 
      } while (result == FALSE);
871
 
      if (result == FALSE)  break; /* some error occured */
872
 
      /* now we should be at the first MHIT */
873
 
 
874
 
      /* Read Play Count file if available */
875
 
      init_playcounts (parser);
876
 
  } while (FALSE);
877
 
 
878
 
  return result;
879
 
}
880
 
 
881
 
 
882
 
static gboolean
883
 
ipod_parse_playlists (iPodParser *parser)
884
 
{
885
 
    gboolean result = FALSE;
886
 
    guchar data[8];
887
 
 
888
 
    do
889
 
    {
890
 
        guint32 zip;
891
 
        if (seek_get_n_bytes (parser->itunes, data, parser->seek, 8) != 8)  break;
892
 
        if (cmp_n_bytes (data, "mhsd", 4) == TRUE)
893
 
        { /* mhsd header */
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);
897
 
            }
898
 
        }
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);
902
 
        }
903
 
        if (cmp_n_bytes (data, "mhyp", 4) == TRUE)
904
 
        { /* mhyp header -> start of playlists */
905
 
            result = TRUE;
906
 
            break;
907
 
        }
908
 
        zip = get4int (parser->itunes, parser->seek+4);
909
 
        if (zip == 0)  break;
910
 
        parser->seek += zip;
911
 
    } while (result == FALSE);
912
 
 
913
 
    return result;
914
 
}
915
 
 
916
 
static iPodItem *
917
 
ipod_item_new (iPodItemType type, gpointer data)
918
 
{
919
 
        iPodItem *item;
920
 
 
921
 
        item = g_new0 (iPodItem, 1);
922
 
        if (item == NULL) {
923
 
                g_warning ("Couldn't allocate iPodItem\n");
924
 
                return NULL;
925
 
        }
926
 
        item->type = type;
927
 
        item->data = data;
928
 
        return item;
929
 
}
930
 
 
931
 
void
932
 
ipod_item_destroy (iPodItem *item)
933
 
{
934
 
        if (item == NULL) {
935
 
                return;
936
 
        }
937
 
        if (item->type == IPOD_ITEM_SONG) {
938
 
                iPodSong *song = (iPodSong *)item->data;
939
 
                g_assert (song != NULL);
940
 
 
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);
964
 
                
965
 
        } else if (item->type == IPOD_ITEM_PLAYLIST) {
966
 
                iPodPlaylist *playlist = (iPodPlaylist *)item->data;
967
 
                
968
 
                g_free (playlist->name);
969
 
                g_free (playlist->name_utf16);
970
 
                g_list_free (playlist->song_ids);
971
 
        } else {
972
 
                g_assert_not_reached ();
973
 
        }
974
 
 
975
 
        g_free (item->data);
976
 
        g_free (item);
977
 
}
978
 
 
979
 
iPodItem *
980
 
ipod_get_next_item (iPodParser *parser) 
981
 
{
982
 
        switch (parser->state) {
983
 
        case IPOD_PARSER_INIT: {
984
 
                gboolean res;
985
 
                res = itunesdb_parse (parser);
986
 
                if (res == FALSE) {
987
 
                        g_print ("Error parsing iTunesDB\n");
988
 
                        parser->state = IPOD_PARSER_ERROR;
989
 
                        return NULL;
990
 
                }
991
 
                if (parser->nr_tracks == 0) {
992
 
                        parser->state = IPOD_PARSER_PLAYLISTS;
993
 
                } else {
994
 
                        parser->state = IPOD_PARSER_SONGS;
995
 
                }
996
 
                /* break statement omitted on purpose */
997
 
        }
998
 
        case IPOD_PARSER_SONGS: {
999
 
                iPodSong *track;
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;
1008
 
                                return NULL;
1009
 
                        }
1010
 
                        parser->state = IPOD_PARSER_PLAYLISTS;
1011
 
                } else {
1012
 
                        return ipod_item_new (IPOD_ITEM_SONG, track);
1013
 
                }
1014
 
                /* No break here, if we didn't return a song, we want to 
1015
 
                 * parse and return the first playlist */
1016
 
        }
1017
 
        case IPOD_PARSER_PLAYLISTS: {
1018
 
                iPodPlaylist *pl;
1019
 
                pl = get_pl(parser);
1020
 
                if (pl == NULL) {
1021
 
                        parser->state = IPOD_PARSER_END;
1022
 
                        break;
1023
 
                } else {
1024
 
                        return ipod_item_new (IPOD_ITEM_PLAYLIST, pl);
1025
 
                }
1026
 
                break;
1027
 
        }
1028
 
                
1029
 
        case IPOD_PARSER_END:
1030
 
                break;
1031
 
 
1032
 
        case IPOD_PARSER_ERROR: /* Should not be reached */
1033
 
        default: g_assert_not_reached ();
1034
 
        }    
1035
 
        return NULL;    
1036
 
}
1037
 
 
1038
 
iPodParser *
1039
 
ipod_parser_new (const gchar *mount_point)
1040
 
{
1041
 
        iPodParser *parser;
1042
 
        gchar *filename = ipod_get_itunesdb_path (mount_point);
1043
 
        
1044
 
        g_return_val_if_fail ((filename != NULL), NULL);
1045
 
        
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"),
1050
 
                                  filename);
1051
 
                g_free (filename);
1052
 
                g_free (parser);
1053
 
                return NULL;
1054
 
        }
1055
 
        parser->mount_path = g_strdup (mount_point);
1056
 
        parser->state = IPOD_PARSER_INIT;
1057
 
        g_free (filename);
1058
 
        return parser;
1059
 
}
1060
 
 
1061
 
 
1062
 
void
1063
 
ipod_parser_destroy (iPodParser *parser)
1064
 
{
1065
 
        g_assert (parser != NULL);
1066
 
 
1067
 
        if (parser->itunes != NULL) {
1068
 
                fclose (parser->itunes);
1069
 
        }
1070
 
        reset_playcounts (parser);
1071
 
        g_free (parser->mount_path);
1072
 
        g_free (parser);
1073
 
}
1074
 
 
1075
 
 
1076
 
/* up to here we had the routines for reading the iTunesDB                */
1077
 
/* ---------------------------------------------------------------------- */
1078
 
 
1079
 
/* from here on we have the routines for writing the iTunesDB             */
1080
 
 
1081
 
#ifdef ITUNESDB_WRITE
1082
 
 
1083
 
/* Name of the device in utf16 */
1084
 
gunichar2 ipod_name[] = { 'g', 't', 'k', 'p', 'o', 'd', 0 };
1085
 
 
1086
 
 
1087
 
/* Get length of utf16 string in number of characters (words) */
1088
 
static guint32 utf16_strlen (gunichar2 *utf16)
1089
 
{
1090
 
  guint32 i=0;
1091
 
  while (utf16[i] != 0) ++i;
1092
 
  return i;
1093
 
}
1094
 
 
1095
 
 
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)
1099
 
{
1100
 
  data[3] = (n >> 24) & 0xff;
1101
 
  data[2] = (n >> 16) & 0xff;
1102
 
  data[1] = (n >>  8) & 0xff;
1103
 
  data[0] =  n & 0xff;
1104
 
}
1105
 
 
1106
 
 
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)
1110
 
{
1111
 
  if (fwrite (data, 1, n, file) != n) return FALSE;
1112
 
  return TRUE;
1113
 
}
1114
 
 
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)
1118
 
{
1119
 
  gchar data[4];
1120
 
 
1121
 
  store4int (n, data);
1122
 
  return put_data_cur (file, data, 4);
1123
 
}
1124
 
 
1125
 
 
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)
1131
 
{
1132
 
  gboolean result;
1133
 
 
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;
1137
 
  return result;
1138
 
}
1139
 
 
1140
 
 
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)
1144
 
{
1145
 
  guint32 i;
1146
 
  gboolean result = TRUE;
1147
 
 
1148
 
  for (i=0; i<n; ++i)  result &= put_4int_cur (file, 0);
1149
 
  return result;
1150
 
}
1151
 
 
1152
 
 
1153
 
 
1154
 
/* Write out the mhbd header. Size will be written later */
1155
 
static void mk_mhbd (FILE *file)
1156
 
{
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 */
1165
 
}
1166
 
 
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)
1170
 
{
1171
 
  put_4int_seek (file, cur-mhbd_seek, mhbd_seek+8); /* size of whole mhit */
1172
 
}
1173
 
 
1174
 
 
1175
 
/* Write out the mhsd header. Size will be written later */
1176
 
static void mk_mhsd (FILE *file, guint32 type)
1177
 
{
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 */
1183
 
}  
1184
 
 
1185
 
 
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)
1189
 
{
1190
 
  put_4int_seek (file, cur-mhsd_seek, mhsd_seek+8); /* size of whole mhit */
1191
 
}
1192
 
 
1193
 
 
1194
 
/* Write out the mhlt header. */
1195
 
static void mk_mhlt (FILE *file, guint32 track_num)
1196
 
{
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 */
1201
 
}  
1202
 
 
1203
 
 
1204
 
/* Write out the mhit header. Size will be written later */
1205
 
static void mk_mhit (FILE *file, Track *track)
1206
 
{
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                */
1233
 
}  
1234
 
 
1235
 
 
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)
1239
 
{
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        */
1242
 
}
1243
 
 
1244
 
 
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)
1251
 
{
1252
 
  guint32 mod;
1253
 
  guint32 len;
1254
 
 
1255
 
  if (fqid == 1) mod = 40;   /* normal mhod */
1256
 
  else           mod = 44;   /* playlist entry */
1257
 
 
1258
 
  len = utf16_strlen (string);         /* length of string in _words_     */
1259
 
 
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                  */
1268
 
  if (type < 100)
1269
 
    {                                     /* no PL mhod */
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);
1277
 
    }
1278
 
  else
1279
 
    {                                     
1280
 
      put_n0_cur (file, 3);     /* PL mhods are different ... */
1281
 
    }
1282
 
}
1283
 
 
1284
 
 
1285
 
/* Write out the mhlp header. Size will be written later */
1286
 
static void mk_mhlp (FILE *file, guint32 lists)
1287
 
{
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              */
1292
 
}
1293
 
 
1294
 
 
1295
 
/* Fix the mhlp header */
1296
 
static void fix_mhlp (FILE *file, glong mhlp_seek, gint playlist_num)
1297
 
{
1298
 
  put_4int_seek (file, playlist_num, mhlp_seek+8); /* nr of playlists    */
1299
 
}
1300
 
 
1301
 
 
1302
 
 
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
1307
 
   will copy the file! 
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 
1310
 
   FIXME !!! */
1311
 
static void mk_weired (FILE *file)
1312
 
{
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);
1343
 
}
1344
 
 
1345
 
 
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)
1349
 
{
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              */
1360
 
  mk_weired (file);
1361
 
  mk_mhod (file, MHOD_ID_TITLE, listname, 1);
1362
 
}
1363
 
 
1364
 
 
1365
 
/* Fix the mhyp header */
1366
 
static void fix_mhyp (FILE *file, glong mhyp_seek, glong cur)
1367
 
{
1368
 
  put_4int_seek (file, cur-mhyp_seek, mhyp_seek+8);
1369
 
    /* size */
1370
 
}
1371
 
 
1372
 
 
1373
 
/* Header for new PL item */
1374
 
static void mk_mhip (FILE *file, guint32 id)
1375
 
{
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
1384
 
                               playlists? */
1385
 
  put_n0_cur (file, 12); 
1386
 
}
1387
 
 
1388
 
static void
1389
 
write_mhsd_one(FILE *file, gpointer data)
1390
 
{
1391
 
    Track *track;
1392
 
    guint32 i, track_num, mhod_num;
1393
 
    glong mhsd_seek, mhit_seek, mhlt_seek; 
1394
 
 
1395
 
    track_num = it_get_nr_of_tracks(data);
1396
 
 
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 */
1402
 
    {
1403
 
        if((track = it_get_track_by_nr (i, data)) == 0)
1404
 
        {
1405
 
            g_warning ("Invalid track Index!\n");
1406
 
            break;
1407
 
        }
1408
 
        mhit_seek = ftell (file);
1409
 
        mk_mhit (file, track);
1410
 
        mhod_num = 0;
1411
 
        if (utf16_strlen (track->title_utf16) != 0)
1412
 
        {
1413
 
            mk_mhod (file, MHOD_ID_TITLE, track->title_utf16, 1);
1414
 
            ++mhod_num;
1415
 
        }
1416
 
        if (utf16_strlen (track->ipod_path_utf16) != 0)
1417
 
        {
1418
 
            mk_mhod (file, MHOD_ID_PATH, track->ipod_path_utf16, 1);
1419
 
            ++mhod_num;
1420
 
        }
1421
 
        if (utf16_strlen (track->album_utf16) != 0)
1422
 
        {
1423
 
            mk_mhod (file, MHOD_ID_ALBUM, track->album_utf16, 1);
1424
 
            ++mhod_num;
1425
 
        }
1426
 
        if (utf16_strlen (track->artist_utf16) != 0)
1427
 
        {
1428
 
            mk_mhod (file, MHOD_ID_ARTIST, track->artist_utf16, 1);
1429
 
            ++mhod_num;
1430
 
        }
1431
 
        if (utf16_strlen (track->genre_utf16) != 0)
1432
 
        {
1433
 
            mk_mhod (file, MHOD_ID_GENRE, track->genre_utf16, 1);
1434
 
            ++mhod_num;
1435
 
        }
1436
 
        if (utf16_strlen (track->fdesc_utf16) != 0)
1437
 
        {
1438
 
            mk_mhod (file, MHOD_ID_FDESC, track->fdesc_utf16, 1);
1439
 
            ++mhod_num;
1440
 
        }
1441
 
        if (utf16_strlen (track->comment_utf16) != 0)
1442
 
        {
1443
 
            mk_mhod (file, MHOD_ID_COMMENT, track->comment_utf16, 1);
1444
 
            ++mhod_num;
1445
 
        }
1446
 
        if (utf16_strlen (track->composer_utf16) != 0)
1447
 
        {
1448
 
            mk_mhod (file, MHOD_ID_COMPOSER, track->composer_utf16, 1);
1449
 
            ++mhod_num;
1450
 
        }
1451
 
        /* Fill in the missing items of the mhit header */
1452
 
        fix_mhit (file, mhit_seek, ftell (file), mhod_num);
1453
 
    }
1454
 
    fix_mhsd (file, mhsd_seek, ftell (file));
1455
 
}
1456
 
 
1457
 
static void
1458
 
write_playlist(FILE *file, Playlist *pl, gpointer data)
1459
 
{
1460
 
    Track *s;
1461
 
    guint32 i, n;
1462
 
    glong mhyp_seek;
1463
 
    gunichar2 empty = 0;
1464
 
    
1465
 
    mhyp_seek = ftell(file);
1466
 
    n = it_get_nr_of_tracks_in_playlist (pl, data);
1467
 
#if ITUNESDB_DEBUG
1468
 
  fprintf(stderr, "Playlist: %s (%d tracks)\n", pl->name, n);
1469
 
#endif    
1470
 
    mk_mhyp(file, pl->name_utf16, pl->type, n);  
1471
 
    for (i=0; i<n; ++i)
1472
 
    {
1473
 
        if((s = it_get_track_in_playlist_by_nr (pl, i, data)))
1474
 
        {
1475
 
            mk_mhip(file, s->ipod_id);
1476
 
            mk_mhod(file, MHOD_ID_PLAYLIST, &empty, s->ipod_id); 
1477
 
        }
1478
 
    }
1479
 
   fix_mhyp (file, mhyp_seek, ftell(file));
1480
 
}
1481
 
 
1482
 
 
1483
 
 
1484
 
/* Expects the master playlist to be (it_get_playlist_by_nr (0)) */
1485
 
static void
1486
 
write_mhsd_two(FILE *file, gpointer data)
1487
 
{
1488
 
    guint32 playlists, i;
1489
 
    glong mhsd_seek, mhlp_seek;
1490
 
  
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++)
1497
 
    { 
1498
 
        write_playlist(file, it_get_playlist_by_nr(i, data), data);
1499
 
    }
1500
 
    fix_mhlp (file, mhlp_seek, playlists);
1501
 
    fix_mhsd (file, mhsd_seek, ftell (file));
1502
 
}
1503
 
 
1504
 
 
1505
 
/* Do the actual writing to the iTunesDB */
1506
 
gboolean 
1507
 
write_it (FILE *file, gpointer data)
1508
 
{
1509
 
    glong mhbd_seek;
1510
 
 
1511
 
    mhbd_seek = 0;             
1512
 
    mk_mhbd (file);            
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));
1516
 
    return TRUE;
1517
 
}
1518
 
 
1519
 
 
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)
1528
 
{
1529
 
    gchar *filename = NULL;
1530
 
    gboolean result = FALSE;
1531
 
 
1532
 
    filename = g_build_filename (path, "iPod_Control/iTunes/iTunesDB", NULL);
1533
 
    result = itunesdb_write_to_file (filename, data);
1534
 
    g_free (filename);
1535
 
    return result;
1536
 
}
1537
 
 
1538
 
/* Same as itnuesdb_write (), but you specify the filename directly */
1539
 
gboolean itunesdb_write_to_file (const gchar *filename, gpointer data)
1540
 
{
1541
 
  FILE *file = NULL;
1542
 
  gboolean result = FALSE;
1543
 
 
1544
 
#if ITUNESDB_DEBUG
1545
 
  fprintf(stderr, "Writing to %s\n", filename);
1546
 
#endif
1547
 
 
1548
 
  if((file = fopen (filename, "w+")))
1549
 
    {
1550
 
      write_it (file, data);
1551
 
      fclose(file);
1552
 
      result = TRUE;
1553
 
    }
1554
 
  else
1555
 
    {
1556
 
      itunesdb_warning (_("Could not open \"%s\" file for writing.\n"),
1557
 
                      filename);
1558
 
    }
1559
 
  if (result == TRUE)
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))
1565
 
      {
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));
1570
 
          }
1571
 
      }
1572
 
      g_free (dirname);
1573
 
      g_free (plcname_o);
1574
 
      g_free (plcname_n);
1575
 
  }
1576
 
  return result;
1577
 
}
1578
 
 
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,
1587
 
                                      Track *track,
1588
 
                                      const gchar *pcfile)
1589
 
{
1590
 
  static gint dir_num = -1;
1591
 
  gchar *ipod_file = NULL, *ipod_fullfile = NULL;
1592
 
  gchar *original_suffix;
1593
 
  gboolean success;
1594
 
  gint32 oops = 0;
1595
 
  gint pathlen = 0;
1596
 
 
1597
 
  if (path) pathlen = strlen (path); /* length of path in bytes */
1598
 
 
1599
 
#if ITUNESDB_DEBUG
1600
 
  fprintf(stderr, "Entered itunesdb_copy_track_to_ipod: '%s', %p, '%s'\n", path, track, pcfile);
1601
 
#endif
1602
 
  if (dir_num == -1) dir_num = (gint) (19.0*rand()/(RAND_MAX));
1603
 
  if(track->transferred == TRUE) return TRUE; /* nothing to do */
1604
 
  if (track == NULL)
1605
 
    {
1606
 
      g_warning ("Programming error: copy_track_to_ipod () called NULL-track\n");
1607
 
      return FALSE;
1608
 
    }
1609
 
 
1610
 
  /* we may need the original suffix of pcfile to construct a correct
1611
 
     ipod filename */
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 = "";
1616
 
 
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+");
1635
 
          if (file)
1636
 
          { /* OK -- everything's fine -- let's clean up */
1637
 
              fclose (file);
1638
 
              remove (ipod_fullfile);
1639
 
          }
1640
 
          else
1641
 
          { /* let's try to change the ".../Music/F..." to
1642
 
             * ".../Music/f..." and try again */
1643
 
              gchar *bufp = strstr (ipod_fullfile+pathlen,
1644
 
                                    "/Music/F");
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. */
1650
 
          }
1651
 
      }
1652
 
      if (oops == 0)   oops += 90000;
1653
 
      else             ++oops;
1654
 
  } while (g_file_test (ipod_fullfile, G_FILE_TEST_EXISTS));
1655
 
 
1656
 
#if ITUNESDB_DEBUG
1657
 
  fprintf(stderr, "ipod_fullfile: '%s'\n", ipod_fullfile);
1658
 
#endif
1659
 
 
1660
 
  success = itunesdb_cp (pcfile, ipod_fullfile);
1661
 
  if (success)
1662
 
  {
1663
 
      gint i, len;
1664
 
      track->transferred = TRUE;
1665
 
      ++dir_num;
1666
 
      if (dir_num == 20) dir_num = 0;
1667
 
      if (ipod_file)
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);
1675
 
#endif
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);
1679
 
      }
1680
 
  }
1681
 
  if (ipod_file )    g_free (ipod_file);
1682
 
  if (ipod_fullfile) g_free (ipod_fullfile);
1683
 
  return success;
1684
 
}
1685
 
 
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)
1689
 
{
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;
1695
 
 
1696
 
#if ITUNESDB_DEBUG
1697
 
  fprintf(stderr, "Entered itunesdb_cp: '%s', '%s'\n", from_file, to_file);
1698
 
#endif
1699
 
 
1700
 
  do { /* dummy loop for easier error handling */
1701
 
    file_in = fopen (from_file, "r");
1702
 
    if (file_in == NULL)
1703
 
      {
1704
 
        itunesdb_warning (_("Could not open \"%s\" file for reading.\n"), from_file);
1705
 
        success = FALSE;
1706
 
        break;
1707
 
      }
1708
 
    file_out = fopen (to_file, "w");
1709
 
    if (file_out == NULL)
1710
 
      {
1711
 
        itunesdb_warning (_("Could not open \"%s\" file for writing.\n"), to_file);
1712
 
        success = FALSE;
1713
 
        break;
1714
 
      }
1715
 
    do {
1716
 
      bread = fread (data, 1, ITUNESDB_COPYBLK, file_in);
1717
 
#if ITUNESDB_DEBUG
1718
 
      fprintf(stderr, "itunesdb_cp: read %ld bytes\n", bread);
1719
 
#endif
1720
 
      if (bread == 0)
1721
 
        {
1722
 
          if (feof (file_in) == 0)
1723
 
            { /* error -- not end of file! */
1724
 
              itunesdb_warning (_("Error reading file \"%s\".\n"), from_file);
1725
 
              success = FALSE;
1726
 
            }
1727
 
        }
1728
 
      else
1729
 
        {
1730
 
          bwrite = fwrite (data, 1, bread, file_out);
1731
 
#if ITUNESDB_DEBUG
1732
 
      fprintf(stderr, "itunesdb_cp: wrote %ld bytes\n", bwrite);
1733
 
#endif
1734
 
          if (bwrite != bread)
1735
 
            {
1736
 
              itunesdb_warning (_("Error writing PC file \"%s\".\n"),to_file);
1737
 
              success = FALSE;
1738
 
            }
1739
 
        } 
1740
 
    } while (success && (bread != 0));
1741
 
  } while (FALSE);
1742
 
  if (file_in)  fclose (file_in);
1743
 
  if (file_out)
1744
 
    {
1745
 
      fclose (file_out);
1746
 
      if (!success)
1747
 
      { /* error occured -> delete to_file */
1748
 
#if ITUNESDB_DEBUG
1749
 
          fprintf(stderr, "itunesdb_cp: copy unsuccessful, removing '%s'\n", to_file);
1750
 
#endif
1751
 
        remove (to_file);
1752
 
      }
1753
 
    }
1754
 
  g_free (data);
1755
 
  return success;
1756
 
}
1757
 
 
1758
 
#endif /* ITUNESDB_WRITE */
1759
 
/*------------------------------------------------------------------*\
1760
 
 *                                                                  *
1761
 
 *                       Timestamp stuff                            *
1762
 
 *                                                                  *
1763
 
\*------------------------------------------------------------------*/
1764
 
 
1765
 
guint32 itunesdb_time_get_mac_time (void)
1766
 
{
1767
 
    GTimeVal time;
1768
 
 
1769
 
    g_get_current_time (&time);
1770
 
    return itunesdb_time_host_to_mac (time.tv_sec);
1771
 
}
1772
 
 
1773
 
 
1774
 
/* convert Macintosh timestamp to host system time stamp -- modify
1775
 
 * this function if necessary to port to host systems with different
1776
 
 * start of Epoch */
1777
 
/* A "0" time will not be converted */
1778
 
time_t itunesdb_time_mac_to_host (guint32 mactime)
1779
 
{
1780
 
    if (mactime != 0)  return ((time_t)mactime) - 2082844800;
1781
 
    else               return (time_t)mactime;
1782
 
}
1783
 
 
1784
 
 
1785
 
/* convert host system timestamp to Macintosh time stamp -- modify
1786
 
 * this function if necessary to port to host systems with different
1787
 
 * start of Epoch */
1788
 
guint32 itunesdb_time_host_to_mac (time_t time)
1789
 
{
1790
 
    return (guint32)(time + 2082844800);
1791
 
}
1792
 
 
1793
 
 
1794
 
/* Return the full iPod filename as stored in @s.
1795
 
   @s: track
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)
1803
 
{
1804
 
    gchar *result = NULL;
1805
 
 
1806
 
    if(s && s->ipod_path && *s->ipod_path)
1807
 
    {
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))
1822
 
        {
1823
 
            gchar *bufp = strstr (buf, "/Music/F");
1824
 
            if (bufp)
1825
 
            {
1826
 
                gchar *result2;
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))
1830
 
                {
1831
 
                    g_free (result);
1832
 
                    result = result2;
1833
 
                }
1834
 
                else g_free (result2);
1835
 
            }
1836
 
        }
1837
 
        g_free (buf);
1838
 
    }
1839
 
    return result;
1840
 
}