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 
     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
     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          (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 
    117 void 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, 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  */
    181 int 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  */
    262 int 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  */
    328 static 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  */
    452 static 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  */
    509 static 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  */
    525 static 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  */
    541 static 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  */
    563 static 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  */
    591 static 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
    666 static 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  */
    727 static 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  */
    788 static 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  */
    813 static 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  */
    844 static 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