diff options
-rw-r--r-- | apps/playlist.c | 3753 | ||||
-rw-r--r-- | apps/playlist.h | 37 |
2 files changed, 1881 insertions, 1909 deletions
diff --git a/apps/playlist.c b/apps/playlist.c index d71257a515..23195b4417 100644 --- a/apps/playlist.c +++ b/apps/playlist.c | |||
@@ -29,7 +29,7 @@ | |||
29 | directory, there will be no playlist file. | 29 | directory, there will be no playlist file. |
30 | 2. Control file : This file is automatically created when a playlist is | 30 | 2. Control file : This file is automatically created when a playlist is |
31 | started and contains all the commands done to it. | 31 | started and contains all the commands done to it. |
32 | 32 | ||
33 | The first non-comment line in a control file must begin with | 33 | The first non-comment line in a control file must begin with |
34 | "P:VERSION:DIR:FILE" where VERSION is the playlist control file version, | 34 | "P:VERSION:DIR:FILE" where VERSION is the playlist control file version, |
35 | DIR is the directory where the playlist is located and FILE is the | 35 | DIR is the directory where the playlist is located and FILE is the |
@@ -68,8 +68,8 @@ | |||
68 | */ | 68 | */ |
69 | 69 | ||
70 | #include <stdio.h> | 70 | #include <stdio.h> |
71 | #include <stdlib.h> | 71 | #include <stdlib.h> |
72 | #include <ctype.h> | 72 | #include <ctype.h> |
73 | #include "string-extra.h" | 73 | #include "string-extra.h" |
74 | #include "playlist.h" | 74 | #include "playlist.h" |
75 | #include "ata_idle_notify.h" | 75 | #include "ata_idle_notify.h" |
@@ -127,14 +127,15 @@ | |||
127 | /* default load buffer size (should be at least 1 KiB) */ | 127 | /* default load buffer size (should be at least 1 KiB) */ |
128 | #define PLAYLIST_LOAD_BUFLEN (32*1024) | 128 | #define PLAYLIST_LOAD_BUFLEN (32*1024) |
129 | 129 | ||
130 | |||
131 | #define PLAYLIST_CONTROL_FILE_VERSION 2 | 130 | #define PLAYLIST_CONTROL_FILE_VERSION 2 |
132 | 131 | ||
132 | #define PLAYLIST_COMMAND_SIZE (MAX_PATH+12) | ||
133 | |||
133 | /* | 134 | /* |
134 | Each playlist index has a flag associated with it which identifies what | 135 | Each playlist index has a flag associated with it which identifies what |
135 | type of track it is. These flags are stored in the 4 high order bits of | 136 | type of track it is. These flags are stored in the 4 high order bits of |
136 | the index. | 137 | the index. |
137 | 138 | ||
138 | NOTE: This limits the playlist file size to a max of 256M. | 139 | NOTE: This limits the playlist file size to a max of 256M. |
139 | 140 | ||
140 | Bits 31-30: | 141 | Bits 31-30: |
@@ -169,56 +170,12 @@ struct directory_search_context { | |||
169 | 170 | ||
170 | static struct playlist_info current_playlist; | 171 | static struct playlist_info current_playlist; |
171 | 172 | ||
172 | static void empty_playlist(struct playlist_info* playlist, bool resume); | ||
173 | static void new_playlist(struct playlist_info* playlist, const char *dir, | ||
174 | const char *file); | ||
175 | static void create_control(struct playlist_info* playlist); | ||
176 | static int check_control(struct playlist_info* playlist); | ||
177 | static int recreate_control(struct playlist_info* playlist); | ||
178 | static void update_playlist_filename(struct playlist_info* playlist, | ||
179 | const char *dir, const char *file); | ||
180 | static int add_indices_to_playlist(struct playlist_info* playlist, | ||
181 | char* buffer, size_t buflen); | ||
182 | static int add_track_to_playlist(struct playlist_info* playlist, | ||
183 | const char *filename, int position, | ||
184 | bool queue, int seek_pos); | ||
185 | static int directory_search_callback(char* filename, void* context); | ||
186 | static int remove_track_from_playlist(struct playlist_info* playlist, | ||
187 | int position, bool write); | ||
188 | static int randomise_playlist(struct playlist_info* playlist, | ||
189 | unsigned int seed, bool start_current, | ||
190 | bool write); | ||
191 | static int sort_playlist(struct playlist_info* playlist, bool start_current, | ||
192 | bool write); | ||
193 | static int get_next_index(const struct playlist_info* playlist, int steps, | ||
194 | int repeat_mode); | ||
195 | static void find_and_set_playlist_index(struct playlist_info* playlist, | ||
196 | unsigned int seek); | ||
197 | static int compare(const void* p1, const void* p2); | ||
198 | static int get_filename(struct playlist_info* playlist, int index, int seek, | ||
199 | bool control_file, char *buf, int buf_length); | ||
200 | static int get_next_directory(char *dir); | ||
201 | static int get_next_dir(char *dir, bool is_forward); | ||
202 | static int get_previous_directory(char *dir); | ||
203 | static int check_subdir_for_music(char *dir, const char *subdir, bool recurse); | ||
204 | static ssize_t format_track_path(char *dest, char *src, int buf_length, | ||
205 | const char *dir); | ||
206 | static void display_playlist_count(int count, const unsigned char *fmt, | ||
207 | bool final); | ||
208 | static void display_buffer_full(void); | ||
209 | static int flush_cached_control(struct playlist_info* playlist); | ||
210 | static int update_control(struct playlist_info* playlist, | ||
211 | enum playlist_command command, int i1, int i2, | ||
212 | const char* s1, const char* s2, void* data); | ||
213 | static void sync_control(struct playlist_info* playlist, bool force); | ||
214 | static int rotate_index(const struct playlist_info* playlist, int index); | ||
215 | |||
216 | #ifdef HAVE_DIRCACHE | 173 | #ifdef HAVE_DIRCACHE |
217 | #define PLAYLIST_LOAD_POINTERS 1 | 174 | #define PLAYLIST_LOAD_POINTERS 1 |
218 | 175 | ||
219 | static struct event_queue playlist_queue SHAREDBSS_ATTR; | 176 | static struct event_queue playlist_queue SHAREDBSS_ATTR; |
220 | static long playlist_stack[(DEFAULT_STACK_SIZE + 0x800)/sizeof(long)]; | 177 | static long playlist_stack[(DEFAULT_STACK_SIZE + 0x800)/sizeof(long)]; |
221 | static const char playlist_thread_name[] = "playlist cachectrl"; | 178 | static const char thread_playlist_name[] = "playlist cachectrl"; |
222 | #endif | 179 | #endif |
223 | 180 | ||
224 | static struct mutex current_playlist_mutex SHAREDBSS_ATTR; | 181 | static struct mutex current_playlist_mutex SHAREDBSS_ATTR; |
@@ -253,14 +210,14 @@ static bool is_m3u8(const char* filename) | |||
253 | } | 210 | } |
254 | 211 | ||
255 | 212 | ||
256 | /* Convert a filename in an M3U playlist to UTF-8. | 213 | /* Convert a filename in an M3U playlist to UTF-8. |
257 | * | 214 | * |
258 | * buf - the filename to convert; can contain more than one line from the | 215 | * buf - the filename to convert; can contain more than one line from the |
259 | * playlist. | 216 | * playlist. |
260 | * buf_len - amount of buf that is used. | 217 | * buf_len - amount of buf that is used. |
261 | * buf_max - total size of buf. | 218 | * buf_max - total size of buf. |
262 | * temp - temporary conversion buffer, at least buf_max bytes. | 219 | * temp - temporary conversion buffer, at least buf_max bytes. |
263 | * | 220 | * |
264 | * Returns the length of the converted filename. | 221 | * Returns the length of the converted filename. |
265 | */ | 222 | */ |
266 | static int convert_m3u(char* buf, int buf_len, int buf_max, char* temp) | 223 | static int convert_m3u(char* buf, int buf_len, int buf_max, char* temp) |
@@ -279,10 +236,10 @@ static int convert_m3u(char* buf, int buf_len, int buf_max, char* temp) | |||
279 | { | 236 | { |
280 | i--; | 237 | i--; |
281 | } | 238 | } |
282 | 239 | ||
283 | buf_len = i; | 240 | buf_len = i; |
284 | dest = temp; | 241 | dest = temp; |
285 | 242 | ||
286 | /* Convert char by char, so as to not overflow temp (iso_decode should | 243 | /* Convert char by char, so as to not overflow temp (iso_decode should |
287 | * preferably handle this). No more than 4 bytes should be generated for | 244 | * preferably handle this). No more than 4 bytes should be generated for |
288 | * each input char. | 245 | * each input char. |
@@ -291,13 +248,222 @@ static int convert_m3u(char* buf, int buf_len, int buf_max, char* temp) | |||
291 | { | 248 | { |
292 | dest = iso_decode(&buf[i], dest, -1, 1); | 249 | dest = iso_decode(&buf[i], dest, -1, 1); |
293 | } | 250 | } |
294 | 251 | ||
295 | *dest = 0; | 252 | *dest = 0; |
296 | strcpy(buf, temp); | 253 | strcpy(buf, temp); |
297 | return dest - temp; | 254 | return dest - temp; |
298 | } | 255 | } |
299 | 256 | ||
300 | /* | 257 | /* |
258 | * create control file for playlist | ||
259 | */ | ||
260 | static void create_control(struct playlist_info* playlist) | ||
261 | { | ||
262 | playlist->control_fd = open(playlist->control_filename, | ||
263 | O_CREAT|O_RDWR|O_TRUNC, 0666); | ||
264 | if (playlist->control_fd < 0) | ||
265 | { | ||
266 | if (check_rockboxdir()) | ||
267 | { | ||
268 | cond_talk_ids_fq(LANG_PLAYLIST_CONTROL_ACCESS_ERROR); | ||
269 | splashf(HZ*2, (unsigned char *)"%s (%d)", | ||
270 | str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR), | ||
271 | playlist->control_fd); | ||
272 | } | ||
273 | playlist->control_created = false; | ||
274 | } | ||
275 | else | ||
276 | { | ||
277 | playlist->control_created = true; | ||
278 | } | ||
279 | } | ||
280 | |||
281 | /* | ||
282 | * Rotate indices such that first_index is index 0 | ||
283 | */ | ||
284 | static int rotate_index(const struct playlist_info* playlist, int index) | ||
285 | { | ||
286 | index -= playlist->first_index; | ||
287 | if (index < 0) | ||
288 | index += playlist->amount; | ||
289 | |||
290 | return index; | ||
291 | } | ||
292 | |||
293 | /* | ||
294 | * sync control file to disk | ||
295 | */ | ||
296 | static void sync_control(struct playlist_info* playlist, bool force) | ||
297 | { | ||
298 | #ifdef HAVE_DIRCACHE | ||
299 | if (playlist->started && force) | ||
300 | #else | ||
301 | (void) force; | ||
302 | |||
303 | if (playlist->started) | ||
304 | #endif | ||
305 | { | ||
306 | if (playlist->pending_control_sync) | ||
307 | { | ||
308 | mutex_lock(playlist->control_mutex); | ||
309 | fsync(playlist->control_fd); | ||
310 | playlist->pending_control_sync = false; | ||
311 | mutex_unlock(playlist->control_mutex); | ||
312 | } | ||
313 | } | ||
314 | } | ||
315 | |||
316 | /* | ||
317 | * Flush any cached control commands to disk. Called when playlist is being | ||
318 | * modified. Returns 0 on success and -1 on failure. | ||
319 | */ | ||
320 | static int flush_cached_control(struct playlist_info* playlist) | ||
321 | { | ||
322 | int result = 0; | ||
323 | int i; | ||
324 | |||
325 | if (!playlist->num_cached) | ||
326 | return 0; | ||
327 | |||
328 | lseek(playlist->control_fd, 0, SEEK_END); | ||
329 | |||
330 | for (i=0; i<playlist->num_cached; i++) | ||
331 | { | ||
332 | struct playlist_control_cache* cache = | ||
333 | &(playlist->control_cache[i]); | ||
334 | |||
335 | switch (cache->command) | ||
336 | { | ||
337 | case PLAYLIST_COMMAND_PLAYLIST: | ||
338 | result = fdprintf(playlist->control_fd, "P:%d:%s:%s\n", | ||
339 | cache->i1, cache->s1, cache->s2); | ||
340 | break; | ||
341 | case PLAYLIST_COMMAND_ADD: | ||
342 | case PLAYLIST_COMMAND_QUEUE: | ||
343 | result = fdprintf(playlist->control_fd, "%c:%d:%d:", | ||
344 | (cache->command == PLAYLIST_COMMAND_ADD)?'A':'Q', | ||
345 | cache->i1, cache->i2); | ||
346 | if (result > 0) | ||
347 | { | ||
348 | /* save the position in file where name is written */ | ||
349 | int* seek_pos = (int *)cache->data; | ||
350 | *seek_pos = lseek(playlist->control_fd, 0, SEEK_CUR); | ||
351 | result = fdprintf(playlist->control_fd, "%s\n", | ||
352 | cache->s1); | ||
353 | } | ||
354 | break; | ||
355 | case PLAYLIST_COMMAND_DELETE: | ||
356 | result = fdprintf(playlist->control_fd, "D:%d\n", cache->i1); | ||
357 | break; | ||
358 | case PLAYLIST_COMMAND_SHUFFLE: | ||
359 | result = fdprintf(playlist->control_fd, "S:%d:%d\n", | ||
360 | cache->i1, cache->i2); | ||
361 | break; | ||
362 | case PLAYLIST_COMMAND_UNSHUFFLE: | ||
363 | result = fdprintf(playlist->control_fd, "U:%d\n", cache->i1); | ||
364 | break; | ||
365 | case PLAYLIST_COMMAND_RESET: | ||
366 | result = fdprintf(playlist->control_fd, "R\n"); | ||
367 | break; | ||
368 | default: | ||
369 | break; | ||
370 | } | ||
371 | |||
372 | if (result <= 0) | ||
373 | break; | ||
374 | } | ||
375 | |||
376 | if (result > 0) | ||
377 | { | ||
378 | playlist->num_cached = 0; | ||
379 | playlist->pending_control_sync = true; | ||
380 | |||
381 | result = 0; | ||
382 | } | ||
383 | else | ||
384 | { | ||
385 | result = -1; | ||
386 | splash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_UPDATE_ERROR)); | ||
387 | } | ||
388 | |||
389 | return result; | ||
390 | } | ||
391 | |||
392 | /* | ||
393 | * Update control data with new command. Depending on the command, it may be | ||
394 | * cached or flushed to disk. | ||
395 | */ | ||
396 | static int update_control(struct playlist_info* playlist, | ||
397 | enum playlist_command command, int i1, int i2, | ||
398 | const char* s1, const char* s2, void* data) | ||
399 | { | ||
400 | int result = 0; | ||
401 | struct playlist_control_cache* cache; | ||
402 | bool flush = false; | ||
403 | |||
404 | mutex_lock(playlist->control_mutex); | ||
405 | |||
406 | cache = &(playlist->control_cache[playlist->num_cached++]); | ||
407 | |||
408 | cache->command = command; | ||
409 | cache->i1 = i1; | ||
410 | cache->i2 = i2; | ||
411 | cache->s1 = s1; | ||
412 | cache->s2 = s2; | ||
413 | cache->data = data; | ||
414 | |||
415 | switch (command) | ||
416 | { | ||
417 | case PLAYLIST_COMMAND_PLAYLIST: | ||
418 | case PLAYLIST_COMMAND_ADD: | ||
419 | case PLAYLIST_COMMAND_QUEUE: | ||
420 | #ifndef HAVE_DIRCACHE | ||
421 | case PLAYLIST_COMMAND_DELETE: | ||
422 | case PLAYLIST_COMMAND_RESET: | ||
423 | #endif | ||
424 | flush = true; | ||
425 | break; | ||
426 | case PLAYLIST_COMMAND_SHUFFLE: | ||
427 | case PLAYLIST_COMMAND_UNSHUFFLE: | ||
428 | default: | ||
429 | /* only flush when needed */ | ||
430 | break; | ||
431 | } | ||
432 | |||
433 | if (flush || playlist->num_cached == PLAYLIST_MAX_CACHE) | ||
434 | result = flush_cached_control(playlist); | ||
435 | |||
436 | mutex_unlock(playlist->control_mutex); | ||
437 | |||
438 | return result; | ||
439 | } | ||
440 | |||
441 | /* | ||
442 | * store directory and name of playlist file | ||
443 | */ | ||
444 | static void update_playlist_filename(struct playlist_info* playlist, | ||
445 | const char *dir, const char *file) | ||
446 | { | ||
447 | char *sep=""; | ||
448 | int dirlen = strlen(dir); | ||
449 | |||
450 | playlist->utf8 = is_m3u8(file); | ||
451 | |||
452 | /* If the dir does not end in trailing slash, we use a separator. | ||
453 | Otherwise we don't. */ | ||
454 | if(!dirlen || '/' != dir[dirlen-1]) | ||
455 | { | ||
456 | sep="/"; | ||
457 | dirlen++; | ||
458 | } | ||
459 | |||
460 | playlist->dirlen = dirlen; | ||
461 | |||
462 | snprintf(playlist->filename, sizeof(playlist->filename), | ||
463 | "%s%s%s", dir, sep, file); | ||
464 | } | ||
465 | |||
466 | /* | ||
301 | * remove any files and indices associated with the playlist | 467 | * remove any files and indices associated with the playlist |
302 | */ | 468 | */ |
303 | static void empty_playlist(struct playlist_info* playlist, bool resume) | 469 | static void empty_playlist(struct playlist_info* playlist, bool resume) |
@@ -344,6 +510,73 @@ static void empty_playlist(struct playlist_info* playlist, bool resume) | |||
344 | } | 510 | } |
345 | 511 | ||
346 | /* | 512 | /* |
513 | * Returns absolute path of track | ||
514 | * | ||
515 | * dest: output buffer | ||
516 | * src: the file name from the playlist | ||
517 | * dir: the absolute path to the directory where the playlist resides | ||
518 | * | ||
519 | * The type of path in "src" determines what will be written to "dest": | ||
520 | * | ||
521 | * 1. UNIX-style absolute paths (/foo/bar) remain unaltered | ||
522 | * 2. Windows-style absolute paths (C:/foo/bar) will be converted into an | ||
523 | * absolute path by replacing the drive letter with the volume that the | ||
524 | * *playlist* resides on, ie. the volume in "dir" | ||
525 | * 3. Relative paths are converted to absolute paths by prepending "dir". | ||
526 | * This also applies to Windows-style relative paths "C:foo/bar" where | ||
527 | * the drive letter is accepted but ignored. | ||
528 | */ | ||
529 | static ssize_t format_track_path(char *dest, char *src, int buf_length, | ||
530 | const char *dir) | ||
531 | { | ||
532 | size_t len = 0; | ||
533 | |||
534 | /* Look for the end of the string */ | ||
535 | while (1) | ||
536 | { | ||
537 | int c = src[len]; | ||
538 | if (c == '\n' || c == '\r' || c == '\0') | ||
539 | break; | ||
540 | len++; | ||
541 | } | ||
542 | |||
543 | /* Now work back killing white space */ | ||
544 | while (len > 0) | ||
545 | { | ||
546 | int c = src[len - 1]; | ||
547 | if (c != '\t' && c != ' ') | ||
548 | break; | ||
549 | len--; | ||
550 | } | ||
551 | |||
552 | src[len] = '\0'; | ||
553 | |||
554 | /* Replace backslashes with forward slashes */ | ||
555 | path_correct_separators(src, src); | ||
556 | |||
557 | /* Handle Windows-style absolute paths */ | ||
558 | if (path_strip_drive(src, (const char **)&src, true) >= 0 && | ||
559 | src[-1] == PATH_SEPCH) | ||
560 | { | ||
561 | #ifdef HAVE_MULTIVOLUME | ||
562 | const char *p; | ||
563 | path_strip_last_volume(dir, &p, false); | ||
564 | dir = strmemdupa(dir, p - dir); /* empty if no volspec on dir */ | ||
565 | #else | ||
566 | dir = ""; /* only volume is root */ | ||
567 | #endif | ||
568 | } | ||
569 | |||
570 | len = path_append(dest, *dir ? dir : PATH_ROOTSTR, src, buf_length); | ||
571 | if (len >= (size_t)buf_length) | ||
572 | return -1; /* buffer too small */ | ||
573 | |||
574 | path_remove_dot_segments (dest, dest); | ||
575 | |||
576 | return strlen (dest); | ||
577 | } | ||
578 | |||
579 | /* | ||
347 | * Initialize a new playlist for viewing/editing/playing. dir is the | 580 | * Initialize a new playlist for viewing/editing/playing. dir is the |
348 | * directory where the playlist is located and file is the filename. | 581 | * directory where the playlist is located and file is the filename. |
349 | */ | 582 | */ |
@@ -363,7 +596,7 @@ static void new_playlist(struct playlist_info* playlist, const char *dir, | |||
363 | else | 596 | else |
364 | dirused = ""; /* empty playlist */ | 597 | dirused = ""; /* empty playlist */ |
365 | } | 598 | } |
366 | 599 | ||
367 | update_playlist_filename(playlist, dirused, fileused); | 600 | update_playlist_filename(playlist, dirused, fileused); |
368 | 601 | ||
369 | if (playlist->control_fd >= 0) | 602 | if (playlist->control_fd >= 0) |
@@ -375,30 +608,6 @@ static void new_playlist(struct playlist_info* playlist, const char *dir, | |||
375 | } | 608 | } |
376 | 609 | ||
377 | /* | 610 | /* |
378 | * create control file for playlist | ||
379 | */ | ||
380 | static void create_control(struct playlist_info* playlist) | ||
381 | { | ||
382 | playlist->control_fd = open(playlist->control_filename, | ||
383 | O_CREAT|O_RDWR|O_TRUNC, 0666); | ||
384 | if (playlist->control_fd < 0) | ||
385 | { | ||
386 | if (check_rockboxdir()) | ||
387 | { | ||
388 | cond_talk_ids_fq(LANG_PLAYLIST_CONTROL_ACCESS_ERROR); | ||
389 | splashf(HZ*2, (unsigned char *)"%s (%d)", | ||
390 | str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR), | ||
391 | playlist->control_fd); | ||
392 | } | ||
393 | playlist->control_created = false; | ||
394 | } | ||
395 | else | ||
396 | { | ||
397 | playlist->control_created = true; | ||
398 | } | ||
399 | } | ||
400 | |||
401 | /* | ||
402 | * validate the control file. This may include creating/initializing it if | 611 | * validate the control file. This may include creating/initializing it if |
403 | * necessary; | 612 | * necessary; |
404 | */ | 613 | */ |
@@ -429,6 +638,40 @@ static int check_control(struct playlist_info* playlist) | |||
429 | return 0; | 638 | return 0; |
430 | } | 639 | } |
431 | 640 | ||
641 | |||
642 | /* | ||
643 | * Display buffer full message | ||
644 | */ | ||
645 | static void display_buffer_full(void) | ||
646 | { | ||
647 | splash(HZ*2, ID2P(LANG_PLAYLIST_BUFFER_FULL)); | ||
648 | } | ||
649 | |||
650 | /* | ||
651 | * Display splash message showing progress of playlist/directory insertion or | ||
652 | * save. | ||
653 | */ | ||
654 | static void display_playlist_count(int count, const unsigned char *fmt, | ||
655 | bool final) | ||
656 | { | ||
657 | static long talked_tick = 0; | ||
658 | long id = P2ID(fmt); | ||
659 | if(global_settings.talk_menu && id>=0) | ||
660 | { | ||
661 | if(final || (count && (talked_tick == 0 | ||
662 | || TIME_AFTER(current_tick, talked_tick+5*HZ)))) | ||
663 | { | ||
664 | talked_tick = current_tick; | ||
665 | talk_number(count, false); | ||
666 | talk_id(id, true); | ||
667 | } | ||
668 | } | ||
669 | fmt = P2STR(fmt); | ||
670 | |||
671 | splashf(0, fmt, count, str(LANG_OFF_ABORT)); | ||
672 | } | ||
673 | |||
674 | |||
432 | /* | 675 | /* |
433 | * recreate the control file based on current playlist entries | 676 | * recreate the control file based on current playlist entries |
434 | */ | 677 | */ |
@@ -532,31 +775,6 @@ static int recreate_control(struct playlist_info* playlist) | |||
532 | } | 775 | } |
533 | 776 | ||
534 | /* | 777 | /* |
535 | * store directory and name of playlist file | ||
536 | */ | ||
537 | static void update_playlist_filename(struct playlist_info* playlist, | ||
538 | const char *dir, const char *file) | ||
539 | { | ||
540 | char *sep=""; | ||
541 | int dirlen = strlen(dir); | ||
542 | |||
543 | playlist->utf8 = is_m3u8(file); | ||
544 | |||
545 | /* If the dir does not end in trailing slash, we use a separator. | ||
546 | Otherwise we don't. */ | ||
547 | if(!dirlen || '/' != dir[dirlen-1]) | ||
548 | { | ||
549 | sep="/"; | ||
550 | dirlen++; | ||
551 | } | ||
552 | |||
553 | playlist->dirlen = dirlen; | ||
554 | |||
555 | snprintf(playlist->filename, sizeof(playlist->filename), | ||
556 | "%s%s%s", dir, sep, file); | ||
557 | } | ||
558 | |||
559 | /* | ||
560 | * calculate track offsets within a playlist file | 778 | * calculate track offsets within a playlist file |
561 | */ | 779 | */ |
562 | static int add_indices_to_playlist(struct playlist_info* playlist, | 780 | static int add_indices_to_playlist(struct playlist_info* playlist, |
@@ -588,7 +806,7 @@ static int add_indices_to_playlist(struct playlist_info* playlist, | |||
588 | i = lseek(playlist->fd, playlist->utf8 ? BOM_UTF_8_SIZE : 0, SEEK_SET); | 806 | i = lseek(playlist->fd, playlist->utf8 ? BOM_UTF_8_SIZE : 0, SEEK_SET); |
589 | 807 | ||
590 | playlist_fd = playlist->fd; | 808 | playlist_fd = playlist->fd; |
591 | playlist->fd = -2; /* DEBUGGING Remove me! */ | 809 | playlist->fd = -2; /* DEBUGGING Remove me! */ |
592 | 810 | ||
593 | splash(0, ID2P(LANG_WAIT)); | 811 | splash(0, ID2P(LANG_WAIT)); |
594 | 812 | ||
@@ -642,11 +860,11 @@ static int add_indices_to_playlist(struct playlist_info* playlist, | |||
642 | } | 860 | } |
643 | 861 | ||
644 | exit: | 862 | exit: |
645 | /* v DEBUGGING Remove me! */ | 863 | /* v DEBUGGING Remove me! */ |
646 | if (playlist->fd != -2) | 864 | if (playlist->fd != -2) |
647 | splashf(HZ, "Error playlist fd"); | 865 | splashf(HZ, "Error playlist fd"); |
648 | playlist->fd = playlist_fd; | 866 | playlist->fd = playlist_fd; |
649 | /* ^ DEBUGGING Remove me! */ | 867 | /* ^ DEBUGGING Remove me! */ |
650 | playlist->amount = amount; | 868 | playlist->amount = amount; |
651 | playlist->max_playlist_size = max_playlist_size; | 869 | playlist->max_playlist_size = max_playlist_size; |
652 | mutex_unlock(playlist->control_mutex); | 870 | mutex_unlock(playlist->control_mutex); |
@@ -657,6 +875,372 @@ exit: | |||
657 | return result; | 875 | return result; |
658 | } | 876 | } |
659 | 877 | ||
878 | |||
879 | /* | ||
880 | * Checks if there are any music files in the dir or any of its | ||
881 | * subdirectories. May be called recursively. | ||
882 | */ | ||
883 | static int check_subdir_for_music(char *dir, const char *subdir, bool recurse) | ||
884 | { | ||
885 | int result = -1; | ||
886 | size_t dirlen = strlen(dir); | ||
887 | int num_files = 0; | ||
888 | int i; | ||
889 | struct entry *files; | ||
890 | bool has_music = false; | ||
891 | bool has_subdir = false; | ||
892 | struct tree_context* tc = tree_get_context(); | ||
893 | |||
894 | if (path_append(dir + dirlen, PA_SEP_HARD, subdir, MAX_PATH - dirlen) >= | ||
895 | MAX_PATH - dirlen) | ||
896 | { | ||
897 | return 0; | ||
898 | } | ||
899 | |||
900 | if (ft_load(tc, dir) < 0) | ||
901 | { | ||
902 | return -2; | ||
903 | } | ||
904 | |||
905 | tree_lock_cache(tc); | ||
906 | files = tree_get_entries(tc); | ||
907 | num_files = tc->filesindir; | ||
908 | |||
909 | for (i=0; i<num_files; i++) | ||
910 | { | ||
911 | if (files[i].attr & ATTR_DIRECTORY) | ||
912 | has_subdir = true; | ||
913 | else if ((files[i].attr & FILE_ATTR_MASK) == FILE_ATTR_AUDIO) | ||
914 | { | ||
915 | has_music = true; | ||
916 | break; | ||
917 | } | ||
918 | } | ||
919 | |||
920 | if (has_music) | ||
921 | { | ||
922 | tree_unlock_cache(tc); | ||
923 | return 0; | ||
924 | } | ||
925 | |||
926 | if (has_subdir && recurse) | ||
927 | { | ||
928 | for (i=0; i<num_files; i++) | ||
929 | { | ||
930 | if (action_userabort(TIMEOUT_NOBLOCK)) | ||
931 | { | ||
932 | result = -2; | ||
933 | break; | ||
934 | } | ||
935 | |||
936 | if (files[i].attr & ATTR_DIRECTORY) | ||
937 | { | ||
938 | result = check_subdir_for_music(dir, files[i].name, true); | ||
939 | if (!result) | ||
940 | break; | ||
941 | } | ||
942 | } | ||
943 | } | ||
944 | tree_unlock_cache(tc); | ||
945 | |||
946 | if (result < 0) | ||
947 | { | ||
948 | if (dirlen) | ||
949 | { | ||
950 | dir[dirlen] = '\0'; | ||
951 | } | ||
952 | else | ||
953 | { | ||
954 | strcpy(dir, PATH_ROOTSTR); | ||
955 | } | ||
956 | |||
957 | /* we now need to reload our current directory */ | ||
958 | if(ft_load(tc, dir) < 0) | ||
959 | splash(HZ*2, ID2P(LANG_PLAYLIST_DIRECTORY_ACCESS_ERROR)); | ||
960 | } | ||
961 | return result; | ||
962 | } | ||
963 | |||
964 | /* | ||
965 | * search through all the directories (starting with the current) to find | ||
966 | * one that has tracks to play | ||
967 | */ | ||
968 | static int get_next_dir(char *dir, bool is_forward) | ||
969 | { | ||
970 | struct playlist_info* playlist = ¤t_playlist; | ||
971 | int result = -1; | ||
972 | char *start_dir = NULL; | ||
973 | bool exit = false; | ||
974 | struct tree_context* tc = tree_get_context(); | ||
975 | int saved_dirfilter = *(tc->dirfilter); | ||
976 | unsigned int base_len; | ||
977 | |||
978 | if (global_settings.constrain_next_folder) | ||
979 | { | ||
980 | /* constrain results to directories below user's start directory */ | ||
981 | strcpy(dir, global_settings.start_directory); | ||
982 | base_len = strlen(dir); | ||
983 | |||
984 | /* strip any trailing slash from base directory */ | ||
985 | if (base_len > 0 && dir[base_len - 1] == '/') | ||
986 | { | ||
987 | base_len--; | ||
988 | dir[base_len] = '\0'; | ||
989 | } | ||
990 | } | ||
991 | else | ||
992 | { | ||
993 | /* start from root directory */ | ||
994 | dir[0] = '\0'; | ||
995 | base_len = 0; | ||
996 | } | ||
997 | |||
998 | /* process random folder advance */ | ||
999 | if (global_settings.next_folder == FOLDER_ADVANCE_RANDOM) | ||
1000 | { | ||
1001 | int fd = open(ROCKBOX_DIR "/folder_advance_list.dat", O_RDONLY); | ||
1002 | if (fd >= 0) | ||
1003 | { | ||
1004 | int folder_count = 0; | ||
1005 | ssize_t nread = read(fd,&folder_count,sizeof(int)); | ||
1006 | if ((nread == sizeof(int)) && folder_count) | ||
1007 | { | ||
1008 | char buffer[MAX_PATH]; | ||
1009 | /* give up looking for a directory after we've had four | ||
1010 | times as many tries as there are directories. */ | ||
1011 | unsigned long allowed_tries = folder_count * 4; | ||
1012 | int i; | ||
1013 | srand(current_tick); | ||
1014 | *(tc->dirfilter) = SHOW_MUSIC; | ||
1015 | tc->sort_dir = global_settings.sort_dir; | ||
1016 | while (!exit && allowed_tries--) | ||
1017 | { | ||
1018 | i = rand() % folder_count; | ||
1019 | lseek(fd, sizeof(int) + (MAX_PATH * i), SEEK_SET); | ||
1020 | read(fd, buffer, MAX_PATH); | ||
1021 | /* is the current dir within our base dir and has music? */ | ||
1022 | if ((base_len == 0 || !strncmp(buffer, dir, base_len)) | ||
1023 | && check_subdir_for_music(buffer, "", false) == 0) | ||
1024 | exit = true; | ||
1025 | } | ||
1026 | close(fd); | ||
1027 | *(tc->dirfilter) = saved_dirfilter; | ||
1028 | tc->sort_dir = global_settings.sort_dir; | ||
1029 | reload_directory(); | ||
1030 | if (exit) | ||
1031 | { | ||
1032 | strcpy(dir,buffer); | ||
1033 | return 0; | ||
1034 | } | ||
1035 | } | ||
1036 | else | ||
1037 | close(fd); | ||
1038 | } | ||
1039 | } | ||
1040 | |||
1041 | /* if the current file is within our base dir, use its dir instead */ | ||
1042 | if (base_len == 0 || !strncmp(playlist->filename, dir, base_len)) | ||
1043 | strmemccpy(dir, playlist->filename, playlist->dirlen); | ||
1044 | |||
1045 | /* use the tree browser dircache to load files */ | ||
1046 | *(tc->dirfilter) = SHOW_ALL; | ||
1047 | |||
1048 | /* set up sorting/direction */ | ||
1049 | tc->sort_dir = global_settings.sort_dir; | ||
1050 | if (!is_forward) | ||
1051 | { | ||
1052 | static const char sortpairs[] = | ||
1053 | { | ||
1054 | [SORT_ALPHA] = SORT_ALPHA_REVERSED, | ||
1055 | [SORT_DATE] = SORT_DATE_REVERSED, | ||
1056 | [SORT_TYPE] = SORT_TYPE_REVERSED, | ||
1057 | [SORT_ALPHA_REVERSED] = SORT_ALPHA, | ||
1058 | [SORT_DATE_REVERSED] = SORT_DATE, | ||
1059 | [SORT_TYPE_REVERSED] = SORT_TYPE, | ||
1060 | }; | ||
1061 | |||
1062 | if ((unsigned)tc->sort_dir < sizeof(sortpairs)) | ||
1063 | tc->sort_dir = sortpairs[tc->sort_dir]; | ||
1064 | } | ||
1065 | |||
1066 | while (!exit) | ||
1067 | { | ||
1068 | struct entry *files; | ||
1069 | int num_files = 0; | ||
1070 | int i; | ||
1071 | |||
1072 | if (ft_load(tc, (dir[0]=='\0')?"/":dir) < 0) | ||
1073 | { | ||
1074 | exit = true; | ||
1075 | result = -1; | ||
1076 | break; | ||
1077 | } | ||
1078 | |||
1079 | tree_lock_cache(tc); | ||
1080 | files = tree_get_entries(tc); | ||
1081 | num_files = tc->filesindir; | ||
1082 | |||
1083 | for (i=0; i<num_files; i++) | ||
1084 | { | ||
1085 | /* user abort */ | ||
1086 | if (action_userabort(TIMEOUT_NOBLOCK)) | ||
1087 | { | ||
1088 | result = -1; | ||
1089 | exit = true; | ||
1090 | break; | ||
1091 | } | ||
1092 | |||
1093 | if (files[i].attr & ATTR_DIRECTORY) | ||
1094 | { | ||
1095 | if (!start_dir) | ||
1096 | { | ||
1097 | result = check_subdir_for_music(dir, files[i].name, true); | ||
1098 | if (result != -1) | ||
1099 | { | ||
1100 | exit = true; | ||
1101 | break; | ||
1102 | } | ||
1103 | } | ||
1104 | else if (!strcmp(start_dir, files[i].name)) | ||
1105 | start_dir = NULL; | ||
1106 | } | ||
1107 | } | ||
1108 | tree_unlock_cache(tc); | ||
1109 | |||
1110 | if (!exit) | ||
1111 | { | ||
1112 | /* we've already descended to the base dir with nothing found, | ||
1113 | check whether that contains music */ | ||
1114 | if (strlen(dir) <= base_len) | ||
1115 | { | ||
1116 | result = check_subdir_for_music(dir, "", true); | ||
1117 | if (result == -1) | ||
1118 | /* there's no music files in the base directory, | ||
1119 | treat as a fatal error */ | ||
1120 | result = -2; | ||
1121 | break; | ||
1122 | } | ||
1123 | else | ||
1124 | { | ||
1125 | /* move down to parent directory. current directory name is | ||
1126 | stored as the starting point for the search in parent */ | ||
1127 | start_dir = strrchr(dir, '/'); | ||
1128 | if (start_dir) | ||
1129 | { | ||
1130 | *start_dir = '\0'; | ||
1131 | start_dir++; | ||
1132 | } | ||
1133 | else | ||
1134 | break; | ||
1135 | } | ||
1136 | } | ||
1137 | } | ||
1138 | |||
1139 | /* restore dirfilter */ | ||
1140 | *(tc->dirfilter) = saved_dirfilter; | ||
1141 | tc->sort_dir = global_settings.sort_dir; | ||
1142 | |||
1143 | return result; | ||
1144 | } | ||
1145 | |||
1146 | static int get_next_directory(char *dir){ | ||
1147 | return get_next_dir(dir, true); | ||
1148 | } | ||
1149 | |||
1150 | static int get_previous_directory(char *dir){ | ||
1151 | return get_next_dir(dir, false); | ||
1152 | } | ||
1153 | |||
1154 | |||
1155 | /* | ||
1156 | * gets pathname for track at seek index | ||
1157 | */ | ||
1158 | static int get_filename(struct playlist_info* playlist, int index, int seek, | ||
1159 | bool control_file, char *buf, int buf_length) | ||
1160 | { | ||
1161 | int fd; | ||
1162 | int max = -1; | ||
1163 | char tmp_buf[MAX_PATH+1]; | ||
1164 | char dir_buf[MAX_PATH+1]; | ||
1165 | bool utf8 = playlist->utf8; | ||
1166 | |||
1167 | if (buf_length > MAX_PATH+1) | ||
1168 | buf_length = MAX_PATH+1; | ||
1169 | |||
1170 | #ifdef HAVE_DIRCACHE | ||
1171 | if (playlist->dcfrefs) | ||
1172 | { | ||
1173 | max = dircache_get_fileref_path(&playlist->dcfrefs[index], | ||
1174 | tmp_buf, sizeof(tmp_buf)); | ||
1175 | } | ||
1176 | #endif /* HAVE_DIRCACHE */ | ||
1177 | |||
1178 | if (playlist->in_ram && !control_file && max < 0) | ||
1179 | { | ||
1180 | strmemccpy(tmp_buf, (char*)&playlist->buffer[seek], sizeof(tmp_buf)); | ||
1181 | } | ||
1182 | else if (max < 0) | ||
1183 | { | ||
1184 | mutex_lock(playlist->control_mutex); | ||
1185 | |||
1186 | if (control_file) | ||
1187 | { | ||
1188 | fd = playlist->control_fd; | ||
1189 | utf8 = true; | ||
1190 | } | ||
1191 | else | ||
1192 | { | ||
1193 | if(-1 == playlist->fd) | ||
1194 | playlist->fd = open(playlist->filename, O_RDONLY); | ||
1195 | |||
1196 | fd = playlist->fd; | ||
1197 | } | ||
1198 | |||
1199 | if(-1 != fd) | ||
1200 | { | ||
1201 | |||
1202 | if (lseek(fd, seek, SEEK_SET) != seek) | ||
1203 | max = -1; | ||
1204 | else | ||
1205 | { | ||
1206 | max = read(fd, tmp_buf, MIN((size_t) buf_length, sizeof(tmp_buf))); | ||
1207 | |||
1208 | if (max > 0) | ||
1209 | { | ||
1210 | /* playlist file may end without a new line - terminate buffer */ | ||
1211 | tmp_buf[MIN(max, (int)sizeof(tmp_buf) - 1)] = '\0'; | ||
1212 | |||
1213 | /* Use dir_buf as a temporary buffer. Note that dir_buf must | ||
1214 | * be as large as tmp_buf. | ||
1215 | */ | ||
1216 | if (!utf8) | ||
1217 | max = convert_m3u(tmp_buf, max, sizeof(tmp_buf), dir_buf); | ||
1218 | } | ||
1219 | } | ||
1220 | } | ||
1221 | |||
1222 | mutex_unlock(playlist->control_mutex); | ||
1223 | |||
1224 | if (max < 0) | ||
1225 | { | ||
1226 | if (usb_detect() == USB_INSERTED) | ||
1227 | ; /* ignore error on usb plug */ | ||
1228 | else if (control_file) | ||
1229 | splash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_ACCESS_ERROR)); | ||
1230 | else | ||
1231 | splash(HZ*2, ID2P(LANG_PLAYLIST_ACCESS_ERROR)); | ||
1232 | |||
1233 | return max; | ||
1234 | } | ||
1235 | } | ||
1236 | |||
1237 | strmemccpy(dir_buf, playlist->filename, playlist->dirlen); | ||
1238 | |||
1239 | return format_track_path(buf, tmp_buf, buf_length, dir_buf); | ||
1240 | |||
1241 | (void)index; | ||
1242 | } | ||
1243 | |||
660 | /* | 1244 | /* |
661 | * Utility function to create a new playlist, fill it with the next or | 1245 | * Utility function to create a new playlist, fill it with the next or |
662 | * previous directory, shuffle it if needed, and start playback. | 1246 | * previous directory, shuffle it if needed, and start playback. |
@@ -701,33 +1285,6 @@ static int create_and_play_dir(int direction, bool play_last) | |||
701 | } | 1285 | } |
702 | 1286 | ||
703 | /* | 1287 | /* |
704 | * Removes all tracks, from the playlist, leaving the presently playing | ||
705 | * track queued. | ||
706 | */ | ||
707 | int playlist_remove_all_tracks(struct playlist_info *playlist) | ||
708 | { | ||
709 | int result; | ||
710 | |||
711 | if (playlist == NULL) | ||
712 | playlist = ¤t_playlist; | ||
713 | |||
714 | while (playlist->index > 0) | ||
715 | if ((result = remove_track_from_playlist(playlist, 0, true)) < 0) | ||
716 | return result; | ||
717 | |||
718 | while (playlist->amount > 1) | ||
719 | if ((result = remove_track_from_playlist(playlist, 1, true)) < 0) | ||
720 | return result; | ||
721 | |||
722 | if (playlist->amount == 1) { | ||
723 | playlist->indices[0] |= PLAYLIST_QUEUED; | ||
724 | } | ||
725 | |||
726 | return 0; | ||
727 | } | ||
728 | |||
729 | |||
730 | /* | ||
731 | * Add track to playlist at specified position. There are seven special | 1288 | * Add track to playlist at specified position. There are seven special |
732 | * positions that can be specified: | 1289 | * positions that can be specified: |
733 | * PLAYLIST_PREPEND - Add track at beginning of playlist | 1290 | * PLAYLIST_PREPEND - Add track at beginning of playlist |
@@ -803,16 +1360,16 @@ static int add_track_to_playlist(struct playlist_info* playlist, | |||
803 | int offset; | 1360 | int offset; |
804 | int n = playlist->amount - | 1361 | int n = playlist->amount - |
805 | rotate_index(playlist, playlist->index); | 1362 | rotate_index(playlist, playlist->index); |
806 | 1363 | ||
807 | if (n > 0) | 1364 | if (n > 0) |
808 | offset = rand() % n; | 1365 | offset = rand() % n; |
809 | else | 1366 | else |
810 | offset = 0; | 1367 | offset = 0; |
811 | 1368 | ||
812 | position = playlist->index + offset + 1; | 1369 | position = playlist->index + offset + 1; |
813 | if (position >= playlist->amount) | 1370 | if (position >= playlist->amount) |
814 | position -= playlist->amount; | 1371 | position -= playlist->amount; |
815 | 1372 | ||
816 | insert_position = position; | 1373 | insert_position = position; |
817 | } | 1374 | } |
818 | else | 1375 | else |
@@ -828,11 +1385,11 @@ static int add_track_to_playlist(struct playlist_info* playlist, | |||
828 | case PLAYLIST_REPLACE: | 1385 | case PLAYLIST_REPLACE: |
829 | if (playlist_remove_all_tracks(playlist) < 0) | 1386 | if (playlist_remove_all_tracks(playlist) < 0) |
830 | return -1; | 1387 | return -1; |
831 | 1388 | ||
832 | playlist->last_insert_pos = position = insert_position = playlist->index + 1; | 1389 | playlist->last_insert_pos = position = insert_position = playlist->index + 1; |
833 | break; | 1390 | break; |
834 | } | 1391 | } |
835 | 1392 | ||
836 | if (queue) | 1393 | if (queue) |
837 | flags |= PLAYLIST_QUEUED; | 1394 | flags |= PLAYLIST_QUEUED; |
838 | 1395 | ||
@@ -845,7 +1402,7 @@ static int add_track_to_playlist(struct playlist_info* playlist, | |||
845 | playlist->dcfrefs[i] = playlist->dcfrefs[i-1]; | 1402 | playlist->dcfrefs[i] = playlist->dcfrefs[i-1]; |
846 | #endif | 1403 | #endif |
847 | } | 1404 | } |
848 | 1405 | ||
849 | /* update stored indices if needed */ | 1406 | /* update stored indices if needed */ |
850 | 1407 | ||
851 | if (orig_position < 0) | 1408 | if (orig_position < 0) |
@@ -881,7 +1438,7 @@ static int add_track_to_playlist(struct playlist_info* playlist, | |||
881 | 1438 | ||
882 | playlist->amount++; | 1439 | playlist->amount++; |
883 | playlist->num_inserted_tracks++; | 1440 | playlist->num_inserted_tracks++; |
884 | 1441 | ||
885 | return insert_position; | 1442 | return insert_position; |
886 | } | 1443 | } |
887 | 1444 | ||
@@ -918,7 +1475,7 @@ static int directory_search_callback(char* filename, void* context) | |||
918 | count_str = ID2P(LANG_PLAYLIST_INSERT_COUNT); | 1475 | count_str = ID2P(LANG_PLAYLIST_INSERT_COUNT); |
919 | 1476 | ||
920 | display_playlist_count(c->count, count_str, false); | 1477 | display_playlist_count(c->count, count_str, false); |
921 | 1478 | ||
922 | if ((c->count) == PLAYLIST_DISPLAY_COUNT && | 1479 | if ((c->count) == PLAYLIST_DISPLAY_COUNT && |
923 | (audio_status() & AUDIO_STATUS_PLAY) && | 1480 | (audio_status() & AUDIO_STATUS_PLAY) && |
924 | c->playlist->started) | 1481 | c->playlist->started) |
@@ -981,11 +1538,32 @@ static int remove_track_from_playlist(struct playlist_info* playlist, | |||
981 | 1538 | ||
982 | sync_control(playlist, false); | 1539 | sync_control(playlist, false); |
983 | } | 1540 | } |
984 | 1541 | ||
985 | return 0; | 1542 | return 0; |
986 | } | 1543 | } |
987 | 1544 | ||
988 | /* | 1545 | /* |
1546 | * Search for the seek track and set appropriate indices. Used after shuffle | ||
1547 | * to make sure the current index is still pointing to correct track. | ||
1548 | */ | ||
1549 | static void find_and_set_playlist_index(struct playlist_info* playlist, | ||
1550 | unsigned int seek) | ||
1551 | { | ||
1552 | int i; | ||
1553 | |||
1554 | /* Set the index to the current song */ | ||
1555 | for (i=0; i<playlist->amount; i++) | ||
1556 | { | ||
1557 | if (playlist->indices[i] == seek) | ||
1558 | { | ||
1559 | playlist->index = playlist->first_index = i; | ||
1560 | |||
1561 | break; | ||
1562 | } | ||
1563 | } | ||
1564 | } | ||
1565 | |||
1566 | /* | ||
989 | * randomly rearrange the array of indices for the playlist. If start_current | 1567 | * randomly rearrange the array of indices for the playlist. If start_current |
990 | * is true then update the index to the new index of the current playing track | 1568 | * is true then update the index to the new index of the current playing track |
991 | */ | 1569 | */ |
@@ -996,7 +1574,7 @@ static int randomise_playlist(struct playlist_info* playlist, | |||
996 | int count; | 1574 | int count; |
997 | int candidate; | 1575 | int candidate; |
998 | unsigned int current = playlist->indices[playlist->index]; | 1576 | unsigned int current = playlist->indices[playlist->index]; |
999 | 1577 | ||
1000 | /* seed 0 is used to identify sorted playlist for resume purposes */ | 1578 | /* seed 0 is used to identify sorted playlist for resume purposes */ |
1001 | if (seed == 0) | 1579 | if (seed == 0) |
1002 | seed = 1; | 1580 | seed = 1; |
@@ -1039,11 +1617,38 @@ static int randomise_playlist(struct playlist_info* playlist, | |||
1039 | update_control(playlist, PLAYLIST_COMMAND_SHUFFLE, seed, | 1617 | update_control(playlist, PLAYLIST_COMMAND_SHUFFLE, seed, |
1040 | playlist->first_index, NULL, NULL, NULL); | 1618 | playlist->first_index, NULL, NULL, NULL); |
1041 | } | 1619 | } |
1042 | 1620 | ||
1043 | return 0; | 1621 | return 0; |
1044 | } | 1622 | } |
1045 | 1623 | ||
1046 | /* | 1624 | /* |
1625 | * used to sort track indices. Sort order is as follows: | ||
1626 | * 1. Prepended tracks (in prepend order) | ||
1627 | * 2. Playlist/directory tracks (in playlist order) | ||
1628 | * 3. Inserted/Appended tracks (in insert order) | ||
1629 | */ | ||
1630 | static int compare(const void* p1, const void* p2) | ||
1631 | { | ||
1632 | unsigned long* e1 = (unsigned long*) p1; | ||
1633 | unsigned long* e2 = (unsigned long*) p2; | ||
1634 | unsigned long flags1 = *e1 & PLAYLIST_INSERT_TYPE_MASK; | ||
1635 | unsigned long flags2 = *e2 & PLAYLIST_INSERT_TYPE_MASK; | ||
1636 | |||
1637 | if (flags1 == flags2) | ||
1638 | return (*e1 & PLAYLIST_SEEK_MASK) - (*e2 & PLAYLIST_SEEK_MASK); | ||
1639 | else if (flags1 == PLAYLIST_INSERT_TYPE_PREPEND || | ||
1640 | flags2 == PLAYLIST_INSERT_TYPE_APPEND) | ||
1641 | return -1; | ||
1642 | else if (flags1 == PLAYLIST_INSERT_TYPE_APPEND || | ||
1643 | flags2 == PLAYLIST_INSERT_TYPE_PREPEND) | ||
1644 | return 1; | ||
1645 | else if (flags1 && flags2) | ||
1646 | return (*e1 & PLAYLIST_SEEK_MASK) - (*e2 & PLAYLIST_SEEK_MASK); | ||
1647 | else | ||
1648 | return *e1 - *e2; | ||
1649 | } | ||
1650 | |||
1651 | /* | ||
1047 | * Sort the array of indices for the playlist. If start_current is true then | 1652 | * Sort the array of indices for the playlist. If start_current is true then |
1048 | * set the index to the new index of the current song. | 1653 | * set the index to the new index of the current song. |
1049 | * Also while going to unshuffled mode set the first_index to 0. | 1654 | * Also while going to unshuffled mode set the first_index to 0. |
@@ -1079,7 +1684,7 @@ static int sort_playlist(struct playlist_info* playlist, bool start_current, | |||
1079 | update_control(playlist, PLAYLIST_COMMAND_UNSHUFFLE, | 1684 | update_control(playlist, PLAYLIST_COMMAND_UNSHUFFLE, |
1080 | playlist->first_index, -1, NULL, NULL, NULL); | 1685 | playlist->first_index, -1, NULL, NULL, NULL); |
1081 | } | 1686 | } |
1082 | 1687 | ||
1083 | return 0; | 1688 | return 0; |
1084 | } | 1689 | } |
1085 | 1690 | ||
@@ -1091,7 +1696,7 @@ static int calculate_step_count(const struct playlist_info *playlist, int steps) | |||
1091 | int i, count, direction; | 1696 | int i, count, direction; |
1092 | int index; | 1697 | int index; |
1093 | int stepped_count = 0; | 1698 | int stepped_count = 0; |
1094 | 1699 | ||
1095 | if (steps < 0) | 1700 | if (steps < 0) |
1096 | { | 1701 | { |
1097 | direction = -1; | 1702 | direction = -1; |
@@ -1129,28 +1734,6 @@ static int calculate_step_count(const struct playlist_info *playlist, int steps) | |||
1129 | return steps; | 1734 | return steps; |
1130 | } | 1735 | } |
1131 | 1736 | ||
1132 | /* Marks the index of the track to be skipped that is "steps" away from | ||
1133 | * current playing track. | ||
1134 | */ | ||
1135 | void playlist_skip_entry(struct playlist_info *playlist, int steps) | ||
1136 | { | ||
1137 | int index; | ||
1138 | |||
1139 | if (playlist == NULL) | ||
1140 | playlist = ¤t_playlist; | ||
1141 | |||
1142 | /* need to account for already skipped tracks */ | ||
1143 | steps = calculate_step_count(playlist, steps); | ||
1144 | |||
1145 | index = playlist->index + steps; | ||
1146 | if (index < 0) | ||
1147 | index += playlist->amount; | ||
1148 | else if (index >= playlist->amount) | ||
1149 | index -= playlist->amount; | ||
1150 | |||
1151 | playlist->indices[index] |= PLAYLIST_SKIPPED; | ||
1152 | } | ||
1153 | |||
1154 | /* | 1737 | /* |
1155 | * returns the index of the track that is "steps" away from current playing | 1738 | * returns the index of the track that is "steps" away from current playing |
1156 | * track. | 1739 | * track. |
@@ -1229,56 +1812,8 @@ static int get_next_index(const struct playlist_info* playlist, int steps, | |||
1229 | /* No luck if the whole playlist was bad. */ | 1812 | /* No luck if the whole playlist was bad. */ |
1230 | if (playlist->indices[next_index] & PLAYLIST_SKIPPED) | 1813 | if (playlist->indices[next_index] & PLAYLIST_SKIPPED) |
1231 | return -1; | 1814 | return -1; |
1232 | |||
1233 | return next_index; | ||
1234 | } | ||
1235 | |||
1236 | /* | ||
1237 | * Search for the seek track and set appropriate indices. Used after shuffle | ||
1238 | * to make sure the current index is still pointing to correct track. | ||
1239 | */ | ||
1240 | static void find_and_set_playlist_index(struct playlist_info* playlist, | ||
1241 | unsigned int seek) | ||
1242 | { | ||
1243 | int i; | ||
1244 | |||
1245 | /* Set the index to the current song */ | ||
1246 | for (i=0; i<playlist->amount; i++) | ||
1247 | { | ||
1248 | if (playlist->indices[i] == seek) | ||
1249 | { | ||
1250 | playlist->index = playlist->first_index = i; | ||
1251 | |||
1252 | break; | ||
1253 | } | ||
1254 | } | ||
1255 | } | ||
1256 | |||
1257 | /* | ||
1258 | * used to sort track indices. Sort order is as follows: | ||
1259 | * 1. Prepended tracks (in prepend order) | ||
1260 | * 2. Playlist/directory tracks (in playlist order) | ||
1261 | * 3. Inserted/Appended tracks (in insert order) | ||
1262 | */ | ||
1263 | static int compare(const void* p1, const void* p2) | ||
1264 | { | ||
1265 | unsigned long* e1 = (unsigned long*) p1; | ||
1266 | unsigned long* e2 = (unsigned long*) p2; | ||
1267 | unsigned long flags1 = *e1 & PLAYLIST_INSERT_TYPE_MASK; | ||
1268 | unsigned long flags2 = *e2 & PLAYLIST_INSERT_TYPE_MASK; | ||
1269 | 1815 | ||
1270 | if (flags1 == flags2) | 1816 | return next_index; |
1271 | return (*e1 & PLAYLIST_SEEK_MASK) - (*e2 & PLAYLIST_SEEK_MASK); | ||
1272 | else if (flags1 == PLAYLIST_INSERT_TYPE_PREPEND || | ||
1273 | flags2 == PLAYLIST_INSERT_TYPE_APPEND) | ||
1274 | return -1; | ||
1275 | else if (flags1 == PLAYLIST_INSERT_TYPE_APPEND || | ||
1276 | flags2 == PLAYLIST_INSERT_TYPE_PREPEND) | ||
1277 | return 1; | ||
1278 | else if (flags1 && flags2) | ||
1279 | return (*e1 & PLAYLIST_SEEK_MASK) - (*e2 & PLAYLIST_SEEK_MASK); | ||
1280 | else | ||
1281 | return *e1 - *e2; | ||
1282 | } | 1817 | } |
1283 | 1818 | ||
1284 | #ifdef HAVE_DIRCACHE | 1819 | #ifdef HAVE_DIRCACHE |
@@ -1287,7 +1822,7 @@ static int compare(const void* p1, const void* p2) | |||
1287 | * without affecting playlist load up performance. This thread also flushes | 1822 | * without affecting playlist load up performance. This thread also flushes |
1288 | * any pending control commands when the disk spins up. | 1823 | * any pending control commands when the disk spins up. |
1289 | */ | 1824 | */ |
1290 | static void playlist_flush_callback(void) | 1825 | static void flush_playlist_callback(void) |
1291 | { | 1826 | { |
1292 | struct playlist_info *playlist; | 1827 | struct playlist_info *playlist; |
1293 | playlist = ¤t_playlist; | 1828 | playlist = ¤t_playlist; |
@@ -1303,7 +1838,7 @@ static void playlist_flush_callback(void) | |||
1303 | } | 1838 | } |
1304 | } | 1839 | } |
1305 | 1840 | ||
1306 | static void playlist_thread(void) | 1841 | static void thread_playlist(void) |
1307 | { | 1842 | { |
1308 | struct queue_event ev; | 1843 | struct queue_event ev; |
1309 | bool dirty_pointers = false; | 1844 | bool dirty_pointers = false; |
@@ -1340,12 +1875,12 @@ static void playlist_thread(void) | |||
1340 | if (playlist->control_fd >= 0) | 1875 | if (playlist->control_fd >= 0) |
1341 | { | 1876 | { |
1342 | if (playlist->num_cached > 0) | 1877 | if (playlist->num_cached > 0) |
1343 | register_storage_idle_func(playlist_flush_callback); | 1878 | register_storage_idle_func(flush_playlist_callback); |
1344 | } | 1879 | } |
1345 | 1880 | ||
1346 | if (!playlist->dcfrefs || playlist->amount <= 0) | 1881 | if (!playlist->dcfrefs || playlist->amount <= 0) |
1347 | break ; | 1882 | break ; |
1348 | 1883 | ||
1349 | /* Check if previously loaded pointers are intact. */ | 1884 | /* Check if previously loaded pointers are intact. */ |
1350 | if (!dirty_pointers) | 1885 | if (!dirty_pointers) |
1351 | break ; | 1886 | break ; |
@@ -1364,7 +1899,7 @@ static void playlist_thread(void) | |||
1364 | /* Process only pointers that are superficially stale. */ | 1899 | /* Process only pointers that are superficially stale. */ |
1365 | if (dircache_search(DCS_FILEREF, &playlist->dcfrefs[index], NULL) > 0) | 1900 | if (dircache_search(DCS_FILEREF, &playlist->dcfrefs[index], NULL) > 0) |
1366 | continue ; | 1901 | continue ; |
1367 | 1902 | ||
1368 | control_file = playlist->indices[index] & PLAYLIST_INSERT_TYPE_MASK; | 1903 | control_file = playlist->indices[index] & PLAYLIST_INSERT_TYPE_MASK; |
1369 | seek = playlist->indices[index] & PLAYLIST_SEEK_MASK; | 1904 | seek = playlist->indices[index] & PLAYLIST_SEEK_MASK; |
1370 | 1905 | ||
@@ -1387,10 +1922,10 @@ static void playlist_thread(void) | |||
1387 | 1922 | ||
1388 | if (index == playlist->amount) | 1923 | if (index == playlist->amount) |
1389 | dirty_pointers = false; | 1924 | dirty_pointers = false; |
1390 | 1925 | ||
1391 | break ; | 1926 | break ; |
1392 | } | 1927 | } |
1393 | 1928 | ||
1394 | case SYS_USB_CONNECTED: | 1929 | case SYS_USB_CONNECTED: |
1395 | usb_acknowledge(SYS_USB_CONNECTED_ACK); | 1930 | usb_acknowledge(SYS_USB_CONNECTED_ACK); |
1396 | usb_wait_for_disconnect(&playlist_queue); | 1931 | usb_wait_for_disconnect(&playlist_queue); |
@@ -1401,629 +1936,6 @@ static void playlist_thread(void) | |||
1401 | #endif | 1936 | #endif |
1402 | 1937 | ||
1403 | /* | 1938 | /* |
1404 | * gets pathname for track at seek index | ||
1405 | */ | ||
1406 | static int get_filename(struct playlist_info* playlist, int index, int seek, | ||
1407 | bool control_file, char *buf, int buf_length) | ||
1408 | { | ||
1409 | int fd; | ||
1410 | int max = -1; | ||
1411 | char tmp_buf[MAX_PATH+1]; | ||
1412 | char dir_buf[MAX_PATH+1]; | ||
1413 | bool utf8 = playlist->utf8; | ||
1414 | |||
1415 | if (buf_length > MAX_PATH+1) | ||
1416 | buf_length = MAX_PATH+1; | ||
1417 | |||
1418 | #ifdef HAVE_DIRCACHE | ||
1419 | if (playlist->dcfrefs) | ||
1420 | { | ||
1421 | max = dircache_get_fileref_path(&playlist->dcfrefs[index], | ||
1422 | tmp_buf, sizeof(tmp_buf)); | ||
1423 | } | ||
1424 | #endif /* HAVE_DIRCACHE */ | ||
1425 | |||
1426 | if (playlist->in_ram && !control_file && max < 0) | ||
1427 | { | ||
1428 | strmemccpy(tmp_buf, (char*)&playlist->buffer[seek], sizeof(tmp_buf)); | ||
1429 | } | ||
1430 | else if (max < 0) | ||
1431 | { | ||
1432 | mutex_lock(playlist->control_mutex); | ||
1433 | |||
1434 | if (control_file) | ||
1435 | { | ||
1436 | fd = playlist->control_fd; | ||
1437 | utf8 = true; | ||
1438 | } | ||
1439 | else | ||
1440 | { | ||
1441 | if(-1 == playlist->fd) | ||
1442 | playlist->fd = open(playlist->filename, O_RDONLY); | ||
1443 | |||
1444 | fd = playlist->fd; | ||
1445 | } | ||
1446 | |||
1447 | if(-1 != fd) | ||
1448 | { | ||
1449 | |||
1450 | if (lseek(fd, seek, SEEK_SET) != seek) | ||
1451 | max = -1; | ||
1452 | else | ||
1453 | { | ||
1454 | max = read(fd, tmp_buf, MIN((size_t) buf_length, sizeof(tmp_buf))); | ||
1455 | |||
1456 | if (max > 0) | ||
1457 | { | ||
1458 | /* playlist file may end without a new line - terminate buffer */ | ||
1459 | tmp_buf[MIN(max, (int)sizeof(tmp_buf) - 1)] = '\0'; | ||
1460 | |||
1461 | /* Use dir_buf as a temporary buffer. Note that dir_buf must | ||
1462 | * be as large as tmp_buf. | ||
1463 | */ | ||
1464 | if (!utf8) | ||
1465 | max = convert_m3u(tmp_buf, max, sizeof(tmp_buf), dir_buf); | ||
1466 | } | ||
1467 | } | ||
1468 | } | ||
1469 | |||
1470 | mutex_unlock(playlist->control_mutex); | ||
1471 | |||
1472 | if (max < 0) | ||
1473 | { | ||
1474 | if (usb_detect() == USB_INSERTED) | ||
1475 | ; /* ignore error on usb plug */ | ||
1476 | else if (control_file) | ||
1477 | splash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_ACCESS_ERROR)); | ||
1478 | else | ||
1479 | splash(HZ*2, ID2P(LANG_PLAYLIST_ACCESS_ERROR)); | ||
1480 | |||
1481 | return max; | ||
1482 | } | ||
1483 | } | ||
1484 | |||
1485 | strmemccpy(dir_buf, playlist->filename, playlist->dirlen); | ||
1486 | |||
1487 | return format_track_path(buf, tmp_buf, buf_length, dir_buf); | ||
1488 | |||
1489 | (void)index; | ||
1490 | } | ||
1491 | |||
1492 | static int get_next_directory(char *dir){ | ||
1493 | return get_next_dir(dir, true); | ||
1494 | } | ||
1495 | |||
1496 | static int get_previous_directory(char *dir){ | ||
1497 | return get_next_dir(dir, false); | ||
1498 | } | ||
1499 | |||
1500 | /* | ||
1501 | * search through all the directories (starting with the current) to find | ||
1502 | * one that has tracks to play | ||
1503 | */ | ||
1504 | static int get_next_dir(char *dir, bool is_forward) | ||
1505 | { | ||
1506 | struct playlist_info* playlist = ¤t_playlist; | ||
1507 | int result = -1; | ||
1508 | char *start_dir = NULL; | ||
1509 | bool exit = false; | ||
1510 | struct tree_context* tc = tree_get_context(); | ||
1511 | int saved_dirfilter = *(tc->dirfilter); | ||
1512 | unsigned int base_len; | ||
1513 | |||
1514 | if (global_settings.constrain_next_folder) | ||
1515 | { | ||
1516 | /* constrain results to directories below user's start directory */ | ||
1517 | strcpy(dir, global_settings.start_directory); | ||
1518 | base_len = strlen(dir); | ||
1519 | |||
1520 | /* strip any trailing slash from base directory */ | ||
1521 | if (base_len > 0 && dir[base_len - 1] == '/') | ||
1522 | { | ||
1523 | base_len--; | ||
1524 | dir[base_len] = '\0'; | ||
1525 | } | ||
1526 | } | ||
1527 | else | ||
1528 | { | ||
1529 | /* start from root directory */ | ||
1530 | dir[0] = '\0'; | ||
1531 | base_len = 0; | ||
1532 | } | ||
1533 | |||
1534 | /* process random folder advance */ | ||
1535 | if (global_settings.next_folder == FOLDER_ADVANCE_RANDOM) | ||
1536 | { | ||
1537 | int fd = open(ROCKBOX_DIR "/folder_advance_list.dat", O_RDONLY); | ||
1538 | if (fd >= 0) | ||
1539 | { | ||
1540 | int folder_count = 0; | ||
1541 | ssize_t nread = read(fd,&folder_count,sizeof(int)); | ||
1542 | if ((nread == sizeof(int)) && folder_count) | ||
1543 | { | ||
1544 | char buffer[MAX_PATH]; | ||
1545 | /* give up looking for a directory after we've had four | ||
1546 | times as many tries as there are directories. */ | ||
1547 | unsigned long allowed_tries = folder_count * 4; | ||
1548 | int i; | ||
1549 | srand(current_tick); | ||
1550 | *(tc->dirfilter) = SHOW_MUSIC; | ||
1551 | tc->sort_dir = global_settings.sort_dir; | ||
1552 | while (!exit && allowed_tries--) | ||
1553 | { | ||
1554 | i = rand() % folder_count; | ||
1555 | lseek(fd, sizeof(int) + (MAX_PATH * i), SEEK_SET); | ||
1556 | read(fd, buffer, MAX_PATH); | ||
1557 | /* is the current dir within our base dir and has music? */ | ||
1558 | if ((base_len == 0 || !strncmp(buffer, dir, base_len)) | ||
1559 | && check_subdir_for_music(buffer, "", false) == 0) | ||
1560 | exit = true; | ||
1561 | } | ||
1562 | close(fd); | ||
1563 | *(tc->dirfilter) = saved_dirfilter; | ||
1564 | tc->sort_dir = global_settings.sort_dir; | ||
1565 | reload_directory(); | ||
1566 | if (exit) | ||
1567 | { | ||
1568 | strcpy(dir,buffer); | ||
1569 | return 0; | ||
1570 | } | ||
1571 | } | ||
1572 | else | ||
1573 | close(fd); | ||
1574 | } | ||
1575 | } | ||
1576 | |||
1577 | /* if the current file is within our base dir, use its dir instead */ | ||
1578 | if (base_len == 0 || !strncmp(playlist->filename, dir, base_len)) | ||
1579 | strmemccpy(dir, playlist->filename, playlist->dirlen); | ||
1580 | |||
1581 | /* use the tree browser dircache to load files */ | ||
1582 | *(tc->dirfilter) = SHOW_ALL; | ||
1583 | |||
1584 | /* set up sorting/direction */ | ||
1585 | tc->sort_dir = global_settings.sort_dir; | ||
1586 | if (!is_forward) | ||
1587 | { | ||
1588 | static const char sortpairs[] = | ||
1589 | { | ||
1590 | [SORT_ALPHA] = SORT_ALPHA_REVERSED, | ||
1591 | [SORT_DATE] = SORT_DATE_REVERSED, | ||
1592 | [SORT_TYPE] = SORT_TYPE_REVERSED, | ||
1593 | [SORT_ALPHA_REVERSED] = SORT_ALPHA, | ||
1594 | [SORT_DATE_REVERSED] = SORT_DATE, | ||
1595 | [SORT_TYPE_REVERSED] = SORT_TYPE, | ||
1596 | }; | ||
1597 | |||
1598 | if ((unsigned)tc->sort_dir < sizeof(sortpairs)) | ||
1599 | tc->sort_dir = sortpairs[tc->sort_dir]; | ||
1600 | } | ||
1601 | |||
1602 | while (!exit) | ||
1603 | { | ||
1604 | struct entry *files; | ||
1605 | int num_files = 0; | ||
1606 | int i; | ||
1607 | |||
1608 | if (ft_load(tc, (dir[0]=='\0')?"/":dir) < 0) | ||
1609 | { | ||
1610 | exit = true; | ||
1611 | result = -1; | ||
1612 | break; | ||
1613 | } | ||
1614 | |||
1615 | tree_lock_cache(tc); | ||
1616 | files = tree_get_entries(tc); | ||
1617 | num_files = tc->filesindir; | ||
1618 | |||
1619 | for (i=0; i<num_files; i++) | ||
1620 | { | ||
1621 | /* user abort */ | ||
1622 | if (action_userabort(TIMEOUT_NOBLOCK)) | ||
1623 | { | ||
1624 | result = -1; | ||
1625 | exit = true; | ||
1626 | break; | ||
1627 | } | ||
1628 | |||
1629 | if (files[i].attr & ATTR_DIRECTORY) | ||
1630 | { | ||
1631 | if (!start_dir) | ||
1632 | { | ||
1633 | result = check_subdir_for_music(dir, files[i].name, true); | ||
1634 | if (result != -1) | ||
1635 | { | ||
1636 | exit = true; | ||
1637 | break; | ||
1638 | } | ||
1639 | } | ||
1640 | else if (!strcmp(start_dir, files[i].name)) | ||
1641 | start_dir = NULL; | ||
1642 | } | ||
1643 | } | ||
1644 | tree_unlock_cache(tc); | ||
1645 | |||
1646 | if (!exit) | ||
1647 | { | ||
1648 | /* we've already descended to the base dir with nothing found, | ||
1649 | check whether that contains music */ | ||
1650 | if (strlen(dir) <= base_len) | ||
1651 | { | ||
1652 | result = check_subdir_for_music(dir, "", true); | ||
1653 | if (result == -1) | ||
1654 | /* there's no music files in the base directory, | ||
1655 | treat as a fatal error */ | ||
1656 | result = -2; | ||
1657 | break; | ||
1658 | } | ||
1659 | else | ||
1660 | { | ||
1661 | /* move down to parent directory. current directory name is | ||
1662 | stored as the starting point for the search in parent */ | ||
1663 | start_dir = strrchr(dir, '/'); | ||
1664 | if (start_dir) | ||
1665 | { | ||
1666 | *start_dir = '\0'; | ||
1667 | start_dir++; | ||
1668 | } | ||
1669 | else | ||
1670 | break; | ||
1671 | } | ||
1672 | } | ||
1673 | } | ||
1674 | |||
1675 | /* restore dirfilter */ | ||
1676 | *(tc->dirfilter) = saved_dirfilter; | ||
1677 | tc->sort_dir = global_settings.sort_dir; | ||
1678 | |||
1679 | return result; | ||
1680 | } | ||
1681 | |||
1682 | /* | ||
1683 | * Checks if there are any music files in the dir or any of its | ||
1684 | * subdirectories. May be called recursively. | ||
1685 | */ | ||
1686 | static int check_subdir_for_music(char *dir, const char *subdir, bool recurse) | ||
1687 | { | ||
1688 | int result = -1; | ||
1689 | size_t dirlen = strlen(dir); | ||
1690 | int num_files = 0; | ||
1691 | int i; | ||
1692 | struct entry *files; | ||
1693 | bool has_music = false; | ||
1694 | bool has_subdir = false; | ||
1695 | struct tree_context* tc = tree_get_context(); | ||
1696 | |||
1697 | if (path_append(dir + dirlen, PA_SEP_HARD, subdir, MAX_PATH - dirlen) >= | ||
1698 | MAX_PATH - dirlen) | ||
1699 | { | ||
1700 | return 0; | ||
1701 | } | ||
1702 | |||
1703 | if (ft_load(tc, dir) < 0) | ||
1704 | { | ||
1705 | return -2; | ||
1706 | } | ||
1707 | |||
1708 | tree_lock_cache(tc); | ||
1709 | files = tree_get_entries(tc); | ||
1710 | num_files = tc->filesindir; | ||
1711 | |||
1712 | for (i=0; i<num_files; i++) | ||
1713 | { | ||
1714 | if (files[i].attr & ATTR_DIRECTORY) | ||
1715 | has_subdir = true; | ||
1716 | else if ((files[i].attr & FILE_ATTR_MASK) == FILE_ATTR_AUDIO) | ||
1717 | { | ||
1718 | has_music = true; | ||
1719 | break; | ||
1720 | } | ||
1721 | } | ||
1722 | |||
1723 | if (has_music) | ||
1724 | { | ||
1725 | tree_unlock_cache(tc); | ||
1726 | return 0; | ||
1727 | } | ||
1728 | |||
1729 | if (has_subdir && recurse) | ||
1730 | { | ||
1731 | for (i=0; i<num_files; i++) | ||
1732 | { | ||
1733 | if (action_userabort(TIMEOUT_NOBLOCK)) | ||
1734 | { | ||
1735 | result = -2; | ||
1736 | break; | ||
1737 | } | ||
1738 | |||
1739 | if (files[i].attr & ATTR_DIRECTORY) | ||
1740 | { | ||
1741 | result = check_subdir_for_music(dir, files[i].name, true); | ||
1742 | if (!result) | ||
1743 | break; | ||
1744 | } | ||
1745 | } | ||
1746 | } | ||
1747 | tree_unlock_cache(tc); | ||
1748 | |||
1749 | if (result < 0) | ||
1750 | { | ||
1751 | if (dirlen) | ||
1752 | { | ||
1753 | dir[dirlen] = '\0'; | ||
1754 | } | ||
1755 | else | ||
1756 | { | ||
1757 | strcpy(dir, PATH_ROOTSTR); | ||
1758 | } | ||
1759 | |||
1760 | /* we now need to reload our current directory */ | ||
1761 | if(ft_load(tc, dir) < 0) | ||
1762 | splash(HZ*2, ID2P(LANG_PLAYLIST_DIRECTORY_ACCESS_ERROR)); | ||
1763 | } | ||
1764 | return result; | ||
1765 | } | ||
1766 | |||
1767 | /* | ||
1768 | * Returns absolute path of track | ||
1769 | * | ||
1770 | * dest: output buffer | ||
1771 | * src: the file name from the playlist | ||
1772 | * dir: the absolute path to the directory where the playlist resides | ||
1773 | * | ||
1774 | * The type of path in "src" determines what will be written to "dest": | ||
1775 | * | ||
1776 | * 1. UNIX-style absolute paths (/foo/bar) remain unaltered | ||
1777 | * 2. Windows-style absolute paths (C:/foo/bar) will be converted into an | ||
1778 | * absolute path by replacing the drive letter with the volume that the | ||
1779 | * *playlist* resides on, ie. the volume in "dir" | ||
1780 | * 3. Relative paths are converted to absolute paths by prepending "dir". | ||
1781 | * This also applies to Windows-style relative paths "C:foo/bar" where | ||
1782 | * the drive letter is accepted but ignored. | ||
1783 | */ | ||
1784 | static ssize_t format_track_path(char *dest, char *src, int buf_length, | ||
1785 | const char *dir) | ||
1786 | { | ||
1787 | size_t len = 0; | ||
1788 | |||
1789 | /* Look for the end of the string */ | ||
1790 | while (1) | ||
1791 | { | ||
1792 | int c = src[len]; | ||
1793 | if (c == '\n' || c == '\r' || c == '\0') | ||
1794 | break; | ||
1795 | len++; | ||
1796 | } | ||
1797 | |||
1798 | /* Now work back killing white space */ | ||
1799 | while (len > 0) | ||
1800 | { | ||
1801 | int c = src[len - 1]; | ||
1802 | if (c != '\t' && c != ' ') | ||
1803 | break; | ||
1804 | len--; | ||
1805 | } | ||
1806 | |||
1807 | src[len] = '\0'; | ||
1808 | |||
1809 | /* Replace backslashes with forward slashes */ | ||
1810 | path_correct_separators(src, src); | ||
1811 | |||
1812 | /* Handle Windows-style absolute paths */ | ||
1813 | if (path_strip_drive(src, (const char **)&src, true) >= 0 && | ||
1814 | src[-1] == PATH_SEPCH) | ||
1815 | { | ||
1816 | #ifdef HAVE_MULTIVOLUME | ||
1817 | const char *p; | ||
1818 | path_strip_last_volume(dir, &p, false); | ||
1819 | dir = strmemdupa(dir, p - dir); /* empty if no volspec on dir */ | ||
1820 | #else | ||
1821 | dir = ""; /* only volume is root */ | ||
1822 | #endif | ||
1823 | } | ||
1824 | |||
1825 | len = path_append(dest, *dir ? dir : PATH_ROOTSTR, src, buf_length); | ||
1826 | if (len >= (size_t)buf_length) | ||
1827 | return -1; /* buffer too small */ | ||
1828 | |||
1829 | path_remove_dot_segments (dest, dest); | ||
1830 | |||
1831 | return strlen (dest); | ||
1832 | } | ||
1833 | |||
1834 | /* | ||
1835 | * Display splash message showing progress of playlist/directory insertion or | ||
1836 | * save. | ||
1837 | */ | ||
1838 | static void display_playlist_count(int count, const unsigned char *fmt, | ||
1839 | bool final) | ||
1840 | { | ||
1841 | static long talked_tick = 0; | ||
1842 | long id = P2ID(fmt); | ||
1843 | if(global_settings.talk_menu && id>=0) | ||
1844 | { | ||
1845 | if(final || (count && (talked_tick == 0 | ||
1846 | || TIME_AFTER(current_tick, talked_tick+5*HZ)))) | ||
1847 | { | ||
1848 | talked_tick = current_tick; | ||
1849 | talk_number(count, false); | ||
1850 | talk_id(id, true); | ||
1851 | } | ||
1852 | } | ||
1853 | fmt = P2STR(fmt); | ||
1854 | |||
1855 | splashf(0, fmt, count, str(LANG_OFF_ABORT)); | ||
1856 | } | ||
1857 | |||
1858 | /* | ||
1859 | * Display buffer full message | ||
1860 | */ | ||
1861 | static void display_buffer_full(void) | ||
1862 | { | ||
1863 | splash(HZ*2, ID2P(LANG_PLAYLIST_BUFFER_FULL)); | ||
1864 | } | ||
1865 | |||
1866 | /* | ||
1867 | * Flush any cached control commands to disk. Called when playlist is being | ||
1868 | * modified. Returns 0 on success and -1 on failure. | ||
1869 | */ | ||
1870 | static int flush_cached_control(struct playlist_info* playlist) | ||
1871 | { | ||
1872 | int result = 0; | ||
1873 | int i; | ||
1874 | |||
1875 | if (!playlist->num_cached) | ||
1876 | return 0; | ||
1877 | |||
1878 | lseek(playlist->control_fd, 0, SEEK_END); | ||
1879 | |||
1880 | for (i=0; i<playlist->num_cached; i++) | ||
1881 | { | ||
1882 | struct playlist_control_cache* cache = | ||
1883 | &(playlist->control_cache[i]); | ||
1884 | |||
1885 | switch (cache->command) | ||
1886 | { | ||
1887 | case PLAYLIST_COMMAND_PLAYLIST: | ||
1888 | result = fdprintf(playlist->control_fd, "P:%d:%s:%s\n", | ||
1889 | cache->i1, cache->s1, cache->s2); | ||
1890 | break; | ||
1891 | case PLAYLIST_COMMAND_ADD: | ||
1892 | case PLAYLIST_COMMAND_QUEUE: | ||
1893 | result = fdprintf(playlist->control_fd, "%c:%d:%d:", | ||
1894 | (cache->command == PLAYLIST_COMMAND_ADD)?'A':'Q', | ||
1895 | cache->i1, cache->i2); | ||
1896 | if (result > 0) | ||
1897 | { | ||
1898 | /* save the position in file where name is written */ | ||
1899 | int* seek_pos = (int *)cache->data; | ||
1900 | *seek_pos = lseek(playlist->control_fd, 0, SEEK_CUR); | ||
1901 | result = fdprintf(playlist->control_fd, "%s\n", | ||
1902 | cache->s1); | ||
1903 | } | ||
1904 | break; | ||
1905 | case PLAYLIST_COMMAND_DELETE: | ||
1906 | result = fdprintf(playlist->control_fd, "D:%d\n", cache->i1); | ||
1907 | break; | ||
1908 | case PLAYLIST_COMMAND_SHUFFLE: | ||
1909 | result = fdprintf(playlist->control_fd, "S:%d:%d\n", | ||
1910 | cache->i1, cache->i2); | ||
1911 | break; | ||
1912 | case PLAYLIST_COMMAND_UNSHUFFLE: | ||
1913 | result = fdprintf(playlist->control_fd, "U:%d\n", cache->i1); | ||
1914 | break; | ||
1915 | case PLAYLIST_COMMAND_RESET: | ||
1916 | result = fdprintf(playlist->control_fd, "R\n"); | ||
1917 | break; | ||
1918 | default: | ||
1919 | break; | ||
1920 | } | ||
1921 | |||
1922 | if (result <= 0) | ||
1923 | break; | ||
1924 | } | ||
1925 | |||
1926 | if (result > 0) | ||
1927 | { | ||
1928 | playlist->num_cached = 0; | ||
1929 | playlist->pending_control_sync = true; | ||
1930 | |||
1931 | result = 0; | ||
1932 | } | ||
1933 | else | ||
1934 | { | ||
1935 | result = -1; | ||
1936 | splash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_UPDATE_ERROR)); | ||
1937 | } | ||
1938 | |||
1939 | return result; | ||
1940 | } | ||
1941 | |||
1942 | /* | ||
1943 | * Update control data with new command. Depending on the command, it may be | ||
1944 | * cached or flushed to disk. | ||
1945 | */ | ||
1946 | static int update_control(struct playlist_info* playlist, | ||
1947 | enum playlist_command command, int i1, int i2, | ||
1948 | const char* s1, const char* s2, void* data) | ||
1949 | { | ||
1950 | int result = 0; | ||
1951 | struct playlist_control_cache* cache; | ||
1952 | bool flush = false; | ||
1953 | |||
1954 | mutex_lock(playlist->control_mutex); | ||
1955 | |||
1956 | cache = &(playlist->control_cache[playlist->num_cached++]); | ||
1957 | |||
1958 | cache->command = command; | ||
1959 | cache->i1 = i1; | ||
1960 | cache->i2 = i2; | ||
1961 | cache->s1 = s1; | ||
1962 | cache->s2 = s2; | ||
1963 | cache->data = data; | ||
1964 | |||
1965 | switch (command) | ||
1966 | { | ||
1967 | case PLAYLIST_COMMAND_PLAYLIST: | ||
1968 | case PLAYLIST_COMMAND_ADD: | ||
1969 | case PLAYLIST_COMMAND_QUEUE: | ||
1970 | #ifndef HAVE_DIRCACHE | ||
1971 | case PLAYLIST_COMMAND_DELETE: | ||
1972 | case PLAYLIST_COMMAND_RESET: | ||
1973 | #endif | ||
1974 | flush = true; | ||
1975 | break; | ||
1976 | case PLAYLIST_COMMAND_SHUFFLE: | ||
1977 | case PLAYLIST_COMMAND_UNSHUFFLE: | ||
1978 | default: | ||
1979 | /* only flush when needed */ | ||
1980 | break; | ||
1981 | } | ||
1982 | |||
1983 | if (flush || playlist->num_cached == PLAYLIST_MAX_CACHE) | ||
1984 | result = flush_cached_control(playlist); | ||
1985 | |||
1986 | mutex_unlock(playlist->control_mutex); | ||
1987 | |||
1988 | return result; | ||
1989 | } | ||
1990 | |||
1991 | /* | ||
1992 | * sync control file to disk | ||
1993 | */ | ||
1994 | static void sync_control(struct playlist_info* playlist, bool force) | ||
1995 | { | ||
1996 | #ifdef HAVE_DIRCACHE | ||
1997 | if (playlist->started && force) | ||
1998 | #else | ||
1999 | (void) force; | ||
2000 | |||
2001 | if (playlist->started) | ||
2002 | #endif | ||
2003 | { | ||
2004 | if (playlist->pending_control_sync) | ||
2005 | { | ||
2006 | mutex_lock(playlist->control_mutex); | ||
2007 | fsync(playlist->control_fd); | ||
2008 | playlist->pending_control_sync = false; | ||
2009 | mutex_unlock(playlist->control_mutex); | ||
2010 | } | ||
2011 | } | ||
2012 | } | ||
2013 | |||
2014 | /* | ||
2015 | * Rotate indices such that first_index is index 0 | ||
2016 | */ | ||
2017 | static int rotate_index(const struct playlist_info* playlist, int index) | ||
2018 | { | ||
2019 | index -= playlist->first_index; | ||
2020 | if (index < 0) | ||
2021 | index += playlist->amount; | ||
2022 | |||
2023 | return index; | ||
2024 | } | ||
2025 | |||
2026 | /* | ||
2027 | * Allocate a temporary buffer for loading playlists | 1939 | * Allocate a temporary buffer for loading playlists |
2028 | */ | 1940 | */ |
2029 | static int alloc_tempbuf(size_t* buflen) | 1941 | static int alloc_tempbuf(size_t* buflen) |
@@ -2064,6 +1976,14 @@ static struct buflib_callbacks ops = { | |||
2064 | .move_callback = move_callback, | 1976 | .move_callback = move_callback, |
2065 | .shrink_callback = NULL, | 1977 | .shrink_callback = NULL, |
2066 | }; | 1978 | }; |
1979 | |||
1980 | /******************************************************************************/ | ||
1981 | /******************************************************************************/ | ||
1982 | /* ************************************************************************** */ | ||
1983 | /* * PUBLIC INTERFACE FUNCTIONS * *********************************************/ | ||
1984 | /* ************************************************************************** */ | ||
1985 | /******************************************************************************/ | ||
1986 | /******************************************************************************/ | ||
2067 | /* | 1987 | /* |
2068 | * Initialize playlist entries at startup | 1988 | * Initialize playlist entries at startup |
2069 | */ | 1989 | */ |
@@ -2099,8 +2019,8 @@ void playlist_init(void) | |||
2099 | playlist->max_playlist_size * sizeof(*playlist->dcfrefs), &ops); | 2019 | playlist->max_playlist_size * sizeof(*playlist->dcfrefs), &ops); |
2100 | playlist->dcfrefs = core_get_data(handle); | 2020 | playlist->dcfrefs = core_get_data(handle); |
2101 | copy_filerefs(playlist->dcfrefs, NULL, playlist->max_playlist_size); | 2021 | copy_filerefs(playlist->dcfrefs, NULL, playlist->max_playlist_size); |
2102 | create_thread(playlist_thread, playlist_stack, sizeof(playlist_stack), | 2022 | create_thread(thread_playlist, playlist_stack, sizeof(playlist_stack), |
2103 | 0, playlist_thread_name IF_PRIO(, PRIORITY_BACKGROUND) | 2023 | 0, thread_playlist_name IF_PRIO(, PRIORITY_BACKGROUND) |
2104 | IF_COP(, CPU)); | 2024 | IF_COP(, CPU)); |
2105 | 2025 | ||
2106 | queue_init(&playlist_queue, true); | 2026 | queue_init(&playlist_queue, true); |
@@ -2129,6 +2049,113 @@ void playlist_shutdown(void) | |||
2129 | } | 2049 | } |
2130 | 2050 | ||
2131 | /* | 2051 | /* |
2052 | * Add track to in_ram playlist. Used when playing directories. | ||
2053 | */ | ||
2054 | int playlist_add(const char *filename) | ||
2055 | { | ||
2056 | struct playlist_info* playlist = ¤t_playlist; | ||
2057 | int len = strlen(filename); | ||
2058 | |||
2059 | if((len+1 > playlist->buffer_size - playlist->buffer_end_pos) || | ||
2060 | (playlist->amount >= playlist->max_playlist_size)) | ||
2061 | { | ||
2062 | display_buffer_full(); | ||
2063 | return -1; | ||
2064 | } | ||
2065 | |||
2066 | playlist->indices[playlist->amount] = playlist->buffer_end_pos; | ||
2067 | #ifdef HAVE_DIRCACHE | ||
2068 | copy_filerefs(&playlist->dcfrefs[playlist->amount], NULL, 1); | ||
2069 | #endif | ||
2070 | |||
2071 | playlist->amount++; | ||
2072 | |||
2073 | strcpy((char*)&playlist->buffer[playlist->buffer_end_pos], filename); | ||
2074 | playlist->buffer_end_pos += len; | ||
2075 | playlist->buffer[playlist->buffer_end_pos++] = '\0'; | ||
2076 | |||
2077 | return 0; | ||
2078 | } | ||
2079 | |||
2080 | /* returns number of tracks in playlist (includes queued/inserted tracks) */ | ||
2081 | int playlist_amount_ex(const struct playlist_info* playlist) | ||
2082 | { | ||
2083 | if (!playlist) | ||
2084 | playlist = ¤t_playlist; | ||
2085 | |||
2086 | return playlist->amount; | ||
2087 | } | ||
2088 | |||
2089 | /* returns number of tracks in current playlist */ | ||
2090 | int playlist_amount(void) | ||
2091 | { | ||
2092 | return playlist_amount_ex(NULL); | ||
2093 | } | ||
2094 | |||
2095 | /* | ||
2096 | * Create a new playlist If playlist is not NULL then we're loading a | ||
2097 | * playlist off disk for viewing/editing. The index_buffer is used to store | ||
2098 | * playlist indices (required for and only used if !current playlist). The | ||
2099 | * temp_buffer (if not NULL) is used as a scratchpad when loading indices. | ||
2100 | */ | ||
2101 | int playlist_create_ex(struct playlist_info* playlist, | ||
2102 | const char* dir, const char* file, | ||
2103 | void* index_buffer, int index_buffer_size, | ||
2104 | void* temp_buffer, int temp_buffer_size) | ||
2105 | { | ||
2106 | if (!playlist) | ||
2107 | playlist = ¤t_playlist; | ||
2108 | else | ||
2109 | { | ||
2110 | /* Initialize playlist structure */ | ||
2111 | int r = rand() % 10; | ||
2112 | playlist->current = false; | ||
2113 | |||
2114 | /* Use random name for control file */ | ||
2115 | snprintf(playlist->control_filename, sizeof(playlist->control_filename), | ||
2116 | "%s.%d", PLAYLIST_CONTROL_FILE, r); | ||
2117 | playlist->fd = -1; | ||
2118 | playlist->control_fd = -1; | ||
2119 | |||
2120 | if (index_buffer) | ||
2121 | { | ||
2122 | int num_indices = index_buffer_size / | ||
2123 | playlist_get_required_bufsz(playlist, false, 1); | ||
2124 | |||
2125 | if (num_indices > global_settings.max_files_in_playlist) | ||
2126 | num_indices = global_settings.max_files_in_playlist; | ||
2127 | |||
2128 | playlist->max_playlist_size = num_indices; | ||
2129 | playlist->indices = index_buffer; | ||
2130 | #ifdef HAVE_DIRCACHE | ||
2131 | playlist->dcfrefs = (void *)&playlist->indices[num_indices]; | ||
2132 | #endif | ||
2133 | } | ||
2134 | else | ||
2135 | { | ||
2136 | playlist->max_playlist_size = current_playlist.max_playlist_size; | ||
2137 | playlist->indices = current_playlist.indices; | ||
2138 | #ifdef HAVE_DIRCACHE | ||
2139 | playlist->dcfrefs = current_playlist.dcfrefs; | ||
2140 | #endif | ||
2141 | } | ||
2142 | |||
2143 | playlist->buffer_size = 0; | ||
2144 | playlist->buffer_handle = -1; | ||
2145 | playlist->buffer = NULL; | ||
2146 | playlist->control_mutex = &created_playlist_mutex; | ||
2147 | } | ||
2148 | |||
2149 | new_playlist(playlist, dir, file); | ||
2150 | |||
2151 | if (file) | ||
2152 | /* load the playlist file */ | ||
2153 | add_indices_to_playlist(playlist, temp_buffer, temp_buffer_size); | ||
2154 | |||
2155 | return 0; | ||
2156 | } | ||
2157 | |||
2158 | /* | ||
2132 | * Create new playlist | 2159 | * Create new playlist |
2133 | */ | 2160 | */ |
2134 | int playlist_create(const char *dir, const char *file) | 2161 | int playlist_create(const char *dir, const char *file) |
@@ -2162,474 +2189,196 @@ int playlist_create(const char *dir, const char *file) | |||
2162 | return 0; | 2189 | return 0; |
2163 | } | 2190 | } |
2164 | 2191 | ||
2165 | #define PLAYLIST_COMMAND_SIZE (MAX_PATH+12) | 2192 | /* Returns false if 'steps' is out of bounds, else true */ |
2166 | 2193 | bool playlist_check(int steps) | |
2167 | /* | ||
2168 | * Restore the playlist state based on control file commands. Called to | ||
2169 | * resume playback after shutdown. | ||
2170 | */ | ||
2171 | int playlist_resume(void) | ||
2172 | { | 2194 | { |
2173 | struct playlist_info* playlist = ¤t_playlist; | 2195 | struct playlist_info* playlist = ¤t_playlist; |
2174 | char *buffer; | ||
2175 | size_t buflen; | ||
2176 | int handle; | ||
2177 | int nread; | ||
2178 | int total_read = 0; | ||
2179 | int control_file_size = 0; | ||
2180 | bool first = true; | ||
2181 | bool sorted = true; | ||
2182 | int result = -1; | ||
2183 | 2196 | ||
2184 | splash(0, ID2P(LANG_WAIT)); | 2197 | /* always allow folder navigation */ |
2185 | if (core_allocatable() < (1 << 10)) | 2198 | if (global_settings.next_folder && playlist->in_ram) |
2186 | talk_buffer_set_policy(TALK_BUFFER_LOOSE); /* back off voice buffer */ | 2199 | return true; |
2187 | 2200 | ||
2188 | #ifdef HAVE_DIRCACHE | 2201 | int index = get_next_index(playlist, steps, -1); |
2189 | dircache_wait(); /* we need the dircache to use the files in the playlist */ | ||
2190 | #endif | ||
2191 | 2202 | ||
2192 | handle = alloc_tempbuf(&buflen); | 2203 | if (index < 0 && steps >= 0 && global_settings.repeat_mode == REPEAT_SHUFFLE) |
2193 | if (handle < 0) | 2204 | index = get_next_index(playlist, steps, REPEAT_ALL); |
2194 | { | ||
2195 | splashf(HZ * 2, "%s(): OOM", __func__); | ||
2196 | return -1; | ||
2197 | } | ||
2198 | 2205 | ||
2199 | /* align buffer for faster load times */ | 2206 | return (index >= 0); |
2200 | buffer = core_get_data(handle); | 2207 | } |
2201 | STORAGE_ALIGN_BUFFER(buffer, buflen); | ||
2202 | buflen = ALIGN_DOWN(buflen, 512); /* to avoid partial sector I/O */ | ||
2203 | 2208 | ||
2204 | playlist_shutdown(); /* flush any cached control commands to disk */ | 2209 | /* |
2205 | empty_playlist(playlist, true); | 2210 | * Close files and delete control file for non-current playlist. |
2211 | */ | ||
2212 | void playlist_close(struct playlist_info* playlist) | ||
2213 | { | ||
2214 | if (!playlist) | ||
2215 | return; | ||
2206 | 2216 | ||
2207 | playlist->control_fd = open(playlist->control_filename, O_RDWR); | 2217 | if (playlist->fd >= 0) { |
2208 | if (playlist->control_fd < 0) | 2218 | close(playlist->fd); |
2209 | { | 2219 | playlist->fd = -1; |
2210 | splash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_ACCESS_ERROR)); | ||
2211 | goto out; | ||
2212 | } | 2220 | } |
2213 | playlist->control_created = true; | ||
2214 | 2221 | ||
2215 | control_file_size = filesize(playlist->control_fd); | 2222 | if (playlist->control_fd >= 0) { |
2216 | if (control_file_size <= 0) | 2223 | close(playlist->control_fd); |
2217 | { | 2224 | playlist->control_fd = -1; |
2218 | splash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_ACCESS_ERROR)); | ||
2219 | goto out; | ||
2220 | } | 2225 | } |
2221 | 2226 | ||
2222 | /* read a small amount first to get the header */ | 2227 | if (playlist->control_created) { |
2223 | nread = read(playlist->control_fd, buffer, | 2228 | remove(playlist->control_filename); |
2224 | PLAYLIST_COMMAND_SIZE<buflen?PLAYLIST_COMMAND_SIZE:buflen); | 2229 | playlist->control_created = false; |
2225 | if(nread <= 0) | ||
2226 | { | ||
2227 | splash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_ACCESS_ERROR)); | ||
2228 | goto out; | ||
2229 | } | 2230 | } |
2231 | } | ||
2230 | 2232 | ||
2231 | playlist->started = true; | 2233 | /* |
2234 | * Delete track at specified index. If index is PLAYLIST_DELETE_CURRENT then | ||
2235 | * we want to delete the current playing track. | ||
2236 | */ | ||
2237 | int playlist_delete(struct playlist_info* playlist, int index) | ||
2238 | { | ||
2239 | int result = 0; | ||
2232 | 2240 | ||
2233 | while (1) | 2241 | if (!playlist) |
2242 | playlist = ¤t_playlist; | ||
2243 | |||
2244 | if (check_control(playlist) < 0) | ||
2234 | { | 2245 | { |
2235 | result = 0; | 2246 | splash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_ACCESS_ERROR)); |
2236 | int count; | 2247 | return -1; |
2237 | enum playlist_command current_command = PLAYLIST_COMMAND_COMMENT; | 2248 | } |
2238 | int last_newline = 0; | ||
2239 | int str_count = -1; | ||
2240 | bool newline = true; | ||
2241 | bool exit_loop = false; | ||
2242 | char *p = buffer; | ||
2243 | char *str1 = NULL; | ||
2244 | char *str2 = NULL; | ||
2245 | char *str3 = NULL; | ||
2246 | unsigned long last_tick = current_tick; | ||
2247 | splash_progress_set_delay(HZ / 2); /* wait 1/2 sec before progress */ | ||
2248 | bool useraborted = false; | ||
2249 | |||
2250 | for(count=0; count<nread && !exit_loop && !useraborted; count++,p++) | ||
2251 | { | ||
2252 | /* Show a splash while we are loading. */ | ||
2253 | splash_progress((total_read + count), control_file_size, | ||
2254 | "%s (%s)", str(LANG_WAIT), str(LANG_OFF_ABORT)); | ||
2255 | if (TIME_AFTER(current_tick, last_tick + HZ/4)) | ||
2256 | { | ||
2257 | if (action_userabort(TIMEOUT_NOBLOCK)) | ||
2258 | { | ||
2259 | useraborted = true; | ||
2260 | break; | ||
2261 | } | ||
2262 | last_tick = current_tick; | ||
2263 | } | ||
2264 | /* Are we on a new line? */ | ||
2265 | if((*p == '\n') || (*p == '\r')) | ||
2266 | { | ||
2267 | *p = '\0'; | ||
2268 | 2249 | ||
2269 | /* save last_newline in case we need to load more data */ | 2250 | if (index == PLAYLIST_DELETE_CURRENT) |
2270 | last_newline = count; | 2251 | index = playlist->index; |
2271 | 2252 | ||
2272 | switch (current_command) | 2253 | result = remove_track_from_playlist(playlist, index, true); |
2273 | { | ||
2274 | case PLAYLIST_COMMAND_PLAYLIST: | ||
2275 | { | ||
2276 | /* str1=version str2=dir str3=file */ | ||
2277 | int version; | ||
2278 | 2254 | ||
2279 | if (!str1) | 2255 | if (result != -1 && (audio_status() & AUDIO_STATUS_PLAY) && |
2280 | { | 2256 | playlist->started) |
2281 | result = -2; | 2257 | audio_flush_and_reload_tracks(); |
2282 | exit_loop = true; | ||
2283 | break; | ||
2284 | } | ||
2285 | |||
2286 | if (!str2) | ||
2287 | str2 = ""; | ||
2288 | |||
2289 | if (!str3) | ||
2290 | str3 = ""; | ||
2291 | |||
2292 | version = atoi(str1); | ||
2293 | |||
2294 | if (version != PLAYLIST_CONTROL_FILE_VERSION) | ||
2295 | { | ||
2296 | result = -3; | ||
2297 | goto out; | ||
2298 | } | ||
2299 | |||
2300 | update_playlist_filename(playlist, str2, str3); | ||
2301 | |||
2302 | if (str3[0] != '\0') | ||
2303 | { | ||
2304 | /* NOTE: add_indices_to_playlist() overwrites the | ||
2305 | audiobuf so we need to reload control file | ||
2306 | data */ | ||
2307 | add_indices_to_playlist(playlist, buffer, buflen); | ||
2308 | } | ||
2309 | else if (str2[0] != '\0') | ||
2310 | { | ||
2311 | playlist->in_ram = true; | ||
2312 | resume_directory(str2); | ||
2313 | } | ||
2314 | |||
2315 | /* load the rest of the data */ | ||
2316 | first = false; | ||
2317 | exit_loop = true; | ||
2318 | 2258 | ||
2319 | break; | 2259 | return result; |
2320 | } | 2260 | } |
2321 | case PLAYLIST_COMMAND_ADD: | ||
2322 | case PLAYLIST_COMMAND_QUEUE: | ||
2323 | { | ||
2324 | /* str1=position str2=last_position str3=file */ | ||
2325 | int position, last_position; | ||
2326 | bool queue; | ||
2327 | |||
2328 | if (!str1 || !str2 || !str3) | ||
2329 | { | ||
2330 | result = -4; | ||
2331 | exit_loop = true; | ||
2332 | break; | ||
2333 | } | ||
2334 | |||
2335 | position = atoi(str1); | ||
2336 | last_position = atoi(str2); | ||
2337 | |||
2338 | queue = (current_command == PLAYLIST_COMMAND_ADD)? | ||
2339 | false:true; | ||
2340 | 2261 | ||
2341 | /* seek position is based on str3's position in | 2262 | /* |
2342 | buffer */ | 2263 | * Search specified directory for tracks and notify via callback. May be |
2343 | if (add_track_to_playlist(playlist, str3, position, | 2264 | * called recursively. |
2344 | queue, total_read+(str3-buffer)) < 0) | 2265 | */ |
2345 | { | 2266 | int playlist_directory_tracksearch(const char* dirname, bool recurse, |
2346 | result = -5; | 2267 | int (*callback)(char*, void*), |
2347 | goto out; | 2268 | void* context) |
2348 | } | 2269 | { |
2349 | 2270 | char buf[MAX_PATH+1]; | |
2350 | playlist->last_insert_pos = last_position; | 2271 | int result = 0; |
2272 | int num_files = 0; | ||
2273 | int i;; | ||
2274 | struct tree_context* tc = tree_get_context(); | ||
2275 | struct tree_cache* cache = &tc->cache; | ||
2276 | int old_dirfilter = *(tc->dirfilter); | ||
2351 | 2277 | ||
2352 | break; | 2278 | if (!callback) |
2353 | } | 2279 | return -1; |
2354 | case PLAYLIST_COMMAND_DELETE: | ||
2355 | { | ||
2356 | /* str1=position */ | ||
2357 | int position; | ||
2358 | |||
2359 | if (!str1) | ||
2360 | { | ||
2361 | result = -6; | ||
2362 | exit_loop = true; | ||
2363 | break; | ||
2364 | } | ||
2365 | |||
2366 | position = atoi(str1); | ||
2367 | |||
2368 | if (remove_track_from_playlist(playlist, position, | ||
2369 | false) < 0) | ||
2370 | { | ||
2371 | result = -7; | ||
2372 | goto out; | ||
2373 | } | ||
2374 | 2280 | ||
2375 | break; | 2281 | /* use the tree browser dircache to load files */ |
2376 | } | 2282 | *(tc->dirfilter) = SHOW_ALL; |
2377 | case PLAYLIST_COMMAND_SHUFFLE: | ||
2378 | { | ||
2379 | /* str1=seed str2=first_index */ | ||
2380 | int seed; | ||
2381 | |||
2382 | if (!str1 || !str2) | ||
2383 | { | ||
2384 | result = -8; | ||
2385 | exit_loop = true; | ||
2386 | break; | ||
2387 | } | ||
2388 | |||
2389 | if (!sorted) | ||
2390 | { | ||
2391 | /* Always sort list before shuffling */ | ||
2392 | sort_playlist(playlist, false, false); | ||
2393 | } | ||
2394 | 2283 | ||
2395 | seed = atoi(str1); | 2284 | if (ft_load(tc, dirname) < 0) |
2396 | playlist->first_index = atoi(str2); | 2285 | { |
2397 | 2286 | splash(HZ*2, ID2P(LANG_PLAYLIST_DIRECTORY_ACCESS_ERROR)); | |
2398 | if (randomise_playlist(playlist, seed, false, | 2287 | *(tc->dirfilter) = old_dirfilter; |
2399 | false) < 0) | 2288 | return -1; |
2400 | { | 2289 | } |
2401 | result = -9; | ||
2402 | goto out; | ||
2403 | } | ||
2404 | sorted = false; | ||
2405 | break; | ||
2406 | } | ||
2407 | case PLAYLIST_COMMAND_UNSHUFFLE: | ||
2408 | { | ||
2409 | /* str1=first_index */ | ||
2410 | if (!str1) | ||
2411 | { | ||
2412 | result = -10; | ||
2413 | exit_loop = true; | ||
2414 | break; | ||
2415 | } | ||
2416 | |||
2417 | playlist->first_index = atoi(str1); | ||
2418 | |||
2419 | if (sort_playlist(playlist, false, false) < 0) | ||
2420 | { | ||
2421 | result = -11; | ||
2422 | goto out; | ||
2423 | } | ||
2424 | 2290 | ||
2425 | sorted = true; | 2291 | num_files = tc->filesindir; |
2426 | break; | ||
2427 | } | ||
2428 | case PLAYLIST_COMMAND_RESET: | ||
2429 | { | ||
2430 | playlist->last_insert_pos = -1; | ||
2431 | break; | ||
2432 | } | ||
2433 | case PLAYLIST_COMMAND_COMMENT: | ||
2434 | default: | ||
2435 | break; | ||
2436 | } | ||
2437 | 2292 | ||
2438 | newline = true; | 2293 | /* we've overwritten the dircache so tree browser will need to be |
2294 | reloaded */ | ||
2295 | reload_directory(); | ||
2439 | 2296 | ||
2440 | /* to ignore any extra newlines */ | 2297 | for (i=0; i<num_files; i++) |
2441 | current_command = PLAYLIST_COMMAND_COMMENT; | 2298 | { |
2442 | } | 2299 | /* user abort */ |
2443 | else if(newline) | 2300 | if (action_userabort(TIMEOUT_NOBLOCK)) |
2444 | { | 2301 | { |
2445 | newline = false; | 2302 | result = -1; |
2303 | break; | ||
2304 | } | ||
2446 | 2305 | ||
2447 | /* first non-comment line must always specify playlist */ | 2306 | struct entry *files = core_get_data(cache->entries_handle); |
2448 | if (first && *p != 'P' && *p != '#') | 2307 | if (files[i].attr & ATTR_DIRECTORY) |
2308 | { | ||
2309 | if (recurse) | ||
2310 | { | ||
2311 | /* recursively add directories */ | ||
2312 | if (path_append(buf, dirname, files[i].name, sizeof(buf)) | ||
2313 | >= sizeof(buf)) | ||
2449 | { | 2314 | { |
2450 | result = -12; | 2315 | continue; |
2451 | exit_loop = true; | ||
2452 | break; | ||
2453 | } | 2316 | } |
2454 | 2317 | ||
2455 | switch (*p) | 2318 | result = playlist_directory_tracksearch(buf, recurse, |
2456 | { | 2319 | callback, context); |
2457 | case 'P': | 2320 | if (result < 0) |
2458 | /* playlist can only be specified once */ | 2321 | break; |
2459 | if (!first) | ||
2460 | { | ||
2461 | result = -13; | ||
2462 | exit_loop = true; | ||
2463 | break; | ||
2464 | } | ||
2465 | 2322 | ||
2466 | current_command = PLAYLIST_COMMAND_PLAYLIST; | 2323 | /* we now need to reload our current directory */ |
2467 | break; | 2324 | if(ft_load(tc, dirname) < 0) |
2468 | case 'A': | 2325 | { |
2469 | current_command = PLAYLIST_COMMAND_ADD; | 2326 | result = -1; |
2470 | break; | 2327 | break; |
2471 | case 'Q': | ||
2472 | current_command = PLAYLIST_COMMAND_QUEUE; | ||
2473 | break; | ||
2474 | case 'D': | ||
2475 | current_command = PLAYLIST_COMMAND_DELETE; | ||
2476 | break; | ||
2477 | case 'S': | ||
2478 | current_command = PLAYLIST_COMMAND_SHUFFLE; | ||
2479 | break; | ||
2480 | case 'U': | ||
2481 | current_command = PLAYLIST_COMMAND_UNSHUFFLE; | ||
2482 | break; | ||
2483 | case 'R': | ||
2484 | current_command = PLAYLIST_COMMAND_RESET; | ||
2485 | break; | ||
2486 | case '#': | ||
2487 | current_command = PLAYLIST_COMMAND_COMMENT; | ||
2488 | break; | ||
2489 | default: | ||
2490 | result = -14; | ||
2491 | exit_loop = true; | ||
2492 | break; | ||
2493 | } | 2328 | } |
2494 | 2329 | ||
2495 | str_count = -1; | 2330 | num_files = tc->filesindir; |
2496 | str1 = NULL; | 2331 | if (!num_files) |
2497 | str2 = NULL; | ||
2498 | str3 = NULL; | ||
2499 | } | ||
2500 | else if(current_command != PLAYLIST_COMMAND_COMMENT) | ||
2501 | { | ||
2502 | /* all control file strings are separated with a colon. | ||
2503 | Replace the colon with 0 to get proper strings that can be | ||
2504 | used by commands above */ | ||
2505 | if (*p == ':') | ||
2506 | { | 2332 | { |
2507 | *p = '\0'; | 2333 | result = -1; |
2508 | str_count++; | 2334 | break; |
2509 | |||
2510 | if ((count+1) < nread) | ||
2511 | { | ||
2512 | switch (str_count) | ||
2513 | { | ||
2514 | case 0: | ||
2515 | str1 = p+1; | ||
2516 | break; | ||
2517 | case 1: | ||
2518 | str2 = p+1; | ||
2519 | break; | ||
2520 | case 2: | ||
2521 | str3 = p+1; | ||
2522 | break; | ||
2523 | default: | ||
2524 | /* allow last string to contain colons */ | ||
2525 | *p = ':'; | ||
2526 | break; | ||
2527 | } | ||
2528 | } | ||
2529 | } | 2335 | } |
2530 | } | 2336 | } |
2337 | else | ||
2338 | continue; | ||
2531 | } | 2339 | } |
2532 | 2340 | else if ((files[i].attr & FILE_ATTR_MASK) == FILE_ATTR_AUDIO) | |
2533 | if (result < 0) | ||
2534 | { | ||
2535 | splashf(HZ*2, "Err: %d, %s", result, str(LANG_PLAYLIST_CONTROL_INVALID)); | ||
2536 | goto out; | ||
2537 | } | ||
2538 | |||
2539 | if (useraborted) | ||
2540 | { | ||
2541 | splash(HZ*2, ID2P(LANG_CANCEL)); | ||
2542 | result = -1; | ||
2543 | goto out; | ||
2544 | } | ||
2545 | if (!newline || (exit_loop && count<nread)) | ||
2546 | { | 2341 | { |
2547 | if ((total_read + count) >= control_file_size) | 2342 | if (path_append(buf, dirname, files[i].name, sizeof(buf)) |
2343 | >= sizeof(buf)) | ||
2548 | { | 2344 | { |
2549 | /* no newline at end of control file */ | 2345 | continue; |
2550 | splashf(HZ*2, "Err: EOF, %s", str(LANG_PLAYLIST_CONTROL_INVALID)); | ||
2551 | result = -15; | ||
2552 | goto out; | ||
2553 | } | 2346 | } |
2554 | 2347 | ||
2555 | /* We didn't end on a newline or we exited loop prematurely. | 2348 | if (callback(buf, context) != 0) |
2556 | Either way, re-read the remainder. */ | 2349 | { |
2557 | count = last_newline; | 2350 | result = -1; |
2558 | lseek(playlist->control_fd, total_read+count, SEEK_SET); | 2351 | break; |
2559 | } | 2352 | } |
2560 | |||
2561 | total_read += count; | ||
2562 | |||
2563 | if (first) | ||
2564 | /* still looking for header */ | ||
2565 | nread = read(playlist->control_fd, buffer, | ||
2566 | PLAYLIST_COMMAND_SIZE<buflen?PLAYLIST_COMMAND_SIZE:buflen); | ||
2567 | else | ||
2568 | nread = read(playlist->control_fd, buffer, buflen); | ||
2569 | 2353 | ||
2570 | /* Terminate on EOF */ | 2354 | /* let the other threads work */ |
2571 | if(nread <= 0) | 2355 | yield(); |
2572 | { | ||
2573 | break; | ||
2574 | } | 2356 | } |
2575 | } | 2357 | } |
2576 | 2358 | ||
2577 | #ifdef HAVE_DIRCACHE | 2359 | /* restore dirfilter */ |
2578 | queue_post(&playlist_queue, PLAYLIST_LOAD_POINTERS, 0); | 2360 | *(tc->dirfilter) = old_dirfilter; |
2579 | #endif | ||
2580 | 2361 | ||
2581 | out: | ||
2582 | talk_buffer_set_policy(TALK_BUFFER_DEFAULT); | ||
2583 | core_free(handle); | ||
2584 | return result; | 2362 | return result; |
2585 | } | 2363 | } |
2586 | 2364 | ||
2587 | /* | ||
2588 | * Add track to in_ram playlist. Used when playing directories. | ||
2589 | */ | ||
2590 | int playlist_add(const char *filename) | ||
2591 | { | ||
2592 | struct playlist_info* playlist = ¤t_playlist; | ||
2593 | int len = strlen(filename); | ||
2594 | |||
2595 | if((len+1 > playlist->buffer_size - playlist->buffer_end_pos) || | ||
2596 | (playlist->amount >= playlist->max_playlist_size)) | ||
2597 | { | ||
2598 | display_buffer_full(); | ||
2599 | return -1; | ||
2600 | } | ||
2601 | |||
2602 | playlist->indices[playlist->amount] = playlist->buffer_end_pos; | ||
2603 | #ifdef HAVE_DIRCACHE | ||
2604 | copy_filerefs(&playlist->dcfrefs[playlist->amount], NULL, 1); | ||
2605 | #endif | ||
2606 | |||
2607 | playlist->amount++; | ||
2608 | |||
2609 | strcpy((char*)&playlist->buffer[playlist->buffer_end_pos], filename); | ||
2610 | playlist->buffer_end_pos += len; | ||
2611 | playlist->buffer[playlist->buffer_end_pos++] = '\0'; | ||
2612 | 2365 | ||
2613 | return 0; | 2366 | struct playlist_info *playlist_get_current(void) |
2367 | { | ||
2368 | return ¤t_playlist; | ||
2614 | } | 2369 | } |
2615 | 2370 | ||
2616 | /* shuffle newly created playlist using random seed. */ | 2371 | /* Returns index of current playing track for display purposes. This value |
2617 | int playlist_shuffle(int random_seed, int start_index) | 2372 | should not be used for resume purposes as it doesn't represent the actual |
2373 | index into the playlist */ | ||
2374 | int playlist_get_display_index(void) | ||
2618 | { | 2375 | { |
2619 | struct playlist_info* playlist = ¤t_playlist; | 2376 | struct playlist_info* playlist = ¤t_playlist; |
2620 | 2377 | ||
2621 | bool start_current = false; | 2378 | /* first_index should always be index 0 for display purposes */ |
2622 | 2379 | int index = rotate_index(playlist, playlist->index); | |
2623 | if (start_index >= 0 && global_settings.play_selected) | ||
2624 | { | ||
2625 | /* store the seek position before the shuffle */ | ||
2626 | playlist->index = playlist->first_index = start_index; | ||
2627 | start_current = true; | ||
2628 | } | ||
2629 | |||
2630 | randomise_playlist(playlist, random_seed, start_current, true); | ||
2631 | 2380 | ||
2632 | return playlist->index; | 2381 | return (index+1); |
2633 | } | 2382 | } |
2634 | 2383 | ||
2635 | /* returns the crc32 of the filename of the track at the specified index */ | 2384 | /* returns the crc32 of the filename of the track at the specified index */ |
@@ -2650,212 +2399,48 @@ unsigned int playlist_get_filename_crc32(struct playlist_info *playlist, | |||
2650 | return crc_32(basename, strlen(basename), -1); | 2399 | return crc_32(basename, strlen(basename), -1); |
2651 | } | 2400 | } |
2652 | 2401 | ||
2653 | /* resume a playlist track with the given crc_32 of the track name. */ | 2402 | /* returns index of first track in playlist */ |
2654 | void playlist_resume_track(int start_index, unsigned int crc, | 2403 | int playlist_get_first_index(const struct playlist_info* playlist) |
2655 | unsigned long elapsed, unsigned long offset) | ||
2656 | { | ||
2657 | int i; | ||
2658 | unsigned int tmp_crc; | ||
2659 | struct playlist_info* playlist = ¤t_playlist; | ||
2660 | tmp_crc = playlist_get_filename_crc32(playlist, start_index); | ||
2661 | if (tmp_crc == crc) | ||
2662 | { | ||
2663 | playlist_start(start_index, elapsed, offset); | ||
2664 | return; | ||
2665 | } | ||
2666 | |||
2667 | for (i = 0 ; i < playlist->amount; i++) | ||
2668 | { | ||
2669 | tmp_crc = playlist_get_filename_crc32(playlist, i); | ||
2670 | if (tmp_crc == crc) | ||
2671 | { | ||
2672 | playlist_start(i, elapsed, offset); | ||
2673 | return; | ||
2674 | } | ||
2675 | } | ||
2676 | |||
2677 | /* If we got here the file wasnt found, so start from the beginning */ | ||
2678 | playlist_start(0, 0, 0); | ||
2679 | } | ||
2680 | |||
2681 | /* start playing current playlist at specified index/offset */ | ||
2682 | void playlist_start(int start_index, unsigned long elapsed, | ||
2683 | unsigned long offset) | ||
2684 | { | ||
2685 | struct playlist_info* playlist = ¤t_playlist; | ||
2686 | |||
2687 | playlist->index = start_index; | ||
2688 | |||
2689 | playlist->started = true; | ||
2690 | sync_control(playlist, false); | ||
2691 | audio_play(elapsed, offset); | ||
2692 | audio_resume(); | ||
2693 | } | ||
2694 | |||
2695 | /* Returns false if 'steps' is out of bounds, else true */ | ||
2696 | bool playlist_check(int steps) | ||
2697 | { | 2404 | { |
2698 | struct playlist_info* playlist = ¤t_playlist; | 2405 | if (!playlist) |
2699 | 2406 | playlist = ¤t_playlist; | |
2700 | /* always allow folder navigation */ | ||
2701 | if (global_settings.next_folder && playlist->in_ram) | ||
2702 | return true; | ||
2703 | |||
2704 | int index = get_next_index(playlist, steps, -1); | ||
2705 | |||
2706 | if (index < 0 && steps >= 0 && global_settings.repeat_mode == REPEAT_SHUFFLE) | ||
2707 | index = get_next_index(playlist, steps, REPEAT_ALL); | ||
2708 | 2407 | ||
2709 | return (index >= 0); | 2408 | return playlist->first_index; |
2710 | } | 2409 | } |
2711 | 2410 | ||
2712 | /* get trackname of track that is "steps" away from current playing track. | 2411 | /* returns the playlist filename */ |
2713 | NULL is used to identify end of playlist */ | 2412 | char *playlist_get_name(const struct playlist_info* playlist, char *buf, |
2714 | const char* playlist_peek(int steps, char* buf, size_t buf_size) | 2413 | int buf_size) |
2715 | { | 2414 | { |
2716 | struct playlist_info* playlist = ¤t_playlist; | 2415 | if (!playlist) |
2717 | int seek; | 2416 | playlist = ¤t_playlist; |
2718 | char *temp_ptr; | ||
2719 | int index; | ||
2720 | bool control_file; | ||
2721 | |||
2722 | index = get_next_index(playlist, steps, -1); | ||
2723 | if (index < 0) | ||
2724 | return NULL; | ||
2725 | |||
2726 | /* Just testing - don't care about the file name */ | ||
2727 | if (!buf || !buf_size) | ||
2728 | return ""; | ||
2729 | 2417 | ||
2730 | control_file = playlist->indices[index] & PLAYLIST_INSERT_TYPE_MASK; | 2418 | strmemccpy(buf, playlist->filename, buf_size); |
2731 | seek = playlist->indices[index] & PLAYLIST_SEEK_MASK; | ||
2732 | 2419 | ||
2733 | if (get_filename(playlist, index, seek, control_file, buf, | 2420 | if (!buf[0]) |
2734 | buf_size) < 0) | ||
2735 | return NULL; | 2421 | return NULL; |
2736 | 2422 | ||
2737 | temp_ptr = buf; | 2423 | return buf; |
2738 | |||
2739 | if (!playlist->in_ram || control_file) | ||
2740 | { | ||
2741 | /* remove bogus dirs from beginning of path | ||
2742 | (workaround for buggy playlist creation tools) */ | ||
2743 | while (temp_ptr) | ||
2744 | { | ||
2745 | if (file_exists(temp_ptr)) | ||
2746 | break; | ||
2747 | |||
2748 | temp_ptr = strchr(temp_ptr+1, '/'); | ||
2749 | } | ||
2750 | |||
2751 | if (!temp_ptr) | ||
2752 | { | ||
2753 | /* Even though this is an invalid file, we still need to pass a | ||
2754 | file name to the caller because NULL is used to indicate end | ||
2755 | of playlist */ | ||
2756 | return buf; | ||
2757 | } | ||
2758 | } | ||
2759 | |||
2760 | return temp_ptr; | ||
2761 | } | 2424 | } |
2762 | 2425 | ||
2763 | /* | 2426 | /* return size of buffer needed for playlist to initialize num_indices entries */ |
2764 | * Update indices as track has changed | 2427 | size_t playlist_get_required_bufsz(struct playlist_info* playlist, |
2765 | */ | 2428 | bool include_namebuf, |
2766 | int playlist_next(int steps) | 2429 | int num_indices) |
2767 | { | 2430 | { |
2768 | struct playlist_info* playlist = ¤t_playlist; | 2431 | size_t namebuf = 0; |
2769 | int index; | ||
2770 | |||
2771 | if ( (steps > 0) | ||
2772 | #ifdef AB_REPEAT_ENABLE | ||
2773 | && (global_settings.repeat_mode != REPEAT_AB) | ||
2774 | #endif | ||
2775 | && (global_settings.repeat_mode != REPEAT_ONE) ) | ||
2776 | { | ||
2777 | int i, j; | ||
2778 | |||
2779 | /* We need to delete all the queued songs */ | ||
2780 | for (i=0, j=steps; i<j; i++) | ||
2781 | { | ||
2782 | index = get_next_index(playlist, i, -1); | ||
2783 | |||
2784 | if (index >= 0 && playlist->indices[index] & PLAYLIST_QUEUE_MASK) | ||
2785 | { | ||
2786 | remove_track_from_playlist(playlist, index, true); | ||
2787 | steps--; /* one less track */ | ||
2788 | } | ||
2789 | } | ||
2790 | } | ||
2791 | |||
2792 | index = get_next_index(playlist, steps, -1); | ||
2793 | |||
2794 | if (index < 0) | ||
2795 | { | ||
2796 | /* end of playlist... or is it */ | ||
2797 | if (global_settings.repeat_mode == REPEAT_SHUFFLE && | ||
2798 | playlist->amount > 1) | ||
2799 | { | ||
2800 | /* Repeat shuffle mode. Re-shuffle playlist and resume play */ | ||
2801 | playlist->first_index = 0; | ||
2802 | sort_playlist(playlist, false, false); | ||
2803 | randomise_playlist(playlist, current_tick, false, true); | ||
2804 | |||
2805 | playlist->started = true; | ||
2806 | playlist->index = 0; | ||
2807 | index = 0; | ||
2808 | } | ||
2809 | else if (playlist->in_ram && global_settings.next_folder) | ||
2810 | { | ||
2811 | index = create_and_play_dir(steps, true); | ||
2812 | |||
2813 | if (index >= 0) | ||
2814 | { | ||
2815 | playlist->index = index; | ||
2816 | } | ||
2817 | } | ||
2818 | |||
2819 | return index; | ||
2820 | } | ||
2821 | |||
2822 | playlist->index = index; | ||
2823 | |||
2824 | if (playlist->last_insert_pos >= 0 && steps > 0) | ||
2825 | { | ||
2826 | /* check to see if we've gone beyond the last inserted track */ | ||
2827 | int cur = rotate_index(playlist, index); | ||
2828 | int last_pos = rotate_index(playlist, playlist->last_insert_pos); | ||
2829 | |||
2830 | if (cur > last_pos) | ||
2831 | { | ||
2832 | /* reset last inserted track */ | ||
2833 | playlist->last_insert_pos = -1; | ||
2834 | |||
2835 | if (playlist->control_fd >= 0) | ||
2836 | { | ||
2837 | int result = update_control(playlist, PLAYLIST_COMMAND_RESET, | ||
2838 | -1, -1, NULL, NULL, NULL); | ||
2839 | |||
2840 | if (result < 0) | ||
2841 | return result; | ||
2842 | |||
2843 | sync_control(playlist, false); | ||
2844 | } | ||
2845 | } | ||
2846 | } | ||
2847 | 2432 | ||
2848 | return index; | 2433 | if (!playlist) |
2849 | } | 2434 | playlist = ¤t_playlist; |
2850 | 2435 | ||
2851 | /* try playing next or previous folder */ | 2436 | size_t unit_size = sizeof (*playlist->indices); |
2852 | bool playlist_next_dir(int direction) | 2437 | #ifdef HAVE_DIRCACHE |
2853 | { | 2438 | unit_size += sizeof (*playlist->dcfrefs); |
2854 | /* not to mess up real playlists */ | 2439 | #endif |
2855 | if(!current_playlist.in_ram) | 2440 | if (include_namebuf) |
2856 | return false; | 2441 | namebuf = AVERAGE_FILENAME_LENGTH * global_settings.max_files_in_dir; |
2857 | 2442 | ||
2858 | return create_and_play_dir(direction, false) >= 0; | 2443 | return (num_indices * unit_size) + namebuf; |
2859 | } | 2444 | } |
2860 | 2445 | ||
2861 | /* Get resume info for current playing song. If return value is -1 then | 2446 | /* Get resume info for current playing song. If return value is -1 then |
@@ -2869,256 +2454,54 @@ int playlist_get_resume_info(int *resume_index) | |||
2869 | return 0; | 2454 | return 0; |
2870 | } | 2455 | } |
2871 | 2456 | ||
2872 | /* Update resume info for current playing song. Returns -1 on error. */ | 2457 | /* returns shuffle seed of playlist */ |
2873 | int playlist_update_resume_info(const struct mp3entry* id3) | 2458 | int playlist_get_seed(const struct playlist_info* playlist) |
2874 | { | 2459 | { |
2875 | struct playlist_info* playlist = ¤t_playlist; | 2460 | if (!playlist) |
2876 | 2461 | playlist = ¤t_playlist; | |
2877 | if (id3) | ||
2878 | { | ||
2879 | if (global_status.resume_index != playlist->index || | ||
2880 | global_status.resume_elapsed != id3->elapsed || | ||
2881 | global_status.resume_offset != id3->offset) | ||
2882 | { | ||
2883 | unsigned int crc = playlist_get_filename_crc32(playlist, | ||
2884 | playlist->index); | ||
2885 | global_status.resume_index = playlist->index; | ||
2886 | global_status.resume_crc32 = crc; | ||
2887 | global_status.resume_elapsed = id3->elapsed; | ||
2888 | global_status.resume_offset = id3->offset; | ||
2889 | status_save(); | ||
2890 | } | ||
2891 | } | ||
2892 | else | ||
2893 | { | ||
2894 | global_status.resume_index = -1; | ||
2895 | global_status.resume_crc32 = -1; | ||
2896 | global_status.resume_elapsed = -1; | ||
2897 | global_status.resume_offset = -1; | ||
2898 | status_save(); | ||
2899 | } | ||
2900 | 2462 | ||
2901 | return 0; | 2463 | return playlist->seed; |
2902 | } | 2464 | } |
2903 | 2465 | ||
2904 | /* Returns index of current playing track for display purposes. This value | 2466 | /* Fills info structure with information about track at specified index. |
2905 | should not be used for resume purposes as it doesn't represent the actual | 2467 | Returns 0 on success and -1 on failure */ |
2906 | index into the playlist */ | 2468 | int playlist_get_track_info(struct playlist_info* playlist, int index, |
2907 | int playlist_get_display_index(void) | 2469 | struct playlist_track_info* info) |
2908 | { | 2470 | { |
2909 | struct playlist_info* playlist = ¤t_playlist; | 2471 | int seek; |
2910 | 2472 | bool control_file; | |
2911 | /* first_index should always be index 0 for display purposes */ | ||
2912 | int index = rotate_index(playlist, playlist->index); | ||
2913 | |||
2914 | return (index+1); | ||
2915 | } | ||
2916 | 2473 | ||
2917 | /* returns number of tracks in current playlist */ | ||
2918 | int playlist_amount(void) | ||
2919 | { | ||
2920 | return playlist_amount_ex(NULL); | ||
2921 | } | ||
2922 | /* set playlist->last_shuffle_start to playlist->amount for | ||
2923 | PLAYLIST_INSERT_LAST_SHUFFLED command purposes*/ | ||
2924 | void playlist_set_last_shuffled_start(void) | ||
2925 | { | ||
2926 | struct playlist_info* playlist = ¤t_playlist; | ||
2927 | playlist->last_shuffled_start = playlist->amount; | ||
2928 | } | ||
2929 | /* | ||
2930 | * Create a new playlist If playlist is not NULL then we're loading a | ||
2931 | * playlist off disk for viewing/editing. The index_buffer is used to store | ||
2932 | * playlist indices (required for and only used if !current playlist). The | ||
2933 | * temp_buffer (if not NULL) is used as a scratchpad when loading indices. | ||
2934 | */ | ||
2935 | int playlist_create_ex(struct playlist_info* playlist, | ||
2936 | const char* dir, const char* file, | ||
2937 | void* index_buffer, int index_buffer_size, | ||
2938 | void* temp_buffer, int temp_buffer_size) | ||
2939 | { | ||
2940 | if (!playlist) | 2474 | if (!playlist) |
2941 | playlist = ¤t_playlist; | 2475 | playlist = ¤t_playlist; |
2942 | else | ||
2943 | { | ||
2944 | /* Initialize playlist structure */ | ||
2945 | int r = rand() % 10; | ||
2946 | playlist->current = false; | ||
2947 | |||
2948 | /* Use random name for control file */ | ||
2949 | snprintf(playlist->control_filename, sizeof(playlist->control_filename), | ||
2950 | "%s.%d", PLAYLIST_CONTROL_FILE, r); | ||
2951 | playlist->fd = -1; | ||
2952 | playlist->control_fd = -1; | ||
2953 | |||
2954 | if (index_buffer) | ||
2955 | { | ||
2956 | int num_indices = index_buffer_size / | ||
2957 | playlist_get_required_bufsz(playlist, false, 1); | ||
2958 | |||
2959 | if (num_indices > global_settings.max_files_in_playlist) | ||
2960 | num_indices = global_settings.max_files_in_playlist; | ||
2961 | |||
2962 | playlist->max_playlist_size = num_indices; | ||
2963 | playlist->indices = index_buffer; | ||
2964 | #ifdef HAVE_DIRCACHE | ||
2965 | playlist->dcfrefs = (void *)&playlist->indices[num_indices]; | ||
2966 | #endif | ||
2967 | } | ||
2968 | else | ||
2969 | { | ||
2970 | playlist->max_playlist_size = current_playlist.max_playlist_size; | ||
2971 | playlist->indices = current_playlist.indices; | ||
2972 | #ifdef HAVE_DIRCACHE | ||
2973 | playlist->dcfrefs = current_playlist.dcfrefs; | ||
2974 | #endif | ||
2975 | } | ||
2976 | |||
2977 | playlist->buffer_size = 0; | ||
2978 | playlist->buffer_handle = -1; | ||
2979 | playlist->buffer = NULL; | ||
2980 | playlist->control_mutex = &created_playlist_mutex; | ||
2981 | } | ||
2982 | |||
2983 | new_playlist(playlist, dir, file); | ||
2984 | 2476 | ||
2985 | if (file) | 2477 | if (index < 0 || index >= playlist->amount) |
2986 | /* load the playlist file */ | ||
2987 | add_indices_to_playlist(playlist, temp_buffer, temp_buffer_size); | ||
2988 | |||
2989 | return 0; | ||
2990 | } | ||
2991 | |||
2992 | /* | ||
2993 | * Set the specified playlist as the current. | ||
2994 | * NOTE: You will get undefined behaviour if something is already playing so | ||
2995 | * remember to stop before calling this. Also, this call will | ||
2996 | * effectively close your playlist, making it unusable. | ||
2997 | */ | ||
2998 | int playlist_set_current(struct playlist_info* playlist) | ||
2999 | { | ||
3000 | if (!playlist || (check_control(playlist) < 0)) | ||
3001 | return -1; | 2478 | return -1; |
3002 | 2479 | ||
3003 | empty_playlist(¤t_playlist, false); | 2480 | control_file = playlist->indices[index] & PLAYLIST_INSERT_TYPE_MASK; |
3004 | 2481 | seek = playlist->indices[index] & PLAYLIST_SEEK_MASK; | |
3005 | strmemccpy(current_playlist.filename, playlist->filename, | ||
3006 | sizeof(current_playlist.filename)); | ||
3007 | |||
3008 | current_playlist.utf8 = playlist->utf8; | ||
3009 | current_playlist.fd = playlist->fd; | ||
3010 | 2482 | ||
3011 | close(playlist->control_fd); | 2483 | if (get_filename(playlist, index, seek, control_file, info->filename, |
3012 | playlist->control_fd = -1; | 2484 | sizeof(info->filename)) < 0) |
3013 | close(current_playlist.control_fd); | ||
3014 | current_playlist.control_fd = -1; | ||
3015 | remove(current_playlist.control_filename); | ||
3016 | current_playlist.control_created = false; | ||
3017 | if (rename(playlist->control_filename, | ||
3018 | current_playlist.control_filename) < 0) | ||
3019 | return -1; | ||
3020 | current_playlist.control_fd = open(current_playlist.control_filename, | ||
3021 | O_RDWR); | ||
3022 | if (current_playlist.control_fd < 0) | ||
3023 | return -1; | 2485 | return -1; |
3024 | current_playlist.control_created = true; | ||
3025 | 2486 | ||
3026 | current_playlist.dirlen = playlist->dirlen; | 2487 | info->attr = 0; |
3027 | 2488 | ||
3028 | if (playlist->indices && playlist->indices != current_playlist.indices) | 2489 | if (control_file) |
3029 | { | 2490 | { |
3030 | memcpy((void*)current_playlist.indices, (void*)playlist->indices, | 2491 | if (playlist->indices[index] & PLAYLIST_QUEUE_MASK) |
3031 | playlist->max_playlist_size*sizeof(*playlist->indices)); | 2492 | info->attr |= PLAYLIST_ATTR_QUEUED; |
3032 | #ifdef HAVE_DIRCACHE | 2493 | else |
3033 | copy_filerefs(current_playlist.dcfrefs, playlist->dcfrefs, | 2494 | info->attr |= PLAYLIST_ATTR_INSERTED; |
3034 | playlist->max_playlist_size); | ||
3035 | #endif | ||
3036 | } | ||
3037 | |||
3038 | current_playlist.first_index = playlist->first_index; | ||
3039 | current_playlist.amount = playlist->amount; | ||
3040 | current_playlist.last_insert_pos = playlist->last_insert_pos; | ||
3041 | current_playlist.seed = playlist->seed; | ||
3042 | current_playlist.shuffle_modified = playlist->shuffle_modified; | ||
3043 | current_playlist.deleted = playlist->deleted; | ||
3044 | current_playlist.num_inserted_tracks = playlist->num_inserted_tracks; | ||
3045 | |||
3046 | memcpy(current_playlist.control_cache, playlist->control_cache, | ||
3047 | sizeof(current_playlist.control_cache)); | ||
3048 | current_playlist.num_cached = playlist->num_cached; | ||
3049 | current_playlist.pending_control_sync = playlist->pending_control_sync; | ||
3050 | |||
3051 | return 0; | ||
3052 | } | ||
3053 | struct playlist_info *playlist_get_current(void) | ||
3054 | { | ||
3055 | return ¤t_playlist; | ||
3056 | } | ||
3057 | /* | ||
3058 | * Close files and delete control file for non-current playlist. | ||
3059 | */ | ||
3060 | void playlist_close(struct playlist_info* playlist) | ||
3061 | { | ||
3062 | if (!playlist) | ||
3063 | return; | ||
3064 | |||
3065 | if (playlist->fd >= 0) { | ||
3066 | close(playlist->fd); | ||
3067 | playlist->fd = -1; | ||
3068 | } | ||
3069 | |||
3070 | if (playlist->control_fd >= 0) { | ||
3071 | close(playlist->control_fd); | ||
3072 | playlist->control_fd = -1; | ||
3073 | } | ||
3074 | |||
3075 | if (playlist->control_created) { | ||
3076 | remove(playlist->control_filename); | ||
3077 | playlist->control_created = false; | ||
3078 | } | ||
3079 | } | ||
3080 | |||
3081 | void playlist_sync(struct playlist_info* playlist) | ||
3082 | { | ||
3083 | if (!playlist) | ||
3084 | playlist = ¤t_playlist; | ||
3085 | |||
3086 | sync_control(playlist, false); | ||
3087 | if ((audio_status() & AUDIO_STATUS_PLAY) && playlist->started) | ||
3088 | audio_flush_and_reload_tracks(); | ||
3089 | |||
3090 | #ifdef HAVE_DIRCACHE | ||
3091 | queue_post(&playlist_queue, PLAYLIST_LOAD_POINTERS, 0); | ||
3092 | #endif | ||
3093 | } | ||
3094 | |||
3095 | /* | ||
3096 | * Insert track into playlist at specified position (or one of the special | ||
3097 | * positions). Returns position where track was inserted or -1 if error. | ||
3098 | */ | ||
3099 | int playlist_insert_track(struct playlist_info* playlist, const char *filename, | ||
3100 | int position, bool queue, bool sync) | ||
3101 | { | ||
3102 | int result; | ||
3103 | |||
3104 | if (!playlist) | ||
3105 | playlist = ¤t_playlist; | ||
3106 | 2495 | ||
3107 | if (check_control(playlist) < 0) | ||
3108 | { | ||
3109 | splash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_ACCESS_ERROR)); | ||
3110 | return -1; | ||
3111 | } | 2496 | } |
3112 | 2497 | ||
3113 | result = add_track_to_playlist(playlist, filename, position, queue, -1); | 2498 | if (playlist->indices[index] & PLAYLIST_SKIPPED) |
2499 | info->attr |= PLAYLIST_ATTR_SKIPPED; | ||
3114 | 2500 | ||
3115 | /* Check if we want manually sync later. For example when adding | 2501 | info->index = index; |
3116 | * bunch of files from tagcache, syncing after every file wouldn't be | 2502 | info->display_index = rotate_index(playlist, index) + 1; |
3117 | * a good thing to do. */ | ||
3118 | if (sync && result >= 0) | ||
3119 | playlist_sync(playlist); | ||
3120 | 2503 | ||
3121 | return result; | 2504 | return 0; |
3122 | } | 2505 | } |
3123 | 2506 | ||
3124 | /* | 2507 | /* |
@@ -3160,7 +2543,7 @@ int playlist_insert_directory(struct playlist_info* playlist, | |||
3160 | context.position = position; | 2543 | context.position = position; |
3161 | context.queue = queue; | 2544 | context.queue = queue; |
3162 | context.count = 0; | 2545 | context.count = 0; |
3163 | 2546 | ||
3164 | cpu_boost(true); | 2547 | cpu_boost(true); |
3165 | 2548 | ||
3166 | result = playlist_directory_tracksearch(dirname, recurse, | 2549 | result = playlist_directory_tracksearch(dirname, recurse, |
@@ -3243,11 +2626,11 @@ int playlist_insert_playlist(struct playlist_info* playlist, const char *filenam | |||
3243 | /* user abort */ | 2626 | /* user abort */ |
3244 | if (action_userabort(TIMEOUT_NOBLOCK)) | 2627 | if (action_userabort(TIMEOUT_NOBLOCK)) |
3245 | break; | 2628 | break; |
3246 | 2629 | ||
3247 | if (temp_buf[0] != '#' && temp_buf[0] != '\0') | 2630 | if (temp_buf[0] != '#' && temp_buf[0] != '\0') |
3248 | { | 2631 | { |
3249 | int insert_pos; | 2632 | int insert_pos; |
3250 | 2633 | ||
3251 | if (!utf8) | 2634 | if (!utf8) |
3252 | { | 2635 | { |
3253 | /* Use trackname as a temporay buffer. Note that trackname must | 2636 | /* Use trackname as a temporay buffer. Note that trackname must |
@@ -3315,12 +2698,13 @@ int playlist_insert_playlist(struct playlist_info* playlist, const char *filenam | |||
3315 | } | 2698 | } |
3316 | 2699 | ||
3317 | /* | 2700 | /* |
3318 | * Delete track at specified index. If index is PLAYLIST_DELETE_CURRENT then | 2701 | * Insert track into playlist at specified position (or one of the special |
3319 | * we want to delete the current playing track. | 2702 | * positions). Returns position where track was inserted or -1 if error. |
3320 | */ | 2703 | */ |
3321 | int playlist_delete(struct playlist_info* playlist, int index) | 2704 | int playlist_insert_track(struct playlist_info* playlist, const char *filename, |
2705 | int position, bool queue, bool sync) | ||
3322 | { | 2706 | { |
3323 | int result = 0; | 2707 | int result; |
3324 | 2708 | ||
3325 | if (!playlist) | 2709 | if (!playlist) |
3326 | playlist = ¤t_playlist; | 2710 | playlist = ¤t_playlist; |
@@ -3331,18 +2715,32 @@ int playlist_delete(struct playlist_info* playlist, int index) | |||
3331 | return -1; | 2715 | return -1; |
3332 | } | 2716 | } |
3333 | 2717 | ||
3334 | if (index == PLAYLIST_DELETE_CURRENT) | 2718 | result = add_track_to_playlist(playlist, filename, position, queue, -1); |
3335 | index = playlist->index; | ||
3336 | 2719 | ||
3337 | result = remove_track_from_playlist(playlist, index, true); | 2720 | /* Check if we want manually sync later. For example when adding |
3338 | 2721 | * bunch of files from tagcache, syncing after every file wouldn't be | |
3339 | if (result != -1 && (audio_status() & AUDIO_STATUS_PLAY) && | 2722 | * a good thing to do. */ |
3340 | playlist->started) | 2723 | if (sync && result >= 0) |
3341 | audio_flush_and_reload_tracks(); | 2724 | playlist_sync(playlist); |
3342 | 2725 | ||
3343 | return result; | 2726 | return result; |
3344 | } | 2727 | } |
3345 | 2728 | ||
2729 | |||
2730 | /* returns true if playlist has been modified */ | ||
2731 | bool playlist_modified(const struct playlist_info* playlist) | ||
2732 | { | ||
2733 | if (!playlist) | ||
2734 | playlist = ¤t_playlist; | ||
2735 | |||
2736 | if (playlist->shuffle_modified || | ||
2737 | playlist->deleted || | ||
2738 | playlist->num_inserted_tracks > 0) | ||
2739 | return true; | ||
2740 | |||
2741 | return false; | ||
2742 | } | ||
2743 | |||
3346 | /* | 2744 | /* |
3347 | * Move track at index to new_index. Tracks between the two are shifted | 2745 | * Move track at index to new_index. Tracks between the two are shifted |
3348 | * appropriately. Returns 0 on success and -1 on failure. | 2746 | * appropriately. Returns 0 on success and -1 on failure. |
@@ -3475,182 +2873,669 @@ int playlist_move(struct playlist_info* playlist, int index, int new_index) | |||
3475 | return result; | 2873 | return result; |
3476 | } | 2874 | } |
3477 | 2875 | ||
3478 | /* shuffle currently playing playlist */ | 2876 | /* returns full path of playlist (minus extension) */ |
3479 | int playlist_randomise(struct playlist_info* playlist, unsigned int seed, | 2877 | char *playlist_name(const struct playlist_info* playlist, char *buf, |
3480 | bool start_current) | 2878 | int buf_size) |
3481 | { | 2879 | { |
3482 | int result; | 2880 | char *sep; |
3483 | 2881 | ||
3484 | if (!playlist) | 2882 | if (!playlist) |
3485 | playlist = ¤t_playlist; | 2883 | playlist = ¤t_playlist; |
3486 | 2884 | ||
3487 | check_control(playlist); | 2885 | strmemccpy(buf, playlist->filename+playlist->dirlen, buf_size); |
3488 | 2886 | ||
3489 | result = randomise_playlist(playlist, seed, start_current, true); | 2887 | if (!buf[0]) |
2888 | return NULL; | ||
3490 | 2889 | ||
3491 | if (result != -1 && (audio_status() & AUDIO_STATUS_PLAY) && | 2890 | /* Remove extension */ |
3492 | playlist->started) | 2891 | sep = strrchr(buf, '.'); |
3493 | audio_flush_and_reload_tracks(); | 2892 | if (sep) |
2893 | *sep = 0; | ||
3494 | 2894 | ||
3495 | return result; | 2895 | return buf; |
3496 | } | 2896 | } |
3497 | 2897 | ||
3498 | /* sort currently playing playlist */ | 2898 | /* |
3499 | int playlist_sort(struct playlist_info* playlist, bool start_current) | 2899 | * Update indices as track has changed |
2900 | */ | ||
2901 | int playlist_next(int steps) | ||
3500 | { | 2902 | { |
3501 | int result; | 2903 | struct playlist_info* playlist = ¤t_playlist; |
2904 | int index; | ||
3502 | 2905 | ||
3503 | if (!playlist) | 2906 | if ( (steps > 0) |
3504 | playlist = ¤t_playlist; | 2907 | #ifdef AB_REPEAT_ENABLE |
2908 | && (global_settings.repeat_mode != REPEAT_AB) | ||
2909 | #endif | ||
2910 | && (global_settings.repeat_mode != REPEAT_ONE) ) | ||
2911 | { | ||
2912 | int i, j; | ||
3505 | 2913 | ||
3506 | check_control(playlist); | 2914 | /* We need to delete all the queued songs */ |
2915 | for (i=0, j=steps; i<j; i++) | ||
2916 | { | ||
2917 | index = get_next_index(playlist, i, -1); | ||
3507 | 2918 | ||
3508 | result = sort_playlist(playlist, start_current, true); | 2919 | if (index >= 0 && playlist->indices[index] & PLAYLIST_QUEUE_MASK) |
2920 | { | ||
2921 | remove_track_from_playlist(playlist, index, true); | ||
2922 | steps--; /* one less track */ | ||
2923 | } | ||
2924 | } | ||
2925 | } | ||
3509 | 2926 | ||
3510 | if (result != -1 && (audio_status() & AUDIO_STATUS_PLAY) && | 2927 | index = get_next_index(playlist, steps, -1); |
3511 | playlist->started) | ||
3512 | audio_flush_and_reload_tracks(); | ||
3513 | 2928 | ||
3514 | return result; | 2929 | if (index < 0) |
3515 | } | 2930 | { |
2931 | /* end of playlist... or is it */ | ||
2932 | if (global_settings.repeat_mode == REPEAT_SHUFFLE && | ||
2933 | playlist->amount > 1) | ||
2934 | { | ||
2935 | /* Repeat shuffle mode. Re-shuffle playlist and resume play */ | ||
2936 | playlist->first_index = 0; | ||
2937 | sort_playlist(playlist, false, false); | ||
2938 | randomise_playlist(playlist, current_tick, false, true); | ||
3516 | 2939 | ||
3517 | /* returns true if playlist has been modified */ | 2940 | playlist->started = true; |
3518 | bool playlist_modified(const struct playlist_info* playlist) | 2941 | playlist->index = 0; |
3519 | { | 2942 | index = 0; |
3520 | if (!playlist) | 2943 | } |
3521 | playlist = ¤t_playlist; | 2944 | else if (playlist->in_ram && global_settings.next_folder) |
2945 | { | ||
2946 | index = create_and_play_dir(steps, true); | ||
3522 | 2947 | ||
3523 | if (playlist->shuffle_modified || | 2948 | if (index >= 0) |
3524 | playlist->deleted || | 2949 | { |
3525 | playlist->num_inserted_tracks > 0) | 2950 | playlist->index = index; |
3526 | return true; | 2951 | } |
2952 | } | ||
3527 | 2953 | ||
3528 | return false; | 2954 | return index; |
3529 | } | 2955 | } |
3530 | 2956 | ||
3531 | /* returns index of first track in playlist */ | 2957 | playlist->index = index; |
3532 | int playlist_get_first_index(const struct playlist_info* playlist) | ||
3533 | { | ||
3534 | if (!playlist) | ||
3535 | playlist = ¤t_playlist; | ||
3536 | 2958 | ||
3537 | return playlist->first_index; | 2959 | if (playlist->last_insert_pos >= 0 && steps > 0) |
3538 | } | 2960 | { |
2961 | /* check to see if we've gone beyond the last inserted track */ | ||
2962 | int cur = rotate_index(playlist, index); | ||
2963 | int last_pos = rotate_index(playlist, playlist->last_insert_pos); | ||
3539 | 2964 | ||
3540 | /* returns shuffle seed of playlist */ | 2965 | if (cur > last_pos) |
3541 | int playlist_get_seed(const struct playlist_info* playlist) | 2966 | { |
3542 | { | 2967 | /* reset last inserted track */ |
3543 | if (!playlist) | 2968 | playlist->last_insert_pos = -1; |
3544 | playlist = ¤t_playlist; | ||
3545 | 2969 | ||
3546 | return playlist->seed; | 2970 | if (playlist->control_fd >= 0) |
2971 | { | ||
2972 | int result = update_control(playlist, PLAYLIST_COMMAND_RESET, | ||
2973 | -1, -1, NULL, NULL, NULL); | ||
2974 | |||
2975 | if (result < 0) | ||
2976 | return result; | ||
2977 | |||
2978 | sync_control(playlist, false); | ||
2979 | } | ||
2980 | } | ||
2981 | } | ||
2982 | |||
2983 | return index; | ||
3547 | } | 2984 | } |
3548 | 2985 | ||
3549 | /* returns number of tracks in playlist (includes queued/inserted tracks) */ | 2986 | /* try playing next or previous folder */ |
3550 | int playlist_amount_ex(const struct playlist_info* playlist) | 2987 | bool playlist_next_dir(int direction) |
3551 | { | 2988 | { |
3552 | if (!playlist) | 2989 | /* not to mess up real playlists */ |
3553 | playlist = ¤t_playlist; | 2990 | if(!current_playlist.in_ram) |
2991 | return false; | ||
3554 | 2992 | ||
3555 | return playlist->amount; | 2993 | return create_and_play_dir(direction, false) >= 0; |
3556 | } | 2994 | } |
3557 | 2995 | ||
3558 | /* returns full path of playlist (minus extension) */ | 2996 | /* get trackname of track that is "steps" away from current playing track. |
3559 | char *playlist_name(const struct playlist_info* playlist, char *buf, | 2997 | NULL is used to identify end of playlist */ |
3560 | int buf_size) | 2998 | const char* playlist_peek(int steps, char* buf, size_t buf_size) |
3561 | { | 2999 | { |
3562 | char *sep; | 3000 | struct playlist_info* playlist = ¤t_playlist; |
3001 | int seek; | ||
3002 | char *temp_ptr; | ||
3003 | int index; | ||
3004 | bool control_file; | ||
3563 | 3005 | ||
3564 | if (!playlist) | 3006 | index = get_next_index(playlist, steps, -1); |
3565 | playlist = ¤t_playlist; | 3007 | if (index < 0) |
3008 | return NULL; | ||
3566 | 3009 | ||
3567 | strmemccpy(buf, playlist->filename+playlist->dirlen, buf_size); | 3010 | /* Just testing - don't care about the file name */ |
3568 | 3011 | if (!buf || !buf_size) | |
3569 | if (!buf[0]) | 3012 | return ""; |
3013 | |||
3014 | control_file = playlist->indices[index] & PLAYLIST_INSERT_TYPE_MASK; | ||
3015 | seek = playlist->indices[index] & PLAYLIST_SEEK_MASK; | ||
3016 | |||
3017 | if (get_filename(playlist, index, seek, control_file, buf, | ||
3018 | buf_size) < 0) | ||
3570 | return NULL; | 3019 | return NULL; |
3571 | 3020 | ||
3572 | /* Remove extension */ | 3021 | temp_ptr = buf; |
3573 | sep = strrchr(buf, '.'); | ||
3574 | if (sep) | ||
3575 | *sep = 0; | ||
3576 | 3022 | ||
3577 | return buf; | 3023 | if (!playlist->in_ram || control_file) |
3024 | { | ||
3025 | /* remove bogus dirs from beginning of path | ||
3026 | (workaround for buggy playlist creation tools) */ | ||
3027 | while (temp_ptr) | ||
3028 | { | ||
3029 | if (file_exists(temp_ptr)) | ||
3030 | break; | ||
3031 | |||
3032 | temp_ptr = strchr(temp_ptr+1, '/'); | ||
3033 | } | ||
3034 | |||
3035 | if (!temp_ptr) | ||
3036 | { | ||
3037 | /* Even though this is an invalid file, we still need to pass a | ||
3038 | file name to the caller because NULL is used to indicate end | ||
3039 | of playlist */ | ||
3040 | return buf; | ||
3041 | } | ||
3042 | } | ||
3043 | |||
3044 | return temp_ptr; | ||
3578 | } | 3045 | } |
3579 | 3046 | ||
3580 | /* returns the playlist filename */ | 3047 | /* shuffle currently playing playlist */ |
3581 | char *playlist_get_name(const struct playlist_info* playlist, char *buf, | 3048 | int playlist_randomise(struct playlist_info* playlist, unsigned int seed, |
3582 | int buf_size) | 3049 | bool start_current) |
3583 | { | 3050 | { |
3051 | int result; | ||
3052 | |||
3584 | if (!playlist) | 3053 | if (!playlist) |
3585 | playlist = ¤t_playlist; | 3054 | playlist = ¤t_playlist; |
3586 | 3055 | ||
3587 | strmemccpy(buf, playlist->filename, buf_size); | 3056 | check_control(playlist); |
3588 | 3057 | ||
3589 | if (!buf[0]) | 3058 | result = randomise_playlist(playlist, seed, start_current, true); |
3590 | return NULL; | ||
3591 | 3059 | ||
3592 | return buf; | 3060 | if (result != -1 && (audio_status() & AUDIO_STATUS_PLAY) && |
3061 | playlist->started) | ||
3062 | audio_flush_and_reload_tracks(); | ||
3063 | |||
3064 | return result; | ||
3593 | } | 3065 | } |
3594 | 3066 | ||
3595 | /* return size of buffer needed for playlist to initialize num_indices entries */ | 3067 | /* |
3596 | size_t playlist_get_required_bufsz(struct playlist_info* playlist, | 3068 | * Removes all tracks, from the playlist, leaving the presently playing |
3597 | bool include_namebuf, | 3069 | * track queued. |
3598 | int num_indices) | 3070 | */ |
3071 | int playlist_remove_all_tracks(struct playlist_info *playlist) | ||
3599 | { | 3072 | { |
3600 | size_t namebuf = 0; | 3073 | int result; |
3601 | 3074 | ||
3602 | if (!playlist) | 3075 | if (playlist == NULL) |
3603 | playlist = ¤t_playlist; | 3076 | playlist = ¤t_playlist; |
3604 | 3077 | ||
3605 | size_t unit_size = sizeof (*playlist->indices); | 3078 | while (playlist->index > 0) |
3606 | #ifdef HAVE_DIRCACHE | 3079 | if ((result = remove_track_from_playlist(playlist, 0, true)) < 0) |
3607 | unit_size += sizeof (*playlist->dcfrefs); | 3080 | return result; |
3608 | #endif | ||
3609 | if (include_namebuf) | ||
3610 | namebuf = AVERAGE_FILENAME_LENGTH * global_settings.max_files_in_dir; | ||
3611 | 3081 | ||
3612 | return (num_indices * unit_size) + namebuf; | 3082 | while (playlist->amount > 1) |
3083 | if ((result = remove_track_from_playlist(playlist, 1, true)) < 0) | ||
3084 | return result; | ||
3085 | |||
3086 | if (playlist->amount == 1) { | ||
3087 | playlist->indices[0] |= PLAYLIST_QUEUED; | ||
3088 | } | ||
3089 | |||
3090 | return 0; | ||
3613 | } | 3091 | } |
3614 | 3092 | ||
3615 | /* Fills info structure with information about track at specified index. | 3093 | /* |
3616 | Returns 0 on success and -1 on failure */ | 3094 | * Restore the playlist state based on control file commands. Called to |
3617 | int playlist_get_track_info(struct playlist_info* playlist, int index, | 3095 | * resume playback after shutdown. |
3618 | struct playlist_track_info* info) | 3096 | */ |
3097 | int playlist_resume(void) | ||
3619 | { | 3098 | { |
3620 | int seek; | 3099 | struct playlist_info* playlist = ¤t_playlist; |
3621 | bool control_file; | 3100 | char *buffer; |
3101 | size_t buflen; | ||
3102 | int handle; | ||
3103 | int nread; | ||
3104 | int total_read = 0; | ||
3105 | int control_file_size = 0; | ||
3106 | bool first = true; | ||
3107 | bool sorted = true; | ||
3108 | int result = -1; | ||
3622 | 3109 | ||
3623 | if (!playlist) | 3110 | splash(0, ID2P(LANG_WAIT)); |
3624 | playlist = ¤t_playlist; | 3111 | if (core_allocatable() < (1 << 10)) |
3112 | talk_buffer_set_policy(TALK_BUFFER_LOOSE); /* back off voice buffer */ | ||
3625 | 3113 | ||
3626 | if (index < 0 || index >= playlist->amount) | 3114 | #ifdef HAVE_DIRCACHE |
3115 | dircache_wait(); /* we need the dircache to use the files in the playlist */ | ||
3116 | #endif | ||
3117 | |||
3118 | handle = alloc_tempbuf(&buflen); | ||
3119 | if (handle < 0) | ||
3120 | { | ||
3121 | splashf(HZ * 2, "%s(): OOM", __func__); | ||
3627 | return -1; | 3122 | return -1; |
3123 | } | ||
3628 | 3124 | ||
3629 | control_file = playlist->indices[index] & PLAYLIST_INSERT_TYPE_MASK; | 3125 | /* align buffer for faster load times */ |
3630 | seek = playlist->indices[index] & PLAYLIST_SEEK_MASK; | 3126 | buffer = core_get_data(handle); |
3127 | STORAGE_ALIGN_BUFFER(buffer, buflen); | ||
3128 | buflen = ALIGN_DOWN(buflen, 512); /* to avoid partial sector I/O */ | ||
3631 | 3129 | ||
3632 | if (get_filename(playlist, index, seek, control_file, info->filename, | 3130 | playlist_shutdown(); /* flush any cached control commands to disk */ |
3633 | sizeof(info->filename)) < 0) | 3131 | empty_playlist(playlist, true); |
3634 | return -1; | ||
3635 | 3132 | ||
3636 | info->attr = 0; | 3133 | playlist->control_fd = open(playlist->control_filename, O_RDWR); |
3134 | if (playlist->control_fd < 0) | ||
3135 | { | ||
3136 | splash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_ACCESS_ERROR)); | ||
3137 | goto out; | ||
3138 | } | ||
3139 | playlist->control_created = true; | ||
3637 | 3140 | ||
3638 | if (control_file) | 3141 | control_file_size = filesize(playlist->control_fd); |
3142 | if (control_file_size <= 0) | ||
3639 | { | 3143 | { |
3640 | if (playlist->indices[index] & PLAYLIST_QUEUE_MASK) | 3144 | splash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_ACCESS_ERROR)); |
3641 | info->attr |= PLAYLIST_ATTR_QUEUED; | 3145 | goto out; |
3146 | } | ||
3147 | |||
3148 | /* read a small amount first to get the header */ | ||
3149 | nread = read(playlist->control_fd, buffer, | ||
3150 | PLAYLIST_COMMAND_SIZE<buflen?PLAYLIST_COMMAND_SIZE:buflen); | ||
3151 | if(nread <= 0) | ||
3152 | { | ||
3153 | splash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_ACCESS_ERROR)); | ||
3154 | goto out; | ||
3155 | } | ||
3156 | |||
3157 | playlist->started = true; | ||
3158 | |||
3159 | while (1) | ||
3160 | { | ||
3161 | result = 0; | ||
3162 | int count; | ||
3163 | enum playlist_command current_command = PLAYLIST_COMMAND_COMMENT; | ||
3164 | int last_newline = 0; | ||
3165 | int str_count = -1; | ||
3166 | bool newline = true; | ||
3167 | bool exit_loop = false; | ||
3168 | char *p = buffer; | ||
3169 | char *str1 = NULL; | ||
3170 | char *str2 = NULL; | ||
3171 | char *str3 = NULL; | ||
3172 | unsigned long last_tick = current_tick; | ||
3173 | splash_progress_set_delay(HZ / 2); /* wait 1/2 sec before progress */ | ||
3174 | bool useraborted = false; | ||
3175 | |||
3176 | for(count=0; count<nread && !exit_loop && !useraborted; count++,p++) | ||
3177 | { | ||
3178 | /* Show a splash while we are loading. */ | ||
3179 | splash_progress((total_read + count), control_file_size, | ||
3180 | "%s (%s)", str(LANG_WAIT), str(LANG_OFF_ABORT)); | ||
3181 | if (TIME_AFTER(current_tick, last_tick + HZ/4)) | ||
3182 | { | ||
3183 | if (action_userabort(TIMEOUT_NOBLOCK)) | ||
3184 | { | ||
3185 | useraborted = true; | ||
3186 | break; | ||
3187 | } | ||
3188 | last_tick = current_tick; | ||
3189 | } | ||
3190 | /* Are we on a new line? */ | ||
3191 | if((*p == '\n') || (*p == '\r')) | ||
3192 | { | ||
3193 | *p = '\0'; | ||
3194 | |||
3195 | /* save last_newline in case we need to load more data */ | ||
3196 | last_newline = count; | ||
3197 | |||
3198 | switch (current_command) | ||
3199 | { | ||
3200 | case PLAYLIST_COMMAND_PLAYLIST: | ||
3201 | { | ||
3202 | /* str1=version str2=dir str3=file */ | ||
3203 | int version; | ||
3204 | |||
3205 | if (!str1) | ||
3206 | { | ||
3207 | result = -2; | ||
3208 | exit_loop = true; | ||
3209 | break; | ||
3210 | } | ||
3211 | |||
3212 | if (!str2) | ||
3213 | str2 = ""; | ||
3214 | |||
3215 | if (!str3) | ||
3216 | str3 = ""; | ||
3217 | |||
3218 | version = atoi(str1); | ||
3219 | |||
3220 | if (version != PLAYLIST_CONTROL_FILE_VERSION) | ||
3221 | { | ||
3222 | result = -3; | ||
3223 | goto out; | ||
3224 | } | ||
3225 | |||
3226 | update_playlist_filename(playlist, str2, str3); | ||
3227 | |||
3228 | if (str3[0] != '\0') | ||
3229 | { | ||
3230 | /* NOTE: add_indices_to_playlist() overwrites the | ||
3231 | audiobuf so we need to reload control file | ||
3232 | data */ | ||
3233 | add_indices_to_playlist(playlist, buffer, buflen); | ||
3234 | } | ||
3235 | else if (str2[0] != '\0') | ||
3236 | { | ||
3237 | playlist->in_ram = true; | ||
3238 | resume_directory(str2); | ||
3239 | } | ||
3240 | |||
3241 | /* load the rest of the data */ | ||
3242 | first = false; | ||
3243 | exit_loop = true; | ||
3244 | |||
3245 | break; | ||
3246 | } | ||
3247 | case PLAYLIST_COMMAND_ADD: | ||
3248 | case PLAYLIST_COMMAND_QUEUE: | ||
3249 | { | ||
3250 | /* str1=position str2=last_position str3=file */ | ||
3251 | int position, last_position; | ||
3252 | bool queue; | ||
3253 | |||
3254 | if (!str1 || !str2 || !str3) | ||
3255 | { | ||
3256 | result = -4; | ||
3257 | exit_loop = true; | ||
3258 | break; | ||
3259 | } | ||
3260 | |||
3261 | position = atoi(str1); | ||
3262 | last_position = atoi(str2); | ||
3263 | |||
3264 | queue = (current_command == PLAYLIST_COMMAND_ADD)? | ||
3265 | false:true; | ||
3266 | |||
3267 | /* seek position is based on str3's position in | ||
3268 | buffer */ | ||
3269 | if (add_track_to_playlist(playlist, str3, position, | ||
3270 | queue, total_read+(str3-buffer)) < 0) | ||
3271 | { | ||
3272 | result = -5; | ||
3273 | goto out; | ||
3274 | } | ||
3275 | |||
3276 | playlist->last_insert_pos = last_position; | ||
3277 | |||
3278 | break; | ||
3279 | } | ||
3280 | case PLAYLIST_COMMAND_DELETE: | ||
3281 | { | ||
3282 | /* str1=position */ | ||
3283 | int position; | ||
3284 | |||
3285 | if (!str1) | ||
3286 | { | ||
3287 | result = -6; | ||
3288 | exit_loop = true; | ||
3289 | break; | ||
3290 | } | ||
3291 | |||
3292 | position = atoi(str1); | ||
3293 | |||
3294 | if (remove_track_from_playlist(playlist, position, | ||
3295 | false) < 0) | ||
3296 | { | ||
3297 | result = -7; | ||
3298 | goto out; | ||
3299 | } | ||
3300 | |||
3301 | break; | ||
3302 | } | ||
3303 | case PLAYLIST_COMMAND_SHUFFLE: | ||
3304 | { | ||
3305 | /* str1=seed str2=first_index */ | ||
3306 | int seed; | ||
3307 | |||
3308 | if (!str1 || !str2) | ||
3309 | { | ||
3310 | result = -8; | ||
3311 | exit_loop = true; | ||
3312 | break; | ||
3313 | } | ||
3314 | |||
3315 | if (!sorted) | ||
3316 | { | ||
3317 | /* Always sort list before shuffling */ | ||
3318 | sort_playlist(playlist, false, false); | ||
3319 | } | ||
3320 | |||
3321 | seed = atoi(str1); | ||
3322 | playlist->first_index = atoi(str2); | ||
3323 | |||
3324 | if (randomise_playlist(playlist, seed, false, | ||
3325 | false) < 0) | ||
3326 | { | ||
3327 | result = -9; | ||
3328 | goto out; | ||
3329 | } | ||
3330 | sorted = false; | ||
3331 | break; | ||
3332 | } | ||
3333 | case PLAYLIST_COMMAND_UNSHUFFLE: | ||
3334 | { | ||
3335 | /* str1=first_index */ | ||
3336 | if (!str1) | ||
3337 | { | ||
3338 | result = -10; | ||
3339 | exit_loop = true; | ||
3340 | break; | ||
3341 | } | ||
3342 | |||
3343 | playlist->first_index = atoi(str1); | ||
3344 | |||
3345 | if (sort_playlist(playlist, false, false) < 0) | ||
3346 | { | ||
3347 | result = -11; | ||
3348 | goto out; | ||
3349 | } | ||
3350 | |||
3351 | sorted = true; | ||
3352 | break; | ||
3353 | } | ||
3354 | case PLAYLIST_COMMAND_RESET: | ||
3355 | { | ||
3356 | playlist->last_insert_pos = -1; | ||
3357 | break; | ||
3358 | } | ||
3359 | case PLAYLIST_COMMAND_COMMENT: | ||
3360 | default: | ||
3361 | break; | ||
3362 | } | ||
3363 | |||
3364 | newline = true; | ||
3365 | |||
3366 | /* to ignore any extra newlines */ | ||
3367 | current_command = PLAYLIST_COMMAND_COMMENT; | ||
3368 | } | ||
3369 | else if(newline) | ||
3370 | { | ||
3371 | newline = false; | ||
3372 | |||
3373 | /* first non-comment line must always specify playlist */ | ||
3374 | if (first && *p != 'P' && *p != '#') | ||
3375 | { | ||
3376 | result = -12; | ||
3377 | exit_loop = true; | ||
3378 | break; | ||
3379 | } | ||
3380 | |||
3381 | switch (*p) | ||
3382 | { | ||
3383 | case 'P': | ||
3384 | /* playlist can only be specified once */ | ||
3385 | if (!first) | ||
3386 | { | ||
3387 | result = -13; | ||
3388 | exit_loop = true; | ||
3389 | break; | ||
3390 | } | ||
3391 | |||
3392 | current_command = PLAYLIST_COMMAND_PLAYLIST; | ||
3393 | break; | ||
3394 | case 'A': | ||
3395 | current_command = PLAYLIST_COMMAND_ADD; | ||
3396 | break; | ||
3397 | case 'Q': | ||
3398 | current_command = PLAYLIST_COMMAND_QUEUE; | ||
3399 | break; | ||
3400 | case 'D': | ||
3401 | current_command = PLAYLIST_COMMAND_DELETE; | ||
3402 | break; | ||
3403 | case 'S': | ||
3404 | current_command = PLAYLIST_COMMAND_SHUFFLE; | ||
3405 | break; | ||
3406 | case 'U': | ||
3407 | current_command = PLAYLIST_COMMAND_UNSHUFFLE; | ||
3408 | break; | ||
3409 | case 'R': | ||
3410 | current_command = PLAYLIST_COMMAND_RESET; | ||
3411 | break; | ||
3412 | case '#': | ||
3413 | current_command = PLAYLIST_COMMAND_COMMENT; | ||
3414 | break; | ||
3415 | default: | ||
3416 | result = -14; | ||
3417 | exit_loop = true; | ||
3418 | break; | ||
3419 | } | ||
3420 | |||
3421 | str_count = -1; | ||
3422 | str1 = NULL; | ||
3423 | str2 = NULL; | ||
3424 | str3 = NULL; | ||
3425 | } | ||
3426 | else if(current_command != PLAYLIST_COMMAND_COMMENT) | ||
3427 | { | ||
3428 | /* all control file strings are separated with a colon. | ||
3429 | Replace the colon with 0 to get proper strings that can be | ||
3430 | used by commands above */ | ||
3431 | if (*p == ':') | ||
3432 | { | ||
3433 | *p = '\0'; | ||
3434 | str_count++; | ||
3435 | |||
3436 | if ((count+1) < nread) | ||
3437 | { | ||
3438 | switch (str_count) | ||
3439 | { | ||
3440 | case 0: | ||
3441 | str1 = p+1; | ||
3442 | break; | ||
3443 | case 1: | ||
3444 | str2 = p+1; | ||
3445 | break; | ||
3446 | case 2: | ||
3447 | str3 = p+1; | ||
3448 | break; | ||
3449 | default: | ||
3450 | /* allow last string to contain colons */ | ||
3451 | *p = ':'; | ||
3452 | break; | ||
3453 | } | ||
3454 | } | ||
3455 | } | ||
3456 | } | ||
3457 | } | ||
3458 | |||
3459 | if (result < 0) | ||
3460 | { | ||
3461 | splashf(HZ*2, "Err: %d, %s", result, str(LANG_PLAYLIST_CONTROL_INVALID)); | ||
3462 | goto out; | ||
3463 | } | ||
3464 | |||
3465 | if (useraborted) | ||
3466 | { | ||
3467 | splash(HZ*2, ID2P(LANG_CANCEL)); | ||
3468 | result = -1; | ||
3469 | goto out; | ||
3470 | } | ||
3471 | if (!newline || (exit_loop && count<nread)) | ||
3472 | { | ||
3473 | if ((total_read + count) >= control_file_size) | ||
3474 | { | ||
3475 | /* no newline at end of control file */ | ||
3476 | splashf(HZ*2, "Err: EOF, %s", str(LANG_PLAYLIST_CONTROL_INVALID)); | ||
3477 | result = -15; | ||
3478 | goto out; | ||
3479 | } | ||
3480 | |||
3481 | /* We didn't end on a newline or we exited loop prematurely. | ||
3482 | Either way, re-read the remainder. */ | ||
3483 | count = last_newline; | ||
3484 | lseek(playlist->control_fd, total_read+count, SEEK_SET); | ||
3485 | } | ||
3486 | |||
3487 | total_read += count; | ||
3488 | |||
3489 | if (first) | ||
3490 | /* still looking for header */ | ||
3491 | nread = read(playlist->control_fd, buffer, | ||
3492 | PLAYLIST_COMMAND_SIZE<buflen?PLAYLIST_COMMAND_SIZE:buflen); | ||
3642 | else | 3493 | else |
3643 | info->attr |= PLAYLIST_ATTR_INSERTED; | 3494 | nread = read(playlist->control_fd, buffer, buflen); |
3644 | 3495 | ||
3496 | /* Terminate on EOF */ | ||
3497 | if(nread <= 0) | ||
3498 | { | ||
3499 | break; | ||
3500 | } | ||
3645 | } | 3501 | } |
3646 | 3502 | ||
3647 | if (playlist->indices[index] & PLAYLIST_SKIPPED) | 3503 | #ifdef HAVE_DIRCACHE |
3648 | info->attr |= PLAYLIST_ATTR_SKIPPED; | 3504 | queue_post(&playlist_queue, PLAYLIST_LOAD_POINTERS, 0); |
3649 | 3505 | #endif | |
3650 | info->index = index; | ||
3651 | info->display_index = rotate_index(playlist, index) + 1; | ||
3652 | 3506 | ||
3653 | return 0; | 3507 | out: |
3508 | talk_buffer_set_policy(TALK_BUFFER_DEFAULT); | ||
3509 | core_free(handle); | ||
3510 | return result; | ||
3511 | } | ||
3512 | |||
3513 | /* resume a playlist track with the given crc_32 of the track name. */ | ||
3514 | void playlist_resume_track(int start_index, unsigned int crc, | ||
3515 | unsigned long elapsed, unsigned long offset) | ||
3516 | { | ||
3517 | int i; | ||
3518 | unsigned int tmp_crc; | ||
3519 | struct playlist_info* playlist = ¤t_playlist; | ||
3520 | tmp_crc = playlist_get_filename_crc32(playlist, start_index); | ||
3521 | if (tmp_crc == crc) | ||
3522 | { | ||
3523 | playlist_start(start_index, elapsed, offset); | ||
3524 | return; | ||
3525 | } | ||
3526 | |||
3527 | for (i = 0 ; i < playlist->amount; i++) | ||
3528 | { | ||
3529 | tmp_crc = playlist_get_filename_crc32(playlist, i); | ||
3530 | if (tmp_crc == crc) | ||
3531 | { | ||
3532 | playlist_start(i, elapsed, offset); | ||
3533 | return; | ||
3534 | } | ||
3535 | } | ||
3536 | |||
3537 | /* If we got here the file wasnt found, so start from the beginning */ | ||
3538 | playlist_start(0, 0, 0); | ||
3654 | } | 3539 | } |
3655 | 3540 | ||
3656 | /* save the current dynamic playlist to specified file. The | 3541 | /* save the current dynamic playlist to specified file. The |
@@ -3838,104 +3723,194 @@ reset_old_buffer: | |||
3838 | } | 3723 | } |
3839 | 3724 | ||
3840 | /* | 3725 | /* |
3841 | * Search specified directory for tracks and notify via callback. May be | 3726 | * Set the specified playlist as the current. |
3842 | * called recursively. | 3727 | * NOTE: You will get undefined behaviour if something is already playing so |
3728 | * remember to stop before calling this. Also, this call will | ||
3729 | * effectively close your playlist, making it unusable. | ||
3843 | */ | 3730 | */ |
3844 | int playlist_directory_tracksearch(const char* dirname, bool recurse, | 3731 | int playlist_set_current(struct playlist_info* playlist) |
3845 | int (*callback)(char*, void*), | ||
3846 | void* context) | ||
3847 | { | 3732 | { |
3848 | char buf[MAX_PATH+1]; | 3733 | if (!playlist || (check_control(playlist) < 0)) |
3849 | int result = 0; | 3734 | return -1; |
3850 | int num_files = 0; | ||
3851 | int i;; | ||
3852 | struct tree_context* tc = tree_get_context(); | ||
3853 | struct tree_cache* cache = &tc->cache; | ||
3854 | int old_dirfilter = *(tc->dirfilter); | ||
3855 | 3735 | ||
3856 | if (!callback) | 3736 | empty_playlist(¤t_playlist, false); |
3737 | |||
3738 | strmemccpy(current_playlist.filename, playlist->filename, | ||
3739 | sizeof(current_playlist.filename)); | ||
3740 | |||
3741 | current_playlist.utf8 = playlist->utf8; | ||
3742 | current_playlist.fd = playlist->fd; | ||
3743 | |||
3744 | close(playlist->control_fd); | ||
3745 | playlist->control_fd = -1; | ||
3746 | close(current_playlist.control_fd); | ||
3747 | current_playlist.control_fd = -1; | ||
3748 | remove(current_playlist.control_filename); | ||
3749 | current_playlist.control_created = false; | ||
3750 | if (rename(playlist->control_filename, | ||
3751 | current_playlist.control_filename) < 0) | ||
3857 | return -1; | 3752 | return -1; |
3753 | current_playlist.control_fd = open(current_playlist.control_filename, | ||
3754 | O_RDWR); | ||
3755 | if (current_playlist.control_fd < 0) | ||
3756 | return -1; | ||
3757 | current_playlist.control_created = true; | ||
3858 | 3758 | ||
3859 | /* use the tree browser dircache to load files */ | 3759 | current_playlist.dirlen = playlist->dirlen; |
3860 | *(tc->dirfilter) = SHOW_ALL; | ||
3861 | 3760 | ||
3862 | if (ft_load(tc, dirname) < 0) | 3761 | if (playlist->indices && playlist->indices != current_playlist.indices) |
3863 | { | 3762 | { |
3864 | splash(HZ*2, ID2P(LANG_PLAYLIST_DIRECTORY_ACCESS_ERROR)); | 3763 | memcpy((void*)current_playlist.indices, (void*)playlist->indices, |
3865 | *(tc->dirfilter) = old_dirfilter; | 3764 | playlist->max_playlist_size*sizeof(*playlist->indices)); |
3866 | return -1; | 3765 | #ifdef HAVE_DIRCACHE |
3766 | copy_filerefs(current_playlist.dcfrefs, playlist->dcfrefs, | ||
3767 | playlist->max_playlist_size); | ||
3768 | #endif | ||
3867 | } | 3769 | } |
3868 | 3770 | ||
3869 | num_files = tc->filesindir; | 3771 | current_playlist.first_index = playlist->first_index; |
3772 | current_playlist.amount = playlist->amount; | ||
3773 | current_playlist.last_insert_pos = playlist->last_insert_pos; | ||
3774 | current_playlist.seed = playlist->seed; | ||
3775 | current_playlist.shuffle_modified = playlist->shuffle_modified; | ||
3776 | current_playlist.deleted = playlist->deleted; | ||
3777 | current_playlist.num_inserted_tracks = playlist->num_inserted_tracks; | ||
3870 | 3778 | ||
3871 | /* we've overwritten the dircache so tree browser will need to be | 3779 | memcpy(current_playlist.control_cache, playlist->control_cache, |
3872 | reloaded */ | 3780 | sizeof(current_playlist.control_cache)); |
3873 | reload_directory(); | 3781 | current_playlist.num_cached = playlist->num_cached; |
3782 | current_playlist.pending_control_sync = playlist->pending_control_sync; | ||
3874 | 3783 | ||
3875 | for (i=0; i<num_files; i++) | 3784 | return 0; |
3785 | } | ||
3786 | |||
3787 | /* set playlist->last_shuffle_start to playlist->amount for | ||
3788 | PLAYLIST_INSERT_LAST_SHUFFLED command purposes*/ | ||
3789 | void playlist_set_last_shuffled_start(void) | ||
3790 | { | ||
3791 | struct playlist_info* playlist = ¤t_playlist; | ||
3792 | playlist->last_shuffled_start = playlist->amount; | ||
3793 | } | ||
3794 | |||
3795 | /* shuffle newly created playlist using random seed. */ | ||
3796 | int playlist_shuffle(int random_seed, int start_index) | ||
3797 | { | ||
3798 | struct playlist_info* playlist = ¤t_playlist; | ||
3799 | |||
3800 | bool start_current = false; | ||
3801 | |||
3802 | if (start_index >= 0 && global_settings.play_selected) | ||
3876 | { | 3803 | { |
3877 | /* user abort */ | 3804 | /* store the seek position before the shuffle */ |
3878 | if (action_userabort(TIMEOUT_NOBLOCK)) | 3805 | playlist->index = playlist->first_index = start_index; |
3879 | { | 3806 | start_current = true; |
3880 | result = -1; | 3807 | } |
3881 | break; | ||
3882 | } | ||
3883 | 3808 | ||
3884 | struct entry *files = core_get_data(cache->entries_handle); | 3809 | randomise_playlist(playlist, random_seed, start_current, true); |
3885 | if (files[i].attr & ATTR_DIRECTORY) | ||
3886 | { | ||
3887 | if (recurse) | ||
3888 | { | ||
3889 | /* recursively add directories */ | ||
3890 | if (path_append(buf, dirname, files[i].name, sizeof(buf)) | ||
3891 | >= sizeof(buf)) | ||
3892 | { | ||
3893 | continue; | ||
3894 | } | ||
3895 | 3810 | ||
3896 | result = playlist_directory_tracksearch(buf, recurse, | 3811 | return playlist->index; |
3897 | callback, context); | 3812 | } |
3898 | if (result < 0) | ||
3899 | break; | ||
3900 | 3813 | ||
3901 | /* we now need to reload our current directory */ | 3814 | /* Marks the index of the track to be skipped that is "steps" away from |
3902 | if(ft_load(tc, dirname) < 0) | 3815 | * current playing track. |
3903 | { | 3816 | */ |
3904 | result = -1; | 3817 | void playlist_skip_entry(struct playlist_info *playlist, int steps) |
3905 | break; | 3818 | { |
3906 | } | 3819 | int index; |
3907 | 3820 | ||
3908 | num_files = tc->filesindir; | 3821 | if (playlist == NULL) |
3909 | if (!num_files) | 3822 | playlist = ¤t_playlist; |
3910 | { | ||
3911 | result = -1; | ||
3912 | break; | ||
3913 | } | ||
3914 | } | ||
3915 | else | ||
3916 | continue; | ||
3917 | } | ||
3918 | else if ((files[i].attr & FILE_ATTR_MASK) == FILE_ATTR_AUDIO) | ||
3919 | { | ||
3920 | if (path_append(buf, dirname, files[i].name, sizeof(buf)) | ||
3921 | >= sizeof(buf)) | ||
3922 | { | ||
3923 | continue; | ||
3924 | } | ||
3925 | 3823 | ||
3926 | if (callback(buf, context) != 0) | 3824 | /* need to account for already skipped tracks */ |
3927 | { | 3825 | steps = calculate_step_count(playlist, steps); |
3928 | result = -1; | ||
3929 | break; | ||
3930 | } | ||
3931 | 3826 | ||
3932 | /* let the other threads work */ | 3827 | index = playlist->index + steps; |
3933 | yield(); | 3828 | if (index < 0) |
3934 | } | 3829 | index += playlist->amount; |
3935 | } | 3830 | else if (index >= playlist->amount) |
3831 | index -= playlist->amount; | ||
3936 | 3832 | ||
3937 | /* restore dirfilter */ | 3833 | playlist->indices[index] |= PLAYLIST_SKIPPED; |
3938 | *(tc->dirfilter) = old_dirfilter; | 3834 | } |
3835 | |||
3836 | /* sort currently playing playlist */ | ||
3837 | int playlist_sort(struct playlist_info* playlist, bool start_current) | ||
3838 | { | ||
3839 | int result; | ||
3840 | |||
3841 | if (!playlist) | ||
3842 | playlist = ¤t_playlist; | ||
3843 | |||
3844 | check_control(playlist); | ||
3845 | |||
3846 | result = sort_playlist(playlist, start_current, true); | ||
3847 | |||
3848 | if (result != -1 && (audio_status() & AUDIO_STATUS_PLAY) && | ||
3849 | playlist->started) | ||
3850 | audio_flush_and_reload_tracks(); | ||
3939 | 3851 | ||
3940 | return result; | 3852 | return result; |
3941 | } | 3853 | } |
3854 | |||
3855 | /* start playing current playlist at specified index/offset */ | ||
3856 | void playlist_start(int start_index, unsigned long elapsed, | ||
3857 | unsigned long offset) | ||
3858 | { | ||
3859 | struct playlist_info* playlist = ¤t_playlist; | ||
3860 | |||
3861 | playlist->index = start_index; | ||
3862 | |||
3863 | playlist->started = true; | ||
3864 | sync_control(playlist, false); | ||
3865 | audio_play(elapsed, offset); | ||
3866 | audio_resume(); | ||
3867 | } | ||
3868 | |||
3869 | void playlist_sync(struct playlist_info* playlist) | ||
3870 | { | ||
3871 | if (!playlist) | ||
3872 | playlist = ¤t_playlist; | ||
3873 | |||
3874 | sync_control(playlist, false); | ||
3875 | if ((audio_status() & AUDIO_STATUS_PLAY) && playlist->started) | ||
3876 | audio_flush_and_reload_tracks(); | ||
3877 | |||
3878 | #ifdef HAVE_DIRCACHE | ||
3879 | queue_post(&playlist_queue, PLAYLIST_LOAD_POINTERS, 0); | ||
3880 | #endif | ||
3881 | } | ||
3882 | |||
3883 | /* Update resume info for current playing song. Returns -1 on error. */ | ||
3884 | int playlist_update_resume_info(const struct mp3entry* id3) | ||
3885 | { | ||
3886 | struct playlist_info* playlist = ¤t_playlist; | ||
3887 | |||
3888 | if (id3) | ||
3889 | { | ||
3890 | if (global_status.resume_index != playlist->index || | ||
3891 | global_status.resume_elapsed != id3->elapsed || | ||
3892 | global_status.resume_offset != id3->offset) | ||
3893 | { | ||
3894 | unsigned int crc = playlist_get_filename_crc32(playlist, | ||
3895 | playlist->index); | ||
3896 | global_status.resume_index = playlist->index; | ||
3897 | global_status.resume_crc32 = crc; | ||
3898 | global_status.resume_elapsed = id3->elapsed; | ||
3899 | global_status.resume_offset = id3->offset; | ||
3900 | status_save(); | ||
3901 | } | ||
3902 | } | ||
3903 | else | ||
3904 | { | ||
3905 | global_status.resume_index = -1; | ||
3906 | global_status.resume_crc32 = -1; | ||
3907 | global_status.resume_elapsed = -1; | ||
3908 | global_status.resume_offset = -1; | ||
3909 | status_save(); | ||
3910 | } | ||
3911 | |||
3912 | return 0; | ||
3913 | } | ||
3914 | |||
3915 | |||
3916 | |||
diff --git a/apps/playlist.h b/apps/playlist.h index 2eca7355e4..0ecc7ccf77 100644 --- a/apps/playlist.h +++ b/apps/playlist.h | |||
@@ -74,22 +74,16 @@ struct playlist_control_cache { | |||
74 | struct playlist_info | 74 | struct playlist_info |
75 | { | 75 | { |
76 | bool current; /* current playing playlist */ | 76 | bool current; /* current playing playlist */ |
77 | char filename[MAX_PATH]; /* path name of m3u playlist on disk */ | ||
78 | char control_filename[MAX_PATH]; /* full path of control file */ | ||
79 | bool utf8; /* playlist is in .m3u8 format */ | 77 | bool utf8; /* playlist is in .m3u8 format */ |
78 | bool control_created; /* has control file been created? */ | ||
79 | bool in_ram; /* playlist stored in ram (dirplay) */ | ||
80 | int fd; /* descriptor of the open playlist file */ | 80 | int fd; /* descriptor of the open playlist file */ |
81 | int control_fd; /* descriptor of the open control file */ | 81 | int control_fd; /* descriptor of the open control file */ |
82 | bool control_created; /* has control file been created? */ | ||
83 | int dirlen; /* Length of the path to the playlist file */ | ||
84 | volatile unsigned long *indices; /* array of indices */ | ||
85 | #ifdef HAVE_DIRCACHE | ||
86 | struct dircache_fileref *dcfrefs; /* Dircache entry shortcuts */ | ||
87 | #endif | ||
88 | int max_playlist_size; /* Max number of files in playlist. Mirror of | 82 | int max_playlist_size; /* Max number of files in playlist. Mirror of |
89 | global_settings.max_files_in_playlist */ | 83 | global_settings.max_files_in_playlist */ |
90 | bool in_ram; /* playlist stored in ram (dirplay) */ | 84 | int num_inserted_tracks; /* number of tracks inserted */ |
85 | volatile unsigned long *indices; /* array of indices */ | ||
91 | int buffer_handle; /* handle to the below buffer (-1 if non-buflib) */ | 86 | int buffer_handle; /* handle to the below buffer (-1 if non-buflib) */ |
92 | |||
93 | volatile char *buffer;/* buffer for in-ram playlists */ | 87 | volatile char *buffer;/* buffer for in-ram playlists */ |
94 | int buffer_size; /* size of buffer */ | 88 | int buffer_size; /* size of buffer */ |
95 | int buffer_end_pos; /* last position where buffer was written */ | 89 | int buffer_end_pos; /* last position where buffer was written */ |
@@ -97,22 +91,25 @@ struct playlist_info | |||
97 | int first_index; /* index of first song in playlist */ | 91 | int first_index; /* index of first song in playlist */ |
98 | int amount; /* number of tracks in the index */ | 92 | int amount; /* number of tracks in the index */ |
99 | int last_insert_pos; /* last position we inserted a track */ | 93 | int last_insert_pos; /* last position we inserted a track */ |
100 | int seed; /* shuffle seed */ | ||
101 | bool shuffle_modified; /* has playlist been shuffled with | ||
102 | inserted tracks? */ | ||
103 | bool deleted; /* have any tracks been deleted? */ | 94 | bool deleted; /* have any tracks been deleted? */ |
104 | int num_inserted_tracks; /* number of tracks inserted */ | ||
105 | bool started; /* has playlist been started? */ | 95 | bool started; /* has playlist been started? */ |
106 | 96 | bool pending_control_sync; /* control file needs to be synced */ | |
97 | bool shuffle_modified; /* has playlist been shuffled with | ||
98 | inserted tracks? */ | ||
99 | int last_shuffled_start; /* number of tracks when insert last | ||
100 | shuffled command start */ | ||
101 | int seed; /* shuffle seed */ | ||
107 | /* cache of playlist control commands waiting to be flushed to | 102 | /* cache of playlist control commands waiting to be flushed to |
108 | to disk */ | 103 | to disk */ |
109 | struct playlist_control_cache control_cache[PLAYLIST_MAX_CACHE]; | 104 | struct playlist_control_cache control_cache[PLAYLIST_MAX_CACHE]; |
110 | int num_cached; /* number of cached entries */ | 105 | int num_cached; /* number of cached entries */ |
111 | bool pending_control_sync; /* control file needs to be synced */ | 106 | struct mutex mutex; /* mutex for control file access */ |
112 | 107 | #ifdef HAVE_DIRCACHE | |
113 | struct mutex *control_mutex; /* mutex for control file access */ | 108 | struct dircache_fileref *dcfrefs; /* Dircache entry shortcuts */ |
114 | int last_shuffled_start; /* number of tracks when insert last | 109 | #endif |
115 | shuffled command start */ | 110 | int dirlen; /* Length of the path to the playlist file */ |
111 | char filename[MAX_PATH]; /* path name of m3u playlist on disk */ | ||
112 | char control_filename[MAX_PATH]; /* full path of control file */ | ||
116 | }; | 113 | }; |
117 | 114 | ||
118 | struct playlist_track_info | 115 | struct playlist_track_info |