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