A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
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 == ¤t_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 == ¤t_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 == ¤t_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 == ¤t_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 == ¤t_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 = ¤t_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 = ¤t_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 = ¤t_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 = ¤t_playlist;
1943 mutex_init(¤t_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(¤t_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 = ¤t_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 = ¤t_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 = ¤t_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 = ¤t_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 = ¤t_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 ¤t_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 = ¤t_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 = ¤t_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 = ¤t_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 = ¤t_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 = ¤t_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 = ¤t_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 = ¤t_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 = ¤t_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 = ¤t_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 = ¤t_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 = ¤t_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 = ¤t_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 = ¤t_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 = ¤t_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 = ¤t_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(¤t_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 = ¤t_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 = ¤t_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 = ¤t_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 = ¤t_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 = ¤t_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(¤t_playlist);
3591 playlist_write_lock(¤t_playlist);
3592
3593 empty_playlist_unlocked(¤t_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(¤t_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(¤t_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(¤t_playlist);
3637 dc_thread_start(¤t_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 = ¤t_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 = ¤t_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 = ¤t_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 = ¤t_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 = ¤t_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 = ¤t_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 = ¤t_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 == ¤t_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 = ¤t_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}