A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 4148 lines 124 kB view raw
1/*************************************************************************** 2 * __________ __ ___. 3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 * \/ \/ \/ \/ \/ 8 * $Id$ 9 * 10 * Copyright (C) 2002 by wavey@wavey.org 11 * 12 * This program is free software; you can redistribute it and/or 13 * modify it under the terms of the GNU General Public License 14 * as published by the Free Software Foundation; either version 2 15 * of the License, or (at your option) any later version. 16 * 17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 18 * KIND, either express or implied. 19 * 20 ****************************************************************************/ 21 22/* 23 Dynamic playlist design (based on design originally proposed by ricII) 24 25 There are two files associated with a dynamic playlist: 26 1. Playlist file : This file contains the initial songs in the playlist. 27 The file is created by the user and stored on the hard 28 drive. NOTE: If we are playing the contents of a 29 directory, there will be no playlist file. 30 2. Control file : This file is automatically created when a playlist is 31 started and contains all the commands done to it. 32 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 35 DIR is the directory where the playlist is located and FILE is the 36 playlist filename (without the directory part). 37 38 When there is an on-disk playlist file, both DIR and FILE are nonempty. 39 Dynamically generated playlists (whether by the file browser, database, 40 or another means) have an empty FILE. For dirplay, DIR will be nonempty. 41 42 Control file commands: 43 a. Add track (A:<position>:<last position>:<path to track>) 44 - Insert a track at the specified position in the current 45 playlist. Last position is used to specify where last insertion 46 occurred. 47 b. Queue track (Q:<position>:<last position>:<path to track>) 48 - Queue a track at the specified position in the current 49 playlist. Queued tracks differ from added tracks in that they 50 are deleted from the playlist as soon as they are played and 51 they are not saved to disk as part of the playlist. 52 c. Delete track (D:<position>) 53 - Delete track from specified position in the current playlist. 54 d. Shuffle playlist (S:<seed>:<index>) 55 - Shuffle entire playlist with specified seed. The index 56 identifies the first index in the newly shuffled playlist 57 (needed for repeat mode). 58 e. Unshuffle playlist (U:<index>) 59 - Unshuffle entire playlist. The index identifies the first index 60 in the newly unshuffled playlist. 61 f. Reset last insert position (R) 62 - Needed so that insertions work properly after resume 63 64 Resume: 65 The only resume info that needs to be saved is the current index in the 66 playlist and the position in the track. When resuming, all the commands 67 in the control file will be reapplied so that the playlist indices are 68 exactly the same as before shutdown. To avoid unnecessary disk 69 accesses, the shuffle mode settings are also saved in settings and only 70 flushed to disk when required. 71 */ 72 73// #define LOGF_ENABLE 74#include <stdio.h> 75#include <stdlib.h> 76#include <ctype.h> 77#include "string-extra.h" 78#include "playlist.h" 79#include "ata_idle_notify.h" 80#include "file.h" 81#include "action.h" 82#include "mv.h" 83#include "debug.h" 84#include "audio.h" 85#include "lcd.h" 86#include "kernel.h" 87#include "settings.h" 88#include "status.h" 89#include "applimits.h" 90#include "screens.h" 91#include "core_alloc.h" 92#include "misc.h" 93#include "pathfuncs.h" 94#include "button.h" 95#include "filetree.h" 96#include "abrepeat.h" 97#include "thread.h" 98#include "usb.h" 99#include "filetypes.h" 100#include "icons.h" 101#include "system.h" 102#include "misc.h" 103 104#include "lang.h" 105#include "talk.h" 106#include "splash.h" 107#include "rbunicode.h" 108#include "root_menu.h" 109#include "plugin.h" /* To borrow a temp buffer to rewrite a .m3u8 file */ 110#include "logdiskf.h" 111#ifdef HAVE_DIRCACHE 112#include "dircache.h" 113#endif 114#include "logf.h" 115#include "panic.h" 116 117#if 0//def ROCKBOX_HAS_LOGDISKF 118#undef DEBUGF 119#undef ERRORF 120#undef WARNF 121#undef NOTEF 122#define DEBUGF logf 123#define ERRORF DEBUGF 124#define WARNF DEBUGF 125#define NOTEF DEBUGF 126#endif 127 128/* default load buffer size (should be at least 1 KiB) */ 129#define PLAYLIST_LOAD_BUFLEN (32*1024) 130 131/* 132 * Minimum supported version and current version of the control file. 133 * Any versions outside of this range will be rejected by the loader. 134 * 135 * v1 was the initial version when dynamic playlists were first implemented. 136 * v2 was added shortly thereafter and has been used since 2003. 137 * v3 added the (C)lear command and is otherwise identical to v2. 138 * v4 added the (F)lags command. 139 * v5 added index to the (C)lear command. Added PLAYLIST_INSERT_LAST_ROTATED (-8) as 140 * a supported position for (A)dd or (Q)eue commands. 141 * v6 removed the (C)lear command. 142 */ 143#define PLAYLIST_CONTROL_FILE_LTS_VERSION 2 /* v2 still supported */ 144#define PLAYLIST_CONTROL_FILE_MIN_VERSION 6 145#define PLAYLIST_CONTROL_FILE_VERSION 6 146 147#define PLAYLIST_COMMAND_SIZE (MAX_PATH+12) 148 149/* 150 Each playlist index has a flag associated with it which identifies what 151 type of track it is. These flags are stored in the 4 high order bits of 152 the index. 153 154 NOTE: This limits the playlist file size to a max of 256M. 155 156 Bits 31-30: 157 00 = Playlist track 158 01 = Track was prepended into playlist 159 10 = Track was inserted into playlist 160 11 = Track was appended into playlist 161 Bit 29: 162 0 = Added track 163 1 = Queued track 164 Bit 28: 165 0 = Track entry is valid 166 1 = Track does not exist on disk and should be skipped 167 */ 168#define PLAYLIST_SEEK_MASK 0x0FFFFFFF 169#define PLAYLIST_INSERT_TYPE_MASK 0xC0000000 170 171#define PLAYLIST_INSERT_TYPE_PREPEND 0x40000000 172#define PLAYLIST_INSERT_TYPE_INSERT 0x80000000 173#define PLAYLIST_INSERT_TYPE_APPEND 0xC0000000 174 175#define PLAYLIST_QUEUED 0x20000000 176#define PLAYLIST_SKIPPED 0x10000000 177 178static struct playlist_info current_playlist; 179static struct playlist_info on_disk_playlist; 180 181/* REPEAT_ONE support function from playback.c */ 182extern bool audio_pending_track_skip_is_manual(void); 183static inline bool is_manual_skip(void) 184{ 185 return audio_pending_track_skip_is_manual(); 186} 187 188/* Directory Cache*/ 189static void dc_init_filerefs(struct playlist_info *playlist, 190 int start, int count) 191{ 192#ifdef HAVE_DIRCACHE 193 if (!playlist->dcfrefs_handle) 194 return; 195 196 struct dircache_fileref *dcfrefs = core_get_data(playlist->dcfrefs_handle); 197 int end = start + count; 198 199 for (int i = start; i < end; i++) 200 dircache_fileref_init(&dcfrefs[i]); 201#else 202 (void)playlist; 203 (void)start; 204 (void)count; 205#endif 206} 207 208#ifdef HAVE_DIRCACHE 209#define PLAYLIST_DC_SCAN_START 1 210#define PLAYLIST_DC_SCAN_STOP 2 211 212static struct event_queue playlist_queue; 213static struct queue_sender_list playlist_queue_sender_list; 214static long playlist_stack[(DEFAULT_STACK_SIZE + 0x800)/sizeof(long)]; 215static const char dc_thread_playlist_name[] = "playlist cachectrl"; 216#endif 217 218#define playlist_read_lock(p) mutex_lock(&(p)->mutex) 219#define playlist_read_unlock(p) mutex_unlock(&(p)->mutex) 220#define playlist_write_lock(p) mutex_lock(&(p)->mutex) 221#define playlist_write_unlock(p) mutex_unlock(&(p)->mutex) 222 223#if defined(PLAYLIST_DEBUG_ACCESS_ERRORS) 224#define notify_access_error() (splashf(HZ*2, "%s %s", \ 225 __func__, ID2P(LANG_PLAYLIST_ACCESS_ERROR))) 226#define notify_control_access_error() (splashf(HZ*2, "%s %s", \ 227 __func__, ID2P(LANG_PLAYLIST_CONTROL_ACCESS_ERROR))) 228#else 229static void notify_access_error(void) { 230 splash(HZ*2, ID2P(LANG_PLAYLIST_ACCESS_ERROR)); 231} 232static void notify_control_access_error(void) { 233 splash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_ACCESS_ERROR)); 234} 235#endif 236 237/* 238 * Display buffer full message 239 */ 240static void notify_buffer_full(void) 241{ 242 splash(HZ*2, ID2P(LANG_PLAYLIST_BUFFER_FULL)); 243} 244 245static void dc_thread_start(struct playlist_info *playlist, bool is_dirty) 246{ 247#ifdef HAVE_DIRCACHE 248 if (playlist == &current_playlist) 249 queue_post(&playlist_queue, PLAYLIST_DC_SCAN_START, is_dirty); 250#else 251 (void)playlist; 252 (void)is_dirty; 253#endif 254} 255 256static void dc_thread_stop(struct playlist_info *playlist) 257{ 258#ifdef HAVE_DIRCACHE 259 if (playlist == &current_playlist) 260 queue_send(&playlist_queue, PLAYLIST_DC_SCAN_STOP, 0); 261#else 262 (void)playlist; 263#endif 264} 265 266/* 267 * Open playlist file and return file descriptor or -1 on error. 268 * The fd should not be closed manually. Not thread-safe. 269 */ 270static int pl_open_playlist(struct playlist_info *playlist) 271{ 272 if (playlist->fd >= 0) 273 return playlist->fd; 274 275 int fd = open_utf8(playlist->filename, O_RDONLY); 276 if (fd < 0) 277 return fd; 278 279 /* Presence of UTF-8 BOM forces UTF-8 encoding. */ 280 if (lseek(fd, 0, SEEK_CUR) > 0) 281 playlist->utf8 = true; 282 283 playlist->fd = fd; 284 return fd; 285} 286 287static void pl_close_fd(int *fdptr) 288{ 289 int fd = *fdptr; 290 if (fd >= 0) 291 { 292 close(fd); 293 *fdptr = -1; 294 } 295} 296 297/* 298 * Close any open file descriptor for the playlist file. 299 * Not thread-safe. 300 */ 301static void pl_close_playlist(struct playlist_info *playlist) 302{ 303 pl_close_fd(&playlist->fd); 304} 305 306/* 307 * Close any open playlist control file descriptor. 308 * Not thread-safe. 309 */ 310static void pl_close_control(struct playlist_info *playlist) 311{ 312 pl_close_fd(&playlist->control_fd); 313} 314 315/* Check if the filename suggests M3U or M3U8 format. */ 316static bool is_m3u8_name(const char* filename) 317{ 318 char *dot = strrchr(filename, '.'); 319 320 /* Default to M3U8 unless explicitly told otherwise. */ 321 return (!dot || strcasecmp(dot, ".m3u") != 0); 322} 323 324/* Convert a filename in an M3U playlist to UTF-8. 325 * 326 * buf - the filename to convert; can contain more than one line from the 327 * playlist. 328 * buf_len - amount of buf that is used. 329 * buf_max - total size of buf. 330 * temp - temporary conversion buffer, at least buf_max bytes. 331 * 332 * Returns the length of the converted filename. 333 */ 334static int convert_m3u_name(char* buf, int buf_len, int buf_max, char* temp) 335{ 336 int i = 0; 337 char* dest; 338 339 /* Locate EOL. */ 340 while ((i < buf_len) && (buf[i] != '\n') && (buf[i] != '\r')) 341 { 342 i++; 343 } 344 345 /* Work back killing white space. */ 346 while ((i > 0) && isspace(buf[i - 1])) 347 { 348 i--; 349 } 350 351 buf_len = i; 352 dest = temp; 353 354 dest = iso_decode_ex(buf, dest, -1, buf_len, buf_max - 1); 355 356 *dest = 0; 357 strcpy(buf, temp); 358 return dest - temp; 359} 360 361/* 362 * create control file for playlist 363 */ 364static void create_control_unlocked(struct playlist_info* playlist) 365{ 366 if (playlist == &current_playlist && file_exists(PLAYLIST_CONTROL_FILE)) 367 rename(PLAYLIST_CONTROL_FILE, PLAYLIST_CONTROL_FILE".old"); 368 369 playlist->control_fd = open(playlist->control_filename, 370 O_CREAT|O_RDWR|O_TRUNC, 0666); 371 372 playlist->control_created = (playlist->control_fd >= 0); 373 374 if (!playlist->control_created) 375 { 376 if (!check_rockboxdir()) 377 return; /* No RB directory exists! */ 378 379 cond_talk_ids_fq(LANG_PLAYLIST_CONTROL_ACCESS_ERROR); 380 int fd = playlist->control_fd; 381 splashf(HZ*2, "%s (%d)", str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR), fd); 382 } 383} 384 385/* 386 * Rotate indices such that first_index is index 0 387 */ 388static int rotate_index(const struct playlist_info* playlist, int index) 389{ 390 index -= playlist->first_index; 391 if (index < 0) 392 index += playlist->amount; 393 394 return index; 395} 396 397static void sync_control_unlocked(struct playlist_info* playlist) 398{ 399 if (playlist->control_fd >= 0) 400 fsync(playlist->control_fd); 401} 402 403static int update_control_unlocked(struct playlist_info* playlist, 404 enum playlist_command command, int i1, int i2, 405 const char* s1, const char* s2, int *seekpos) 406{ 407 int fd = playlist->control_fd; 408 int result; 409 410 lseek(fd, 0, SEEK_END); 411 412 switch (command) 413 { 414 case PLAYLIST_COMMAND_PLAYLIST: 415 result = fdprintf(fd, "P:%d:%s:%s\n", i1, s1, s2); 416 break; 417 case PLAYLIST_COMMAND_ADD: 418 case PLAYLIST_COMMAND_QUEUE: 419 result = fdprintf(fd, "%c:%d:%d:", 420 command == PLAYLIST_COMMAND_ADD ? 'A' : 'Q', i1, i2); 421 if (result > 0) 422 { 423 *seekpos = lseek(fd, 0, SEEK_CUR); 424 result = fdprintf(fd, "%s\n", s1); 425 } 426 break; 427 case PLAYLIST_COMMAND_DELETE: 428 result = fdprintf(fd, "D:%d\n", i1); 429 break; 430 case PLAYLIST_COMMAND_SHUFFLE: 431 result = fdprintf(fd, "S:%d:%d\n", i1, i2); 432 break; 433 case PLAYLIST_COMMAND_UNSHUFFLE: 434 result = fdprintf(fd, "U:%d\n", i1); 435 break; 436 case PLAYLIST_COMMAND_RESET: 437 result = write(fd, "R\n", 2); 438 break; 439 case PLAYLIST_COMMAND_FLAGS: 440 result = fdprintf(fd, "F:%u:%u\n", i1, i2); 441 break; 442 default: 443 return -1; 444 } 445 446 return result; 447} 448 449/* 450 * store directory and name of playlist file 451 */ 452static void update_playlist_filename_unlocked(struct playlist_info* playlist, 453 const char *dir, const char *file) 454{ 455 char *sep=""; 456 int dirlen = strlen(dir); 457 458 playlist->utf8 = is_m3u8_name(file); 459 460 /* If the dir does not end in trailing slash, we use a separator. 461 Otherwise we don't. */ 462 if(!dirlen || '/' != dir[dirlen-1]) 463 { 464 sep="/"; 465 dirlen++; 466 } 467 468 playlist->dirlen = dirlen; 469 470 snprintf(playlist->filename, sizeof(playlist->filename), 471 "%s%s%s", dir, sep, file); 472} 473 474/* 475 * remove any files and indices associated with the playlist 476 */ 477static void empty_playlist_unlocked(struct playlist_info* playlist, bool resume) 478{ 479 pl_close_playlist(playlist); 480 pl_close_control(playlist); 481 482 playlist->filename[0] = '\0'; 483 484 playlist->seed = 0; 485 486 playlist->utf8 = true; 487 playlist->control_created = false; 488 playlist->flags = 0; 489 490 playlist->index = 0; 491 playlist->first_index = 0; 492 playlist->amount = 0; 493 playlist->last_insert_pos = -1; 494 495 playlist->started = false; 496 497 if (!resume && playlist == &current_playlist) 498 { 499 /* start with fresh playlist control file when starting new 500 playlist */ 501 create_control_unlocked(playlist); 502 } 503} 504 505int update_playlist_flags_unlocked(struct playlist_info *playlist, 506 unsigned int setf, unsigned int clearf) 507{ 508 unsigned int newflags = (playlist->flags & ~clearf) | setf; 509 if (newflags == playlist->flags) 510 return 0; 511 512 playlist->flags = newflags; 513 514 if (playlist->control_fd >= 0) 515 { 516 int res = update_control_unlocked(playlist, PLAYLIST_COMMAND_FLAGS, 517 setf, clearf, NULL, NULL, NULL); 518 if (res < 0) 519 return res; 520 521 sync_control_unlocked(playlist); 522 } 523 524 return 0; 525} 526 527/* 528 * Returns absolute path of track 529 * 530 * dest: output buffer 531 * src: the file name from the playlist 532 * dir: the absolute path to the directory where the playlist resides 533 * dlen used to truncate dir -- supply -1u to ignore 534 * 535 * The type of path in "src" determines what will be written to "dest": 536 * 537 * 1. UNIX-style absolute paths (/foo/bar) remain unaltered 538 * 2. Windows-style absolute paths (C:/foo/bar) will be converted into an 539 * absolute path by replacing the drive letter with the volume that the 540 * *playlist* resides on, ie. the volume in "dir" 541 * 3. Relative paths are converted to absolute paths by prepending "dir". 542 * This also applies to Windows-style relative paths "C:foo/bar" where 543 * the drive letter is accepted but ignored. 544 */ 545static ssize_t format_track_path(char *dest, char *src, int buf_length, 546 const char *dir, size_t dlen) 547{ 548 /* Look for the end of the string (includes NULL) */ 549 550 if (!src || !dest || !dir) 551 { 552 DEBUGF("%s() bad pointer", __func__); 553 return -2; /* bad pointers */ 554 } 555 size_t len = strcspn(src, "\r\n");; 556 /* Now work back killing white space */ 557 while (len > 0) 558 { 559 int c = src[len - 1]; 560 if (c != '\t' && c != ' ') 561 break; 562 len--; 563 } 564 565 src[len] = '\0'; 566 567 /* Replace backslashes with forward slashes */ 568 path_correct_separators(src, src); 569 570 /* Handle Windows-style absolute paths */ 571 if (path_strip_drive(src, (const char **)&src, true) >= 0 && 572 src[-1] == PATH_SEPCH) 573 { 574 #ifdef HAVE_MULTIVOLUME 575 const char *p; 576 path_strip_last_volume(dir, &p, false); 577 //dir = strmemdupa(dir, p - dir); 578 dlen = (p-dir); /* empty if no volspec on dir */ 579 #else 580 dir = ""; /* only volume is root */ 581 #endif 582 } 583 584 if (*dir == '\0') 585 { 586 dir = PATH_ROOTSTR; 587 dlen = -1u; 588 } 589 590 logf("dir: %s", dir); 591 592 len = path_append_ex(dest, dir, dlen, src, buf_length); 593 if (len >= (size_t)buf_length) 594 return -1; /* buffer too small */ 595 596 path_remove_dot_segments (dest, dest); 597 logf("%s %s", __func__, dest); 598 return strlen (dest); 599} 600 601/* 602 * Initialize a new playlist for viewing/editing/playing. dir is the 603 * directory where the playlist is located and file is the filename. 604 */ 605static void new_playlist_unlocked(struct playlist_info* playlist, 606 const char *dir, const char *file) 607{ 608 empty_playlist_unlocked(playlist, false); 609 610 /* enable dirplay for the current playlist if there's a DIR but no FILE */ 611 if (!file && dir && playlist == &current_playlist) 612 playlist->flags |= PLAYLIST_FLAG_DIRPLAY; 613 614 dir = dir ?: ""; 615 file = file ?: ""; 616 617 update_playlist_filename_unlocked(playlist, dir, file); 618 619 if (playlist->control_fd >= 0) 620 { 621 update_control_unlocked(playlist, PLAYLIST_COMMAND_PLAYLIST, 622 PLAYLIST_CONTROL_FILE_VERSION, -1, dir, file, NULL); 623 sync_control_unlocked(playlist); 624 } 625} 626 627/* 628 * validate the control file. This may include creating/initializing it if 629 * necessary; 630 */ 631static int check_control(struct playlist_info* playlist) 632{ 633 int ret = 0; 634 playlist_write_lock(playlist); 635 636 if (!playlist->control_created) 637 { 638 create_control_unlocked(playlist); 639 640 if (playlist->control_fd >= 0) 641 { 642 char* dir = playlist->filename; 643 char* file = playlist->filename+playlist->dirlen; 644 char c = playlist->filename[playlist->dirlen-1]; 645 646 playlist->filename[playlist->dirlen-1] = '\0'; 647 648 update_control_unlocked(playlist, PLAYLIST_COMMAND_PLAYLIST, 649 PLAYLIST_CONTROL_FILE_VERSION, -1, dir, file, NULL); 650 sync_control_unlocked(playlist); 651 playlist->filename[playlist->dirlen-1] = c; 652 } 653 } 654 655 if (playlist->control_fd < 0) 656 ret = -1; 657 658 playlist_write_unlock(playlist); 659 return ret; 660} 661 662 663/* 664 * Display splash message showing progress of playlist/directory insertion or 665 * save. 666 */ 667static void display_playlist_count(int count, const unsigned char *fmt, 668 bool final) 669{ 670 static long talked_tick = 0; 671 long id = P2ID(fmt); 672 673 if(id >= 0 && global_settings.talk_menu) 674 { 675 long next_tick = talked_tick + (HZ * 5); 676 677 if (final || talked_tick == 0) 678 next_tick = current_tick - 1; 679 680 if(count && TIME_AFTER(current_tick, next_tick)) 681 { 682 talked_tick = current_tick; 683 if (final) 684 { 685 talk_id(LANG_ALL, false); 686 talk_number(count, true); 687 talk_id(id, true); 688 talk_force_enqueue_next(); /* Don't interrupt final announcement */ 689 } 690 else 691 { 692 talk_number(count, false); 693 talk_id(id, true); 694 } 695 } 696 } 697 698 splashf(0, P2STR(fmt), count, str(LANG_OFF_ABORT)); /* (voiced above) */ 699} 700 701/* 702 * calculate track offsets within a playlist file 703 */ 704static int add_indices_to_playlist(struct playlist_info* playlist, 705 char* buffer, size_t buflen) 706{ 707 ssize_t nread; 708 unsigned int i, count = 0; 709 bool store_index; 710 unsigned char *p; 711 int result = 0; 712 /* get emergency buffer so we don't fail horribly */ 713 if (!buflen) 714 buffer = alloca((buflen = 64)); 715 716 playlist_write_lock(playlist); 717 718 /* Close and re-open the playlist to ensure we are properly 719 * positioned at the start of the file after any UTF-8 BOM. */ 720 pl_close_playlist(playlist); 721 if (pl_open_playlist(playlist) < 0) 722 { 723 result = -1; 724 goto exit; 725 } 726 727 i = lseek(playlist->fd, 0, SEEK_CUR); 728 729 splash(0, ID2P(LANG_WAIT)); 730 store_index = true; 731 732 while(1) 733 { 734 nread = read(playlist->fd, buffer, buflen); 735 /* Terminate on EOF */ 736 if(nread <= 0) 737 break; 738 739 p = (unsigned char *)buffer; 740 741 for(count=0; count < (unsigned int)nread; count++,p++) { 742 743 /* Are we on a new line? */ 744 if((*p == '\n') || (*p == '\r')) 745 { 746 store_index = true; 747 } 748 else if(store_index) 749 { 750 store_index = false; 751 752 if(*p != '#') 753 { 754 if ( playlist->amount >= playlist->max_playlist_size ) { 755 notify_buffer_full(); 756 result = -1; 757 goto exit; 758 } 759 760 /* Store a new entry */ 761 playlist->indices[ playlist->amount ] = i+count; 762 dc_init_filerefs(playlist, playlist->amount, 1); 763 playlist->amount++; 764 } 765 } 766 } 767 768 i+= count; 769 } 770 771exit: 772 playlist_write_unlock(playlist); 773 return result; 774} 775 776/* 777 * Checks if there are any music files in the dir or any of its 778 * subdirectories. May be called recursively. 779 */ 780static int check_subdir_for_music(char *dir, const char *subdir, bool recurse) 781{ 782 int result = -1; 783 size_t dirlen = strlen(dir); 784 int num_files = 0; 785 int i; 786 struct entry *files; 787 bool has_music = false; 788 bool has_subdir = false; 789 struct tree_context* tc = tree_get_context(); 790 791 if (path_append(dir + dirlen, PA_SEP_HARD, subdir, MAX_PATH - dirlen) >= 792 MAX_PATH - dirlen) 793 { 794 return 0; 795 } 796 797 if (ft_load(tc, dir) < 0) 798 { 799 return -2; 800 } 801 802 tree_lock_cache(tc); 803 files = tree_get_entries(tc); 804 num_files = tc->filesindir; 805 806 for (i=0; i<num_files; i++) 807 { 808 if (files[i].attr & ATTR_DIRECTORY) 809 has_subdir = true; 810 else if ((files[i].attr & FILE_ATTR_MASK) == FILE_ATTR_AUDIO) 811 { 812 has_music = true; 813 break; 814 } 815 } 816 817 if (has_music) 818 { 819 tree_unlock_cache(tc); 820 return 0; 821 } 822 823 if (has_subdir && recurse) 824 { 825 for (i=0; i<num_files; i++) 826 { 827 if (action_userabort(TIMEOUT_NOBLOCK)) 828 { 829 result = -2; 830 break; 831 } 832 833 if (files[i].attr & ATTR_DIRECTORY) 834 { 835 result = check_subdir_for_music(dir, files[i].name, true); 836 if (!result) 837 break; 838 } 839 } 840 } 841 tree_unlock_cache(tc); 842 843 if (result < 0) 844 { 845 if (dirlen) 846 { 847 dir[dirlen] = '\0'; 848 } 849 else 850 { 851 strcpy(dir, PATH_ROOTSTR); 852 } 853 854 /* we now need to reload our current directory */ 855 if(ft_load(tc, dir) < 0) 856 splash(HZ*2, ID2P(LANG_PLAYLIST_DIRECTORY_ACCESS_ERROR)); 857 } 858 return result; 859} 860 861/* 862 * search through all the directories (starting with the current) to find 863 * one that has tracks to play 864 */ 865static int get_next_dir(char *dir, int direction) 866{ 867 struct playlist_info* playlist = &current_playlist; 868 int result = -1; 869 char *start_dir = NULL; 870 bool exit = false; 871 struct tree_context* tc = tree_get_context(); 872 int saved_dirfilter = *(tc->dirfilter); 873 unsigned int base_len; 874 875 if (global_settings.constrain_next_folder) 876 { 877 /* constrain results to directories below user's start directory */ 878 strcpy(dir, global_settings.start_directory); 879 base_len = strlen(dir); 880 881 /* strip any trailing slash from base directory */ 882 if (base_len > 0 && dir[base_len - 1] == '/') 883 { 884 base_len--; 885 dir[base_len] = '\0'; 886 } 887 } 888 else 889 { 890 /* start from root directory */ 891 dir[0] = '\0'; 892 base_len = 0; 893 } 894 895 /* process random folder advance */ 896 if (global_settings.next_folder == FOLDER_ADVANCE_RANDOM) 897 { 898 int fd = open(ROCKBOX_DIR "/folder_advance_list.dat", O_RDONLY); 899 if (fd >= 0) 900 { 901 int folder_count = 0; 902 ssize_t nread = read(fd,&folder_count,sizeof(int)); 903 if ((nread == sizeof(int)) && folder_count) 904 { 905 char buffer[MAX_PATH]; 906 /* give up looking for a directory after we've had four 907 times as many tries as there are directories. */ 908 unsigned long allowed_tries = folder_count * 4; 909 int i; 910 srand(current_tick); 911 *(tc->dirfilter) = SHOW_MUSIC; 912 tc->sort_dir = global_settings.sort_dir; 913 while (!exit && allowed_tries--) 914 { 915 i = rand() % folder_count; 916 lseek(fd, sizeof(int) + (MAX_PATH * i), SEEK_SET); 917 read(fd, buffer, MAX_PATH); 918 /* is the current dir within our base dir and has music? */ 919 if ((base_len == 0 || !strncmp(buffer, dir, base_len)) 920 && check_subdir_for_music(buffer, "", false) == 0) 921 exit = true; 922 } 923 close(fd); 924 *(tc->dirfilter) = saved_dirfilter; 925 tc->sort_dir = global_settings.sort_dir; 926 reload_directory(); 927 if (exit) 928 { 929 strcpy(dir,buffer); 930 return 0; 931 } 932 } 933 else 934 close(fd); 935 } 936 } 937 938 /* if the current file is within our base dir, use its dir instead */ 939 if (base_len == 0 || !strncmp(playlist->filename, dir, base_len)) 940 strmemccpy(dir, playlist->filename, playlist->dirlen); 941 942 /* use the tree browser dircache to load files */ 943 *(tc->dirfilter) = SHOW_ALL; 944 945 /* set up sorting/direction */ 946 tc->sort_dir = global_settings.sort_dir; 947 if (direction < 0) 948 { 949 static const char sortpairs[] = 950 { 951 [SORT_ALPHA] = SORT_ALPHA_REVERSED, 952 [SORT_DATE] = SORT_DATE_REVERSED, 953 [SORT_TYPE] = SORT_TYPE_REVERSED, 954 [SORT_ALPHA_REVERSED] = SORT_ALPHA, 955 [SORT_DATE_REVERSED] = SORT_DATE, 956 [SORT_TYPE_REVERSED] = SORT_TYPE, 957 }; 958 959 if ((unsigned)tc->sort_dir < sizeof(sortpairs)) 960 tc->sort_dir = sortpairs[tc->sort_dir]; 961 } 962 963 while (!exit) 964 { 965 struct entry *files; 966 int num_files = 0; 967 int i; 968 969 if (ft_load(tc, (dir[0]=='\0')?"/":dir) < 0) 970 { 971 exit = true; 972 result = -1; 973 break; 974 } 975 976 tree_lock_cache(tc); 977 files = tree_get_entries(tc); 978 num_files = tc->filesindir; 979 980 for (i=0; i<num_files; i++) 981 { 982 /* user abort */ 983 if (action_userabort(TIMEOUT_NOBLOCK)) 984 { 985 result = -1; 986 exit = true; 987 break; 988 } 989 990 if (files[i].attr & ATTR_DIRECTORY) 991 { 992 if (!start_dir) 993 { 994 result = check_subdir_for_music(dir, files[i].name, true); 995 if (result != -1) 996 { 997 exit = true; 998 break; 999 } 1000 } 1001 else if (!strcmp(start_dir, files[i].name)) 1002 start_dir = NULL; 1003 } 1004 } 1005 tree_unlock_cache(tc); 1006 1007 if (!exit) 1008 { 1009 /* we've already descended to the base dir with nothing found, 1010 check whether that contains music */ 1011 if (strlen(dir) <= base_len) 1012 { 1013 result = check_subdir_for_music(dir, "", true); 1014 if (result == -1) 1015 /* there's no music files in the base directory, 1016 treat as a fatal error */ 1017 result = -2; 1018 break; 1019 } 1020 else 1021 { 1022 /* move down to parent directory. current directory name is 1023 stored as the starting point for the search in parent */ 1024 start_dir = strrchr(dir, '/'); 1025 if (start_dir) 1026 { 1027 *start_dir = '\0'; 1028 start_dir++; 1029 } 1030 else 1031 break; 1032 } 1033 } 1034 } 1035 1036 /* restore dirfilter */ 1037 *(tc->dirfilter) = saved_dirfilter; 1038 tc->sort_dir = global_settings.sort_dir; 1039 1040 return result; 1041} 1042 1043/* 1044 * gets pathname for track at seek index 1045 */ 1046static int get_track_filename(struct playlist_info* playlist, int index, 1047 char *buf, int buf_length) 1048{ 1049 int fd; 1050 int max = -1; 1051 char tmp_buf[MAX_PATH+1]; 1052 char dir_buf[MAX_PATH+1]; 1053 bool utf8 = playlist->utf8; 1054 if (buf_length > 0) 1055 buf[0] = '\0'; 1056 1057 if (index < 0 || index >= playlist->amount) 1058 return -1; 1059 1060 playlist_write_lock(playlist); 1061 1062 bool control_file = playlist->indices[index] & PLAYLIST_INSERT_TYPE_MASK; 1063 unsigned long seek = playlist->indices[index] & PLAYLIST_SEEK_MASK; 1064 1065#ifdef HAVE_DIRCACHE 1066 if (playlist->dcfrefs_handle) 1067 { 1068 struct dircache_fileref *dcfrefs = core_get_data_pinned(playlist->dcfrefs_handle); 1069 max = dircache_get_fileref_path(&dcfrefs[index], 1070 tmp_buf, sizeof(tmp_buf)); 1071 1072 NOTEF("%s [in DCache]: 0x%x %s", __func__, dcfrefs[index], tmp_buf); 1073 core_put_data_pinned(dcfrefs); 1074 } 1075#endif /* HAVE_DIRCACHE */ 1076 1077 if (max < 0) 1078 { 1079 if (control_file) 1080 { 1081 fd = playlist->control_fd; 1082 utf8 = true; 1083 } 1084 else 1085 { 1086 fd = pl_open_playlist(playlist); 1087 } 1088 1089 if(-1 != fd) 1090 { 1091 if (lseek(fd, seek, SEEK_SET) != (off_t)seek) 1092 max = -1; 1093 else 1094 { 1095 max = read(fd, tmp_buf, MIN((size_t) buf_length, sizeof(tmp_buf))); 1096 1097 if (max > 0) 1098 { 1099 /* playlist file may end without a new line - terminate buffer */ 1100 tmp_buf[MIN(max, (int)sizeof(tmp_buf) - 1)] = '\0'; 1101 1102 /* Use dir_buf as a temporary buffer. Note that dir_buf must 1103 * be as large as tmp_buf. 1104 */ 1105 if (!utf8) 1106 max = convert_m3u_name(tmp_buf, max, 1107 sizeof(tmp_buf), dir_buf); 1108 1109 NOTEF("%s [in File]: 0x%x %s", __func__, seek, tmp_buf); 1110 } 1111 } 1112 } 1113 1114 if (max < 0) 1115 { 1116 playlist_write_unlock(playlist); 1117 1118 if (usb_detect() == USB_INSERTED) 1119 ; /* ignore error on usb plug */ 1120 else if (control_file) 1121 notify_control_access_error(); 1122 else 1123 notify_access_error(); 1124 1125 return max; 1126 } 1127 } 1128 1129 playlist_write_unlock(playlist); 1130 1131 if (format_track_path(buf, tmp_buf, buf_length, 1132 playlist->filename, playlist->dirlen) < 0) 1133 return -1; 1134 1135 return 0; 1136} 1137 1138/* 1139 * Utility function to create a new playlist, fill it with the next or 1140 * previous directory, shuffle it if needed, and start playback. 1141 * If play_last is true and direction zero or negative, start playing 1142 * the last file in the directory, otherwise start playing the first. 1143 */ 1144static int create_and_play_dir(int direction, bool play_last) 1145{ 1146 char dir[MAX_PATH + 1]; 1147 int res = get_next_dir(dir, direction); 1148 int index = -1; 1149 1150 if (res < 0) /* return the error encountered */ 1151 return res; 1152 1153 if (playlist_create(dir, NULL) != -1) 1154 { 1155 ft_build_playlist(tree_get_context(), 0); 1156 1157 if (global_settings.playlist_shuffle) 1158 playlist_shuffle(current_tick, -1); 1159 1160 if (play_last && direction <= 0) 1161 index = current_playlist.amount - 1; 1162 else 1163 index = 0; 1164 1165 current_playlist.started = true; 1166 } 1167 1168 /* we've overwritten the dircache when getting the next/previous dir, 1169 so the tree browser context will need to be reloaded */ 1170 reload_directory(); 1171 1172 return index; 1173} 1174 1175/* 1176 * remove all tracks, leaving the current track queued 1177 */ 1178static int remove_all_tracks_unlocked(struct playlist_info *playlist) 1179{ 1180 char filename[MAX_PATH]; 1181 int seek_pos = -1; 1182 1183 if (playlist->amount <= 0) 1184 return 0; 1185 1186 if (playlist->control_fd < 0) 1187 return -1; 1188 1189 if (get_track_filename(playlist, playlist->index, 1190 filename, sizeof(filename)) != 0) 1191 return -1; 1192 1193 /* Start over with fresh control file for emptied dynamic playlist */ 1194 pl_close_control(playlist); 1195 create_control_unlocked(playlist); 1196 update_control_unlocked(playlist, PLAYLIST_COMMAND_PLAYLIST, 1197 PLAYLIST_CONTROL_FILE_VERSION, -1, 1198 "", "", NULL); 1199 update_control_unlocked(playlist, PLAYLIST_COMMAND_QUEUE, 1200 0, 0, filename, NULL, &seek_pos); 1201 sync_control_unlocked(playlist); 1202 1203 /* Move current track down to position 0 */ 1204 playlist->indices[0] = playlist->indices[playlist->index]; 1205#ifdef HAVE_DIRCACHE 1206 if (playlist->dcfrefs_handle) 1207 { 1208 struct dircache_fileref *dcfrefs = 1209 core_get_data(playlist->dcfrefs_handle); 1210 dcfrefs[0] = dcfrefs[playlist->index]; 1211 } 1212#endif 1213 1214 /* Update playlist state as if by remove_track_unlocked() */ 1215 playlist->first_index = 0; 1216 playlist->index = 0; 1217 playlist->amount = 1; 1218 playlist->indices[0] |= PLAYLIST_QUEUED; 1219 playlist->flags = 0; /* Reset dirplay and modified flags */ 1220 1221 if (playlist->last_insert_pos == 0) 1222 playlist->last_insert_pos = -1; 1223 else 1224 playlist->last_insert_pos = 0; 1225 1226 if (seek_pos == -1) 1227 return 0; 1228 1229 /* Update seek offset so it points into the new control file. */ 1230 playlist->indices[0] &= ~PLAYLIST_INSERT_TYPE_MASK & ~PLAYLIST_SEEK_MASK; 1231 playlist->indices[0] |= PLAYLIST_INSERT_TYPE_INSERT | seek_pos; 1232 1233 /* Cut connection to playlist file */ 1234 update_playlist_filename_unlocked(playlist, "", ""); 1235 1236 return 0; 1237} 1238 1239/* 1240 * Add track to playlist at specified position. There are seven special 1241 * positions that can be specified: 1242 * PLAYLIST_PREPEND - Add track at beginning of playlist 1243 * PLAYLIST_INSERT - Add track after current song. NOTE: If 1244 * there are already inserted tracks then track 1245 * is added to the end of the insertion list 1246 * PLAYLIST_INSERT_FIRST - Add track immediately after current song, no 1247 * matter what other tracks have been inserted 1248 * PLAYLIST_INSERT_LAST - Add track to end of playlist 1249 * PLAYLIST_INSERT_LAST_ROTATED - Add track to end of playlist, by inserting at 1250 * first_index, then increasing first_index by 1 1251 * PLAYLIST_INSERT_SHUFFLED - Add track at some random point between the 1252 * current playing track and end of playlist 1253 * PLAYLIST_INSERT_LAST_SHUFFLED - Add tracks in random order to the end of 1254 * the playlist. 1255 * PLAYLIST_REPLACE - Erase current playlist, Cue the current track 1256 * and inster this track at the end. 1257 */ 1258static int add_track_to_playlist_unlocked(struct playlist_info* playlist, 1259 const char *filename, int position, 1260 bool queue, int seek_pos) 1261{ 1262 int insert_position, orig_position; 1263 unsigned long flags = PLAYLIST_INSERT_TYPE_INSERT; 1264 int i; 1265 1266 insert_position = orig_position = position; 1267 1268 if (playlist->amount >= playlist->max_playlist_size) 1269 { 1270 notify_buffer_full(); 1271 return -1; 1272 } 1273 1274 switch (position) 1275 { 1276 case PLAYLIST_PREPEND: 1277 position = insert_position = playlist->first_index; 1278 break; 1279 case PLAYLIST_INSERT: 1280 /* if there are already inserted tracks then add track to end of 1281 insertion list else add after current playing track */ 1282 if (playlist->last_insert_pos >= 0 && 1283 playlist->last_insert_pos < playlist->amount && 1284 (playlist->indices[playlist->last_insert_pos]& 1285 PLAYLIST_INSERT_TYPE_MASK) == PLAYLIST_INSERT_TYPE_INSERT) 1286 position = insert_position = playlist->last_insert_pos+1; 1287 else if (playlist->amount > 0) 1288 position = insert_position = playlist->index + 1; 1289 else 1290 position = insert_position = 0; 1291 1292 playlist->last_insert_pos = position; 1293 break; 1294 case PLAYLIST_INSERT_FIRST: 1295 if (playlist->amount > 0) 1296 position = insert_position = playlist->index + 1; 1297 else 1298 position = insert_position = 0; 1299 1300 playlist->last_insert_pos = position; 1301 break; 1302 case PLAYLIST_INSERT_LAST: 1303 if (playlist->first_index <= 0) 1304 { 1305 position = insert_position = playlist->amount; 1306 playlist->last_insert_pos = position; 1307 break; 1308 } 1309 /* fallthrough */ 1310 case PLAYLIST_INSERT_LAST_ROTATED: 1311 position = insert_position = playlist->first_index; 1312 playlist->last_insert_pos = position; 1313 break; 1314 case PLAYLIST_INSERT_SHUFFLED: 1315 { 1316 if (playlist->started) 1317 { 1318 int offset; 1319 int n = playlist->amount - 1320 rotate_index(playlist, playlist->index); 1321 1322 if (n > 0) 1323 offset = rand() % n; 1324 else 1325 offset = 0; 1326 1327 position = playlist->index + offset + 1; 1328 if (position >= playlist->amount) 1329 position -= playlist->amount; 1330 1331 insert_position = position; 1332 } 1333 else 1334 position = insert_position = (rand() % (playlist->amount+1)); 1335 break; 1336 } 1337 case PLAYLIST_INSERT_LAST_SHUFFLED: 1338 { 1339 int playlist_end = playlist->first_index > 0 ? 1340 playlist->first_index : playlist->amount; 1341 1342 int newpos = playlist->last_shuffled_start + 1343 rand() % (playlist_end - playlist->last_shuffled_start + 1); 1344 1345 position = insert_position = newpos; 1346 break; 1347 } 1348 case PLAYLIST_REPLACE: 1349 if (remove_all_tracks_unlocked(playlist) < 0) 1350 return -1; 1351 int newpos = playlist->index + 1; 1352 playlist->last_insert_pos = position = insert_position = newpos; 1353 break; 1354 } 1355 1356 if (queue) 1357 flags |= PLAYLIST_QUEUED; 1358 1359#ifdef HAVE_DIRCACHE 1360 struct dircache_fileref *dcfrefs = NULL; 1361 if (playlist->dcfrefs_handle) 1362 dcfrefs = core_get_data(playlist->dcfrefs_handle); 1363#else 1364 int *dcfrefs = NULL; 1365#endif 1366 1367 /* shift indices so that track can be added */ 1368 for (i=playlist->amount; i>insert_position; i--) 1369 { 1370 playlist->indices[i] = playlist->indices[i-1]; 1371 if (dcfrefs) 1372 dcfrefs[i] = dcfrefs[i-1]; 1373 } 1374 1375 /* update stored indices if needed */ 1376 1377 if (orig_position < 0) 1378 { 1379 if (playlist->amount > 0 && insert_position <= playlist->index && 1380 playlist->started && orig_position != PLAYLIST_INSERT_LAST_ROTATED) 1381 playlist->index++; 1382 1383 /* 1384 * When inserting into a playlist at positions before or equal to first_index 1385 * (unless PLAYLIST_PREPEND is specified explicitly), adjust first_index, so 1386 * that track insertion near the end does not affect the start of the playlist 1387 */ 1388 if (playlist->amount > 0 && insert_position <= playlist->first_index && 1389 orig_position != PLAYLIST_PREPEND && playlist->started) 1390 { 1391 /* 1392 * To ensure proper resuming from control file for a track that is supposed 1393 * to be appended, but is inserted at first_index, store position as special 1394 * value. 1395 * If we were to store the position unchanged, i.e. use first_index, 1396 * track would be prepended, instead, after resuming. 1397 */ 1398 if (insert_position == playlist->first_index) 1399 position = PLAYLIST_INSERT_LAST_ROTATED; 1400 1401 playlist->first_index++; 1402 } 1403 } 1404 else if (playlist->amount > 0 && insert_position < playlist->first_index && 1405 playlist->started) 1406 playlist->first_index++; 1407 1408 if (insert_position < playlist->last_insert_pos || 1409 (insert_position == playlist->last_insert_pos && position < 0 && 1410 position != PLAYLIST_INSERT_LAST_ROTATED)) 1411 playlist->last_insert_pos++; 1412 1413 if (seek_pos < 0 && playlist->control_fd >= 0) 1414 { 1415 int result = update_control_unlocked(playlist, 1416 (queue?PLAYLIST_COMMAND_QUEUE:PLAYLIST_COMMAND_ADD), position, 1417 playlist->last_insert_pos, filename, NULL, &seek_pos); 1418 1419 if (result < 0) 1420 return result; 1421 } 1422 1423 playlist->indices[insert_position] = flags | seek_pos; 1424 dc_init_filerefs(playlist, insert_position, 1); 1425 1426 playlist->amount++; 1427 1428 return insert_position; 1429} 1430 1431/* 1432 * Callback for playlist_directory_tracksearch to insert track into 1433 * playlist. 1434 */ 1435static int directory_search_callback(char* filename, void* context) 1436{ 1437 return playlist_insert_context_add(context, filename); 1438} 1439 1440/* 1441 * remove track at specified position 1442 */ 1443static int remove_track_unlocked(struct playlist_info* playlist, 1444 int position, bool write) 1445{ 1446 int i; 1447 int result = 0; 1448 1449 if (playlist->amount <= 0) 1450 return -1; 1451 1452#ifdef HAVE_DIRCACHE 1453 struct dircache_fileref *dcfrefs = NULL; 1454 if (playlist->dcfrefs_handle) 1455 dcfrefs = core_get_data(playlist->dcfrefs_handle); 1456#else 1457 int *dcfrefs = NULL; 1458#endif 1459 1460 /* shift indices now that track has been removed */ 1461 for (i=position; i<playlist->amount; i++) 1462 { 1463 playlist->indices[i] = playlist->indices[i+1]; 1464 if (dcfrefs) 1465 dcfrefs[i] = dcfrefs[i+1]; 1466 } 1467 1468 playlist->amount--; 1469 1470 /* update stored indices if needed */ 1471 if (position < playlist->index) 1472 playlist->index--; 1473 1474 if (position < playlist->first_index) 1475 { 1476 playlist->first_index--; 1477 } 1478 1479 if (position <= playlist->last_insert_pos) 1480 playlist->last_insert_pos--; 1481 1482 if (write && playlist->control_fd >= 0) 1483 { 1484 result = update_control_unlocked(playlist, PLAYLIST_COMMAND_DELETE, 1485 position, -1, NULL, NULL, NULL); 1486 if (result >= 0) 1487 sync_control_unlocked(playlist); 1488 } 1489 1490 return result; 1491} 1492 1493/* 1494 * Search for the seek track and set appropriate indices. Used after shuffle 1495 * to make sure the current index is still pointing to correct track. 1496 */ 1497static void find_and_set_playlist_index_unlocked(struct playlist_info* playlist, 1498 unsigned long seek) 1499{ 1500 int i; 1501 1502 /* Set the index to the current song */ 1503 for (i=0; i<playlist->amount; i++) 1504 { 1505 if (playlist->indices[i] == seek) 1506 { 1507 playlist->index = playlist->first_index = i; 1508 1509 break; 1510 } 1511 } 1512} 1513 1514/* 1515 * randomly rearrange the array of indices for the playlist. If start_current 1516 * is true then update the index to the new index of the current playing track 1517 */ 1518static int randomise_playlist_unlocked(struct playlist_info* playlist, 1519 unsigned int seed, bool start_current, 1520 bool write) 1521{ 1522 int count; 1523 int candidate; 1524 unsigned long current = playlist->indices[playlist->index]; 1525 1526 /* seed 0 is used to identify sorted playlist for resume purposes */ 1527 if (seed == 0) 1528 seed = 1; 1529 1530 /* seed with the given seed */ 1531 srand(seed); 1532 1533 /* randomise entire indices list */ 1534 for(count = playlist->amount - 1; count >= 0; count--) 1535 { 1536 /* the rand is from 0 to RAND_MAX, so adjust to our value range */ 1537 candidate = rand() % (count + 1); 1538 1539 /* now swap the values at the 'count' and 'candidate' positions */ 1540 unsigned long indextmp = playlist->indices[candidate]; 1541 playlist->indices[candidate] = playlist->indices[count]; 1542 playlist->indices[count] = indextmp; 1543#ifdef HAVE_DIRCACHE 1544 if (playlist->dcfrefs_handle) 1545 { 1546 struct dircache_fileref *dcfrefs = core_get_data(playlist->dcfrefs_handle); 1547 struct dircache_fileref dcftmp = dcfrefs[candidate]; 1548 dcfrefs[candidate] = dcfrefs[count]; 1549 dcfrefs[count] = dcftmp; 1550 } 1551#endif 1552 } 1553 1554 if (start_current) 1555 find_and_set_playlist_index_unlocked(playlist, current); 1556 1557 /* indices have been moved so last insert position is no longer valid */ 1558 playlist->last_insert_pos = -1; 1559 1560 playlist->seed = seed; 1561 1562 if (write) 1563 { 1564 update_control_unlocked(playlist, PLAYLIST_COMMAND_SHUFFLE, seed, 1565 playlist->first_index, NULL, NULL, NULL); 1566 } 1567 1568 return 0; 1569} 1570 1571/* 1572 * used to sort track indices. Sort order is as follows: 1573 * 1. Prepended tracks (in prepend order) 1574 * 2. Playlist/directory tracks (in playlist order) 1575 * 3. Inserted/Appended tracks (in insert order) 1576 */ 1577static int sort_compare_fn(const void* p1, const void* p2) 1578{ 1579 unsigned long* e1 = (unsigned long*) p1; 1580 unsigned long* e2 = (unsigned long*) p2; 1581 unsigned long flags1 = *e1 & PLAYLIST_INSERT_TYPE_MASK; 1582 unsigned long flags2 = *e2 & PLAYLIST_INSERT_TYPE_MASK; 1583 1584 if (flags1 == flags2) 1585 return (*e1 & PLAYLIST_SEEK_MASK) - (*e2 & PLAYLIST_SEEK_MASK); 1586 else if (flags1 == PLAYLIST_INSERT_TYPE_PREPEND || 1587 flags2 == PLAYLIST_INSERT_TYPE_APPEND) 1588 return -1; 1589 else if (flags1 == PLAYLIST_INSERT_TYPE_APPEND || 1590 flags2 == PLAYLIST_INSERT_TYPE_PREPEND) 1591 return 1; 1592 else if (flags1 && flags2) 1593 return (*e1 & PLAYLIST_SEEK_MASK) - (*e2 & PLAYLIST_SEEK_MASK); 1594 else 1595 return *e1 - *e2; 1596} 1597 1598/* 1599 * Sort the array of indices for the playlist. If start_current is true then 1600 * set the index to the new index of the current song. 1601 * Also while going to unshuffled mode set the first_index to 0. 1602 */ 1603static int sort_playlist_unlocked(struct playlist_info* playlist, 1604 bool start_current, bool write) 1605{ 1606 unsigned long current = playlist->indices[playlist->index]; 1607 1608 if (playlist->amount > 0) 1609 qsort((void*)playlist->indices, playlist->amount, 1610 sizeof(playlist->indices[0]), sort_compare_fn); 1611 1612#ifdef HAVE_DIRCACHE 1613 /** We need to re-check the song names from disk because qsort can't 1614 * sort two arrays at once :/ 1615 * FIXME: Please implement a better way to do this. */ 1616 dc_init_filerefs(playlist, 0, playlist->max_playlist_size); 1617#endif 1618 1619 if (start_current) 1620 find_and_set_playlist_index_unlocked(playlist, current); 1621 1622 /* indices have been moved so last insert position is no longer valid */ 1623 playlist->last_insert_pos = -1; 1624 1625 if (write && playlist->control_fd >= 0) 1626 { 1627 playlist->first_index = 0; 1628 update_control_unlocked(playlist, PLAYLIST_COMMAND_UNSHUFFLE, 1629 playlist->first_index, -1, NULL, NULL, NULL); 1630 } 1631 1632 return 0; 1633} 1634 1635/* Calculate how many steps we have to really step when skipping entries 1636 * marked as bad. 1637 */ 1638static int calculate_step_count(const struct playlist_info *playlist, int steps) 1639{ 1640 int i, count, direction; 1641 int index; 1642 int stepped_count = 0; 1643 1644 if (steps < 0) 1645 { 1646 direction = -1; 1647 count = -steps; 1648 } 1649 else 1650 { 1651 direction = 1; 1652 count = steps; 1653 } 1654 1655 index = playlist->index; 1656 i = 0; 1657 do { 1658 /* Boundary check */ 1659 if (index < 0) 1660 index += playlist->amount; 1661 if (index >= playlist->amount) 1662 index -= playlist->amount; 1663 1664 /* Check if we found a bad entry. */ 1665 if (playlist->indices[index] & PLAYLIST_SKIPPED) 1666 { 1667 steps += direction; 1668 /* Are all entries bad? */ 1669 if (stepped_count++ > playlist->amount) 1670 break ; 1671 } 1672 else 1673 i++; 1674 1675 index += direction; 1676 } while (i <= count); 1677 1678 return steps; 1679} 1680 1681/* 1682 * returns the index of the track that is "steps" away from current playing 1683 * track. 1684 */ 1685static int get_next_index(const struct playlist_info* playlist, int steps, 1686 int repeat_mode) 1687{ 1688 int current_index = playlist->index; 1689 int next_index = -1; 1690 1691 if (playlist->amount <= 0) 1692 return -1; 1693 1694 if (repeat_mode == -1) 1695 repeat_mode = global_settings.repeat_mode; 1696 1697 if (repeat_mode == REPEAT_SHUFFLE && playlist->amount <= 1) 1698 { 1699 repeat_mode = REPEAT_ALL; 1700 } 1701 1702 steps = calculate_step_count(playlist, steps); 1703 switch (repeat_mode) 1704 { 1705 case REPEAT_SHUFFLE: 1706 /* Treat repeat shuffle just like repeat off. At end of playlist, 1707 play will be resumed in playlist_next() */ 1708 case REPEAT_OFF: 1709 { 1710 current_index = rotate_index(playlist, current_index); 1711 next_index = current_index+steps; 1712 if ((next_index < 0) || (next_index >= playlist->amount)) 1713 next_index = -1; 1714 else 1715 next_index = (next_index+playlist->first_index) % 1716 playlist->amount; 1717 1718 break; 1719 } 1720 1721 case REPEAT_ONE: 1722#ifdef AB_REPEAT_ENABLE 1723 case REPEAT_AB: 1724#endif 1725 next_index = current_index; 1726 break; 1727 1728 case REPEAT_ALL: 1729 default: 1730 { 1731 next_index = (current_index+steps) % playlist->amount; 1732 while (next_index < 0) 1733 next_index += playlist->amount; 1734 1735 if (steps >= playlist->amount) 1736 { 1737 int i, index; 1738 1739 index = next_index; 1740 next_index = -1; 1741 1742 /* second time around so skip the queued files */ 1743 for (i=0; i<playlist->amount; i++) 1744 { 1745 if (playlist->indices[index] & PLAYLIST_QUEUED) 1746 index = (index+1) % playlist->amount; 1747 else 1748 { 1749 next_index = index; 1750 break; 1751 } 1752 } 1753 } 1754 break; 1755 } 1756 } 1757 1758 /* No luck if the whole playlist was bad. */ 1759 if (next_index < 0 || next_index >= playlist->amount || 1760 playlist->indices[next_index] & PLAYLIST_SKIPPED) 1761 return -1; 1762 1763 return next_index; 1764} 1765 1766#ifdef HAVE_DIRCACHE 1767/** 1768 * Thread to update filename pointers to dircache on background 1769 * without affecting playlist load up performance. 1770 */ 1771static void dc_thread_playlist(void) 1772{ 1773 struct queue_event ev; 1774 static char tmp[MAX_PATH+1]; 1775 1776 struct playlist_info *playlist = &current_playlist; 1777 struct dircache_fileref *dcfrefs; 1778 int index; 1779 1780 /* Thread starts out stopped */ 1781 long sleep_time = TIMEOUT_BLOCK; 1782 int stop_count = 1; 1783 bool is_dirty = false; 1784 1785 while (1) 1786 { 1787 queue_wait_w_tmo(&playlist_queue, &ev, sleep_time); 1788 1789 switch (ev.id) 1790 { 1791 case PLAYLIST_DC_SCAN_START: 1792 if (ev.data) 1793 is_dirty = true; 1794 1795 stop_count--; 1796 if (is_dirty && stop_count == 0) 1797 { 1798 /* Start the background scanning after either the disk 1799 * spindown timeout or 5s, whichever is less */ 1800 sleep_time = 5 * HZ; 1801#ifdef HAVE_DISK_STORAGE 1802 if (global_settings.disk_spindown > 1 && 1803 global_settings.disk_spindown <= 5) 1804 sleep_time = (global_settings.disk_spindown - 1) * HZ; 1805#endif 1806 } 1807 break; 1808 1809 case PLAYLIST_DC_SCAN_STOP: 1810 stop_count++; 1811 sleep_time = TIMEOUT_BLOCK; 1812 queue_reply(&playlist_queue, 0); 1813 break; 1814 1815 case SYS_TIMEOUT: 1816 { 1817 /* Nothing to do if there are no dcfrefs or tracks */ 1818 if (!playlist->dcfrefs_handle || playlist->amount <= 0) 1819 { 1820 is_dirty = false; 1821 sleep_time = TIMEOUT_BLOCK; 1822 logf("%s: nothing to scan", __func__); 1823 break; 1824 } 1825 1826 /* Retry at a later time if the dircache is not ready */ 1827 struct dircache_info info; 1828 dircache_get_info(&info); 1829 if (info.status != DIRCACHE_READY) 1830 { 1831 logf("%s: dircache not ready", __func__); 1832 break; 1833 } 1834 1835 logf("%s: scan start", __func__); 1836#ifdef LOGF_ENABLE 1837 long scan_start_tick = current_tick; 1838#endif 1839 1840 trigger_cpu_boost(); 1841 dcfrefs = core_get_data_pinned(playlist->dcfrefs_handle); 1842 1843 for (index = 0; index < playlist->amount; index++) 1844 { 1845 /* Process only pointers that are superficially stale. */ 1846 if (dircache_search(DCS_FILEREF, &dcfrefs[index], NULL) > 0) 1847 continue; 1848 1849 /* Bail out if a command needs servicing. */ 1850 if (!queue_empty(&playlist_queue)) 1851 { 1852 logf("%s: scan interrupted", __func__); 1853 break; 1854 } 1855 1856 /* Load the filename from playlist file. */ 1857 if (get_track_filename(playlist, index, tmp, sizeof(tmp)) != 0) 1858 break; 1859 1860 /* Obtain the dircache file entry cookie. */ 1861 dircache_search(DCS_CACHED_PATH | DCS_UPDATE_FILEREF, 1862 &dcfrefs[index], tmp); 1863 1864 /* And be on background so user doesn't notice any delays. */ 1865 yield(); 1866 } 1867 1868 /* If we indexed the whole playlist without being interrupted 1869 * then there are no dirty references; go to sleep. */ 1870 if (index == playlist->amount) 1871 { 1872 is_dirty = false; 1873 sleep_time = TIMEOUT_BLOCK; 1874 logf("%s: scan complete", __func__); 1875 } 1876 1877 core_put_data_pinned(dcfrefs); 1878 cancel_cpu_boost(); 1879 1880 logf("%s: %ld ticks", __func__, current_tick - scan_start_tick); 1881 break; 1882 } 1883 1884 case SYS_USB_CONNECTED: 1885 usb_acknowledge(SYS_USB_CONNECTED_ACK); 1886 usb_wait_for_disconnect(&playlist_queue); 1887 break; 1888 } 1889 } 1890} 1891#endif 1892 1893/* 1894 * Allocate a temporary buffer for loading playlists 1895 */ 1896static int alloc_tempbuf(size_t* buflen) 1897{ 1898 /* request a reasonable size first */ 1899 int handle = core_alloc_ex(PLAYLIST_LOAD_BUFLEN, &buflib_ops_locked); 1900 if (handle > 0) 1901 { 1902 *buflen = PLAYLIST_LOAD_BUFLEN; 1903 return handle; 1904 } 1905 1906 /* otherwise, try being unreasonable */ 1907 return core_alloc_maximum(buflen, &buflib_ops_locked); 1908} 1909 1910/* 1911 * Need no movement protection since all 3 allocations are not passed to 1912 * other functions which can yield(). 1913 */ 1914static int move_callback(int handle, void* current, void* new) 1915{ 1916 (void)handle; 1917 struct playlist_info* playlist = &current_playlist; 1918 if (current == playlist->indices) 1919 playlist->indices = new; 1920 return BUFLIB_CB_OK; 1921} 1922 1923 1924static struct buflib_callbacks ops = { 1925 .move_callback = move_callback, 1926 .shrink_callback = NULL, 1927}; 1928 1929/******************************************************************************/ 1930/******************************************************************************/ 1931/* ************************************************************************** */ 1932/* * PUBLIC INTERFACE FUNCTIONS * *********************************************/ 1933/* ************************************************************************** */ 1934/******************************************************************************/ 1935/******************************************************************************/ 1936/* 1937 * Initialize playlist entries at startup 1938 */ 1939void playlist_init(void) 1940{ 1941 int handle; 1942 struct playlist_info* playlist = &current_playlist; 1943 mutex_init(&current_playlist.mutex); 1944 mutex_init(&on_disk_playlist.mutex); 1945 1946 strmemccpy(current_playlist.control_filename, PLAYLIST_CONTROL_FILE, 1947 sizeof(current_playlist.control_filename)); 1948 1949 strmemccpy(on_disk_playlist.control_filename, PLAYLIST_CONTROL_FILE ".tmp", 1950 sizeof(on_disk_playlist.control_filename)); 1951 1952 current_playlist.fd = -1; 1953 on_disk_playlist.fd = -1; 1954 current_playlist.control_fd = -1; 1955 on_disk_playlist.control_fd = -1; 1956 playlist->max_playlist_size = global_settings.max_files_in_playlist; 1957 1958 handle = core_alloc_ex(playlist->max_playlist_size * sizeof(*playlist->indices), &ops); 1959 playlist->indices = core_get_data(handle); 1960 1961 empty_playlist_unlocked(playlist, true); 1962 1963#ifdef HAVE_DIRCACHE 1964 playlist->dcfrefs_handle = core_alloc( 1965 playlist->max_playlist_size * sizeof(struct dircache_fileref)); 1966 dc_init_filerefs(playlist, 0, playlist->max_playlist_size); 1967 1968 unsigned int playlist_thread_id = 1969 create_thread(dc_thread_playlist, playlist_stack, sizeof(playlist_stack), 1970 0, dc_thread_playlist_name IF_PRIO(, PRIORITY_BACKGROUND) 1971 IF_COP(, CPU)); 1972 1973 queue_init(&playlist_queue, true); 1974 queue_enable_queue_send(&playlist_queue, 1975 &playlist_queue_sender_list, playlist_thread_id); 1976 1977 dc_thread_start(&current_playlist, false); 1978#endif /* HAVE_DIRCACHE */ 1979} 1980 1981/* 1982 * Clean playlist at shutdown 1983 */ 1984void playlist_shutdown(void) 1985 1986{ 1987 /*BugFix we need to save resume info first */ 1988 /*if (usb_detect() == USB_INSERTED)*/ 1989 audio_stop(); 1990 struct playlist_info* playlist = &current_playlist; 1991 playlist_write_lock(playlist); 1992 logf("Closing Control %s", __func__); 1993 if (playlist->control_fd >= 0) 1994 pl_close_control(playlist); 1995 1996 playlist_write_unlock(playlist); 1997} 1998 1999/* returns number of tracks in playlist (includes queued/inserted tracks) */ 2000int playlist_amount_ex(const struct playlist_info* playlist) 2001{ 2002 if (!playlist) 2003 playlist = &current_playlist; 2004 2005 return playlist->amount; 2006} 2007 2008/* returns number of tracks in current playlist */ 2009int playlist_amount(void) 2010{ 2011 return playlist_amount_ex(NULL); 2012} 2013 2014/* Return desired index buffer size for loading a playlist from disk, 2015 * as determined by the user's 'max files in playlist' setting. 2016 * 2017 * Buffer size is constrained by given max_sz. 2018 */ 2019size_t playlist_get_index_bufsz(size_t max_sz) 2020{ 2021 size_t index_buffer_size = (global_settings.max_files_in_playlist * 2022 sizeof(*on_disk_playlist.indices)); 2023 2024 return index_buffer_size > max_sz ? max_sz : index_buffer_size; 2025} 2026 2027/* 2028 * Load a playlist off disk for viewing/editing. 2029 * This will close a previously loaded playlist and its control file, 2030 * if one has been left open. 2031 * 2032 * The index_buffer is used to store playlist indices. If no index buffer is 2033 * provided, the current playlist's index buffer is shared. 2034 * FIXME: When using the shared buffer, you must ensure that playback is 2035 * stopped and that no other playlist will be started while this 2036 * one is loaded. The current playlist's indices will be trashed! 2037 * 2038 * The temp_buffer (if not NULL) is used as a scratchpad when loading indices. 2039 */ 2040struct playlist_info* playlist_load(const char* dir, const char* file, 2041 void* index_buffer, int index_buffer_size, 2042 void* temp_buffer, int temp_buffer_size) 2043{ 2044 struct playlist_info* playlist = &on_disk_playlist; 2045 if (index_buffer) 2046 { 2047 int num_indices = index_buffer_size / sizeof(*playlist->indices); 2048 2049 if (num_indices > global_settings.max_files_in_playlist) 2050 num_indices = global_settings.max_files_in_playlist; 2051 2052 playlist->max_playlist_size = num_indices; 2053 playlist->indices = index_buffer; 2054 } 2055 else 2056 { 2057 playlist->max_playlist_size = current_playlist.max_playlist_size; 2058 playlist->indices = current_playlist.indices; 2059 current_playlist.started = false; 2060 } 2061 2062 new_playlist_unlocked(playlist, dir, file); 2063 2064 /* load the playlist file */ 2065 if (file) 2066 add_indices_to_playlist(playlist, temp_buffer, temp_buffer_size); 2067 2068 return playlist; 2069} 2070 2071/* 2072 * Create new (current) playlist 2073 */ 2074int playlist_create(const char *dir, const char *file) 2075{ 2076 struct playlist_info* playlist = &current_playlist; 2077 int status = 0; 2078 2079 dc_thread_stop(playlist); 2080 playlist_write_lock(playlist); 2081 2082 new_playlist_unlocked(playlist, dir, file); 2083 2084 if (file) 2085 { 2086 size_t buflen; 2087 int handle = alloc_tempbuf(&buflen); 2088 if (handle > 0) 2089 { 2090 /* align for faster load times */ 2091 void* buf = core_get_data(handle); 2092 STORAGE_ALIGN_BUFFER(buf, buflen); 2093 buflen = ALIGN_DOWN(buflen, 512); /* to avoid partial sector I/O */ 2094 /* load the playlist file */ 2095 add_indices_to_playlist(playlist, buf, buflen); 2096 core_free(handle); 2097 } 2098 else 2099 { 2100 /* should not happen -- happens if plugin takes audio buffer */ 2101 splashf(HZ * 2, "%s(): OOM", __func__); 2102 status = -1; 2103 } 2104 } 2105 2106 playlist_write_unlock(playlist); 2107 dc_thread_start(playlist, true); 2108 2109 return status; 2110} 2111 2112/* Returns false if 'steps' is out of bounds, else true */ 2113bool playlist_check(int steps) 2114{ 2115 struct playlist_info* playlist = &current_playlist; 2116 2117 /* always allow folder navigation */ 2118 if (global_settings.next_folder && playlist_allow_dirplay(playlist)) 2119 return true; 2120 2121 int index = get_next_index(playlist, steps, -1); 2122 2123 if (index < 0 && steps >= 0 && global_settings.repeat_mode == REPEAT_SHUFFLE) 2124 index = get_next_index(playlist, steps, REPEAT_ALL); 2125 2126 return (index >= 0); 2127} 2128 2129/* 2130 * Close files and delete control file for non-current playlist. 2131 */ 2132void playlist_close(struct playlist_info* playlist) 2133{ 2134 if (!playlist) 2135 return; 2136 2137 playlist_write_lock(playlist); 2138 2139 pl_close_playlist(playlist); 2140 pl_close_control(playlist); 2141 2142 if (playlist->control_created) 2143 { 2144 remove(playlist->control_filename); 2145 playlist->control_created = false; 2146 } 2147 2148 playlist_write_unlock(playlist); 2149} 2150 2151/* 2152 * Delete track at specified index. 2153 */ 2154int playlist_delete(struct playlist_info* playlist, int index) 2155{ 2156 int result = 0; 2157 2158 if (!playlist) 2159 playlist = &current_playlist; 2160 2161 dc_thread_stop(playlist); 2162 playlist_write_lock(playlist); 2163 2164 if (check_control(playlist) < 0) 2165 { 2166 notify_control_access_error(); 2167 result = -1; 2168 goto out; 2169 } 2170 2171 result = remove_track_unlocked(playlist, index, true); 2172 2173out: 2174 playlist_write_unlock(playlist); 2175 dc_thread_start(playlist, false); 2176 2177 if (result != -1 && (audio_status() & AUDIO_STATUS_PLAY) && 2178 playlist->started) 2179 audio_flush_and_reload_tracks(); 2180 2181 return result; 2182} 2183 2184/* 2185 * Search specified directory for tracks and notify via callback. May be 2186 * called recursively. 2187 */ 2188int playlist_directory_tracksearch(const char* dirname, bool recurse, 2189 int (*callback)(char*, void*), 2190 void* context) 2191{ 2192 char buf[MAX_PATH+1]; 2193 int result = 0; 2194 int num_files = 0; 2195 int i;; 2196 struct tree_context* tc = tree_get_context(); 2197 struct tree_cache* cache = &tc->cache; 2198 int old_dirfilter = *(tc->dirfilter); 2199 2200 if (!callback) 2201 return -1; 2202 2203 /* use the tree browser dircache to load files */ 2204 *(tc->dirfilter) = SHOW_ALL; 2205 2206 if (ft_load(tc, dirname) < 0) 2207 { 2208 splash(HZ*2, ID2P(LANG_PLAYLIST_DIRECTORY_ACCESS_ERROR)); 2209 *(tc->dirfilter) = old_dirfilter; 2210 return -1; 2211 } 2212 2213 num_files = tc->filesindir; 2214 2215 /* we've overwritten the dircache so tree browser will need to be 2216 reloaded */ 2217 reload_directory(); 2218 2219 for (i=0; i<num_files; i++) 2220 { 2221 /* user abort */ 2222 if (action_userabort(TIMEOUT_NOBLOCK)) 2223 { 2224 result = -1; 2225 break; 2226 } 2227 2228 struct entry *files = core_get_data(cache->entries_handle); 2229 if (files[i].attr & ATTR_DIRECTORY) 2230 { 2231 if (recurse) 2232 { 2233 /* recursively add directories */ 2234 if (path_append(buf, dirname, files[i].name, sizeof(buf)) 2235 >= sizeof(buf)) 2236 { 2237 continue; 2238 } 2239 2240 result = playlist_directory_tracksearch(buf, recurse, 2241 callback, context); 2242 if (result < 0) 2243 break; 2244 2245 /* we now need to reload our current directory */ 2246 if(ft_load(tc, dirname) < 0) 2247 { 2248 result = -1; 2249 break; 2250 } 2251 2252 num_files = tc->filesindir; 2253 if (!num_files) 2254 { 2255 result = -1; 2256 break; 2257 } 2258 } 2259 else 2260 continue; 2261 } 2262 else if ((files[i].attr & FILE_ATTR_MASK) == FILE_ATTR_AUDIO) 2263 { 2264 if (path_append(buf, dirname, files[i].name, sizeof(buf)) 2265 >= sizeof(buf)) 2266 { 2267 continue; 2268 } 2269 2270 if (callback(buf, context) != 0) 2271 { 2272 result = -1; 2273 break; 2274 } 2275 2276 /* let the other threads work */ 2277 yield(); 2278 } 2279 } 2280 2281 /* restore dirfilter */ 2282 *(tc->dirfilter) = old_dirfilter; 2283 2284 return result; 2285} 2286 2287 2288struct playlist_info *playlist_get_current(void) 2289{ 2290 return &current_playlist; 2291} 2292 2293/* Returns index of current playing track for display purposes. This value 2294 should not be used for resume purposes as it doesn't represent the actual 2295 index into the playlist */ 2296int playlist_get_display_index(void) 2297{ 2298 struct playlist_info* playlist = &current_playlist; 2299 2300 /* first_index should always be index 0 for display purposes */ 2301 int index = rotate_index(playlist, playlist->index); 2302 2303 return (index+1); 2304} 2305 2306/* returns the crc32 of the filename of the track at the specified index */ 2307unsigned int playlist_get_filename_crc32(struct playlist_info *playlist, 2308 int index) 2309{ 2310 const char *basename; 2311 char filename[MAX_PATH]; /* path name of mp3 file */ 2312 if (!playlist) 2313 playlist = &current_playlist; 2314 2315 if (get_track_filename(playlist, index, filename, sizeof(filename)) != 0) 2316 return -1; 2317 2318#ifdef HAVE_MULTIVOLUME 2319 /* remove the volume identifier it might change just use the relative part*/ 2320 path_strip_volume(filename, &basename, false); 2321 if (basename == NULL) 2322#endif 2323 basename = filename; 2324 NOTEF("%s: %s", __func__, basename); 2325 return crc_32(basename, strlen(basename), -1); 2326} 2327 2328/* returns index of first track in playlist */ 2329int playlist_get_first_index(const struct playlist_info* playlist) 2330{ 2331 if (!playlist) 2332 playlist = &current_playlist; 2333 2334 return playlist->first_index; 2335} 2336 2337/* returns the playlist filename */ 2338char *playlist_get_name(const struct playlist_info* playlist, char *buf, 2339 int buf_size) 2340{ 2341 if (!playlist) 2342 playlist = &current_playlist; 2343 2344 strmemccpy(buf, playlist->filename, buf_size); 2345 2346 if (!buf[0]) 2347 return NULL; 2348 2349 return buf; 2350} 2351 2352/* Get resume info for current playing song. If return value is -1 then 2353 settings shouldn't be saved. */ 2354int playlist_get_resume_info(int *resume_index) 2355{ 2356 struct playlist_info* playlist = &current_playlist; 2357 2358 return (*resume_index = playlist->index); 2359} 2360 2361/* returns shuffle seed of playlist */ 2362int playlist_get_seed(const struct playlist_info* playlist) 2363{ 2364 if (!playlist) 2365 playlist = &current_playlist; 2366 2367 return playlist->seed; 2368} 2369 2370/* Fills info structure with information about track at specified index. 2371 Returns 0 on success and -1 on failure */ 2372int playlist_get_track_info(struct playlist_info* playlist, int index, 2373 struct playlist_track_info* info) 2374{ 2375 if (!playlist) 2376 playlist = &current_playlist; 2377 2378 if (get_track_filename(playlist, index, 2379 info->filename, sizeof(info->filename)) != 0) 2380 return -1; 2381 2382 info->attr = 0; 2383 2384 if (playlist->indices[index] & PLAYLIST_INSERT_TYPE_MASK) 2385 { 2386 if (playlist->indices[index] & PLAYLIST_QUEUED) 2387 info->attr |= PLAYLIST_ATTR_QUEUED; 2388 else 2389 info->attr |= PLAYLIST_ATTR_INSERTED; 2390 } 2391 2392 if (playlist->indices[index] & PLAYLIST_SKIPPED) 2393 info->attr |= PLAYLIST_ATTR_SKIPPED; 2394 2395 info->index = index; 2396 info->display_index = rotate_index(playlist, index) + 1; 2397 2398 return 0; 2399} 2400 2401/* 2402 * initialize an insert context to add tracks to a playlist 2403 * don't forget to release it when finished adding files 2404 */ 2405int playlist_insert_context_create(struct playlist_info* playlist, 2406 struct playlist_insert_context *context, 2407 int position, bool queue, bool progress) 2408{ 2409 2410 if (!playlist) 2411 playlist = &current_playlist; 2412 2413 context->playlist = playlist; 2414 context->initialized = false; 2415 2416 dc_thread_stop(playlist); 2417 playlist_write_lock(playlist); 2418 2419 if (check_control(playlist) < 0) 2420 { 2421 notify_control_access_error(); 2422 return -1; 2423 } 2424 2425 if (position == PLAYLIST_REPLACE) 2426 { 2427 if (remove_all_tracks_unlocked(playlist) == 0) 2428 position = PLAYLIST_INSERT_LAST; 2429 else 2430 { 2431 return -1; 2432 } 2433 } 2434 2435 context->playlist = playlist; 2436 context->position = position; 2437 context->queue = queue; 2438 context->count = 0; 2439 context->progress = progress; 2440 context->initialized = true; 2441 2442 if (queue) 2443 context->count_langid = LANG_PLAYLIST_QUEUE_COUNT; 2444 else 2445 context->count_langid = LANG_PLAYLIST_INSERT_COUNT; 2446 2447 return 0; 2448} 2449 2450/* 2451 * add tracks to playlist using opened insert context 2452 */ 2453int playlist_insert_context_add(struct playlist_insert_context *context, 2454 const char *filename) 2455{ 2456 struct playlist_insert_context* c = context; 2457 int insert_pos; 2458 2459 insert_pos = add_track_to_playlist_unlocked(c->playlist, filename, 2460 c->position, c->queue, -1); 2461 2462 if (insert_pos < 0) 2463 return -1; 2464 2465 (c->count)++; 2466 2467 /* After first INSERT_FIRST switch to INSERT so that all the 2468 rest of the tracks get inserted one after the other */ 2469 if (c->position == PLAYLIST_INSERT_FIRST) 2470 c->position = PLAYLIST_INSERT; 2471 2472 if (((c->count)%PLAYLIST_DISPLAY_COUNT) == 0) 2473 { 2474 if (c->progress) 2475 display_playlist_count(c->count, ID2P(c->count_langid), false); 2476 2477 if ((c->count) == PLAYLIST_DISPLAY_COUNT && 2478 (audio_status() & AUDIO_STATUS_PLAY) && 2479 c->playlist->started) 2480 audio_flush_and_reload_tracks(); 2481 } 2482 2483 return 0; 2484} 2485 2486/* 2487 * release opened insert context, sync playlist 2488 */ 2489void playlist_insert_context_release(struct playlist_insert_context *context) 2490{ 2491 2492 struct playlist_info* playlist = context->playlist; 2493 if (context->initialized) 2494 sync_control_unlocked(playlist); 2495 if (context->progress) 2496 display_playlist_count(context->count, ID2P(context->count_langid), true); 2497 2498 playlist_write_unlock(playlist); 2499 dc_thread_start(playlist, true); 2500 2501 if ((audio_status() & AUDIO_STATUS_PLAY) && playlist->started) 2502 audio_flush_and_reload_tracks(); 2503} 2504 2505/* 2506 * Insert all tracks from specified directory into playlist. 2507 */ 2508int playlist_insert_directory(struct playlist_info* playlist, 2509 const char *dirname, int position, bool queue, 2510 bool recurse) 2511{ 2512 int result = -1; 2513 struct playlist_insert_context context; 2514 result = playlist_insert_context_create(playlist, &context, 2515 position, queue, true); 2516 if (result >= 0) 2517 { 2518 cpu_boost(true); 2519 2520 result = playlist_directory_tracksearch(dirname, recurse, 2521 directory_search_callback, &context); 2522 2523 cpu_boost(false); 2524 } 2525 playlist_insert_context_release(&context); 2526 return result; 2527} 2528 2529/* 2530 * If action_cb is *not* NULL, it will be called for every track contained 2531 * in the playlist specified by filename. If action_cb is NULL, you must 2532 * instead provide a playlist insert context to use for adding each track 2533 * into a dynamic playlist. 2534 */ 2535bool playlist_entries_iterate(const char *filename, 2536 struct playlist_insert_context *pl_context, 2537 bool (*action_cb)(const char *file_name)) 2538{ 2539 int fd = -1, i = 0; 2540 bool ret = false; 2541 int max; 2542 char *dir; 2543 off_t filesize; 2544 2545 char temp_buf[MAX_PATH+1]; 2546 char trackname[MAX_PATH+1]; 2547 2548 bool utf8 = is_m3u8_name(filename); 2549 2550 cpu_boost(true); 2551 2552 fd = open_utf8(filename, O_RDONLY); 2553 if (fd < 0) 2554 { 2555 notify_access_error(); 2556 goto out; 2557 } 2558 off_t start = lseek(fd, 0, SEEK_CUR); 2559 filesize = lseek(fd, 0, SEEK_END); 2560 lseek(fd, start, SEEK_SET); 2561 /* we need the directory name for formatting purposes */ 2562 size_t dirlen = path_dirname(filename, (const char **)&dir); 2563 //dir = strmemdupa(dir, dirlen); 2564 2565 2566 if (action_cb) 2567 show_search_progress(true, 0, 0, 0); 2568 2569 while ((max = read_line(fd, temp_buf, sizeof(temp_buf))) > 0) 2570 { 2571 /* user abort */ 2572 if (!action_cb && action_userabort(TIMEOUT_NOBLOCK)) 2573 break; 2574 2575 if (temp_buf[0] != '#' && temp_buf[0] != '\0') 2576 { 2577 i++; 2578 if (!utf8) 2579 { 2580 /* Use trackname as a temporay buffer. Note that trackname must 2581 * be as large as temp_buf. 2582 */ 2583 max = convert_m3u_name(temp_buf, max, sizeof(temp_buf), trackname); 2584 } 2585 2586 /* we need to format so that relative paths are correctly 2587 handled */ 2588 if ((max = format_track_path(trackname, temp_buf, 2589 sizeof(trackname), dir, dirlen)) < 0) 2590 { 2591 goto out; 2592 } 2593 start += max; 2594 if (action_cb) 2595 { 2596 if (!action_cb(trackname)) 2597 goto out; 2598 else if (!show_search_progress(false, i, start, filesize)) 2599 break; 2600 } 2601 else if (playlist_insert_context_add(pl_context, trackname) < 0) 2602 goto out; 2603 } 2604 2605 /* let the other threads work */ 2606 yield(); 2607 } 2608 ret = true; 2609 2610out: 2611 close(fd); 2612 cpu_boost(false); 2613 return ret; 2614} 2615 2616/* 2617 * Insert all tracks from specified playlist into dynamic playlist. 2618 */ 2619int playlist_insert_playlist(struct playlist_info* playlist, const char *filename, 2620 int position, bool queue) 2621{ 2622 2623 int result = -1; 2624 2625 struct playlist_insert_context pl_context; 2626 cpu_boost(true); 2627 2628 if (playlist_insert_context_create(playlist, &pl_context, position, queue, true) >= 0 2629 && playlist_entries_iterate(filename, &pl_context, NULL)) 2630 result = 0; 2631 2632 cpu_boost(false); 2633 playlist_insert_context_release(&pl_context); 2634 return result; 2635} 2636 2637/* 2638 * Insert track into playlist at specified position (or one of the special 2639 * positions). Returns position where track was inserted or -1 if error. 2640 */ 2641int playlist_insert_track(struct playlist_info* playlist, const char *filename, 2642 int position, bool queue, bool sync) 2643{ 2644 int result; 2645 2646 if (!playlist) 2647 playlist = &current_playlist; 2648 2649 dc_thread_stop(playlist); 2650 playlist_write_lock(playlist); 2651 2652 if (check_control(playlist) < 0) 2653 { 2654 notify_control_access_error(); 2655 return -1; 2656 } 2657 2658 result = add_track_to_playlist_unlocked(playlist, filename, 2659 position, queue, -1); 2660 2661 /* Check if we want manually sync later. For example when adding 2662 * bunch of files from tagcache, syncing after every file wouldn't be 2663 * a good thing to do. */ 2664 if (sync && result >= 0) 2665 playlist_sync(playlist); 2666 2667 playlist_write_unlock(playlist); 2668 dc_thread_start(playlist, true); 2669 2670 return result; 2671} 2672 2673/* returns true if playlist has been modified by the user */ 2674bool playlist_modified(const struct playlist_info* playlist) 2675{ 2676 if (!playlist) 2677 { 2678 playlist = &current_playlist; 2679 if (!current_playlist.control_created) 2680 return global_status.resume_modified; 2681 } 2682 2683 return !!(playlist->flags & PLAYLIST_FLAG_MODIFIED); 2684} 2685 2686/* 2687 * Set the playlist modified status. Should be called to set the flag after 2688 * an explicit user action that modifies the playlist. You should not clear 2689 * the modified flag without properly warning the user. 2690 */ 2691void playlist_set_modified(struct playlist_info* playlist, bool modified) 2692{ 2693 if (!playlist) 2694 playlist = &current_playlist; 2695 2696 playlist_write_lock(playlist); 2697 2698 if (modified) 2699 update_playlist_flags_unlocked(playlist, PLAYLIST_FLAG_MODIFIED, 0); 2700 else 2701 update_playlist_flags_unlocked(playlist, 0, PLAYLIST_FLAG_MODIFIED); 2702 2703 playlist_write_unlock(playlist); 2704} 2705 2706/* returns true if directory playback features should be enabled */ 2707bool playlist_allow_dirplay(const struct playlist_info *playlist) 2708{ 2709 if (!playlist) 2710 playlist = &current_playlist; 2711 2712 if (playlist_modified(playlist)) 2713 return false; 2714 2715 return !!(playlist->flags & PLAYLIST_FLAG_DIRPLAY); 2716} 2717 2718/* 2719 * Returns true if the current playlist is neither 2720 * associated with a folder nor with an on-disk playlist. 2721 */ 2722bool playlist_dynamic_only(void) 2723{ 2724 /* NOTE: New dynamic playlists currently use root dir ("/") 2725 * as their placeholder filename – this could change. 2726 */ 2727 if (!strcmp(current_playlist.filename, "/") && 2728 !(current_playlist.flags & PLAYLIST_FLAG_DIRPLAY)) 2729 return true; 2730 2731 return false; 2732} 2733 2734/* 2735 * Move track at index to new_index. Tracks between the two are shifted 2736 * appropriately. Returns 0 on success and -1 on failure. 2737 */ 2738int playlist_move(struct playlist_info* playlist, int index, int new_index) 2739{ 2740 int result = -1; 2741 bool queue; 2742 bool current = false; 2743 int r; 2744 struct playlist_track_info info; 2745 int idx_cur; /* display index of the currently playing track */ 2746 int idx_from; /* display index of the track we're moving */ 2747 int idx_to; /* display index of the position we're moving to */ 2748 bool displace_current = false; 2749 char filename[MAX_PATH]; 2750 2751 if (!playlist) 2752 playlist = &current_playlist; 2753 2754 dc_thread_stop(playlist); 2755 playlist_write_lock(playlist); 2756 2757 if (check_control(playlist) < 0) 2758 { 2759 notify_control_access_error(); 2760 goto out; 2761 } 2762 2763 if (index == new_index) 2764 goto out; 2765 2766 if (index == playlist->index) 2767 { 2768 /* Moving the current track */ 2769 current = true; 2770 } 2771 else 2772 { 2773 /* Get display index of the currently playing track */ 2774 if (playlist_get_track_info(playlist, playlist->index, &info) != -1) 2775 { 2776 idx_cur = info.display_index; 2777 /* Get display index of the position we're moving to */ 2778 if (playlist_get_track_info(playlist, new_index, &info) != -1) 2779 { 2780 idx_to = info.display_index; 2781 /* Get display index of the track we're trying to move */ 2782 if (playlist_get_track_info(playlist, index, &info) != -1) 2783 { 2784 idx_from = info.display_index; 2785 /* Check if moving will displace the current track. 2786 Displace happens when moving from after current to 2787 before, but also when moving from before to before 2788 due to the removal from the original position */ 2789 if ( ((idx_from > idx_cur) && (idx_to <= idx_cur)) || 2790 ((idx_from < idx_cur) && (idx_to < idx_cur)) ) 2791 displace_current = true; 2792 } 2793 } 2794 } 2795 } 2796 2797 queue = playlist->indices[index] & PLAYLIST_QUEUED; 2798 2799 if (get_track_filename(playlist, index, filename, sizeof(filename)) != 0) 2800 goto out; 2801 2802 /* We want to insert the track at the position that was specified by 2803 new_index. This may be different then new_index because of the 2804 shifting that will occur after the delete. 2805 We calculate this before we do the remove as it depends on the 2806 size of the playlist before the track removal */ 2807 r = rotate_index(playlist, new_index); 2808 2809 /* Delete track from original position */ 2810 result = remove_track_unlocked(playlist, index, true); 2811 if (result == -1) 2812 goto out; 2813 2814 if (r == 0)/* First index */ 2815 { 2816 new_index = PLAYLIST_PREPEND; 2817 } 2818 else if (r == playlist->amount) 2819 { 2820 /* Append */ 2821 new_index = PLAYLIST_INSERT_LAST; 2822 } 2823 else /* Calculate index of desired position */ 2824 { 2825 new_index = (r+playlist->first_index)%playlist->amount; 2826 2827 if ((new_index < playlist->first_index) && (new_index <= playlist->index)) 2828 displace_current = true; 2829 else if ((new_index >= playlist->first_index) && (playlist->index < playlist->first_index)) 2830 displace_current = false; 2831 } 2832 2833 result = add_track_to_playlist_unlocked(playlist, filename, 2834 new_index, queue, -1); 2835 if (result == -1) 2836 goto out; 2837 2838 if (current) 2839 { 2840 /* Moved the current track */ 2841 switch (new_index) 2842 { 2843 case PLAYLIST_PREPEND: 2844 playlist->index = playlist->first_index; 2845 break; 2846 case PLAYLIST_INSERT_LAST: 2847 playlist->index = playlist->first_index - 1; 2848 if (playlist->index < 0) 2849 playlist->index += playlist->amount; 2850 break; 2851 default: 2852 playlist->index = new_index; 2853 break; 2854 } 2855 } 2856 else if ((displace_current) && (new_index != PLAYLIST_PREPEND)) 2857 { 2858 /* make the index point to the currently playing track */ 2859 playlist->index++; 2860 } 2861 2862out: 2863 playlist_write_unlock(playlist); 2864 dc_thread_start(playlist, true); 2865 2866 if (result != -1 && playlist->started && (audio_status() & AUDIO_STATUS_PLAY)) 2867 audio_flush_and_reload_tracks(); 2868 2869 return result; 2870} 2871 2872/* returns full path of playlist (minus extension) */ 2873char *playlist_name(const struct playlist_info* playlist, char *buf, 2874 int buf_size) 2875{ 2876 char *sep; 2877 2878 if (!playlist) 2879 playlist = &current_playlist; 2880 2881 strmemccpy(buf, playlist->filename+playlist->dirlen, buf_size); 2882 2883 if (!buf[0]) 2884 return NULL; 2885 2886 /* Remove extension */ 2887 sep = strrchr(buf, '.'); 2888 if (sep) 2889 *sep = 0; 2890 2891 return buf; 2892} 2893 2894/* 2895 * Update indices as track has changed 2896 */ 2897int playlist_next(int steps) 2898{ 2899 struct playlist_info* playlist = &current_playlist; 2900 2901 dc_thread_stop(playlist); 2902 playlist_write_lock(playlist); 2903 2904 int index; 2905 int repeat_mode = global_settings.repeat_mode; 2906 if (repeat_mode == REPEAT_ONE) 2907 { 2908 if (is_manual_skip()) 2909 repeat_mode = REPEAT_ALL; 2910 } 2911 if (steps > 0) 2912 { 2913#ifdef AB_REPEAT_ENABLE 2914 if (repeat_mode != REPEAT_AB && repeat_mode != REPEAT_ONE) 2915#else 2916 if (repeat_mode != REPEAT_ONE) 2917#endif 2918 { 2919 int i, j; 2920 /* We need to delete all the queued songs */ 2921 for (i=0, j=steps; i<j; i++) 2922 { 2923 index = get_next_index(playlist, i, -1); 2924 2925 if (index >= 0 && playlist->indices[index] & PLAYLIST_QUEUED) 2926 { 2927 remove_track_unlocked(playlist, index, true); 2928 steps--; /* one less track */ 2929 } 2930 } 2931 } 2932 } /*steps > 0*/ 2933 index = get_next_index(playlist, steps, repeat_mode); 2934 2935 if (index < 0) 2936 { 2937 /* end of playlist... or is it */ 2938 if (repeat_mode == REPEAT_SHUFFLE && playlist->amount > 1) 2939 { 2940 /* Repeat shuffle mode. Re-shuffle playlist and resume play */ 2941 playlist->first_index = 0; 2942 sort_playlist_unlocked(playlist, false, false); 2943 randomise_playlist_unlocked(playlist, current_tick, false, true); 2944 global_settings.playlist_shuffle = true; 2945 2946 playlist->started = true; 2947 playlist->index = 0; 2948 index = 0; 2949 } 2950 else if (global_settings.next_folder && playlist_allow_dirplay(playlist)) 2951 { 2952 /* we switch playlists here */ 2953 index = create_and_play_dir(steps, true); 2954 if (index >= 0) 2955 { 2956 playlist->index = index; 2957 } 2958 } 2959 goto out; 2960 } 2961 2962 playlist->index = index; 2963 2964 if (playlist->last_insert_pos >= 0 && steps > 0) 2965 { 2966 /* check to see if we've gone beyond the last inserted track */ 2967 int cur = rotate_index(playlist, index); 2968 int last_pos = rotate_index(playlist, playlist->last_insert_pos); 2969 2970 if (cur > last_pos) 2971 { 2972 /* reset last inserted track */ 2973 playlist->last_insert_pos = -1; 2974 if (playlist->control_fd >= 0) 2975 { 2976 int result = update_control_unlocked(playlist, 2977 PLAYLIST_COMMAND_RESET, 2978 -1, -1, NULL, NULL, NULL); 2979 if (result < 0) 2980 { 2981 index = result; 2982 goto out; 2983 } 2984 2985 sync_control_unlocked(playlist); 2986 } 2987 } 2988 } 2989 2990out: 2991 playlist_write_unlock(playlist); 2992 dc_thread_start(playlist, true); 2993 2994 return index; 2995} 2996 2997/* try playing next or previous folder */ 2998bool playlist_next_dir(int direction) 2999{ 3000 /* not to mess up real playlists */ 3001 if (!playlist_allow_dirplay(&current_playlist)) 3002 return false; 3003 3004 return create_and_play_dir(direction, false) >= 0; 3005} 3006 3007/* get trackname of track that is "steps" away from current playing track. 3008 NULL is used to identify end of playlist */ 3009const char* playlist_peek(int steps, char* buf, size_t buf_size) 3010{ 3011 struct playlist_info* playlist = &current_playlist; 3012 char *temp_ptr; 3013 int index = get_next_index(playlist, steps, -1); 3014 3015 if (index < 0) 3016 return NULL; 3017 3018 /* Just testing - don't care about the file name */ 3019 if (!buf || !buf_size) 3020 return ""; 3021 3022 if (get_track_filename(playlist, index, buf, buf_size) != 0) 3023 return NULL; 3024 3025 temp_ptr = buf; 3026 3027 /* remove bogus dirs from beginning of path 3028 (workaround for buggy playlist creation tools) */ 3029 while (temp_ptr) 3030 { 3031 if (file_exists(temp_ptr)) 3032 break; 3033 3034 temp_ptr = strchr(temp_ptr+1, '/'); 3035 } 3036 3037 if (!temp_ptr) 3038 { 3039 /* Even though this is an invalid file, we still need to pass a 3040 file name to the caller because NULL is used to indicate end 3041 of playlist */ 3042 return buf; 3043 } 3044 3045 return temp_ptr; 3046} 3047 3048/* 3049 * shuffle currently playing playlist 3050 * 3051 * TODO: Merge this with playlist_shuffle()? 3052 */ 3053int playlist_randomise(struct playlist_info* playlist, unsigned int seed, 3054 bool start_current) 3055{ 3056 int result; 3057 3058 if (!playlist) 3059 playlist = &current_playlist; 3060 3061 dc_thread_stop(playlist); 3062 playlist_write_lock(playlist); 3063 3064 check_control(playlist); 3065 3066 result = randomise_playlist_unlocked(playlist, seed, start_current, true); 3067 if (result != -1 && (audio_status() & AUDIO_STATUS_PLAY) && 3068 playlist->started) 3069 { 3070 audio_flush_and_reload_tracks(); 3071 } 3072 3073 playlist_write_unlock(playlist); 3074 dc_thread_start(playlist, true); 3075 3076 return result; 3077} 3078 3079int playlist_randomise_current(unsigned int seed, bool start_current) 3080{ 3081 return playlist_randomise(NULL, seed, start_current); 3082} 3083 3084/* 3085 * Removes all tracks, from the playlist, leaving the presently playing 3086 * track queued. 3087 */ 3088int playlist_remove_all_tracks(struct playlist_info *playlist) 3089{ 3090 int result; 3091 3092 if (playlist == NULL) 3093 playlist = &current_playlist; 3094 3095 dc_thread_stop(playlist); 3096 playlist_write_lock(playlist); 3097 3098 result = remove_all_tracks_unlocked(playlist); 3099 3100 playlist_write_unlock(playlist); 3101 dc_thread_start(playlist, false); 3102 return result; 3103} 3104 3105/* playlist_resume helper function 3106 * only allows comments (#) and PLAYLIST_COMMAND_PLAYLIST (P) 3107 */ 3108static enum playlist_command pl_cmds_start(char cmd) 3109{ 3110 if (cmd == 'P') 3111 return PLAYLIST_COMMAND_PLAYLIST; 3112 if (cmd == '#') 3113 return PLAYLIST_COMMAND_COMMENT; 3114 3115 return PLAYLIST_COMMAND_ERROR; 3116} 3117 3118/* playlist resume helper function excludes PLAYLIST_COMMAND_PLAYLIST (P) */ 3119static enum playlist_command pl_cmds_run(char cmd) 3120{ 3121 switch (cmd) 3122 { 3123 case 'A': 3124 return PLAYLIST_COMMAND_ADD; 3125 case 'Q': 3126 return PLAYLIST_COMMAND_QUEUE; 3127 case 'D': 3128 return PLAYLIST_COMMAND_DELETE; 3129 case 'S': 3130 return PLAYLIST_COMMAND_SHUFFLE; 3131 case 'U': 3132 return PLAYLIST_COMMAND_UNSHUFFLE; 3133 case 'R': 3134 return PLAYLIST_COMMAND_RESET; 3135 case 'F': 3136 return PLAYLIST_COMMAND_FLAGS; 3137 case '#': 3138 return PLAYLIST_COMMAND_COMMENT; 3139 default: /* ERROR */ 3140 break; 3141 } 3142 return PLAYLIST_COMMAND_ERROR; 3143} 3144 3145/* 3146 * Restore the playlist state based on control file commands. Called to 3147 * resume playback after shutdown. 3148 */ 3149int playlist_resume(void) 3150{ 3151 char *buffer; 3152 size_t buflen; 3153 size_t readsize; 3154 int handle; 3155 int nread; 3156 int total_read = 0; 3157 int control_file_size = 0; 3158 bool sorted = true; 3159 int result = -1; 3160 enum playlist_command (*pl_cmd)(char) = &pl_cmds_start; 3161 3162 splash(0, ID2P(LANG_WAIT)); 3163 cpu_boost(true); 3164 3165 struct playlist_info* playlist = &current_playlist; 3166 dc_thread_stop(playlist); 3167 playlist_write_lock(playlist); 3168 3169 if (core_allocatable() < (1 << 10)) 3170 talk_buffer_set_policy(TALK_BUFFER_LOOSE); /* back off voice buffer */ 3171 3172#ifdef HAVE_DIRCACHE 3173 dircache_wait(); /* we need the dircache to use the files in the playlist */ 3174#endif 3175 3176 handle = alloc_tempbuf(&buflen); 3177 if (handle < 0) 3178 { 3179 splashf(HZ * 2, "%s(): OOM", __func__); 3180 goto out; 3181 } 3182 3183 /* align buffer for faster load times */ 3184 buffer = core_get_data(handle); 3185 STORAGE_ALIGN_BUFFER(buffer, buflen); 3186 buflen = ALIGN_DOWN(buflen, 512); /* to avoid partial sector I/O */ 3187 3188 empty_playlist_unlocked(playlist, true); 3189 3190 if (!file_exists(playlist->control_filename)) 3191 goto out; 3192 3193 playlist->control_fd = open(playlist->control_filename, O_RDWR); 3194 if (playlist->control_fd < 0) 3195 { 3196 notify_control_access_error(); 3197 goto out; 3198 } 3199 playlist->control_created = true; 3200 3201 control_file_size = filesize(playlist->control_fd); 3202 if (control_file_size <= 0) 3203 { 3204 notify_control_access_error(); 3205 goto out; 3206 } 3207 3208 /* read a small amount first to get the header */ 3209 readsize = (PLAYLIST_COMMAND_SIZE<buflen?PLAYLIST_COMMAND_SIZE:buflen); 3210 nread = read(playlist->control_fd, buffer, readsize); 3211 if(nread <= 0) 3212 { 3213 notify_control_access_error(); 3214 goto out; 3215 } 3216 3217 playlist->started = true; 3218 3219 while (1) 3220 { 3221 result = 0; 3222 int count; 3223 enum playlist_command current_command = PLAYLIST_COMMAND_COMMENT; 3224 int last_newline = 0; 3225 int str_count = -1; 3226 bool newline = true; 3227 bool exit_loop = false; 3228 char *p = buffer; 3229 char *strp[3] = {NULL}; 3230 3231 unsigned long last_tick = current_tick; 3232 splash_progress_set_delay(HZ / 2); /* wait 1/2 sec before progress */ 3233 bool useraborted = false; 3234 bool queue = false; 3235 3236 for(count=0; count<nread && !exit_loop; count++,p++) 3237 { 3238 /* Show a splash while we are loading. */ 3239 splash_progress((total_read + count), control_file_size, 3240 "%s (%s)", str(LANG_WAIT), str(LANG_OFF_ABORT)); 3241 if (TIME_AFTER(current_tick, last_tick + HZ/4)) 3242 { 3243 if (action_userabort(TIMEOUT_NOBLOCK)) 3244 { 3245 useraborted = true; 3246 exit_loop = true; 3247 break; 3248 } 3249 last_tick = current_tick; 3250 } 3251 /* Are we on a new line? */ 3252 if((*p == '\n') || (*p == '\r')) 3253 { 3254 *p = '\0'; 3255 3256 switch (current_command) 3257 { 3258 case PLAYLIST_COMMAND_ERROR: 3259 { 3260 /* first non-comment line does not specify playlist */ 3261 /* ( below handled by pl_cmds_run() ) */ 3262 /* OR playlist is specified more than once */ 3263 /* OR unknown command -- pl corrupted?? */ 3264 result = -12; 3265 exit_loop = true; 3266 break; 3267 } 3268 case PLAYLIST_COMMAND_PLAYLIST: 3269 { 3270 /* strp[0]=version strp[1]=dir strp[2]=file */ 3271 int version; 3272 3273 if (!strp[0]) 3274 { 3275 result = -2; 3276 exit_loop = true; 3277 break; 3278 } 3279 3280 if (!strp[1]) 3281 strp[1] = ""; 3282 3283 if (!strp[2]) 3284 strp[2] = ""; 3285 3286 version = atoi(strp[0]); 3287 3288 /* 3289 * TODO: Playlist control file version upgrades 3290 * 3291 * If an older version control file is loaded then 3292 * the header should be updated to the latest version 3293 * in case any incompatible commands are written out. 3294 * (It's not a big deal since the error message will 3295 * be practically the same either way...) 3296 */ 3297 if ((version < PLAYLIST_CONTROL_FILE_MIN_VERSION || 3298 version > PLAYLIST_CONTROL_FILE_VERSION) 3299 && version != PLAYLIST_CONTROL_FILE_LTS_VERSION) 3300 { 3301 result = -3; 3302 goto out; 3303 } 3304 3305 update_playlist_filename_unlocked(playlist, strp[1], strp[2]); 3306 3307 if (strp[2][0] != '\0') 3308 { 3309 /* NOTE: add_indices_to_playlist() overwrites the 3310 audiobuf so we need to reload control file 3311 data */ 3312 add_indices_to_playlist(playlist, buffer, buflen); 3313 } 3314 else if (strp[1][0] != '\0') 3315 { 3316 playlist->flags |= PLAYLIST_FLAG_DIRPLAY; 3317 } 3318 3319 /* load the rest of the data */ 3320 exit_loop = true; 3321 readsize = buflen; 3322 pl_cmd = &pl_cmds_run; 3323 break; 3324 } 3325 case PLAYLIST_COMMAND_QUEUE: 3326 queue = true; 3327 /*Fall-through*/ 3328 case PLAYLIST_COMMAND_ADD: 3329 { 3330 /* strp[0]=position strp[1]=last_position strp[2]=file */ 3331 if (!strp[0] || !strp[1] || !strp[2]) 3332 { 3333 result = -4; 3334 exit_loop = true; 3335 break; 3336 } 3337 3338 int position = atoi(strp[0]); 3339 int last_position = atoi(strp[1]); 3340 3341 /* seek position is based on strp[2]'s position in 3342 buffer */ 3343 if (add_track_to_playlist_unlocked(playlist, strp[2], 3344 position, queue, total_read+(strp[2]-buffer)) < 0) 3345 { 3346 result = -5; 3347 goto out; 3348 } 3349 3350 playlist->last_insert_pos = last_position; 3351 queue = false; 3352 break; 3353 } 3354 case PLAYLIST_COMMAND_DELETE: 3355 { 3356 /* strp[0]=position */ 3357 int position; 3358 3359 if (!strp[0]) 3360 { 3361 result = -6; 3362 exit_loop = true; 3363 break; 3364 } 3365 3366 position = atoi(strp[0]); 3367 3368 if (remove_track_unlocked(playlist, position, false) < 0) 3369 { 3370 result = -7; 3371 goto out; 3372 } 3373 3374 break; 3375 } 3376 case PLAYLIST_COMMAND_SHUFFLE: 3377 { 3378 /* strp[0]=seed strp[1]=first_index */ 3379 int seed; 3380 3381 if (!strp[0] || !strp[1]) 3382 { 3383 result = -8; 3384 exit_loop = true; 3385 break; 3386 } 3387 3388 if (!sorted) 3389 { 3390 /* Always sort list before shuffling */ 3391 sort_playlist_unlocked(playlist, false, false); 3392 } 3393 3394 seed = atoi(strp[0]); 3395 playlist->first_index = atoi(strp[1]); 3396 3397 if (randomise_playlist_unlocked(playlist, seed, false, 3398 false) < 0) 3399 { 3400 result = -9; 3401 goto out; 3402 } 3403 sorted = false; 3404 3405 break; 3406 } 3407 case PLAYLIST_COMMAND_UNSHUFFLE: 3408 { 3409 /* strp[0]=first_index */ 3410 if (!strp[0]) 3411 { 3412 result = -10; 3413 exit_loop = true; 3414 break; 3415 } 3416 3417 playlist->first_index = atoi(strp[0]); 3418 3419 if (sort_playlist_unlocked(playlist, false, false) < 0) 3420 { 3421 result = -11; 3422 goto out; 3423 } 3424 3425 sorted = true; 3426 break; 3427 } 3428 case PLAYLIST_COMMAND_RESET: 3429 { 3430 playlist->last_insert_pos = -1; 3431 break; 3432 } 3433 case PLAYLIST_COMMAND_FLAGS: 3434 { 3435 if (!strp[0] || !strp[1]) 3436 { 3437 result = -18; 3438 exit_loop = true; 3439 break; 3440 } 3441 unsigned int setf = atoi(strp[0]); 3442 unsigned int clearf = atoi(strp[1]); 3443 3444 playlist->flags = (playlist->flags & ~clearf) | setf; 3445 break; 3446 } 3447 case PLAYLIST_COMMAND_COMMENT: 3448 default: 3449 break; 3450 } 3451 3452 /* save last_newline in case we need to load more data */ 3453 last_newline = count; 3454 newline = true; 3455 3456 /* to ignore any extra newlines */ 3457 current_command = PLAYLIST_COMMAND_COMMENT; 3458 } 3459 else if(newline) 3460 { 3461 newline = false; 3462 current_command = (*pl_cmd)(*p); 3463 str_count = -1; 3464 strp[0] = NULL; 3465 strp[1] = NULL; 3466 strp[2] = NULL; 3467 } 3468 else if(current_command < PLAYLIST_COMMAND_COMMENT) 3469 { 3470 /* all control file strings are separated with a colon. 3471 Replace the colon with 0 to get proper strings that can be 3472 used by commands above */ 3473 if (*p == ':') 3474 { 3475 *p = '\0'; 3476 str_count++; 3477 3478 if ((count+1) < nread) 3479 { 3480 switch (str_count) 3481 { 3482 case 0: 3483 case 1: 3484 case 2: 3485 strp[str_count] = p+1; 3486 break; 3487 default: 3488 /* allow last string to contain colons */ 3489 *p = ':'; 3490 break; 3491 } 3492 } 3493 } 3494 } 3495 } 3496 3497 if (result < 0 || current_command == PLAYLIST_COMMAND_ERROR) 3498 { 3499 splashf(HZ*2, "Err: %d, %s", result, str(LANG_PLAYLIST_CONTROL_INVALID)); 3500 goto out; 3501 } 3502 3503 if (useraborted) 3504 { 3505 splash(HZ*2, ID2P(LANG_CANCEL)); 3506 result = -1; 3507 goto out; 3508 } 3509 3510 if (!newline || (exit_loop && count<nread)) 3511 { 3512 if ((total_read + count) >= control_file_size) 3513 { 3514 /* no newline at end of control file */ 3515 splashf(HZ*2, "Err: EOF, %s", str(LANG_PLAYLIST_CONTROL_INVALID)); 3516 result = -15; 3517 goto out; 3518 } 3519 3520 /* We didn't end on a newline or we exited loop prematurely. 3521 Either way, re-read the remainder. */ 3522 count = last_newline; 3523 lseek(playlist->control_fd, total_read+count, SEEK_SET); 3524 } 3525 3526 total_read += count; 3527 3528 nread = read(playlist->control_fd, buffer, readsize); 3529 3530 /* Terminate on EOF */ 3531 if(nread <= 0) 3532 { 3533 break; 3534 } 3535 } 3536 3537 if (global_status.resume_index != -1) 3538 playlist->index = global_status.resume_index; 3539 3540out: 3541 playlist_write_unlock(playlist); 3542 dc_thread_start(playlist, true); 3543 3544 talk_buffer_set_policy(TALK_BUFFER_DEFAULT); 3545 core_free(handle); 3546 cpu_boost(false); 3547 return result; 3548} 3549 3550/* resume a playlist track with the given crc_32 of the track name. */ 3551void playlist_resume_track(int start_index, unsigned int crc, 3552 unsigned long elapsed, unsigned long offset) 3553{ 3554 int i; 3555 unsigned int tmp_crc; 3556 struct playlist_info* playlist = &current_playlist; 3557 3558 for (i = 0 ; i < playlist->amount; i++) 3559 { 3560 int index = (i + start_index) % playlist->amount; 3561 3562 tmp_crc = playlist_get_filename_crc32(playlist, index); 3563 if (tmp_crc == crc) 3564 { 3565 playlist_start(index, elapsed, offset); 3566 return; 3567 } 3568 } 3569 3570 /* If we got here the file wasnt found, so start from the beginning */ 3571 playlist_start(0, 0, 0); 3572} 3573 3574/* 3575 * Set the specified playlist as the current. 3576 * NOTE: You will get undefined behaviour if something is already playing so 3577 * remember to stop before calling this. Also, this call will 3578 * effectively close your playlist, making it unusable. 3579 */ 3580int playlist_set_current(struct playlist_info* playlist) 3581{ 3582 int result = -1; 3583 3584 if (!playlist || (check_control(playlist) < 0)) 3585 { 3586 playlist_close(playlist); 3587 return result; 3588 } 3589 3590 dc_thread_stop(&current_playlist); 3591 playlist_write_lock(&current_playlist); 3592 3593 empty_playlist_unlocked(&current_playlist, false); 3594 3595 strmemccpy(current_playlist.filename, playlist->filename, 3596 sizeof(current_playlist.filename)); 3597 3598 current_playlist.utf8 = playlist->utf8; 3599 3600 /* Transfer ownership of fd to current playlist */ 3601 current_playlist.fd = playlist->fd; 3602 playlist->fd = -1; 3603 3604 pl_close_control(playlist); 3605 pl_close_control(&current_playlist); 3606 3607 remove(current_playlist.control_filename); 3608 current_playlist.control_created = false; 3609 3610 if (rename(playlist->control_filename, current_playlist.control_filename) < 0) 3611 goto out; 3612 3613 current_playlist.control_fd = open(current_playlist.control_filename, 3614 O_RDWR); 3615 3616 if (current_playlist.control_fd < 0) 3617 goto out; 3618 3619 current_playlist.control_created = true; 3620 current_playlist.dirlen = playlist->dirlen; 3621 3622 if (playlist->indices && playlist->indices != current_playlist.indices) 3623 memcpy((void*)current_playlist.indices, (void*)playlist->indices, 3624 playlist->max_playlist_size*sizeof(*playlist->indices)); 3625 dc_init_filerefs(&current_playlist, 0, current_playlist.max_playlist_size); 3626 3627 current_playlist.first_index = playlist->first_index; 3628 current_playlist.amount = playlist->amount; 3629 current_playlist.last_insert_pos = playlist->last_insert_pos; 3630 current_playlist.seed = playlist->seed; 3631 current_playlist.flags = playlist->flags; 3632 3633 result = 0; 3634 3635out: 3636 playlist_write_unlock(&current_playlist); 3637 dc_thread_start(&current_playlist, true); 3638 3639 return result; 3640} 3641 3642/* set playlist->last_shuffle_start to playlist end for 3643 PLAYLIST_INSERT_LAST_SHUFFLED command purposes*/ 3644void playlist_set_last_shuffled_start(void) 3645{ 3646 struct playlist_info* playlist = &current_playlist; 3647 playlist_write_lock(playlist); 3648 playlist->last_shuffled_start = playlist->first_index > 0 ? 3649 playlist->first_index : playlist->amount; 3650 playlist_write_unlock(playlist); 3651} 3652 3653/* shuffle newly created playlist using random seed. */ 3654int playlist_shuffle(int random_seed, int start_index) 3655{ 3656 struct playlist_info* playlist = &current_playlist; 3657 bool start_current = false; 3658 3659 dc_thread_stop(playlist); 3660 playlist_write_lock(playlist); 3661 3662 if (start_index >= 0 && global_settings.play_selected) 3663 { 3664 /* store the seek position before the shuffle */ 3665 playlist->index = playlist->first_index = start_index; 3666 start_current = true; 3667 } 3668 3669 randomise_playlist_unlocked(playlist, random_seed, start_current, true); 3670 3671 playlist_write_unlock(playlist); 3672 dc_thread_start(playlist, true); 3673 3674 return playlist->index; 3675} 3676 3677/* Marks the index of the track to be skipped that is "steps" away from 3678 * current playing track. 3679 */ 3680void playlist_skip_entry(struct playlist_info *playlist, int steps) 3681{ 3682 int index; 3683 3684 if (playlist == NULL) 3685 playlist = &current_playlist; 3686 3687 playlist_write_lock(playlist); 3688 3689 /* need to account for already skipped tracks */ 3690 steps = calculate_step_count(playlist, steps); 3691 3692 index = playlist->index + steps; 3693 if (index < 0) 3694 index += playlist->amount; 3695 else if (index >= playlist->amount) 3696 index -= playlist->amount; 3697 3698 playlist->indices[index] |= PLAYLIST_SKIPPED; 3699 playlist_write_unlock(playlist); 3700} 3701 3702/* sort currently playing playlist */ 3703int playlist_sort(struct playlist_info* playlist, bool start_current) 3704{ 3705 int result; 3706 3707 if (!playlist) 3708 playlist = &current_playlist; 3709 3710 dc_thread_stop(playlist); 3711 playlist_write_lock(playlist); 3712 3713 check_control(playlist); 3714 result = sort_playlist_unlocked(playlist, start_current, true); 3715 3716 if (result != -1 && (audio_status() & AUDIO_STATUS_PLAY) && playlist->started) 3717 audio_flush_and_reload_tracks(); 3718 3719 playlist_write_unlock(playlist); 3720 dc_thread_start(playlist, true); 3721 return result; 3722} 3723 3724int playlist_sort_current(bool start_current) 3725{ 3726 return playlist_sort(NULL, start_current); 3727} 3728 3729/* start playing current playlist at specified index/offset */ 3730void playlist_start(int start_index, unsigned long elapsed, 3731 unsigned long offset) 3732{ 3733 struct playlist_info* playlist = &current_playlist; 3734 3735 playlist_write_lock(playlist); 3736 3737 playlist->index = start_index; 3738 playlist->started = true; 3739 3740 sync_control_unlocked(playlist); 3741 3742 playlist_write_unlock(playlist); 3743 3744 audio_play(elapsed, offset); 3745 audio_resume(); 3746} 3747 3748void playlist_sync(struct playlist_info* playlist) 3749{ 3750 if (!playlist) 3751 playlist = &current_playlist; 3752 3753 playlist_write_lock(playlist); 3754 3755 sync_control_unlocked(playlist); 3756 3757 playlist_write_unlock(playlist); 3758 3759 if ((audio_status() & AUDIO_STATUS_PLAY) && playlist->started) 3760 audio_flush_and_reload_tracks(); 3761} 3762 3763/* Update resume info for current playing song. Returns -1 on error. */ 3764int playlist_update_resume_info(const struct mp3entry* id3) 3765{ 3766 struct playlist_info* playlist = &current_playlist; 3767 3768 bool pl_modified = (PLAYLIST_FLAG_MODIFIED == 3769 (playlist->flags & PLAYLIST_FLAG_MODIFIED)); 3770 if (id3) 3771 { 3772 if (global_status.resume_index != playlist->index || 3773 global_status.resume_elapsed != id3->elapsed || 3774 global_status.resume_offset != id3->offset || 3775 global_status.resume_modified != pl_modified) 3776 { 3777 unsigned int crc = playlist_get_filename_crc32(playlist, 3778 playlist->index); 3779 global_status.resume_index = playlist->index; 3780 global_status.resume_crc32 = crc; 3781 global_status.resume_elapsed = id3->elapsed; 3782 global_status.resume_offset = id3->offset; 3783 global_status.resume_modified = pl_modified; 3784 status_save(false); 3785 } 3786 } 3787 else 3788 { 3789 global_status.resume_index = -1; 3790 global_status.resume_crc32 = -1; 3791 global_status.resume_elapsed = -1; 3792 global_status.resume_offset = -1; 3793 global_status.resume_modified = false; 3794 status_save(true); 3795 return -1; 3796 } 3797 3798 return 0; 3799} 3800 3801static int pl_get_tempname(const char *filename, char *buf, size_t bufsz) 3802{ 3803 if (strlcpy(buf, filename, bufsz) >= bufsz) 3804 return -1; 3805 3806 if (strlcat(buf, "_temp", bufsz) >= bufsz) 3807 return -1; 3808 3809 return 0; 3810} 3811 3812/* 3813 * Save all non-queued tracks to an M3U playlist with the given filename. 3814 * On success, the playlist is updated to point to the new playlist file. 3815 * On failure, the playlist filename is unchanged, but playlist indices 3816 * may be trashed; the current playlist should be reloaded. 3817 * 3818 * Returns 0 on success, < 0 on error, and > 0 if user canceled. 3819 */ 3820static int pl_save_playlist(struct playlist_info* playlist, 3821 const char *filename, 3822 char *tmpbuf, size_t tmpsize) 3823{ 3824 int fd, index, num_saved; 3825 off_t offset; 3826 int ret, err; 3827 3828 if (pl_get_tempname(filename, tmpbuf, tmpsize)) 3829 return -1; 3830 3831 /* 3832 * We always save playlists as UTF-8. Add a BOM only when 3833 * saving to the .m3u file extension. 3834 */ 3835 if (is_m3u8_name(filename)) 3836 fd = open(tmpbuf, O_CREAT|O_WRONLY|O_TRUNC, 0666); 3837 else 3838 fd = open_utf8(tmpbuf, O_CREAT|O_WRONLY|O_TRUNC); 3839 3840 if (fd < 0) 3841 return -1; 3842 3843 offset = lseek(fd, 0, SEEK_CUR); 3844 index = playlist->first_index; 3845 num_saved = 0; 3846 3847 display_playlist_count(num_saved, ID2P(LANG_PLAYLIST_SAVE_COUNT), true); 3848 3849 for (int i = 0; i < playlist->amount; ++i, ++index) 3850 { 3851 if (index == playlist->amount) 3852 index = 0; 3853 3854 /* TODO: Disabled for now, as we can't restore the playlist state yet */ 3855 if (false && action_userabort(TIMEOUT_NOBLOCK)) 3856 { 3857 err = 1; 3858 goto error; 3859 } 3860 3861 /* Do not save queued files to playlist. */ 3862 if (playlist->indices[index] & PLAYLIST_QUEUED) 3863 continue; 3864 3865 if (get_track_filename(playlist, index, tmpbuf, tmpsize) != 0) 3866 { 3867 err = -2; 3868 goto error; 3869 } 3870 3871 /* Update seek offset so it points into the new file. */ 3872 playlist->indices[index] &= ~PLAYLIST_INSERT_TYPE_MASK; 3873 playlist->indices[index] &= ~PLAYLIST_SEEK_MASK; 3874 playlist->indices[index] |= offset; 3875 3876 ret = fdprintf(fd, "%s\n", tmpbuf); 3877 if (ret < 0) 3878 { 3879 err = -3; 3880 goto error; 3881 } 3882 3883 offset += ret; 3884 num_saved++; 3885 3886 if ((num_saved % PLAYLIST_DISPLAY_COUNT) == 0) 3887 display_playlist_count(num_saved, ID2P(LANG_PLAYLIST_SAVE_COUNT), false); 3888 } 3889 3890 display_playlist_count(num_saved, ID2P(LANG_PLAYLIST_SAVE_COUNT), true); 3891 close(fd); 3892 pl_close_playlist(playlist); 3893 3894 pl_get_tempname(filename, tmpbuf, tmpsize); 3895 if (rename(tmpbuf, filename)) 3896 return -4; 3897 3898 strcpy(tmpbuf, filename); 3899 char *dir = tmpbuf; 3900 char *file = strrchr(tmpbuf, '/') + 1; 3901 file[-1] = '\0'; 3902 3903 update_playlist_filename_unlocked(playlist, dir, file); 3904 return 0; 3905 3906error: 3907 close(fd); 3908 pl_get_tempname(filename, tmpbuf, tmpsize); 3909 remove(tmpbuf); 3910 return err; 3911} 3912 3913static void pl_reverse(struct playlist_info *playlist, int start, int end) 3914{ 3915 for (; start < end; start++, end--) 3916 { 3917 unsigned long index_swap = playlist->indices[start]; 3918 playlist->indices[start] = playlist->indices[end]; 3919 playlist->indices[end] = index_swap; 3920 3921#ifdef HAVE_DIRCACHE 3922 if (playlist->dcfrefs_handle) 3923 { 3924 struct dircache_fileref *dcfrefs = core_get_data(playlist->dcfrefs_handle); 3925 struct dircache_fileref dcf_swap = dcfrefs[start]; 3926 dcfrefs[start] = dcfrefs[end]; 3927 dcfrefs[end] = dcf_swap; 3928 } 3929#endif 3930 } 3931} 3932 3933/* 3934 * Update the control file after saving the playlist under a new name. 3935 * A new control file is generated, containing the new playlist filename. 3936 * Queued tracks are copied to the new control file. 3937 * 3938 * On success, the new control file replaces the old control file. 3939 * On failure, indices may be trashed and the playlist should be 3940 * reloaded. This may not be possible if the playlist was overwritten. 3941 */ 3942static int pl_save_update_control(struct playlist_info* playlist, 3943 char *tmpbuf, size_t tmpsize) 3944{ 3945 int old_fd; 3946 int err = 0; 3947 char c; 3948 bool any_queued = false; 3949 3950 /* Nothing to update if we don't have any control file */ 3951 if (!playlist->control_created) 3952 return 0; 3953 3954 if (pl_get_tempname(playlist->control_filename, tmpbuf, tmpsize)) 3955 return -1; 3956 3957 /* Close the existing control file, reopen it as read-only */ 3958 pl_close_control(playlist); 3959 old_fd = open(playlist->control_filename, O_RDONLY); 3960 if (old_fd < 0) 3961 return -2; 3962 3963 /* Create new control file, pointing it at a tempfile */ 3964 playlist->control_fd = open(tmpbuf, O_CREAT|O_RDWR|O_TRUNC, 0666); 3965 if (playlist->control_fd < 0) 3966 { 3967 err = -3; 3968 goto error; 3969 } 3970 3971 /* Write out playlist filename */ 3972 c = playlist->filename[playlist->dirlen-1]; 3973 playlist->filename[playlist->dirlen-1] = '\0'; 3974 3975 err = update_control_unlocked(playlist, PLAYLIST_COMMAND_PLAYLIST, 3976 PLAYLIST_CONTROL_FILE_VERSION, -1, 3977 playlist->filename, 3978 &playlist->filename[playlist->dirlen], NULL); 3979 3980 playlist->filename[playlist->dirlen-1] = c; 3981 3982 if (err <= 0) 3983 { 3984 err = -4; 3985 goto error; 3986 } 3987 3988 if (playlist->first_index > 0) 3989 { 3990 /* rotate indices so they'll be in sync with new control file */ 3991 pl_reverse(playlist, 0, playlist->amount - 1); 3992 pl_reverse(playlist, 0, playlist->amount - playlist->first_index - 1); 3993 pl_reverse(playlist, playlist->amount - playlist->first_index, playlist->amount - 1); 3994 3995 playlist->index = rotate_index(playlist, playlist->index); 3996 playlist->last_insert_pos = rotate_index(playlist, playlist->last_insert_pos); 3997 playlist->first_index = 0; 3998 } 3999 4000 for (int index = 0; index < playlist->amount; ++index) 4001 { 4002 /* We only need to update queued files */ 4003 if (!(playlist->indices[index] & PLAYLIST_INSERT_TYPE_MASK && 4004 playlist->indices[index] & PLAYLIST_QUEUED)) 4005 continue; 4006 4007 /* Read filename from old control file */ 4008 lseek(old_fd, playlist->indices[index] & PLAYLIST_SEEK_MASK, SEEK_SET); 4009 read_line(old_fd, tmpbuf, tmpsize); 4010 4011 /* Write it out to the new control file */ 4012 int seekpos; 4013 err = update_control_unlocked(playlist, PLAYLIST_COMMAND_QUEUE, 4014 index, playlist->last_insert_pos, 4015 tmpbuf, NULL, &seekpos); 4016 if (err <= 0) 4017 { 4018 err = -5; 4019 goto error; 4020 } 4021 /* Update seek offset for the new control file. */ 4022 playlist->indices[index] &= ~PLAYLIST_SEEK_MASK; 4023 playlist->indices[index] |= seekpos; 4024 any_queued = true; 4025 } 4026 4027 /* Preserve modified state */ 4028 if (playlist_modified(playlist)) 4029 { 4030 if (any_queued) 4031 { 4032 err = update_control_unlocked(playlist, PLAYLIST_COMMAND_FLAGS, 4033 PLAYLIST_FLAG_MODIFIED, 0, NULL, NULL, NULL); 4034 if (err <= 0) 4035 { 4036 err = -6; 4037 goto error; 4038 } 4039 } 4040 else 4041 { 4042 playlist->flags &= ~PLAYLIST_FLAG_MODIFIED; 4043 } 4044 } 4045 4046 /* Clear dirplay flag, since we now point at a playlist */ 4047 playlist->flags &= ~PLAYLIST_FLAG_DIRPLAY; 4048 4049 /* Reset shuffle seed */ 4050 playlist->seed = 0; 4051 if (playlist == &current_playlist) 4052 global_settings.playlist_shuffle = false; 4053 4054 pl_close_control(playlist); 4055 close(old_fd); 4056 remove(playlist->control_filename); 4057 4058 /* TODO: Check for errors? The old control file is gone by this point... */ 4059 pl_get_tempname(playlist->control_filename, tmpbuf, tmpsize); 4060 rename(tmpbuf, playlist->control_filename); 4061 4062 playlist->control_fd = open(playlist->control_filename, O_RDWR); 4063 playlist->control_created = (playlist->control_fd >= 0); 4064 4065 return 0; 4066 4067error: 4068 close(old_fd); 4069 return err; 4070} 4071 4072int playlist_save(struct playlist_info* playlist, char *filename) 4073{ 4074 char save_path[MAX_PATH+1]; 4075 char tmpbuf[MAX_PATH+1]; 4076 ssize_t pathlen; 4077 int rc = 0; 4078 bool reload_tracks = false; 4079 4080 if (!playlist) 4081 playlist = &current_playlist; 4082 4083 pathlen = format_track_path(save_path, filename, 4084 sizeof(save_path), PATH_ROOTSTR, -1u); 4085 if (pathlen < 0) 4086 return -1; 4087 4088 cpu_boost(true); 4089 dc_thread_stop(playlist); 4090 playlist_write_lock(playlist); 4091 4092 if (playlist->amount <= 0) 4093 { 4094 rc = -1; 4095 goto error; 4096 } 4097 4098 /* Ask if queued tracks should be removed, so that 4099 playlist can be bookmarked after it's been saved */ 4100 for (int i = playlist->amount - 1; i >= 0; i--) 4101 if (playlist->indices[i] & PLAYLIST_QUEUED) 4102 { 4103 if (reload_tracks || (reload_tracks = 4104 (yesno_pop(ID2P(LANG_REMOVE_QUEUED_TRACKS))))) 4105 remove_track_unlocked(playlist, i, false); 4106 else 4107 break; 4108 } 4109 4110 rc = pl_save_playlist(playlist, save_path, tmpbuf, sizeof(tmpbuf)); 4111 if (rc < 0) 4112 { 4113 // TODO: If we fail here, we just need to reparse the old playlist file 4114 panicf("Failed to save playlist: %d", rc); 4115 goto error; 4116 } 4117 4118 /* User cancelled? */ 4119 if (rc > 0) 4120 goto error; 4121 4122 rc = pl_save_update_control(playlist, tmpbuf, sizeof(tmpbuf)); 4123 if (rc) 4124 { 4125 // TODO: If we fail here, then there are two possibilities depending on 4126 // whether we overwrote the old playlist file: 4127 // 4128 // - if it still exists, we could reparse it + old control file 4129 // - otherwise, we need to selectively reload the old control file 4130 // and somehow make use of the new playlist file 4131 // 4132 // The latter case poses other issues though, like what happens after we 4133 // resume, because replaying the old control file over the new playlist 4134 // won't work properly. We could simply choose to reset the control file, 4135 // seeing as by this point it only contains transient data (queued tracks). 4136 panicf("Failed to update control file: %d", rc); 4137 goto error; 4138 } 4139 4140error: 4141 playlist_write_unlock(playlist); 4142 dc_thread_start(playlist, true); 4143 cpu_boost(false); 4144 if (reload_tracks && playlist->started && 4145 (audio_status() & AUDIO_STATUS_PLAY)) 4146 audio_flush_and_reload_tracks(); 4147 return rc; 4148}