• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 #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  */
is_spl_playlist(PTPObjectInfo * oi)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 
spl_to_playlist_t(LIBMTP_mtpdevice_t * device,PTPObjectInfo * oi,const uint32_t id,LIBMTP_playlist_t * const pl)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  */
playlist_t_to_spl(LIBMTP_mtpdevice_t * device,LIBMTP_playlist_t * const pl)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  */
update_spl_playlist(LIBMTP_mtpdevice_t * device,LIBMTP_playlist_t * const newlist)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  */
read_into_spl_text_t(LIBMTP_mtpdevice_t * device,const int fd)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  */
write_from_spl_text_t(LIBMTP_mtpdevice_t * device,const int fd,text_t * p)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  */
free_spl_text_t(text_t * p)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  */
print_spl_text_t(text_t * p)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  */
trackno_spl_text_t(text_t * p)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  */
tracks_from_spl_text_t(text_t * p,uint32_t * tracks,LIBMTP_folder_t * folders,LIBMTP_file_t * files)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  */
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)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
discover_filepath_from_id(char ** p,uint32_t track,LIBMTP_folder_t * folders,LIBMTP_file_t * files)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  */
discover_id_from_filepath(const char * s,LIBMTP_folder_t * folders,LIBMTP_file_t * files)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  */
find_folder_name(LIBMTP_folder_t * folders,uint32_t * id,char ** name)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  */
find_folder_id(LIBMTP_folder_t * folders,uint32_t parent,char * name)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  */
append_text_t(text_t ** t,char * s)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