blob: d4d5be1ade35c38ef99d87c87c2d77a510b76dfa [file] [log] [blame]
Linus Walleijf3c44052008-08-16 21:14:56 +00001/**
Linus Walleijd866d242009-08-23 21:50:39 +00002 * \File playlist-spl.c
Linus Walleijf3c44052008-08-16 21:14:56 +00003 *
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
Linus Walleijd866d242009-08-23 21:50:39 +000024#include <config.h>
25
Linus Walleijf3c44052008-08-16 21:14:56 +000026#include <stdio.h>
27#include <stdlib.h> // mkstmp()
Linus Walleij2eaaff02009-01-15 21:30:36 +000028#include <unistd.h>
Linus Walleijf3c44052008-08-16 21:14:56 +000029#include <errno.h>
30#include <sys/stat.h>
Linus Walleij2eaaff02009-01-15 21:30:36 +000031#include <sys/types.h>
Linus Walleij6f050022009-05-06 21:14:41 +000032#ifdef HAVE_SYS_UIO_H
Linus Walleij2eaaff02009-01-15 21:30:36 +000033#include <sys/uio.h>
Linus Walleij6f050022009-05-06 21:14:41 +000034#endif
Linus Walleijf3c44052008-08-16 21:14:56 +000035#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
nicklas79daadbf22009-09-28 18:19:34 +000046/**
47 * Debug macro
48 */
49#define LIBMTP_PLST_DEBUG(format, args...) \
50 do { \
nicklas7903584082009-09-28 18:20:16 +000051 if ((LIBMTP_debug & LIBMTP_DEBUG_PLST) != 0) \
nicklas79daadbf22009-09-28 18:19:34 +000052 fprintf(stdout, "LIBMTP %s[%d]: " format, __FUNCTION__, __LINE__, ##args); \
53 } while (0)
Linus Walleijf3c44052008-08-16 21:14:56 +000054
Linus Walleijf3c44052008-08-16 21:14:56 +000055
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
Linus Walleij6f050022009-05-06 21:14:41 +000098#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
Linus Walleijf3c44052008-08-16 21:14:56 +0000107/**
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);
Richard Low35240002009-04-18 11:02:38 +0000124 // Set terminating character
125 pl->name[strlen(oi->Filename) - 4] = 0;
Linus Walleijf3c44052008-08-16 21:14:56 +0000126 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
nicklas79daadbf22009-09-28 18:19:34 +0000132 LIBMTP_PLST_DEBUG("pl->name='%s'\n", pl->name);
Linus Walleijf3c44052008-08-16 21:14:56 +0000133
134 // open a temporary file
135 char tmpname[] = "/tmp/mtp-spl2pl-XXXXXX";
136 int fd = mkstemp(tmpname);
137 if(fd < 0) {
nicklas79daadbf22009-09-28 18:19:34 +0000138 LIBMTP_ERROR("failed to make temp file for %s.spl -> %s, errno=%s\n", pl->name, tmpname, strerror(errno));
Linus Walleijf3c44052008-08-16 21:14:56 +0000139 return;
140 }
141 // make sure the file will be deleted afterwards
142 if(unlink(tmpname) < 0)
nicklas79daadbf22009-09-28 18:19:34 +0000143 LIBMTP_ERROR("failed to delete temp file for %s.spl -> %s, errno=%s\n", pl->name, tmpname, strerror(errno));
Linus Walleijf3c44052008-08-16 21:14:56 +0000144 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);
nicklas79daadbf22009-09-28 18:19:34 +0000148 LIBMTP_INFO("FIXME closed\n");
Linus Walleijf3c44052008-08-16 21:14:56 +0000149 }
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);
nicklas79daadbf22009-09-28 18:19:34 +0000162 LIBMTP_PLST_DEBUG("%u track%s found\n", pl->no_tracks, pl->no_tracks==1?"":"s");
Linus Walleijf3c44052008-08-16 21:14:56 +0000163 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
nicklas79daadbf22009-09-28 18:19:34 +0000169 LIBMTP_PLST_DEBUG("------------\n\n");
Linus Walleijf3c44052008-08-16 21:14:56 +0000170}
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
nicklas79daadbf22009-09-28 18:19:34 +0000192 LIBMTP_PLST_DEBUG("pl->name='%s'\n",pl->name);
Linus Walleijf3c44052008-08-16 21:14:56 +0000193
194 // open a file descriptor
195 int fd = mkstemp(tmpname);
196 if(fd < 0) {
nicklas79daadbf22009-09-28 18:19:34 +0000197 LIBMTP_ERROR("failed to make temp file for %s.spl -> %s, errno=%s\n", pl->name, tmpname, strerror(errno));
Linus Walleijf3c44052008-08-16 21:14:56 +0000198 return -1;
199 }
200 // make sure the file will be deleted afterwards
201 if(unlink(tmpname) < 0)
nicklas79daadbf22009-09-28 18:19:34 +0000202 LIBMTP_ERROR("failed to delete temp file for %s.spl -> %s, errno=%s\n", pl->name, tmpname, strerror(errno));
Linus Walleijf3c44052008-08-16 21:14:56 +0000203
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
nicklas79daadbf22009-09-28 18:19:34 +0000211 LIBMTP_PLST_DEBUG("%u track%s\n", pl->no_tracks, pl->no_tracks==1?"":"s");
212 LIBMTP_PLST_DEBUG(".spl version %d.%02d\n", ver_major, ver_minor);
Linus Walleijf3c44052008-08-16 21:14:56 +0000213
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
nicklas79daadbf22009-09-28 18:19:34 +0000231 LIBMTP_PLST_DEBUG("%s is %dB\n", f->filename, (int)f->filesize);
Linus Walleijf3c44052008-08-16 21:14:56 +0000232
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
nicklas79daadbf22009-09-28 18:19:34 +0000243 LIBMTP_PLST_DEBUG("------------\n\n");
Linus Walleijf3c44052008-08-16 21:14:56 +0000244
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,
Linus Walleijd866d242009-08-23 21:50:39 +0000263 LIBMTP_playlist_t * const newlist)
Linus Walleijf3c44052008-08-16 21:14:56 +0000264{
nicklas79daadbf22009-09-28 18:19:34 +0000265 LIBMTP_PLST_DEBUG("pl->name='%s'\n",newlist->name);
Linus Walleijf3c44052008-08-16 21:14:56 +0000266
267 // read in the playlist of interest
Linus Walleijd866d242009-08-23 21:50:39 +0000268 LIBMTP_playlist_t * old = LIBMTP_Get_Playlist(device, newlist->playlist_id);
Richard Lowfce7b1c2009-05-02 09:54:00 +0000269
270 // check to see if we found it
271 if (!old)
272 return -1;
Linus Walleijf3c44052008-08-16 21:14:56 +0000273
274 // check if the playlists match
275 int delta = 0;
276 int i;
Linus Walleijd866d242009-08-23 21:50:39 +0000277 if(old->no_tracks != newlist->no_tracks)
Linus Walleijf3c44052008-08-16 21:14:56 +0000278 delta++;
Linus Walleijd866d242009-08-23 21:50:39 +0000279 for(i=0;i<newlist->no_tracks && delta==0;i++) {
280 if(old->tracks[i] != newlist->tracks[i])
Linus Walleijf3c44052008-08-16 21:14:56 +0000281 delta++;
282 }
283
284 // if not, kill the playlist and replace it
285 if(delta) {
nicklas79daadbf22009-09-28 18:19:34 +0000286 LIBMTP_PLST_DEBUG("new tracks detected:\n");
287 LIBMTP_PLST_DEBUG("delete old playlist and build a new one\n");
288 LIBMTP_PLST_DEBUG(" NOTE: new playlist_id will result!\n");
Linus Walleijf3c44052008-08-16 21:14:56 +0000289 if(LIBMTP_Delete_Object(device, old->playlist_id) != 0)
290 return -1;
291
nicklas79daadbf22009-09-28 18:19:34 +0000292 if(strcmp(old->name,newlist->name) == 0)
293 LIBMTP_PLST_DEBUG("name unchanged\n");
294 else
295 LIBMTP_PLST_DEBUG("name is changing too -> %s\n",newlist->name);
Linus Walleijf3c44052008-08-16 21:14:56 +0000296
Linus Walleijd866d242009-08-23 21:50:39 +0000297 return LIBMTP_Create_New_Playlist(device, newlist);
Linus Walleijf3c44052008-08-16 21:14:56 +0000298 }
299
300
301 // update the name only
Linus Walleijd866d242009-08-23 21:50:39 +0000302 if(strcmp(old->name,newlist->name) != 0) {
nicklas79daadbf22009-09-28 18:19:34 +0000303 LIBMTP_PLST_DEBUG("ONLY name is changing -> %s\n",newlist->name);
304 LIBMTP_PLST_DEBUG("playlist_id will remain unchanged\n");
Linus Walleijd866d242009-08-23 21:50:39 +0000305 char* s = malloc(sizeof(char)*(strlen(newlist->name)+5));
306 strcpy(s, newlist->name);
Linus Walleijf3c44052008-08-16 21:14:56 +0000307 strcat(s,".spl"); // FIXME check for success
Linus Walleijd866d242009-08-23 21:50:39 +0000308 int ret = LIBMTP_Set_Playlist_Name(device, newlist, s);
Linus Walleijf3c44052008-08-16 21:14:56 +0000309 free(s);
310 return ret;
311 }
312
nicklas79daadbf22009-09-28 18:19:34 +0000313 LIBMTP_PLST_DEBUG("no change\n");
Linus Walleijf3c44052008-08-16 21:14:56 +0000314 return 0; // nothing to be done, success
315}
316
317
318/**
319 * Load a file descriptor into a string.
320 *
321 * @param device a pointer to the current device.
322 * (needed for ucs2->utf8 charset conversion)
323 * @param fd the file descriptor to load
324 * @return text_t* a linked list of lines of text, id is left blank, NULL if nothing read in
325 */
326static text_t* read_into_spl_text_t(LIBMTP_mtpdevice_t *device, const int fd)
327{
328 // set MAXREAD to match STRING_BUFFER_LENGTH in unicode.h conversion function
329 const size_t MAXREAD = 1024*2;
330 char t[MAXREAD];
331 // upto 3 bytes per utf8 character, 2 bytes per ucs2 character,
332 // +1 for '\0' at end of string
333 const size_t WSIZE = MAXREAD/2*3+1;
334 char w[WSIZE];
335 char* it = t; // iterator on t
336 char* iw = w;
337 ssize_t rdcnt;
338 off_t offcnt;
339 text_t* head = NULL;
340 text_t* tail = NULL;
341 int eof = 0;
342
343 // reset file descriptor (fd) to start of file
344 offcnt = lseek(fd, 0, SEEK_SET);
345
346 while(!eof) {
347 // find the current offset in the file
348 // to allow us to determine how many bytes we read if we hit the EOF
349 // where returned rdcnt=0 from read()
350 offcnt = lseek(fd, 0, SEEK_CUR);
351 // read to refill buffer
352 // (there might be data left from an incomplete last string in t,
353 // hence start filling at it)
354 it = t; // set ptr to start of buffer
355 rdcnt = read(fd, it, sizeof(char)*MAXREAD);
356 if(rdcnt < 0)
nicklas79daadbf22009-09-28 18:19:34 +0000357 LIBMTP_INFO("load_spl_fd read err %s\n", strerror(errno));
Linus Walleijf3c44052008-08-16 21:14:56 +0000358 else if(rdcnt == 0) { // for EOF, fix rdcnt
359 if(it-t == MAXREAD)
nicklas79daadbf22009-09-28 18:19:34 +0000360 LIBMTP_ERROR("error -- buffer too small to read in .spl playlist entry\n");
Linus Walleijf3c44052008-08-16 21:14:56 +0000361
362 rdcnt = lseek(fd, 0, SEEK_CUR) - offcnt;
363 eof = 1;
364 }
365
nicklas79daadbf22009-09-28 18:19:34 +0000366 LIBMTP_PLST_DEBUG("read buff= {%dB new, %dB old/left-over}%s\n",(int)rdcnt, (int)(iw-w), eof?", EOF":"");
Linus Walleijf3c44052008-08-16 21:14:56 +0000367
368 // while more input bytes
369 char* it_end = t + rdcnt;
370 while(it < it_end) {
371 // copy byte, unless EOL (then replace with end-of-string \0)
372 if(*it == '\r' || *it == '\n')
373 *iw = '\0';
374 else
375 *iw = *it;
376
377 it++;
378 iw++;
379
380 // EOL -- store it
381 if( (iw-w) >= 2 && // we must have at least two bytes
382 *(iw-1) == '\0' && *(iw-2) == '\0' && // 0x0000 is end-of-string
383 // but it must be aligned such that we have an {odd,even} set of
384 // bytes since we are expecting to consume bytes two-at-a-time
385 !((iw-w)%2) ) {
386
387 // drop empty lines
388 // ... cast as a string of 2 byte characters
389 if(ucs2_strlen((uint16_t*)w) == 0) {
390 iw = w;
391 continue;
392 }
393
394 // create a new node in the list
395 if(head == NULL) {
396 head = malloc(sizeof(text_t));
397 tail = head;
398 }
399 else {
400 tail->next = malloc(sizeof(text_t));
401 tail = tail->next;
402 }
403 // fill in the data for the node
404 // ... cast as a string of 2 byte characters
405 tail->text = utf16_to_utf8(device, (uint16_t*) w);
406 iw = w; // start again
407
nicklas79daadbf22009-09-28 18:19:34 +0000408 LIBMTP_PLST_DEBUG("line: %s\n", tail->text);
Linus Walleijf3c44052008-08-16 21:14:56 +0000409 }
410
411 // prevent buffer overflow
412 if(iw >= w + WSIZE) {
413 // if we ever see this error its BAD:
414 // we are dropping all the processed bytes for this line and
415 // proceeding on as if everything is okay, probably losing a track
416 // from the playlist
nicklas79daadbf22009-09-28 18:19:34 +0000417 LIBMTP_ERROR("ERROR %s:%u:%s(): buffer overflow! .spl line too long @ %zuB\n",
Linus Walleijf3c44052008-08-16 21:14:56 +0000418 __FILE__, __LINE__, __func__, WSIZE);
419 iw = w; // reset buffer
420 }
421 }
422
423 // if the last thing we did was save our line, then we finished working
424 // on the input buffer and we can start fresh
425 // otherwise we need to save our partial work, if we're not quiting (eof).
426 // there is nothing special we need to do, to achieve this since the
427 // partially completed string will sit in 'w' until we return to complete
428 // the line
429
430 }
431
432 // set the next pointer at the end
433 // if there is any list
434 if(head != NULL)
435 tail->next = NULL;
436
437 // return the head of the list (NULL if no list)
438 return head;
439}
440
441
442/**
443 * Write a .spl text file to a file in preparation for pushing it
444 * to the device.
445 *
446 * @param fd file descriptor to write to
447 * @param p the text to output one line per string in the linked list
448 * @see playlist_t_to_spl()
449 */
450static void write_from_spl_text_t(LIBMTP_mtpdevice_t *device,
451 const int fd,
452 text_t* p) {
453 ssize_t ret;
454 // write out BOM for utf16/ucs2 (byte order mark)
455 ret = write(fd,"\xff\xfe",2);
456 while(p != NULL) {
457 char *const t = (char*) utf8_to_utf16(device, p->text);
458 // note: 2 bytes per ucs2 character
459 const size_t len = ucs2_strlen((uint16_t*)t)*sizeof(uint16_t);
460 int i;
461
nicklas79daadbf22009-09-28 18:19:34 +0000462 LIBMTP_PLST_DEBUG("\nutf8=%s ",p->text);
463 for(i=0;i<strlen(p->text);i++)
464 LIBMTP_PLST_DEBUG("%02x ", p->text[i] & 0xff);
465 LIBMTP_PLST_DEBUG("\n");
466 LIBMTP_PLST_DEBUG("ucs2=");
467 for(i=0;i<ucs2_strlen((uint16_t*)t)*sizeof(uint16_t);i++)
468 LIBMTP_PLST_DEBUG("%02x ", t[i] & 0xff);
469 LIBMTP_PLST_DEBUG("\n");
Linus Walleijf3c44052008-08-16 21:14:56 +0000470
471 // write: utf8 -> utf16
472 ret += write(fd, t, len);
473
474 // release the converted string
475 free(t);
476
477 // check for failures
478 if(ret < 0)
nicklas79daadbf22009-09-28 18:19:34 +0000479 LIBMTP_ERROR("write spl file failed: %s\n", strerror(errno));
Linus Walleijf3c44052008-08-16 21:14:56 +0000480 else if(ret != len +2)
nicklas79daadbf22009-09-28 18:19:34 +0000481 LIBMTP_ERROR("write spl file wrong number of bytes ret=%d len=%d '%s'\n", (int)ret, (int)len, p->text);
Linus Walleijf3c44052008-08-16 21:14:56 +0000482
483 // write carriage return, line feed in ucs2
484 ret = write(fd, "\r\0\n\0", 4);
485 if(ret < 0)
nicklas79daadbf22009-09-28 18:19:34 +0000486 LIBMTP_ERROR("write spl file failed: %s\n", strerror(errno));
Linus Walleijf3c44052008-08-16 21:14:56 +0000487 else if(ret != 4)
nicklas79daadbf22009-09-28 18:19:34 +0000488 LIBMTP_ERROR("failed to write the correct number of bytes '\\n'!\n");
Linus Walleijf3c44052008-08-16 21:14:56 +0000489
490 // fake out count (first time through has two extra bytes from BOM)
491 ret = 2;
492
493 // advance to the next line
494 p = p->next;
495 }
496}
497
498/**
499 * Destroy a linked-list of strings.
500 *
501 * @param p the list to destroy
502 * @see spl_to_playlist_t()
503 * @see playlist_t_to_spl()
504 */
505static void free_spl_text_t(text_t* p)
506{
507 text_t* d;
508 while(p != NULL) {
509 d = p;
510 free(p->text);
511 p = p->next;
512 free(d);
513 }
514}
515
516/**
517 * Print a linked-list of strings to stdout.
nicklas79daadbf22009-09-28 18:19:34 +0000518 * Used to debug.
Linus Walleijf3c44052008-08-16 21:14:56 +0000519 *
520 * @param p the list to print
521 */
522static void print_spl_text_t(text_t* p)
523{
524 while(p != NULL) {
nicklas79daadbf22009-09-28 18:19:34 +0000525 LIBMTP_PLST_DEBUG("%s\n",p->text);
Linus Walleijf3c44052008-08-16 21:14:56 +0000526 p = p->next;
527 }
528}
529
530/**
531 * Count the number of tracks in this playlist. A track will be counted as
532 * such if the line starts with a leading slash.
533 *
534 * @param p the text to search
535 * @return number of tracks in the playlist
536 * @see spl_to_playlist_t()
537 */
538static uint32_t trackno_spl_text_t(text_t* p) {
539 uint32_t c = 0;
540 while(p != NULL) {
541 if(p->text[0] == '\\' ) c++;
542 p = p->next;
543 }
544
545 return c;
546}
547
548/**
549 * Find the track ids for this playlist's files.
550 * (ie: \Music\song.mp3 -> 12345)
551 *
552 * @param p the text to search
553 * @param tracks returned list of track id's for the playlist_t, must be large
554 * enough to accomodate all the tracks as reported by
555 * trackno_spl_text_t()
556 * @param folders the folders list for the device
557 * @param fiels the files list for the device
558 * @see spl_to_playlist_t()
559 */
560static void tracks_from_spl_text_t(text_t* p,
561 uint32_t* tracks,
562 LIBMTP_folder_t* folders,
563 LIBMTP_file_t* files)
564{
565 uint32_t c = 0;
566 while(p != NULL) {
567 if(p->text[0] == '\\' ) {
568 tracks[c] = discover_id_from_filepath(p->text, folders, files);
nicklas79daadbf22009-09-28 18:19:34 +0000569 LIBMTP_PLST_DEBUG("track %d = %s (%u)\n", c+1, p->text, tracks[c]);
Linus Walleijf3c44052008-08-16 21:14:56 +0000570 c++;
571 }
572 p = p->next;
573 }
574}
575
576
577/**
578 * Find the track names (including path) for this playlist's track ids.
579 * (ie: 12345 -> \Music\song.mp3)
580 *
581 * @param p the text to search
582 * @param tracks list of track id's to look up
583 * @param folders the folders list for the device
584 * @param fiels the files list for the device
585 * @see playlist_t_to_spl()
586 */
587static void spl_text_t_from_tracks(text_t** p,
588 uint32_t* tracks,
589 const uint32_t trackno,
590 const uint32_t ver_major,
591 const uint32_t ver_minor,
592 char* dnse,
593 LIBMTP_folder_t* folders,
594 LIBMTP_file_t* files)
595{
596
597 // HEADER
598 text_t* c = NULL;
599 append_text_t(&c, "SPL PLAYLIST");
600 *p = c; // save the top of the list!
601
602 char vs[14]; // "VERSION 2.00\0"
603 sprintf(vs,"VERSION %d.%02d",ver_major,ver_minor);
604
605 append_text_t(&c, vs);
606 append_text_t(&c, "");
607
608 // TRACKS
609 int i;
610 char* f;
611 for(i=0;i<trackno;i++) {
612 discover_filepath_from_id(&f, tracks[i], folders, files);
613
614 if(f != NULL) {
615 append_text_t(&c, f);
nicklas79daadbf22009-09-28 18:19:34 +0000616 LIBMTP_PLST_DEBUG("track %d = %s (%u)\n", i+1, f, tracks[i]);
Linus Walleijf3c44052008-08-16 21:14:56 +0000617 }
618 else
nicklas79daadbf22009-09-28 18:19:34 +0000619 LIBMTP_ERROR("failed to find filepath for track=%d\n", tracks[i]);
Linus Walleijf3c44052008-08-16 21:14:56 +0000620 }
621
622 // FOOTER
623 append_text_t(&c, "");
624 append_text_t(&c, "END PLAYLIST");
625 if(ver_major == 2) {
626 append_text_t(&c, "");
627 append_text_t(&c, "myDNSe DATA");
628 if(dnse != NULL) {
629 append_text_t(&c, dnse);
630 }
631 else {
632 append_text_t(&c, "");
633 append_text_t(&c, "");
634 }
635 append_text_t(&c, "END myDNSe");
636 }
637
638 c->next = NULL;
639
640 // debug
nicklas79daadbf22009-09-28 18:19:34 +0000641 LIBMTP_PLST_DEBUG(".spl playlist:\n");
642 print_spl_text_t(*p);
Linus Walleijf3c44052008-08-16 21:14:56 +0000643}
644
645
646/**
647 * Find the track names (including path) given a fileid
648 * (ie: 12345 -> \Music\song.mp3)
649 *
650 * @param p returns the file path (ie: \Music\song.mp3),
651 * (*p) == NULL if the look up fails
652 * @param track track id to look up
653 * @param folders the folders list for the device
654 * @param files the files list for the device
655 * @see spl_text_t_from_tracks()
656 */
657
658// returns p = NULL on failure, else the filepath to the track including track name, allocated as a correct length string
659static void discover_filepath_from_id(char** p,
660 uint32_t track,
661 LIBMTP_folder_t* folders,
662 LIBMTP_file_t* files)
663{
664 // fill in a string from the right side since we don't know the root till the end
665 const int M = 1024;
666 char w[M];
667 char* iw = w + M; // iterator on w
668
669 // in case of failure return NULL string
670 *p = NULL;
671
672
673 // find the right file
674 while(files != NULL && files->item_id != track) {
675 files = files->next;
676 }
677 // if we didn't find a matching file, abort
678 if(files == NULL)
679 return;
680
681 // stuff the filename into our string
682 // FIXME: check for string overflow before it occurs
683 iw = iw - (strlen(files->filename) +1); // leave room for '\0' at the end
684 strcpy(iw,files->filename);
685
686 // next follow the directories to the root
687 // prepending folders to the path as we go
688 uint32_t id = files->parent_id;
689 char* f = NULL;
690 while(id != 0) {
691 find_folder_name(folders, &id, &f);
692 if(f == NULL) return; // fail if the next part of the path couldn't be found
693 iw = iw - (strlen(f) +1);
694 // FIXME: check for string overflow before it occurs
695 strcpy(iw, f);
696 iw[strlen(f)] = '\\';
697 free(f);
698 }
699
700 // prepend a slash
701 iw--;
702 iw[0] = '\\';
703
704 // now allocate a string of the right length to be returned
705 *p = strdup(iw);
706}
707
708
709/**
710 * Find the track id given a track's name (including path)
711 * (ie: \Music\song.mp3 -> 12345)
712 *
713 * @param s file path to look up (ie: \Music\song.mp3),
714 * (*p) == NULL if the look up fails
715 * @param folders the folders list for the device
716 * @param files the files list for the device
717 * @return track id, 0 means failure
718 * @see tracks_from_spl_text_t()
719 */
720static uint32_t discover_id_from_filepath(const char* s, LIBMTP_folder_t* folders, LIBMTP_file_t* files)
721{
722 // abort if this isn't a path
723 if(s[0] != '\\')
724 return 0;
725
726 int i;
727 uint32_t id = 0;
728 char* sc = strdup(s);
729 char* sci = sc +1; // iterator
730 // skip leading slash in path
731
732 // convert all \ to \0
733 size_t len = strlen(s);
734 for(i=0;i<len;i++) {
735 if(sc[i] == '\\') {
736 sc[i] = '\0';
737 }
738 }
739
740 // now for each part of the string, find the id
741 while(sci != sc + len +1) {
742 // if its the last part of the string, its the filename
743 if(sci + strlen(sci) == sc + len) {
744
745 while(files != NULL) {
746 // check parent matches id and name matches sci
747 if( (files->parent_id == id) &&
748 (strcmp(files->filename, sci) == 0) ) { // found it!
749 id = files->item_id;
750 break;
751 }
752 files = files->next;
753 }
754 }
755 else { // otherwise its part of the directory path
756 id = find_folder_id(folders, id, sci);
757 }
758
759 // move to next folder/file
760 sci += strlen(sci) +1;
761 }
762
763 // release our copied string
764 free(sc);
765
766 // FIXME check that we actually have a file
767
768 return id;
769}
770
771
772
773/**
774 * Find the folder name given the folder's id.
775 *
776 * @param folders the folders list for the device
777 * @param id the folder_id to look up, returns the folder's parent folder_id
778 * @param name returns the name of the folder or NULL on failure
779 * @see discover_filepath_from_id()
780 */
781static void find_folder_name(LIBMTP_folder_t* folders, uint32_t* id, char** name)
782{
783
784 // FIXME this function is exactly LIBMTP_Find_Folder
785
786 LIBMTP_folder_t* f = LIBMTP_Find_Folder(folders, *id);
787 if(f == NULL) {
788 *name = NULL;
789 }
790 else { // found it!
791 *name = strdup(f->name);
792 *id = f->parent_id;
793 }
794}
795
796
797/**
798 * Find the folder id given the folder's name and parent id.
799 *
800 * @param folders the folders list for the device
801 * @param parent the folder's parent's id
802 * @param name the name of the folder
803 * @return the folder_id or 0 on failure
804 * @see discover_filepath_from_id()
805 */
806static uint32_t find_folder_id(LIBMTP_folder_t* folders, uint32_t parent, char* name) {
807
808 if(folders == NULL)
809 return 0;
810
811 // found it!
812 else if( (folders->parent_id == parent) &&
813 (strcmp(folders->name, name) == 0) )
814 return folders->folder_id;
815
816 // no luck so far, search both siblings and children
817 else {
818 uint32_t id = 0;
819
820 if(folders->sibling != NULL)
821 id = find_folder_id(folders->sibling, parent, name);
822 if( (id == 0) && (folders->child != NULL) )
823 id = find_folder_id(folders->child, parent, name);
824
825 return id;
826 }
827}
828
829
830/**
831 * Append a string to a linked-list of strings.
832 *
833 * @param t the list-of-strings, returns with the added string
834 * @param s the string to append
835 * @see spl_text_t_from_tracks()
836 */
837static void append_text_t(text_t** t, char* s)
838{
839 if(*t == NULL) {
840 *t = malloc(sizeof(text_t));
841 }
842 else {
843 (*t)->next = malloc(sizeof(text_t));
844 (*t) = (*t)->next;
845 }
846 (*t)->text = strdup(s);
847}