/**
|
* \File playlist-spl.c
|
*
|
* Playlist_t to Samsung (.spl) and back conversion functions.
|
*
|
* Copyright (C) 2008 Alistair Boyle <alistair.js.boyle@gmail.com>
|
*
|
* This library is free software; you can redistribute it and/or
|
* modify it under the terms of the GNU Lesser General Public
|
* License as published by the Free Software Foundation; either
|
* version 2 of the License, or (at your option) any later version.
|
*
|
* This library is distributed in the hope that it will be useful,
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
* Lesser General Public License for more details.
|
*
|
* You should have received a copy of the GNU Lesser General Public
|
* License along with this library; if not, write to the
|
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
* Boston, MA 02111-1307, USA.
|
*/
|
|
#include "config.h"
|
|
#include <stdio.h>
|
#include <stdlib.h> // mkstmp()
|
#include <unistd.h>
|
#include <errno.h>
|
#include <sys/stat.h>
|
#include <sys/types.h>
|
#ifdef HAVE_SYS_UIO_H
|
#include <sys/uio.h>
|
#endif
|
#include <fcntl.h>
|
#include <string.h>
|
|
#include "libmtp.h"
|
#include "libusb-glue.h"
|
#include "ptp.h"
|
#include "unicode.h"
|
#include "util.h"
|
|
#include "playlist-spl.h"
|
|
/**
|
* Debug macro
|
*/
|
#define LIBMTP_PLST_DEBUG(format, args...) \
|
do { \
|
if ((LIBMTP_debug & LIBMTP_DEBUG_PLST) != 0) \
|
fprintf(stdout, "LIBMTP %s[%d]: " format, __FUNCTION__, __LINE__, ##args); \
|
} while (0)
|
|
|
// Internal singly linked list of strings
|
// used to hold .spl playlist in memory
|
typedef struct text_struct {
|
char* text; // String
|
struct text_struct *next; // Link to next line, NULL if end of list
|
} text_t;
|
|
|
/**
|
* Forward declarations of local (static) functions.
|
*/
|
static text_t* read_into_spl_text_t(LIBMTP_mtpdevice_t *device, const int fd);
|
static void write_from_spl_text_t(LIBMTP_mtpdevice_t *device, const int fd, text_t* p);
|
static void free_spl_text_t(text_t* p);
|
static void print_spl_text_t(text_t* p);
|
static uint32_t trackno_spl_text_t(text_t* p);
|
static void tracks_from_spl_text_t(text_t* p, uint32_t* tracks, LIBMTP_folder_t* folders, LIBMTP_file_t* files);
|
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);
|
|
static uint32_t discover_id_from_filepath(const char* s, LIBMTP_folder_t* folders, LIBMTP_file_t* files); // TODO add file/dir cached args
|
static void discover_filepath_from_id(char** p, uint32_t track, LIBMTP_folder_t* folders, LIBMTP_file_t* files);
|
static void find_folder_name(LIBMTP_folder_t* folders, uint32_t* id, char** name);
|
static uint32_t find_folder_id(LIBMTP_folder_t* folders, uint32_t parent, char* name);
|
|
static void append_text_t(text_t** t, char* s);
|
|
|
|
|
/**
|
* Decides if the indicated object index is an .spl playlist.
|
*
|
* @param oi object we are deciding on
|
* @return 1 if this is a Samsung .spl object, 0 otherwise
|
*/
|
int is_spl_playlist(PTPObjectInfo *oi)
|
{
|
return ((oi->ObjectFormat == PTP_OFC_Undefined) ||
|
(oi->ObjectFormat == PTP_OFC_MTP_SamsungPlaylist)) &&
|
(strlen(oi->Filename) > 4) &&
|
(strcmp((oi->Filename + strlen(oi->Filename) - 4), ".spl") == 0);
|
}
|
|
#ifndef HAVE_MKSTEMP
|
# ifdef __WIN32__
|
# include <fcntl.h>
|
# define mkstemp(_pattern) _open(_mktemp(_pattern), _O_CREAT | _O_SHORT_LIVED | _O_EXCL)
|
# else
|
# error Missing mkstemp() function.
|
# endif
|
#endif
|
|
/**
|
* Take an object ID, a .spl playlist on the MTP device,
|
* and convert it to a playlist_t object.
|
*
|
* @param device mtp device pointer
|
* @param oi object we are reading
|
* @param id .spl playlist id on MTP device
|
* @param pl the LIBMTP_playlist_t pointer to be filled with info from id
|
*/
|
|
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");
|
}
|
|
|
/**
|
* 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;
|
}
|
|
|
|
/**
|
* Update a playlist on the device. If only the playlist's name is being
|
* changed the pl->playlist_id will likely remain the same. An updated track
|
* list will result in the old playlist being replaced (ie: new playlist_id).
|
* NOTE: Other playlist metadata aside from playlist name and tracks are
|
* ignored.
|
*
|
* @param device mtp device pointer
|
* @param new 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 update_spl_playlist(LIBMTP_mtpdevice_t *device,
|
LIBMTP_playlist_t * const newlist)
|
{
|
LIBMTP_PLST_DEBUG("pl->name='%s'\n",newlist->name);
|
|
// read in the playlist of interest
|
LIBMTP_playlist_t * old = LIBMTP_Get_Playlist(device, newlist->playlist_id);
|
|
// check to see if we found it
|
if (!old)
|
return -1;
|
|
// check if the playlists match
|
int delta = 0;
|
int i;
|
if(old->no_tracks != newlist->no_tracks)
|
delta++;
|
for(i=0;i<newlist->no_tracks && delta==0;i++) {
|
if(old->tracks[i] != newlist->tracks[i])
|
delta++;
|
}
|
|
// if not, kill the playlist and replace it
|
if(delta) {
|
LIBMTP_PLST_DEBUG("new tracks detected:\n");
|
LIBMTP_PLST_DEBUG("delete old playlist and build a new one\n");
|
LIBMTP_PLST_DEBUG(" NOTE: new playlist_id will result!\n");
|
if(LIBMTP_Delete_Object(device, old->playlist_id) != 0)
|
return -1;
|
|
if(strcmp(old->name,newlist->name) == 0)
|
LIBMTP_PLST_DEBUG("name unchanged\n");
|
else
|
LIBMTP_PLST_DEBUG("name is changing too -> %s\n",newlist->name);
|
|
return LIBMTP_Create_New_Playlist(device, newlist);
|
}
|
|
|
// update the name only
|
if(strcmp(old->name,newlist->name) != 0) {
|
LIBMTP_PLST_DEBUG("ONLY name is changing -> %s\n",newlist->name);
|
LIBMTP_PLST_DEBUG("playlist_id will remain unchanged\n");
|
char* s = malloc(sizeof(char)*(strlen(newlist->name)+5));
|
strcpy(s, newlist->name);
|
strcat(s,".spl"); // FIXME check for success
|
int ret = LIBMTP_Set_Playlist_Name(device, newlist, s);
|
free(s);
|
return ret;
|
}
|
|
LIBMTP_PLST_DEBUG("no change\n");
|
return 0; // nothing to be done, success
|
}
|
|
|
/**
|
* 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;
|
}
|
|
|
/**
|
* 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;
|
}
|
}
|
|
/**
|
* Destroy a linked-list of strings.
|
*
|
* @param p the list to destroy
|
* @see spl_to_playlist_t()
|
* @see playlist_t_to_spl()
|
*/
|
static void free_spl_text_t(text_t* p)
|
{
|
text_t* d;
|
while(p != NULL) {
|
d = p;
|
free(p->text);
|
p = p->next;
|
free(d);
|
}
|
}
|
|
/**
|
* Print a linked-list of strings to stdout.
|
* Used to debug.
|
*
|
* @param p the list to print
|
*/
|
static void print_spl_text_t(text_t* p)
|
{
|
while(p != NULL) {
|
LIBMTP_PLST_DEBUG("%s\n",p->text);
|
p = p->next;
|
}
|
}
|
|
/**
|
* Count the number of tracks in this playlist. A track will be counted as
|
* such if the line starts with a leading slash.
|
*
|
* @param p the text to search
|
* @return number of tracks in the playlist
|
* @see spl_to_playlist_t()
|
*/
|
static uint32_t trackno_spl_text_t(text_t* p) {
|
uint32_t c = 0;
|
while(p != NULL) {
|
if(p->text[0] == '\\' ) c++;
|
p = p->next;
|
}
|
|
return c;
|
}
|
|
/**
|
* Find the track ids for this playlist's files.
|
* (ie: \Music\song.mp3 -> 12345)
|
*
|
* @param p the text to search
|
* @param tracks returned list of track id's for the playlist_t, must be large
|
* enough to accomodate all the tracks as reported by
|
* trackno_spl_text_t()
|
* @param folders the folders list for the device
|
* @param fiels the files list for the device
|
* @see spl_to_playlist_t()
|
*/
|
static void tracks_from_spl_text_t(text_t* p,
|
uint32_t* tracks,
|
LIBMTP_folder_t* folders,
|
LIBMTP_file_t* files)
|
{
|
uint32_t c = 0;
|
while(p != NULL) {
|
if(p->text[0] == '\\' ) {
|
tracks[c] = discover_id_from_filepath(p->text, folders, files);
|
LIBMTP_PLST_DEBUG("track %d = %s (%u)\n", c+1, p->text, tracks[c]);
|
c++;
|
}
|
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]);
|
free(f);
|
}
|
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);
|
}
|
|
|
/**
|
* Find the track names (including path) given a fileid
|
* (ie: 12345 -> \Music\song.mp3)
|
*
|
* @param p returns the file path (ie: \Music\song.mp3),
|
* (*p) == NULL if the look up fails
|
* @param track track id to look up
|
* @param folders the folders list for the device
|
* @param files the files list for the device
|
* @see spl_text_t_from_tracks()
|
*/
|
|
// returns p = NULL on failure, else the filepath to the track including track name, allocated as a correct length string
|
static void discover_filepath_from_id(char** p,
|
uint32_t track,
|
LIBMTP_folder_t* folders,
|
LIBMTP_file_t* files)
|
{
|
// fill in a string from the right side since we don't know the root till the end
|
const int M = 1024;
|
char w[M];
|
char* iw = w + M; // iterator on w
|
|
// in case of failure return NULL string
|
*p = NULL;
|
|
|
// find the right file
|
while(files != NULL && files->item_id != track) {
|
files = files->next;
|
}
|
// if we didn't find a matching file, abort
|
if(files == NULL)
|
return;
|
|
// stuff the filename into our string
|
// FIXME: check for string overflow before it occurs
|
iw = iw - (strlen(files->filename) +1); // leave room for '\0' at the end
|
strcpy(iw,files->filename);
|
|
// next follow the directories to the root
|
// prepending folders to the path as we go
|
uint32_t id = files->parent_id;
|
char* f = NULL;
|
while(id != 0) {
|
find_folder_name(folders, &id, &f);
|
if(f == NULL) return; // fail if the next part of the path couldn't be found
|
iw = iw - (strlen(f) +1);
|
// FIXME: check for string overflow before it occurs
|
strcpy(iw, f);
|
iw[strlen(f)] = '\\';
|
free(f);
|
}
|
|
// prepend a slash
|
iw--;
|
iw[0] = '\\';
|
|
// now allocate a string of the right length to be returned
|
*p = strdup(iw);
|
}
|
|
|
/**
|
* Find the track id given a track's name (including path)
|
* (ie: \Music\song.mp3 -> 12345)
|
*
|
* @param s file path to look up (ie: \Music\song.mp3),
|
* (*p) == NULL if the look up fails
|
* @param folders the folders list for the device
|
* @param files the files list for the device
|
* @return track id, 0 means failure
|
* @see tracks_from_spl_text_t()
|
*/
|
static uint32_t discover_id_from_filepath(const char* s, LIBMTP_folder_t* folders, LIBMTP_file_t* files)
|
{
|
// abort if this isn't a path
|
if(s[0] != '\\')
|
return 0;
|
|
int i;
|
uint32_t id = 0;
|
char* sc = strdup(s);
|
char* sci = sc +1; // iterator
|
// skip leading slash in path
|
|
// convert all \ to \0
|
size_t len = strlen(s);
|
for(i=0;i<len;i++) {
|
if(sc[i] == '\\') {
|
sc[i] = '\0';
|
}
|
}
|
|
// now for each part of the string, find the id
|
while(sci != sc + len +1) {
|
// if its the last part of the string, its the filename
|
if(sci + strlen(sci) == sc + len) {
|
|
while(files != NULL) {
|
// check parent matches id and name matches sci
|
if( (files->parent_id == id) &&
|
(strcmp(files->filename, sci) == 0) ) { // found it!
|
id = files->item_id;
|
break;
|
}
|
files = files->next;
|
}
|
}
|
else { // otherwise its part of the directory path
|
id = find_folder_id(folders, id, sci);
|
}
|
|
// move to next folder/file
|
sci += strlen(sci) +1;
|
}
|
|
// release our copied string
|
free(sc);
|
|
// FIXME check that we actually have a file
|
|
return id;
|
}
|
|
|
|
/**
|
* Find the folder name given the folder's id.
|
*
|
* @param folders the folders list for the device
|
* @param id the folder_id to look up, returns the folder's parent folder_id
|
* @param name returns the name of the folder or NULL on failure
|
* @see discover_filepath_from_id()
|
*/
|
static void find_folder_name(LIBMTP_folder_t* folders, uint32_t* id, char** name)
|
{
|
|
// FIXME this function is exactly LIBMTP_Find_Folder
|
|
LIBMTP_folder_t* f = LIBMTP_Find_Folder(folders, *id);
|
if(f == NULL) {
|
*name = NULL;
|
}
|
else { // found it!
|
*name = strdup(f->name);
|
*id = f->parent_id;
|
}
|
}
|
|
|
/**
|
* Find the folder id given the folder's name and parent id.
|
*
|
* @param folders the folders list for the device
|
* @param parent the folder's parent's id
|
* @param name the name of the folder
|
* @return the folder_id or 0 on failure
|
* @see discover_filepath_from_id()
|
*/
|
static uint32_t find_folder_id(LIBMTP_folder_t* folders, uint32_t parent, char* name) {
|
|
if(folders == NULL)
|
return 0;
|
|
// found it!
|
else if( (folders->parent_id == parent) &&
|
(strcmp(folders->name, name) == 0) )
|
return folders->folder_id;
|
|
// no luck so far, search both siblings and children
|
else {
|
uint32_t id = 0;
|
|
if(folders->sibling != NULL)
|
id = find_folder_id(folders->sibling, parent, name);
|
if( (id == 0) && (folders->child != NULL) )
|
id = find_folder_id(folders->child, parent, name);
|
|
return id;
|
}
|
}
|
|
|
/**
|
* Append a string to a linked-list of strings.
|
*
|
* @param t the list-of-strings, returns with the added string
|
* @param s the string to append
|
* @see spl_text_t_from_tracks()
|
*/
|
static void append_text_t(text_t** t, char* s)
|
{
|
if(*t == NULL) {
|
*t = malloc(sizeof(text_t));
|
}
|
else {
|
(*t)->next = malloc(sizeof(text_t));
|
(*t) = (*t)->next;
|
}
|
(*t)->text = strdup(s);
|
}
|