void spl_to_playlist_t(LIBMTP_mtpdevice_t* device, PTPObjectInfo *oi, const uint32_t id, LIBMTP_playlist_t * const pl) { // Fill in playlist metadata // Use the Filename as the playlist name, dropping the ".spl" extension pl->name = malloc(sizeof(char)*(strlen(oi->Filename) -4 +1)); memcpy(pl->name, oi->Filename, strlen(oi->Filename) -4); // Set terminating character pl->name[strlen(oi->Filename) - 4] = 0; pl->playlist_id = id; pl->parent_id = oi->ParentObject; pl->storage_id = oi->StorageID; pl->tracks = NULL; pl->no_tracks = 0; LIBMTP_PLST_DEBUG("pl->name='%s'\n", pl->name); // open a temporary file char tmpname[] = "/tmp/mtp-spl2pl-XXXXXX"; int fd = mkstemp(tmpname); if(fd < 0) { LIBMTP_ERROR("failed to make temp file for %s.spl -> %s, errno=%s\n", pl->name, tmpname, strerror(errno)); return; } // make sure the file will be deleted afterwards if(unlink(tmpname) < 0) LIBMTP_ERROR("failed to delete temp file for %s.spl -> %s, errno=%s\n", pl->name, tmpname, strerror(errno)); int ret = LIBMTP_Get_File_To_File_Descriptor(device, pl->playlist_id, fd, NULL, NULL); if( ret < 0 ) { // FIXME add_ptp_error_to_errorstack(device, ret, "LIBMTP_Get_Playlist: Could not get .spl playlist file."); close(fd); LIBMTP_INFO("FIXME closed\n"); } text_t* p = read_into_spl_text_t(device, fd); close(fd); // FIXME cache these somewhere else so we don't keep calling this! LIBMTP_folder_t *folders; LIBMTP_file_t *files; folders = LIBMTP_Get_Folder_List(device); files = LIBMTP_Get_Filelisting_With_Callback(device, NULL, NULL); // convert the playlist listing to track ids pl->no_tracks = trackno_spl_text_t(p); LIBMTP_PLST_DEBUG("%u track%s found\n", pl->no_tracks, pl->no_tracks==1?"":"s"); pl->tracks = malloc(sizeof(uint32_t)*(pl->no_tracks)); tracks_from_spl_text_t(p, pl->tracks, folders, files); free_spl_text_t(p); // debug: add a break since this is the top level function call LIBMTP_PLST_DEBUG("------------\n\n"); }
/** * This prints to stdout info about device being UNKNOWN, its * ids, and libmtp's version number. * * @param dev_number the device number * @param id_vendor vendor ID from the usb_device_desc struct * @param id_product product ID from the usb_device_desc struct */ void device_unknown(const int dev_number, const int id_vendor, const int id_product) { // This device is unknown to the developers LIBMTP_ERROR("Device %d (VID=%04x and PID=%04x) is UNKNOWN in libmtp v%s.\n", dev_number, id_vendor, id_product, LIBMTP_VERSION_STRING); LIBMTP_ERROR("Please report this VID/PID and the device model to the " "libmtp development team\n"); /* * Trying to get iManufacturer or iProduct from the device at this * point would require opening a device handle, that we don't want * to do right now. (Takes time for no good enough reason.) */ }
/** * Write a .spl text file to a file in preparation for pushing it * to the device. * * @param fd file descriptor to write to * @param p the text to output one line per string in the linked list * @see playlist_t_to_spl() */ static void write_from_spl_text_t(LIBMTP_mtpdevice_t *device, const int fd, text_t* p) { ssize_t ret; // write out BOM for utf16/ucs2 (byte order mark) ret = write(fd,"\xff\xfe",2); while(p != NULL) { char *const t = (char*) utf8_to_utf16(device, p->text); // note: 2 bytes per ucs2 character const size_t len = ucs2_strlen((uint16_t*)t)*sizeof(uint16_t); int i; LIBMTP_PLST_DEBUG("\nutf8=%s ",p->text); for(i=0;i<strlen(p->text);i++) LIBMTP_PLST_DEBUG("%02x ", p->text[i] & 0xff); LIBMTP_PLST_DEBUG("\n"); LIBMTP_PLST_DEBUG("ucs2="); for(i=0;i<ucs2_strlen((uint16_t*)t)*sizeof(uint16_t);i++) LIBMTP_PLST_DEBUG("%02x ", t[i] & 0xff); LIBMTP_PLST_DEBUG("\n"); // write: utf8 -> utf16 ret += write(fd, t, len); // release the converted string free(t); // check for failures if(ret < 0) LIBMTP_ERROR("write spl file failed: %s\n", strerror(errno)); else if(ret != len +2) LIBMTP_ERROR("write spl file wrong number of bytes ret=%d len=%d '%s'\n", (int)ret, (int)len, p->text); // write carriage return, line feed in ucs2 ret = write(fd, "\r\0\n\0", 4); if(ret < 0) LIBMTP_ERROR("write spl file failed: %s\n", strerror(errno)); else if(ret != 4) LIBMTP_ERROR("failed to write the correct number of bytes '\\n'!\n"); // fake out count (first time through has two extra bytes from BOM) ret = 2; // advance to the next line p = p->next; } }
/** * Find the track names (including path) for this playlist's track ids. * (ie: 12345 -> \Music\song.mp3) * * @param p the text to search * @param tracks list of track id's to look up * @param folders the folders list for the device * @param fiels the files list for the device * @see playlist_t_to_spl() */ 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) { // HEADER text_t* c = NULL; append_text_t(&c, "SPL PLAYLIST"); *p = c; // save the top of the list! char vs[14]; // "VERSION 2.00\0" sprintf(vs,"VERSION %d.%02d",ver_major,ver_minor); append_text_t(&c, vs); append_text_t(&c, ""); // TRACKS int i; char* f; for(i=0;i<trackno;i++) { discover_filepath_from_id(&f, tracks[i], folders, files); if(f != NULL) { append_text_t(&c, f); LIBMTP_PLST_DEBUG("track %d = %s (%u)\n", i+1, f, tracks[i]); } else LIBMTP_ERROR("failed to find filepath for track=%d\n", tracks[i]); } // FOOTER append_text_t(&c, ""); append_text_t(&c, "END PLAYLIST"); if(ver_major == 2) { append_text_t(&c, ""); append_text_t(&c, "myDNSe DATA"); if(dnse != NULL) { append_text_t(&c, dnse); } else { append_text_t(&c, ""); append_text_t(&c, ""); } append_text_t(&c, "END myDNSe"); } c->next = NULL; // debug LIBMTP_PLST_DEBUG(".spl playlist:\n"); print_spl_text_t(*p); }
/** * Load a file descriptor into a string. * * @param device a pointer to the current device. * (needed for ucs2->utf8 charset conversion) * @param fd the file descriptor to load * @return text_t* a linked list of lines of text, id is left blank, NULL if nothing read in */ static text_t* read_into_spl_text_t(LIBMTP_mtpdevice_t *device, const int fd) { // set MAXREAD to match STRING_BUFFER_LENGTH in unicode.h conversion function const size_t MAXREAD = 1024*2; char t[MAXREAD]; // upto 3 bytes per utf8 character, 2 bytes per ucs2 character, // +1 for '\0' at end of string const size_t WSIZE = MAXREAD/2*3+1; char w[WSIZE]; char* it = t; // iterator on t char* iw = w; ssize_t rdcnt; off_t offcnt; text_t* head = NULL; text_t* tail = NULL; int eof = 0; // reset file descriptor (fd) to start of file offcnt = lseek(fd, 0, SEEK_SET); while(!eof) { // find the current offset in the file // to allow us to determine how many bytes we read if we hit the EOF // where returned rdcnt=0 from read() offcnt = lseek(fd, 0, SEEK_CUR); // read to refill buffer // (there might be data left from an incomplete last string in t, // hence start filling at it) it = t; // set ptr to start of buffer rdcnt = read(fd, it, sizeof(char)*MAXREAD); if(rdcnt < 0) LIBMTP_INFO("load_spl_fd read err %s\n", strerror(errno)); else if(rdcnt == 0) { // for EOF, fix rdcnt if(it-t == MAXREAD) LIBMTP_ERROR("error -- buffer too small to read in .spl playlist entry\n"); rdcnt = lseek(fd, 0, SEEK_CUR) - offcnt; eof = 1; } LIBMTP_PLST_DEBUG("read buff= {%dB new, %dB old/left-over}%s\n",(int)rdcnt, (int)(iw-w), eof?", EOF":""); // while more input bytes char* it_end = t + rdcnt; while(it < it_end) { // copy byte, unless EOL (then replace with end-of-string \0) if(*it == '\r' || *it == '\n') *iw = '\0'; else *iw = *it; it++; iw++; // EOL -- store it if( (iw-w) >= 2 && // we must have at least two bytes *(iw-1) == '\0' && *(iw-2) == '\0' && // 0x0000 is end-of-string // but it must be aligned such that we have an {odd,even} set of // bytes since we are expecting to consume bytes two-at-a-time !((iw-w)%2) ) { // drop empty lines // ... cast as a string of 2 byte characters if(ucs2_strlen((uint16_t*)w) == 0) { iw = w; continue; } // create a new node in the list if(head == NULL) { head = malloc(sizeof(text_t)); tail = head; } else { tail->next = malloc(sizeof(text_t)); tail = tail->next; } // fill in the data for the node // ... cast as a string of 2 byte characters tail->text = utf16_to_utf8(device, (uint16_t*) w); iw = w; // start again LIBMTP_PLST_DEBUG("line: %s\n", tail->text); } // prevent buffer overflow if(iw >= w + WSIZE) { // if we ever see this error its BAD: // we are dropping all the processed bytes for this line and // proceeding on as if everything is okay, probably losing a track // from the playlist LIBMTP_ERROR("ERROR %s:%u:%s(): buffer overflow! .spl line too long @ %zuB\n", __FILE__, __LINE__, __func__, WSIZE); iw = w; // reset buffer } } // if the last thing we did was save our line, then we finished working // on the input buffer and we can start fresh // otherwise we need to save our partial work, if we're not quiting (eof). // there is nothing special we need to do, to achieve this since the // partially completed string will sit in 'w' until we return to complete // the line } // set the next pointer at the end // if there is any list if(head != NULL) tail->next = NULL; // return the head of the list (NULL if no list) return head; }
/** * Push a playlist_t onto the device after converting it to a .spl format * * @param device mtp device pointer * @param pl the LIBMTP_playlist_t to convert (pl->playlist_id will be updated * with the newly created object's id) * @return 0 on success, any other value means failure. */ int playlist_t_to_spl(LIBMTP_mtpdevice_t *device, LIBMTP_playlist_t * const pl) { text_t* t; LIBMTP_folder_t *folders; LIBMTP_file_t *files; folders = LIBMTP_Get_Folder_List(device); files = LIBMTP_Get_Filelisting_With_Callback(device, NULL, NULL); char tmpname[] = "/tmp/mtp-spl2pl-XXXXXX"; // must be a var since mkstemp modifies it LIBMTP_PLST_DEBUG("pl->name='%s'\n",pl->name); // open a file descriptor int fd = mkstemp(tmpname); if(fd < 0) { LIBMTP_ERROR("failed to make temp file for %s.spl -> %s, errno=%s\n", pl->name, tmpname, strerror(errno)); return -1; } // make sure the file will be deleted afterwards if(unlink(tmpname) < 0) LIBMTP_ERROR("failed to delete temp file for %s.spl -> %s, errno=%s\n", pl->name, tmpname, strerror(errno)); // decide on which version of the .spl format to use uint32_t ver_major; uint32_t ver_minor = 0; PTP_USB *ptp_usb = (PTP_USB*) device->usbinfo; if(FLAG_PLAYLIST_SPL_V2(ptp_usb)) ver_major = 2; else ver_major = 1; // FLAG_PLAYLIST_SPL_V1() LIBMTP_PLST_DEBUG("%u track%s\n", pl->no_tracks, pl->no_tracks==1?"":"s"); LIBMTP_PLST_DEBUG(".spl version %d.%02d\n", ver_major, ver_minor); // create the text for the playlist spl_text_t_from_tracks(&t, pl->tracks, pl->no_tracks, ver_major, ver_minor, NULL, folders, files); write_from_spl_text_t(device, fd, t); free_spl_text_t(t); // done with the text // create the file object for storing LIBMTP_file_t* f = malloc(sizeof(LIBMTP_file_t)); f->item_id = 0; f->parent_id = pl->parent_id; f->storage_id = pl->storage_id; f->filename = malloc(sizeof(char)*(strlen(pl->name)+5)); strcpy(f->filename, pl->name); strcat(f->filename, ".spl"); // append suffix f->filesize = lseek(fd, 0, SEEK_CUR); // file desc is currently at end of file f->filetype = LIBMTP_FILETYPE_UNKNOWN; f->next = NULL; LIBMTP_PLST_DEBUG("%s is %dB\n", f->filename, (int)f->filesize); // push the playlist to the device lseek(fd, 0, SEEK_SET); // reset file desc. to start of file int ret = LIBMTP_Send_File_From_File_Descriptor(device, fd, f, NULL, NULL); pl->playlist_id = f->item_id; free(f->filename); free(f); // release the memory when we're done with it close(fd); // debug: add a break since this is the top level function call LIBMTP_PLST_DEBUG("------------\n\n"); return ret; }