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