1/**
2 * \File playlist-spl.c
3 *
4 * Playlist_t to Samsung (.spl) and back conversion functions.
5 *
6 * Copyright (C) 2008 Alistair Boyle <alistair.js.boyle@gmail.com>
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 * Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to the
20 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21 * Boston, MA 02111-1307, USA.
22 */
23
24#include <config.h>
25
26#include <stdio.h>
27#include <stdlib.h> // mkstmp()
28#include <unistd.h>
29#include <errno.h>
30#include <sys/stat.h>
31#include <sys/types.h>
32#ifdef HAVE_SYS_UIO_H
33#include <sys/uio.h>
34#endif
35#include <fcntl.h>
36
37#include <string.h>
38
39#include "libmtp.h"
40#include "libusb-glue.h"
41#include "ptp.h"
42#include "unicode.h"
43
44#include "playlist-spl.h"
45
46// set this to 1 to add lots of messy debug output to the playlist code
47#define DEBUG_ENABLED 0
48
49// debug macro
50// d = indenting depth
51#define IF_DEBUG() if(DEBUG_ENABLED) {\
52                     printf("%s:%u:%s(): ", __FILE__, __LINE__, __func__); \
53                   } \
54                   if(DEBUG_ENABLED)
55
56// Internal singly linked list of strings
57// used to hold .spl playlist in memory
58typedef struct text_struct {
59  char* text; // String
60  struct text_struct *next; // Link to next line, NULL if end of list
61} text_t;
62
63
64/**
65 * Forward declarations of local (static) functions.
66 */
67static text_t* read_into_spl_text_t(LIBMTP_mtpdevice_t *device, const int fd);
68static void write_from_spl_text_t(LIBMTP_mtpdevice_t *device, const int fd, text_t* p);
69static void free_spl_text_t(text_t* p);
70static void print_spl_text_t(text_t* p);
71static uint32_t trackno_spl_text_t(text_t* p);
72static void tracks_from_spl_text_t(text_t* p, uint32_t* tracks, LIBMTP_folder_t* folders, LIBMTP_file_t* files);
73static void spl_text_t_from_tracks(text_t** p, uint32_t* tracks, const uint32_t trackno, const uint32_t ver_major, const uint32_t ver_minor, char* dnse, LIBMTP_folder_t* folders, LIBMTP_file_t* files);
74
75static uint32_t discover_id_from_filepath(const char* s, LIBMTP_folder_t* folders, LIBMTP_file_t* files); // TODO add file/dir cached args
76static void discover_filepath_from_id(char** p, uint32_t track, LIBMTP_folder_t* folders, LIBMTP_file_t* files);
77static void find_folder_name(LIBMTP_folder_t* folders, uint32_t* id, char** name);
78static uint32_t find_folder_id(LIBMTP_folder_t* folders, uint32_t parent, char* name);
79
80static void append_text_t(text_t** t, char* s);
81
82
83
84
85/**
86 * Decides if the indicated object index is an .spl playlist.
87 *
88 * @param oi object we are deciding on
89 * @return 1 if this is a Samsung .spl object, 0 otherwise
90 */
91int is_spl_playlist(PTPObjectInfo *oi)
92{
93  return (oi->ObjectFormat == PTP_OFC_Undefined) &&
94         (strlen(oi->Filename) > 4) &&
95         (strcmp((oi->Filename + strlen(oi->Filename) -4), ".spl") == 0);
96}
97
98#ifndef HAVE_MKSTEMP
99# ifdef __WIN32__
100#  include <fcntl.h>
101#  define mkstemp(_pattern) _open(_mktemp(_pattern), _O_CREAT | _O_SHORT_LIVED | _O_EXCL)
102# else
103#  error Missing mkstemp() function.
104# endif
105#endif
106
107/**
108 * Take an object ID, a .spl playlist on the MTP device,
109 * and convert it to a playlist_t object.
110 *
111 * @param device mtp device pointer
112 * @param oi object we are reading
113 * @param id .spl playlist id on MTP device
114 * @param pl the LIBMTP_playlist_t pointer to be filled with info from id
115 */
116
117void spl_to_playlist_t(LIBMTP_mtpdevice_t* device, PTPObjectInfo *oi,
118                       const uint32_t id, LIBMTP_playlist_t * const pl)
119{
120  // Fill in playlist metadata
121  // Use the Filename as the playlist name, dropping the ".spl" extension
122  pl->name = malloc(sizeof(char)*(strlen(oi->Filename) -4 +1));
123  memcpy(pl->name, oi->Filename, strlen(oi->Filename) -4);
124  // Set terminating character
125  pl->name[strlen(oi->Filename) - 4] = 0;
126  pl->playlist_id = id;
127  pl->parent_id = oi->ParentObject;
128  pl->storage_id = oi->StorageID;
129  pl->tracks = NULL;
130  pl->no_tracks = 0;
131
132  IF_DEBUG() printf("pl->name='%s'\n",pl->name);
133
134  // open a temporary file
135  char tmpname[] = "/tmp/mtp-spl2pl-XXXXXX";
136  int fd = mkstemp(tmpname);
137  if(fd < 0) {
138    printf("failed to make temp file for %s.spl -> %s, errno=%s\n", pl->name, tmpname, strerror(errno));
139    return;
140  }
141  // make sure the file will be deleted afterwards
142  if(unlink(tmpname) < 0)
143    printf("failed to delete temp file for %s.spl -> %s, errno=%s\n", pl->name, tmpname, strerror(errno));
144  int ret = LIBMTP_Get_File_To_File_Descriptor(device, pl->playlist_id, fd, NULL, NULL);
145  if( ret < 0 ) {
146    // FIXME     add_ptp_error_to_errorstack(device, ret, "LIBMTP_Get_Playlist: Could not get .spl playlist file.");
147    close(fd);
148    printf("FIXME closed\n");
149  }
150
151  text_t* p = read_into_spl_text_t(device, fd);
152  close(fd);
153
154  // FIXME cache these somewhere else so we don't keep calling this!
155  LIBMTP_folder_t *folders;
156  LIBMTP_file_t *files;
157  folders = LIBMTP_Get_Folder_List(device);
158  files = LIBMTP_Get_Filelisting_With_Callback(device, NULL, NULL);
159
160  // convert the playlist listing to track ids
161  pl->no_tracks = trackno_spl_text_t(p);
162  IF_DEBUG() printf("%u track%s found\n", pl->no_tracks, pl->no_tracks==1?"":"s");
163  pl->tracks = malloc(sizeof(uint32_t)*(pl->no_tracks));
164  tracks_from_spl_text_t(p, pl->tracks, folders, files);
165
166  free_spl_text_t(p);
167
168  // debug: add a break since this is the top level function call
169  IF_DEBUG() printf("------------\n\n");
170}
171
172
173/**
174 * Push a playlist_t onto the device after converting it to a .spl format
175 *
176 * @param device mtp device pointer
177 * @param pl the LIBMTP_playlist_t to convert (pl->playlist_id will be updated
178 *           with the newly created object's id)
179 * @return 0 on success, any other value means failure.
180 */
181int playlist_t_to_spl(LIBMTP_mtpdevice_t *device,
182                      LIBMTP_playlist_t * const pl)
183{
184  text_t* t;
185  LIBMTP_folder_t *folders;
186  LIBMTP_file_t *files;
187  folders = LIBMTP_Get_Folder_List(device);
188  files = LIBMTP_Get_Filelisting_With_Callback(device, NULL, NULL);
189
190  char tmpname[] = "/tmp/mtp-spl2pl-XXXXXX"; // must be a var since mkstemp modifies it
191
192  IF_DEBUG() printf("pl->name='%s'\n",pl->name);
193
194  // open a file descriptor
195  int fd = mkstemp(tmpname);
196  if(fd < 0) {
197    printf("failed to make temp file for %s.spl -> %s, errno=%s\n", pl->name, tmpname, strerror(errno));
198    return -1;
199  }
200  // make sure the file will be deleted afterwards
201  if(unlink(tmpname) < 0)
202    printf("failed to delete temp file for %s.spl -> %s, errno=%s\n", pl->name, tmpname, strerror(errno));
203
204  // decide on which version of the .spl format to use
205  uint32_t ver_major;
206  uint32_t ver_minor = 0;
207  PTP_USB *ptp_usb = (PTP_USB*) device->usbinfo;
208  if(FLAG_PLAYLIST_SPL_V2(ptp_usb)) ver_major = 2;
209  else ver_major = 1; // FLAG_PLAYLIST_SPL_V1()
210
211  IF_DEBUG() printf("%u track%s\n", pl->no_tracks, pl->no_tracks==1?"":"s");
212  IF_DEBUG() printf(".spl version %d.%02d\n", ver_major, ver_minor);
213
214  // create the text for the playlist
215  spl_text_t_from_tracks(&t, pl->tracks, pl->no_tracks, ver_major, ver_minor, NULL, folders, files);
216  write_from_spl_text_t(device, fd, t);
217  free_spl_text_t(t); // done with the text
218
219  // create the file object for storing
220  LIBMTP_file_t* f = malloc(sizeof(LIBMTP_file_t));
221  f->item_id = 0;
222  f->parent_id = pl->parent_id;
223  f->storage_id = pl->storage_id;
224  f->filename = malloc(sizeof(char)*(strlen(pl->name)+5));
225  strcpy(f->filename, pl->name);
226  strcat(f->filename, ".spl"); // append suffix
227  f->filesize = lseek(fd, 0, SEEK_CUR); // file desc is currently at end of file
228  f->filetype = LIBMTP_FILETYPE_UNKNOWN;
229  f->next = NULL;
230
231  IF_DEBUG() printf("%s is %dB\n", f->filename, (int)f->filesize);
232
233  // push the playlist to the device
234  lseek(fd, 0, SEEK_SET); // reset file desc. to start of file
235  int ret = LIBMTP_Send_File_From_File_Descriptor(device, fd, f, NULL, NULL);
236  pl->playlist_id = f->item_id;
237  free(f->filename);
238  free(f);
239
240  // release the memory when we're done with it
241  close(fd);
242  // debug: add a break since this is the top level function call
243  IF_DEBUG() printf("------------\n\n");
244
245  return ret;
246}
247
248
249
250/**
251 * Update a playlist on the device. If only the playlist's name is being
252 * changed the pl->playlist_id will likely remain the same. An updated track
253 * list will result in the old playlist being replaced (ie: new playlist_id).
254 * NOTE: Other playlist metadata aside from playlist name and tracks are
255 * ignored.
256 *
257 * @param device mtp device pointer
258 * @param new the LIBMTP_playlist_t to convert (pl->playlist_id will be updated
259 *           with the newly created object's id)
260 * @return 0 on success, any other value means failure.
261 */
262int update_spl_playlist(LIBMTP_mtpdevice_t *device,
263			  LIBMTP_playlist_t * const newlist)
264{
265  IF_DEBUG() printf("pl->name='%s'\n",newlist->name);
266
267  // read in the playlist of interest
268  LIBMTP_playlist_t * old = LIBMTP_Get_Playlist(device, newlist->playlist_id);
269
270  // check to see if we found it
271  if (!old)
272    return -1;
273
274  // check if the playlists match
275  int delta = 0;
276  int i;
277  if(old->no_tracks != newlist->no_tracks)
278    delta++;
279  for(i=0;i<newlist->no_tracks && delta==0;i++) {
280    if(old->tracks[i] != newlist->tracks[i])
281      delta++;
282  }
283
284  // if not, kill the playlist and replace it
285  if(delta) {
286    IF_DEBUG() printf("new tracks detected:\n");
287    IF_DEBUG() printf("delete old playlist and build a new one\n");
288    IF_DEBUG() printf(" NOTE: new playlist_id will result!\n");
289    if(LIBMTP_Delete_Object(device, old->playlist_id) != 0)
290      return -1;
291
292    IF_DEBUG() {
293      if(strcmp(old->name,newlist->name) == 0)
294        printf("name unchanged\n");
295      else
296        printf("name is changing too -> %s\n",newlist->name);
297    }
298
299    return LIBMTP_Create_New_Playlist(device, newlist);
300  }
301
302
303  // update the name only
304  if(strcmp(old->name,newlist->name) != 0) {
305    IF_DEBUG() printf("ONLY name is changing -> %s\n",newlist->name);
306    IF_DEBUG() printf("playlist_id will remain unchanged\n");
307    char* s = malloc(sizeof(char)*(strlen(newlist->name)+5));
308    strcpy(s, newlist->name);
309    strcat(s,".spl"); // FIXME check for success
310    int ret = LIBMTP_Set_Playlist_Name(device, newlist, s);
311    free(s);
312    return ret;
313  }
314
315  IF_DEBUG() printf("no change\n");
316  return 0; // nothing to be done, success
317}
318
319
320/**
321 * Load a file descriptor into a string.
322 *
323 * @param device a pointer to the current device.
324 *               (needed for ucs2->utf8 charset conversion)
325 * @param fd the file descriptor to load
326 * @return text_t* a linked list of lines of text, id is left blank, NULL if nothing read in
327 */
328static text_t* read_into_spl_text_t(LIBMTP_mtpdevice_t *device, const int fd)
329{
330  // set MAXREAD to match STRING_BUFFER_LENGTH in unicode.h conversion function
331  const size_t MAXREAD = 1024*2;
332  char t[MAXREAD];
333  // upto 3 bytes per utf8 character, 2 bytes per ucs2 character,
334  // +1 for '\0' at end of string
335  const size_t WSIZE = MAXREAD/2*3+1;
336  char w[WSIZE];
337  char* it = t; // iterator on t
338  char* iw = w;
339  ssize_t rdcnt;
340  off_t offcnt;
341  text_t* head = NULL;
342  text_t* tail = NULL;
343  int eof = 0;
344
345  // reset file descriptor (fd) to start of file
346  offcnt = lseek(fd, 0, SEEK_SET);
347
348  while(!eof) {
349    // find the current offset in the file
350    // to allow us to determine how many bytes we read if we hit the EOF
351    // where returned rdcnt=0 from read()
352    offcnt = lseek(fd, 0, SEEK_CUR);
353    // read to refill buffer
354    // (there might be data left from an incomplete last string in t,
355    // hence start filling at it)
356    it = t; // set ptr to start of buffer
357    rdcnt = read(fd, it, sizeof(char)*MAXREAD);
358    if(rdcnt < 0)
359      printf("load_spl_fd read err %s\n", strerror(errno));
360    else if(rdcnt == 0) { // for EOF, fix rdcnt
361      if(it-t == MAXREAD)
362        printf("error -- buffer too small to read in .spl playlist entry\n");
363
364      rdcnt = lseek(fd, 0, SEEK_CUR) - offcnt;
365      eof = 1;
366    }
367
368    IF_DEBUG() printf("read buff= {%dB new, %dB old/left-over}%s\n",(int)rdcnt, (int)(iw-w), eof?", EOF":"");
369
370    // while more input bytes
371    char* it_end = t + rdcnt;
372    while(it < it_end) {
373      // copy byte, unless EOL (then replace with end-of-string \0)
374      if(*it == '\r' || *it == '\n')
375        *iw = '\0';
376      else
377        *iw = *it;
378
379      it++;
380      iw++;
381
382      // EOL -- store it
383      if( (iw-w) >= 2 && // we must have at least two bytes
384          *(iw-1) == '\0' && *(iw-2) == '\0' && // 0x0000 is end-of-string
385          // but it must be aligned such that we have an {odd,even} set of
386          // bytes since we are expecting to consume bytes two-at-a-time
387          !((iw-w)%2) ) {
388
389        // drop empty lines
390        //  ... cast as a string of 2 byte characters
391        if(ucs2_strlen((uint16_t*)w) == 0) {
392          iw = w;
393          continue;
394        }
395
396        // create a new node in the list
397        if(head == NULL) {
398          head = malloc(sizeof(text_t));
399          tail = head;
400        }
401        else {
402          tail->next = malloc(sizeof(text_t));
403          tail = tail->next;
404        }
405        // fill in the data for the node
406        //  ... cast as a string of 2 byte characters
407        tail->text = utf16_to_utf8(device, (uint16_t*) w);
408        iw = w; // start again
409
410        IF_DEBUG() printf("line: %s\n", tail->text);
411      }
412
413      // prevent buffer overflow
414      if(iw >= w + WSIZE) {
415        // if we ever see this error its BAD:
416        //   we are dropping all the processed bytes for this line and
417        //   proceeding on as if everything is okay, probably losing a track
418        //   from the playlist
419        printf("ERROR %s:%u:%s(): buffer overflow! .spl line too long @ %zuB\n",
420               __FILE__, __LINE__, __func__, WSIZE);
421        iw = w; // reset buffer
422      }
423    }
424
425    // if the last thing we did was save our line, then we finished working
426    // on the input buffer and we can start fresh
427    // otherwise we need to save our partial work, if we're not quiting (eof).
428    // there is nothing special we need to do, to achieve this since the
429    // partially completed string will sit in 'w' until we return to complete
430    // the line
431
432  }
433
434  // set the next pointer at the end
435  // if there is any list
436  if(head != NULL)
437    tail->next = NULL;
438
439  // return the head of the list (NULL if no list)
440  return head;
441}
442
443
444/**
445 * Write a .spl text file to a file in preparation for pushing it
446 * to the device.
447 *
448 * @param fd file descriptor to write to
449 * @param p the text to output one line per string in the linked list
450 * @see playlist_t_to_spl()
451 */
452static void write_from_spl_text_t(LIBMTP_mtpdevice_t *device,
453                                  const int fd,
454                                  text_t* p) {
455  ssize_t ret;
456  // write out BOM for utf16/ucs2 (byte order mark)
457  ret = write(fd,"\xff\xfe",2);
458  while(p != NULL) {
459    char *const t = (char*) utf8_to_utf16(device, p->text);
460    // note: 2 bytes per ucs2 character
461    const size_t len = ucs2_strlen((uint16_t*)t)*sizeof(uint16_t);
462    int i;
463
464    IF_DEBUG() {
465      printf("\nutf8=%s ",p->text);
466      for(i=0;i<strlen(p->text);i++)
467        printf("%02x ", p->text[i] & 0xff);
468      printf("\n");
469      printf("ucs2=");
470      for(i=0;i<ucs2_strlen((uint16_t*)t)*sizeof(uint16_t);i++)
471        printf("%02x ", t[i] & 0xff);
472      printf("\n");
473    }
474
475    // write: utf8 -> utf16
476    ret += write(fd, t, len);
477
478    // release the converted string
479    free(t);
480
481    // check for failures
482    if(ret < 0)
483      printf("write spl file failed: %s\n", strerror(errno));
484    else if(ret != len +2)
485      printf("write spl file wrong number of bytes ret=%d len=%d '%s'\n", (int)ret, (int)len, p->text);
486
487    // write carriage return, line feed in ucs2
488    ret = write(fd, "\r\0\n\0", 4);
489    if(ret < 0)
490      printf("write spl file failed: %s\n", strerror(errno));
491    else if(ret != 4)
492      printf("failed to write the correct number of bytes '\\n'!\n");
493
494    // fake out count (first time through has two extra bytes from BOM)
495    ret = 2;
496
497    // advance to the next line
498    p = p->next;
499  }
500}
501
502/**
503 * Destroy a linked-list of strings.
504 *
505 * @param p the list to destroy
506 * @see spl_to_playlist_t()
507 * @see playlist_t_to_spl()
508 */
509static void free_spl_text_t(text_t* p)
510{
511  text_t* d;
512  while(p != NULL) {
513    d = p;
514    free(p->text);
515    p = p->next;
516    free(d);
517  }
518}
519
520/**
521 * Print a linked-list of strings to stdout.
522 *
523 * @param p the list to print
524 */
525static void print_spl_text_t(text_t* p)
526{
527  while(p != NULL) {
528    printf("%s\n",p->text);
529    p = p->next;
530  }
531}
532
533/**
534 * Count the number of tracks in this playlist. A track will be counted as
535 * such if the line starts with a leading slash.
536 *
537 * @param p the text to search
538 * @return number of tracks in the playlist
539 * @see spl_to_playlist_t()
540 */
541static uint32_t trackno_spl_text_t(text_t* p) {
542  uint32_t c = 0;
543  while(p != NULL) {
544    if(p->text[0] == '\\' ) c++;
545    p = p->next;
546  }
547
548  return c;
549}
550
551/**
552 * Find the track ids for this playlist's files.
553 * (ie: \Music\song.mp3 -> 12345)
554 *
555 * @param p the text to search
556 * @param tracks returned list of track id's for the playlist_t, must be large
557 *               enough to accomodate all the tracks as reported by
558 *               trackno_spl_text_t()
559 * @param folders the folders list for the device
560 * @param fiels the files list for the device
561 * @see spl_to_playlist_t()
562 */
563static void tracks_from_spl_text_t(text_t* p,
564                                   uint32_t* tracks,
565                                   LIBMTP_folder_t* folders,
566                                   LIBMTP_file_t* files)
567{
568  uint32_t c = 0;
569  while(p != NULL) {
570    if(p->text[0] == '\\' ) {
571      tracks[c] = discover_id_from_filepath(p->text, folders, files);
572      IF_DEBUG()
573        printf("track %d = %s (%u)\n", c+1, p->text, tracks[c]);
574      c++;
575    }
576    p = p->next;
577  }
578}
579
580
581/**
582 * Find the track names (including path) for this playlist's track ids.
583 * (ie: 12345 -> \Music\song.mp3)
584 *
585 * @param p the text to search
586 * @param tracks list of track id's to look up
587 * @param folders the folders list for the device
588 * @param fiels the files list for the device
589 * @see playlist_t_to_spl()
590 */
591static void spl_text_t_from_tracks(text_t** p,
592                                   uint32_t* tracks,
593                                   const uint32_t trackno,
594                                   const uint32_t ver_major,
595                                   const uint32_t ver_minor,
596                                   char* dnse,
597                                   LIBMTP_folder_t* folders,
598                                   LIBMTP_file_t* files)
599{
600
601  // HEADER
602  text_t* c = NULL;
603  append_text_t(&c, "SPL PLAYLIST");
604  *p = c; // save the top of the list!
605
606  char vs[14]; // "VERSION 2.00\0"
607  sprintf(vs,"VERSION %d.%02d",ver_major,ver_minor);
608
609  append_text_t(&c, vs);
610  append_text_t(&c, "");
611
612  // TRACKS
613  int i;
614  char* f;
615  for(i=0;i<trackno;i++) {
616    discover_filepath_from_id(&f, tracks[i], folders, files);
617
618    if(f != NULL) {
619      append_text_t(&c, f);
620      IF_DEBUG()
621        printf("track %d = %s (%u)\n", i+1, f, tracks[i]);
622    }
623    else
624      printf("failed to find filepath for track=%d\n", tracks[i]);
625  }
626
627  // FOOTER
628  append_text_t(&c, "");
629  append_text_t(&c, "END PLAYLIST");
630  if(ver_major == 2) {
631    append_text_t(&c, "");
632    append_text_t(&c, "myDNSe DATA");
633    if(dnse != NULL) {
634      append_text_t(&c, dnse);
635    }
636    else {
637      append_text_t(&c, "");
638      append_text_t(&c, "");
639    }
640    append_text_t(&c, "END myDNSe");
641  }
642
643  c->next = NULL;
644
645  // debug
646  IF_DEBUG() {
647    printf(".spl playlist:\n");
648    print_spl_text_t(*p);
649  }
650}
651
652
653/**
654 * Find the track names (including path) given a fileid
655 * (ie: 12345 -> \Music\song.mp3)
656 *
657 * @param p returns the file path (ie: \Music\song.mp3),
658 *          (*p) == NULL if the look up fails
659 * @param track track id to look up
660 * @param folders the folders list for the device
661 * @param files the files list for the device
662 * @see spl_text_t_from_tracks()
663 */
664
665// returns p = NULL on failure, else the filepath to the track including track name, allocated as a correct length string
666static void discover_filepath_from_id(char** p,
667                                      uint32_t track,
668                                      LIBMTP_folder_t* folders,
669                                      LIBMTP_file_t* files)
670{
671  // fill in a string from the right side since we don't know the root till the end
672  const int M = 1024;
673  char w[M];
674  char* iw = w + M; // iterator on w
675
676  // in case of failure return NULL string
677  *p = NULL;
678
679
680  // find the right file
681  while(files != NULL && files->item_id != track) {
682    files = files->next;
683  }
684  // if we didn't find a matching file, abort
685  if(files == NULL)
686    return;
687
688  // stuff the filename into our string
689  // FIXME: check for string overflow before it occurs
690  iw = iw - (strlen(files->filename) +1); // leave room for '\0' at the end
691  strcpy(iw,files->filename);
692
693  // next follow the directories to the root
694  // prepending folders to the path as we go
695  uint32_t id = files->parent_id;
696  char* f = NULL;
697  while(id != 0) {
698    find_folder_name(folders, &id, &f);
699    if(f == NULL) return; // fail if the next part of the path couldn't be found
700    iw = iw - (strlen(f) +1);
701    // FIXME: check for string overflow before it occurs
702    strcpy(iw, f);
703    iw[strlen(f)] = '\\';
704    free(f);
705  }
706
707  // prepend a slash
708  iw--;
709  iw[0] = '\\';
710
711  // now allocate a string of the right length to be returned
712  *p = strdup(iw);
713}
714
715
716/**
717 * Find the track id given a track's name (including path)
718 * (ie: \Music\song.mp3 -> 12345)
719 *
720 * @param s file path to look up (ie: \Music\song.mp3),
721 *          (*p) == NULL if the look up fails
722 * @param folders the folders list for the device
723 * @param files the files list for the device
724 * @return track id, 0 means failure
725 * @see tracks_from_spl_text_t()
726 */
727static uint32_t discover_id_from_filepath(const char* s, LIBMTP_folder_t* folders, LIBMTP_file_t* files)
728{
729  // abort if this isn't a path
730  if(s[0] != '\\')
731    return 0;
732
733  int i;
734  uint32_t id = 0;
735  char* sc = strdup(s);
736  char* sci = sc +1; // iterator
737  // skip leading slash in path
738
739  // convert all \ to \0
740  size_t len = strlen(s);
741  for(i=0;i<len;i++) {
742    if(sc[i] == '\\') {
743      sc[i] = '\0';
744    }
745  }
746
747  // now for each part of the string, find the id
748  while(sci != sc + len +1) {
749    // if its the last part of the string, its the filename
750    if(sci + strlen(sci) == sc + len) {
751
752      while(files != NULL) {
753        // check parent matches id and name matches sci
754        if( (files->parent_id == id) &&
755            (strcmp(files->filename, sci) == 0) ) { // found it!
756          id = files->item_id;
757          break;
758        }
759        files = files->next;
760      }
761    }
762    else { // otherwise its part of the directory path
763      id = find_folder_id(folders, id, sci);
764    }
765
766    // move to next folder/file
767    sci += strlen(sci) +1;
768  }
769
770  // release our copied string
771  free(sc);
772
773  // FIXME check that we actually have a file
774
775  return id;
776}
777
778
779
780/**
781 * Find the folder name given the folder's id.
782 *
783 * @param folders the folders list for the device
784 * @param id the folder_id to look up, returns the folder's parent folder_id
785 * @param name returns the name of the folder or NULL on failure
786 * @see discover_filepath_from_id()
787 */
788static void find_folder_name(LIBMTP_folder_t* folders, uint32_t* id, char** name)
789{
790
791  // FIXME this function is exactly LIBMTP_Find_Folder
792
793  LIBMTP_folder_t* f = LIBMTP_Find_Folder(folders, *id);
794  if(f == NULL) {
795    *name = NULL;
796  }
797  else { // found it!
798    *name = strdup(f->name);
799    *id = f->parent_id;
800  }
801}
802
803
804/**
805 * Find the folder id given the folder's name and parent id.
806 *
807 * @param folders the folders list for the device
808 * @param parent the folder's parent's id
809 * @param name the name of the folder
810 * @return the folder_id or 0 on failure
811 * @see discover_filepath_from_id()
812 */
813static uint32_t find_folder_id(LIBMTP_folder_t* folders, uint32_t parent, char* name) {
814
815  if(folders == NULL)
816    return 0;
817
818  // found it!
819  else if( (folders->parent_id == parent) &&
820           (strcmp(folders->name, name) == 0) )
821    return folders->folder_id;
822
823  // no luck so far, search both siblings and children
824  else {
825    uint32_t id = 0;
826
827    if(folders->sibling != NULL)
828      id = find_folder_id(folders->sibling, parent, name);
829    if( (id == 0) && (folders->child != NULL) )
830      id = find_folder_id(folders->child, parent, name);
831
832    return id;
833  }
834}
835
836
837/**
838 * Append a string to a linked-list of strings.
839 *
840 * @param t the list-of-strings, returns with the added string
841 * @param s the string to append
842 * @see spl_text_t_from_tracks()
843 */
844static void append_text_t(text_t** t, char* s)
845{
846  if(*t == NULL) {
847    *t = malloc(sizeof(text_t));
848  }
849  else {
850    (*t)->next = malloc(sizeof(text_t));
851    (*t) = (*t)->next;
852  }
853  (*t)->text = strdup(s);
854}
855