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 58typedef 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 */ 67static text_t* read_into_spl_text_t(LIBMTP_mtpdevice_t *device, const int fd); 68static void write_from_spl_text_t(LIBMTP_mtpdevice_t *device, const int fd, text_t* p); 69static void free_spl_text_t(text_t* p); 70static void print_spl_text_t(text_t* p); 71static uint32_t trackno_spl_text_t(text_t* p); 72static void tracks_from_spl_text_t(text_t* p, uint32_t* tracks, LIBMTP_folder_t* folders, LIBMTP_file_t* files); 73static 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 75static uint32_t discover_id_from_filepath(const char* s, LIBMTP_folder_t* folders, LIBMTP_file_t* files); // TODO add file/dir cached args 76static void discover_filepath_from_id(char** p, uint32_t track, LIBMTP_folder_t* folders, LIBMTP_file_t* files); 77static void find_folder_name(LIBMTP_folder_t* folders, uint32_t* id, char** name); 78static uint32_t find_folder_id(LIBMTP_folder_t* folders, uint32_t parent, char* name); 79 80static 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 */ 91int 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 117void 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 */ 181int 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 */ 262int 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 */ 328static 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 */ 452static 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 */ 509static 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 */ 525static 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 */ 541static 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 */ 563static 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 */ 591static 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 666static 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 */ 727static 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 */ 788static 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 */ 813static 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 */ 844static 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