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