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