Add icon to process dialog button.
[Rockbox.git] / apps / playlist.c
blob39d7cc1e3de6c5304b9b4c03ad8aa95cf4ce9e48
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2002 by wavey@wavey.org
12 * All files in this archive are subject to the GNU General Public License.
13 * See the file COPYING in the source tree root for full license agreement.
15 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
16 * KIND, either express or implied.
18 ****************************************************************************/
21 Dynamic playlist design (based on design originally proposed by ricII)
23 There are two files associated with a dynamic playlist:
24 1. Playlist file : This file contains the initial songs in the playlist.
25 The file is created by the user and stored on the hard
26 drive. NOTE: If we are playing the contents of a
27 directory, there will be no playlist file.
28 2. Control file : This file is automatically created when a playlist is
29 started and contains all the commands done to it.
31 The first non-comment line in a control file must begin with
32 "P:VERSION:DIR:FILE" where VERSION is the playlist control file version,
33 DIR is the directory where the playlist is located and FILE is the
34 playlist filename. For dirplay, FILE will be empty. An empty playlist
35 will have both entries as null.
37 Control file commands:
38 a. Add track (A:<position>:<last position>:<path to track>)
39 - Insert a track at the specified position in the current
40 playlist. Last position is used to specify where last insertion
41 occurred.
42 b. Queue track (Q:<position>:<last position>:<path to track>)
43 - Queue a track at the specified position in the current
44 playlist. Queued tracks differ from added tracks in that they
45 are deleted from the playlist as soon as they are played and
46 they are not saved to disk as part of the playlist.
47 c. Delete track (D:<position>)
48 - Delete track from specified position in the current playlist.
49 d. Shuffle playlist (S:<seed>:<index>)
50 - Shuffle entire playlist with specified seed. The index
51 identifies the first index in the newly shuffled playlist
52 (needed for repeat mode).
53 e. Unshuffle playlist (U:<index>)
54 - Unshuffle entire playlist. The index identifies the first index
55 in the newly unshuffled playlist.
56 f. Reset last insert position (R)
57 - Needed so that insertions work properly after resume
59 Resume:
60 The only resume info that needs to be saved is the current index in the
61 playlist and the position in the track. When resuming, all the commands
62 in the control file will be reapplied so that the playlist indices are
63 exactly the same as before shutdown. To avoid unnecessary disk
64 accesses, the shuffle mode settings are also saved in settings and only
65 flushed to disk when required.
68 #include <stdio.h>
69 #include <stdlib.h>
70 #include <string.h>
71 #include <ctype.h>
72 #include "playlist.h"
73 #include "file.h"
74 #include "action.h"
75 #include "dir.h"
76 #include "sprintf.h"
77 #include "debug.h"
78 #include "audio.h"
79 #include "lcd.h"
80 #include "kernel.h"
81 #include "settings.h"
82 #include "status.h"
83 #include "applimits.h"
84 #include "screens.h"
85 #include "buffer.h"
86 #include "atoi.h"
87 #include "misc.h"
88 #include "button.h"
89 #include "filetree.h"
90 #include "abrepeat.h"
91 #include "thread.h"
92 #include "usb.h"
93 #include "filetypes.h"
94 #ifdef HAVE_LCD_BITMAP
95 #include "icons.h"
96 #endif
98 #include "lang.h"
99 #include "talk.h"
100 #include "splash.h"
101 #include "rbunicode.h"
103 #define PLAYLIST_CONTROL_FILE ROCKBOX_DIR "/.playlist_control"
104 #define PLAYLIST_CONTROL_FILE_VERSION 2
107 Each playlist index has a flag associated with it which identifies what
108 type of track it is. These flags are stored in the 4 high order bits of
109 the index.
111 NOTE: This limits the playlist file size to a max of 256M.
113 Bits 31-30:
114 00 = Playlist track
115 01 = Track was prepended into playlist
116 10 = Track was inserted into playlist
117 11 = Track was appended into playlist
118 Bit 29:
119 0 = Added track
120 1 = Queued track
121 Bit 28:
122 0 = Track entry is valid
123 1 = Track does not exist on disk and should be skipped
125 #define PLAYLIST_SEEK_MASK 0x0FFFFFFF
126 #define PLAYLIST_INSERT_TYPE_MASK 0xC0000000
127 #define PLAYLIST_QUEUE_MASK 0x20000000
129 #define PLAYLIST_INSERT_TYPE_PREPEND 0x40000000
130 #define PLAYLIST_INSERT_TYPE_INSERT 0x80000000
131 #define PLAYLIST_INSERT_TYPE_APPEND 0xC0000000
133 #define PLAYLIST_QUEUED 0x20000000
134 #define PLAYLIST_SKIPPED 0x10000000
136 #define PLAYLIST_DISPLAY_COUNT 10
138 struct directory_search_context {
139 struct playlist_info* playlist;
140 int position;
141 bool queue;
142 int count;
145 static bool changing_dir = false;
147 static struct playlist_info current_playlist;
148 static char now_playing[MAX_PATH+1];
150 static void empty_playlist(struct playlist_info* playlist, bool resume);
151 static void new_playlist(struct playlist_info* playlist, const char *dir,
152 const char *file);
153 static void create_control(struct playlist_info* playlist);
154 static int check_control(struct playlist_info* playlist);
155 static int recreate_control(struct playlist_info* playlist);
156 static void update_playlist_filename(struct playlist_info* playlist,
157 const char *dir, const char *file);
158 static int add_indices_to_playlist(struct playlist_info* playlist,
159 char* buffer, size_t buflen);
160 static int add_track_to_playlist(struct playlist_info* playlist,
161 const char *filename, int position,
162 bool queue, int seek_pos);
163 static int directory_search_callback(char* filename, void* context);
164 static int remove_track_from_playlist(struct playlist_info* playlist,
165 int position, bool write);
166 static int randomise_playlist(struct playlist_info* playlist,
167 unsigned int seed, bool start_current,
168 bool write);
169 static int sort_playlist(struct playlist_info* playlist, bool start_current,
170 bool write);
171 static int get_next_index(const struct playlist_info* playlist, int steps,
172 int repeat_mode);
173 static void find_and_set_playlist_index(struct playlist_info* playlist,
174 unsigned int seek);
175 static int compare(const void* p1, const void* p2);
176 static int get_filename(struct playlist_info* playlist, int index, int seek,
177 bool control_file, char *buf, int buf_length);
178 static int get_next_directory(char *dir);
179 static int get_next_dir(char *dir, bool is_forward, bool recursion);
180 static int get_previous_directory(char *dir);
181 static int check_subdir_for_music(char *dir, char *subdir);
182 static int format_track_path(char *dest, char *src, int buf_length, int max,
183 char *dir);
184 static void display_playlist_count(int count, const unsigned char *fmt);
185 static void display_buffer_full(void);
186 static int flush_cached_control(struct playlist_info* playlist);
187 static int update_control(struct playlist_info* playlist,
188 enum playlist_command command, int i1, int i2,
189 const char* s1, const char* s2, void* data);
190 static void sync_control(struct playlist_info* playlist, bool force);
191 static int rotate_index(const struct playlist_info* playlist, int index);
193 #ifdef HAVE_DIRCACHE
194 #define PLAYLIST_LOAD_POINTERS 1
196 static struct event_queue playlist_queue;
197 static long playlist_stack[(DEFAULT_STACK_SIZE + 0x800)/sizeof(long)];
198 static const char playlist_thread_name[] = "playlist cachectrl";
199 #endif
202 * remove any files and indices associated with the playlist
204 static void empty_playlist(struct playlist_info* playlist, bool resume)
206 playlist->filename[0] = '\0';
207 playlist->utf8 = true;
209 if(playlist->fd >= 0)
210 /* If there is an already open playlist, close it. */
211 close(playlist->fd);
212 playlist->fd = -1;
214 if(playlist->control_fd >= 0)
215 close(playlist->control_fd);
216 playlist->control_fd = -1;
217 playlist->control_created = false;
219 playlist->in_ram = false;
221 if (playlist->buffer)
222 playlist->buffer[0] = 0;
224 playlist->buffer_end_pos = 0;
226 playlist->index = 0;
227 playlist->first_index = 0;
228 playlist->amount = 0;
229 playlist->last_insert_pos = -1;
230 playlist->seed = 0;
231 playlist->shuffle_modified = false;
232 playlist->deleted = false;
233 playlist->num_inserted_tracks = 0;
234 playlist->started = false;
236 playlist->num_cached = 0;
237 playlist->pending_control_sync = false;
239 if (!resume && playlist->current)
241 /* start with fresh playlist control file when starting new
242 playlist */
243 create_control(playlist);
245 /* Reset resume settings */
246 global_status.resume_first_index = 0;
247 global_status.resume_seed = -1;
252 * Initialize a new playlist for viewing/editing/playing. dir is the
253 * directory where the playlist is located and file is the filename.
255 static void new_playlist(struct playlist_info* playlist, const char *dir,
256 const char *file)
258 empty_playlist(playlist, false);
260 if (!file)
262 file = "";
264 if (dir && playlist->current) /* !current cannot be in_ram */
265 playlist->in_ram = true;
266 else
267 dir = ""; /* empty playlist */
270 update_playlist_filename(playlist, dir, file);
272 if (playlist->control_fd >= 0)
274 update_control(playlist, PLAYLIST_COMMAND_PLAYLIST,
275 PLAYLIST_CONTROL_FILE_VERSION, -1, dir, file, NULL);
276 sync_control(playlist, false);
281 * create control file for playlist
283 static void create_control(struct playlist_info* playlist)
285 playlist->control_fd = open(playlist->control_filename,
286 O_CREAT|O_RDWR|O_TRUNC);
287 if (playlist->control_fd < 0)
289 if (check_rockboxdir())
291 gui_syncsplash(HZ*2, (unsigned char *)"%s (%d)",
292 str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR),
293 playlist->control_fd);
295 playlist->control_created = false;
297 else
299 playlist->control_created = true;
304 * validate the control file. This may include creating/initializing it if
305 * necessary;
307 static int check_control(struct playlist_info* playlist)
309 if (!playlist->control_created)
311 create_control(playlist);
313 if (playlist->control_fd >= 0)
315 char* dir = playlist->filename;
316 char* file = playlist->filename+playlist->dirlen;
317 char c = playlist->filename[playlist->dirlen-1];
319 playlist->filename[playlist->dirlen-1] = '\0';
321 update_control(playlist, PLAYLIST_COMMAND_PLAYLIST,
322 PLAYLIST_CONTROL_FILE_VERSION, -1, dir, file, NULL);
323 sync_control(playlist, false);
324 playlist->filename[playlist->dirlen-1] = c;
328 if (playlist->control_fd < 0)
329 return -1;
331 return 0;
335 * recreate the control file based on current playlist entries
337 static int recreate_control(struct playlist_info* playlist)
339 char temp_file[MAX_PATH+1];
340 int temp_fd = -1;
341 int i;
342 int result = 0;
344 if(playlist->control_fd >= 0)
346 char* dir = playlist->filename;
347 char* file = playlist->filename+playlist->dirlen;
348 char c = playlist->filename[playlist->dirlen-1];
350 close(playlist->control_fd);
352 snprintf(temp_file, sizeof(temp_file), "%s_temp",
353 playlist->control_filename);
355 if (rename(playlist->control_filename, temp_file) < 0)
356 return -1;
358 temp_fd = open(temp_file, O_RDONLY);
359 if (temp_fd < 0)
360 return -1;
362 playlist->control_fd = open(playlist->control_filename,
363 O_CREAT|O_RDWR|O_TRUNC);
364 if (playlist->control_fd < 0)
365 return -1;
367 playlist->filename[playlist->dirlen-1] = '\0';
369 /* cannot call update_control() because of mutex */
370 result = fdprintf(playlist->control_fd, "P:%d:%s:%s\n",
371 PLAYLIST_CONTROL_FILE_VERSION, dir, file);
373 playlist->filename[playlist->dirlen-1] = c;
375 if (result < 0)
377 close(temp_fd);
378 return result;
382 playlist->seed = 0;
383 playlist->shuffle_modified = false;
384 playlist->deleted = false;
385 playlist->num_inserted_tracks = 0;
387 if (playlist->current)
389 global_status.resume_seed = -1;
390 status_save();
393 for (i=0; i<playlist->amount; i++)
395 if (playlist->indices[i] & PLAYLIST_INSERT_TYPE_MASK)
397 bool queue = playlist->indices[i] & PLAYLIST_QUEUE_MASK;
398 char inserted_file[MAX_PATH+1];
400 lseek(temp_fd, playlist->indices[i] & PLAYLIST_SEEK_MASK,
401 SEEK_SET);
402 read_line(temp_fd, inserted_file, sizeof(inserted_file));
404 result = fdprintf(playlist->control_fd, "%c:%d:%d:",
405 queue?'Q':'A', i, playlist->last_insert_pos);
406 if (result > 0)
408 /* save the position in file where name is written */
409 int seek_pos = lseek(playlist->control_fd, 0, SEEK_CUR);
411 result = fdprintf(playlist->control_fd, "%s\n",
412 inserted_file);
414 playlist->indices[i] =
415 (playlist->indices[i] & ~PLAYLIST_SEEK_MASK) | seek_pos;
418 if (result < 0)
419 break;
421 playlist->num_inserted_tracks++;
425 close(temp_fd);
426 remove(temp_file);
427 fsync(playlist->control_fd);
429 if (result < 0)
430 return result;
432 return 0;
436 * store directory and name of playlist file
438 static void update_playlist_filename(struct playlist_info* playlist,
439 const char *dir, const char *file)
441 char *sep="";
442 int dirlen = strlen(dir);
443 int filelen = strlen(file);
445 /* Default to utf8 unless explicitly told otherwise. */
446 playlist->utf8 = !(filelen > 4 && strcasecmp(&file[filelen - 4], ".m3u") == 0);
448 /* If the dir does not end in trailing slash, we use a separator.
449 Otherwise we don't. */
450 if('/' != dir[dirlen-1])
452 sep="/";
453 dirlen++;
456 playlist->dirlen = dirlen;
458 snprintf(playlist->filename, sizeof(playlist->filename),
459 "%s%s%s", dir, sep, file);
463 * calculate track offsets within a playlist file
465 static int add_indices_to_playlist(struct playlist_info* playlist,
466 char* buffer, size_t buflen)
468 unsigned int nread;
469 unsigned int i = 0;
470 unsigned int count = 0;
471 bool store_index;
472 unsigned char *p;
473 int result = 0;
475 if(-1 == playlist->fd)
476 playlist->fd = open(playlist->filename, O_RDONLY);
477 if(playlist->fd < 0)
478 return -1; /* failure */
480 #ifdef HAVE_LCD_BITMAP
481 if(global_settings.statusbar)
482 lcd_setmargins(0, STATUSBAR_HEIGHT);
483 else
484 lcd_setmargins(0, 0);
485 #endif
487 gui_syncsplash(0, str(LANG_PLAYLIST_LOAD));
489 if (!buffer)
491 /* use mp3 buffer for maximum load speed */
492 audio_stop();
493 #if CONFIG_CODEC != SWCODEC
494 talk_buffer_steal(); /* we use the mp3 buffer, need to tell */
495 buflen = (audiobufend - audiobuf);
496 buffer = (char *)audiobuf;
497 #else
498 buffer = (char *)audio_get_buffer(false, &buflen);
499 #endif
502 store_index = true;
504 while(1)
506 nread = read(playlist->fd, buffer, buflen);
507 /* Terminate on EOF */
508 if(nread <= 0)
509 break;
511 p = (unsigned char *)buffer;
513 /* utf8 BOM at beginning of file? */
514 if(i == 0 && nread > 3
515 && *p == 0xef && *(p+1) == 0xbb && *(p+2) == 0xbf) {
516 nread -= 3;
517 p += 3;
518 i += 3;
519 playlist->utf8 = true; /* Override any earlier indication. */
522 for(count=0; count < nread; count++,p++) {
524 /* Are we on a new line? */
525 if((*p == '\n') || (*p == '\r'))
527 store_index = true;
529 else if(store_index)
531 store_index = false;
533 if(*p != '#')
535 if ( playlist->amount >= playlist->max_playlist_size ) {
536 display_buffer_full();
537 result = -1;
538 goto exit;
541 /* Store a new entry */
542 playlist->indices[ playlist->amount ] = i+count;
543 #ifdef HAVE_DIRCACHE
544 if (playlist->filenames)
545 playlist->filenames[ playlist->amount ] = NULL;
546 #endif
547 playlist->amount++;
552 i+= count;
555 exit:
556 #ifdef HAVE_DIRCACHE
557 queue_post(&playlist_queue, PLAYLIST_LOAD_POINTERS, 0);
558 #endif
560 return result;
564 * Removes all tracks, from the playlist, leaving the presently playing
565 * track queued.
567 int remove_all_tracks(struct playlist_info *playlist)
569 int result;
571 if (playlist == NULL)
572 playlist = &current_playlist;
574 while (playlist->index > 0)
575 if ((result = remove_track_from_playlist(playlist, 0, true)) < 0)
576 return result;
578 while (playlist->amount > 1)
579 if ((result = remove_track_from_playlist(playlist, 1, true)) < 0)
580 return result;
582 if (playlist->amount == 1) {
583 playlist->indices[0] |= PLAYLIST_QUEUED;
586 return 0;
591 * Add track to playlist at specified position. There are five special
592 * positions that can be specified:
593 * PLAYLIST_PREPEND - Add track at beginning of playlist
594 * PLAYLIST_INSERT - Add track after current song. NOTE: If
595 * there are already inserted tracks then track
596 * is added to the end of the insertion list
597 * PLAYLIST_INSERT_FIRST - Add track immediately after current song, no
598 * matter what other tracks have been inserted
599 * PLAYLIST_INSERT_LAST - Add track to end of playlist
600 * PLAYLIST_INSERT_SHUFFLED - Add track at some random point between the
601 * current playing track and end of playlist
602 * PLAYLIST_REPLACE - Erase current playlist, Cue the current track
603 * and inster this track at the end.
605 static int add_track_to_playlist(struct playlist_info* playlist,
606 const char *filename, int position,
607 bool queue, int seek_pos)
609 int insert_position, orig_position;
610 unsigned long flags = PLAYLIST_INSERT_TYPE_INSERT;
611 int i;
613 insert_position = orig_position = position;
615 if (playlist->amount >= playlist->max_playlist_size)
617 display_buffer_full();
618 return -1;
621 switch (position)
623 case PLAYLIST_PREPEND:
624 position = insert_position = playlist->first_index;
625 break;
626 case PLAYLIST_INSERT:
627 /* if there are already inserted tracks then add track to end of
628 insertion list else add after current playing track */
629 if (playlist->last_insert_pos >= 0 &&
630 playlist->last_insert_pos < playlist->amount &&
631 (playlist->indices[playlist->last_insert_pos]&
632 PLAYLIST_INSERT_TYPE_MASK) == PLAYLIST_INSERT_TYPE_INSERT)
633 position = insert_position = playlist->last_insert_pos+1;
634 else if (playlist->amount > 0)
635 position = insert_position = playlist->index + 1;
636 else
637 position = insert_position = 0;
639 if (playlist->started)
640 playlist->last_insert_pos = position;
641 break;
642 case PLAYLIST_INSERT_FIRST:
643 if (playlist->amount > 0)
644 position = insert_position = playlist->index + 1;
645 else
646 position = insert_position = 0;
648 if (playlist->last_insert_pos < 0 && playlist->started)
649 playlist->last_insert_pos = position;
650 break;
651 case PLAYLIST_INSERT_LAST:
652 if (playlist->first_index > 0)
653 position = insert_position = playlist->first_index;
654 else
655 position = insert_position = playlist->amount;
656 break;
657 case PLAYLIST_INSERT_SHUFFLED:
659 if (playlist->started)
661 int offset;
662 int n = playlist->amount -
663 rotate_index(playlist, playlist->index);
665 if (n > 0)
666 offset = rand() % n;
667 else
668 offset = 0;
670 position = playlist->index + offset + 1;
671 if (position >= playlist->amount)
672 position -= playlist->amount;
674 insert_position = position;
676 else
677 position = insert_position = (rand() % (playlist->amount+1));
678 break;
680 case PLAYLIST_REPLACE:
681 if (remove_all_tracks(playlist) < 0)
682 return -1;
684 position = insert_position = playlist->index + 1;
685 break;
688 if (queue)
689 flags |= PLAYLIST_QUEUED;
691 /* shift indices so that track can be added */
692 for (i=playlist->amount; i>insert_position; i--)
694 playlist->indices[i] = playlist->indices[i-1];
695 #ifdef HAVE_DIRCACHE
696 if (playlist->filenames)
697 playlist->filenames[i] = playlist->filenames[i-1];
698 #endif
701 /* update stored indices if needed */
702 if (playlist->amount > 0 && insert_position <= playlist->index &&
703 playlist->started)
704 playlist->index++;
706 if (playlist->amount > 0 && insert_position <= playlist->first_index &&
707 orig_position != PLAYLIST_PREPEND && playlist->started)
709 playlist->first_index++;
711 if (seek_pos < 0 && playlist->current)
713 global_status.resume_first_index = playlist->first_index;
714 status_save();
718 if (insert_position < playlist->last_insert_pos ||
719 (insert_position == playlist->last_insert_pos && position < 0))
720 playlist->last_insert_pos++;
722 if (seek_pos < 0 && playlist->control_fd >= 0)
724 int result = update_control(playlist,
725 (queue?PLAYLIST_COMMAND_QUEUE:PLAYLIST_COMMAND_ADD), position,
726 playlist->last_insert_pos, filename, NULL, &seek_pos);
728 if (result < 0)
729 return result;
732 playlist->indices[insert_position] = flags | seek_pos;
734 #ifdef HAVE_DIRCACHE
735 if (playlist->filenames)
736 playlist->filenames[insert_position] = NULL;
737 #endif
739 playlist->amount++;
740 playlist->num_inserted_tracks++;
742 return insert_position;
746 * Callback for playlist_directory_tracksearch to insert track into
747 * playlist.
749 static int directory_search_callback(char* filename, void* context)
751 struct directory_search_context* c =
752 (struct directory_search_context*) context;
753 int insert_pos;
755 insert_pos = add_track_to_playlist(c->playlist, filename, c->position,
756 c->queue, -1);
758 if (insert_pos < 0)
759 return -1;
761 (c->count)++;
763 /* Make sure tracks are inserted in correct order if user requests
764 INSERT_FIRST */
765 if (c->position == PLAYLIST_INSERT_FIRST || c->position >= 0)
766 c->position = insert_pos + 1;
768 if (((c->count)%PLAYLIST_DISPLAY_COUNT) == 0)
770 unsigned char* count_str;
772 if (c->queue)
773 count_str = str(LANG_PLAYLIST_QUEUE_COUNT);
774 else
775 count_str = str(LANG_PLAYLIST_INSERT_COUNT);
777 display_playlist_count(c->count, count_str);
779 if ((c->count) == PLAYLIST_DISPLAY_COUNT &&
780 (audio_status() & AUDIO_STATUS_PLAY) &&
781 c->playlist->started)
782 audio_flush_and_reload_tracks();
785 return 0;
789 * remove track at specified position
791 static int remove_track_from_playlist(struct playlist_info* playlist,
792 int position, bool write)
794 int i;
795 bool inserted;
797 if (playlist->amount <= 0)
798 return -1;
800 inserted = playlist->indices[position] & PLAYLIST_INSERT_TYPE_MASK;
802 /* shift indices now that track has been removed */
803 for (i=position; i<playlist->amount; i++)
805 playlist->indices[i] = playlist->indices[i+1];
806 #ifdef HAVE_DIRCACHE
807 if (playlist->filenames)
808 playlist->filenames[i] = playlist->filenames[i+1];
809 #endif
812 playlist->amount--;
814 if (inserted)
815 playlist->num_inserted_tracks--;
816 else
817 playlist->deleted = true;
819 /* update stored indices if needed */
820 if (position < playlist->index)
821 playlist->index--;
823 if (position < playlist->first_index)
825 playlist->first_index--;
827 if (write)
829 global_status.resume_first_index = playlist->first_index;
830 status_save();
834 if (position <= playlist->last_insert_pos)
835 playlist->last_insert_pos--;
837 if (write && playlist->control_fd >= 0)
839 int result = update_control(playlist, PLAYLIST_COMMAND_DELETE,
840 position, -1, NULL, NULL, NULL);
842 if (result < 0)
843 return result;
845 sync_control(playlist, false);
848 return 0;
852 * randomly rearrange the array of indices for the playlist. If start_current
853 * is true then update the index to the new index of the current playing track
855 static int randomise_playlist(struct playlist_info* playlist,
856 unsigned int seed, bool start_current,
857 bool write)
859 int count;
860 int candidate;
861 long store;
862 unsigned int current = playlist->indices[playlist->index];
864 /* seed 0 is used to identify sorted playlist for resume purposes */
865 if (seed == 0)
866 seed = 1;
868 /* seed with the given seed */
869 srand(seed);
871 /* randomise entire indices list */
872 for(count = playlist->amount - 1; count >= 0; count--)
874 /* the rand is from 0 to RAND_MAX, so adjust to our value range */
875 candidate = rand() % (count + 1);
877 /* now swap the values at the 'count' and 'candidate' positions */
878 store = playlist->indices[candidate];
879 playlist->indices[candidate] = playlist->indices[count];
880 playlist->indices[count] = store;
881 #ifdef HAVE_DIRCACHE
882 if (playlist->filenames)
884 store = (long)playlist->filenames[candidate];
885 playlist->filenames[candidate] = playlist->filenames[count];
886 playlist->filenames[count] = (struct dircache_entry *)store;
888 #endif
891 if (start_current)
892 find_and_set_playlist_index(playlist, current);
894 /* indices have been moved so last insert position is no longer valid */
895 playlist->last_insert_pos = -1;
897 playlist->seed = seed;
898 if (playlist->num_inserted_tracks > 0 || playlist->deleted)
899 playlist->shuffle_modified = true;
901 if (write)
903 update_control(playlist, PLAYLIST_COMMAND_SHUFFLE, seed,
904 playlist->first_index, NULL, NULL, NULL);
905 global_status.resume_seed = seed;
906 status_save();
909 return 0;
913 * Sort the array of indices for the playlist. If start_current is true then
914 * set the index to the new index of the current song.
916 static int sort_playlist(struct playlist_info* playlist, bool start_current,
917 bool write)
919 unsigned int current = playlist->indices[playlist->index];
921 if (playlist->amount > 0)
922 qsort(playlist->indices, playlist->amount,
923 sizeof(playlist->indices[0]), compare);
925 #ifdef HAVE_DIRCACHE
926 /** We need to re-check the song names from disk because qsort can't
927 * sort two arrays at once :/
928 * FIXME: Please implement a better way to do this. */
929 memset(playlist->filenames, 0, playlist->max_playlist_size * sizeof(int));
930 queue_post(&playlist_queue, PLAYLIST_LOAD_POINTERS, 0);
931 #endif
933 if (start_current)
934 find_and_set_playlist_index(playlist, current);
936 /* indices have been moved so last insert position is no longer valid */
937 playlist->last_insert_pos = -1;
939 if (!playlist->num_inserted_tracks && !playlist->deleted)
940 playlist->shuffle_modified = false;
941 if (write && playlist->control_fd >= 0)
943 update_control(playlist, PLAYLIST_COMMAND_UNSHUFFLE,
944 playlist->first_index, -1, NULL, NULL, NULL);
945 global_status.resume_seed = 0;
946 status_save();
949 return 0;
952 /* Calculate how many steps we have to really step when skipping entries
953 * marked as bad.
955 static int calculate_step_count(const struct playlist_info *playlist, int steps)
957 int i, count, direction;
958 int index;
959 int stepped_count = 0;
961 if (steps < 0)
963 direction = -1;
964 count = -steps;
966 else
968 direction = 1;
969 count = steps;
972 index = playlist->index;
973 i = 0;
974 do {
975 /* Boundary check */
976 if (index < 0)
977 index += playlist->amount;
978 if (index >= playlist->amount)
979 index -= playlist->amount;
981 /* Check if we found a bad entry. */
982 if (playlist->indices[index] & PLAYLIST_SKIPPED)
984 steps += direction;
985 /* Are all entries bad? */
986 if (stepped_count++ > playlist->amount)
987 break ;
989 else
990 i++;
992 index += direction;
993 } while (i <= count);
995 return steps;
998 /* Marks the index of the track to be skipped that is "steps" away from
999 * current playing track.
1001 void playlist_skip_entry(struct playlist_info *playlist, int steps)
1003 int index;
1005 if (playlist == NULL)
1006 playlist = &current_playlist;
1008 /* need to account for already skipped tracks */
1009 steps = calculate_step_count(playlist, steps);
1011 index = playlist->index + steps;
1012 if (index < 0)
1013 index += playlist->amount;
1014 else if (index >= playlist->amount)
1015 index -= playlist->amount;
1017 playlist->indices[index] |= PLAYLIST_SKIPPED;
1021 * returns the index of the track that is "steps" away from current playing
1022 * track.
1024 static int get_next_index(const struct playlist_info* playlist, int steps,
1025 int repeat_mode)
1027 int current_index = playlist->index;
1028 int next_index = -1;
1030 if (playlist->amount <= 0)
1031 return -1;
1033 if (repeat_mode == -1)
1034 repeat_mode = global_settings.repeat_mode;
1036 if (repeat_mode == REPEAT_SHUFFLE && playlist->amount <= 1)
1037 repeat_mode = REPEAT_ALL;
1039 steps = calculate_step_count(playlist, steps);
1040 switch (repeat_mode)
1042 case REPEAT_SHUFFLE:
1043 /* Treat repeat shuffle just like repeat off. At end of playlist,
1044 play will be resumed in playlist_next() */
1045 case REPEAT_OFF:
1047 current_index = rotate_index(playlist, current_index);
1048 next_index = current_index+steps;
1049 if ((next_index < 0) || (next_index >= playlist->amount))
1050 next_index = -1;
1051 else
1052 next_index = (next_index+playlist->first_index) %
1053 playlist->amount;
1055 break;
1058 case REPEAT_ONE:
1059 #ifdef AB_REPEAT_ENABLE
1060 case REPEAT_AB:
1061 #endif
1062 next_index = current_index;
1063 break;
1065 case REPEAT_ALL:
1066 default:
1068 next_index = (current_index+steps) % playlist->amount;
1069 while (next_index < 0)
1070 next_index += playlist->amount;
1072 if (steps >= playlist->amount)
1074 int i, index;
1076 index = next_index;
1077 next_index = -1;
1079 /* second time around so skip the queued files */
1080 for (i=0; i<playlist->amount; i++)
1082 if (playlist->indices[index] & PLAYLIST_QUEUE_MASK)
1083 index = (index+1) % playlist->amount;
1084 else
1086 next_index = index;
1087 break;
1091 break;
1095 /* No luck if the whole playlist was bad. */
1096 if (playlist->indices[next_index] & PLAYLIST_SKIPPED)
1097 return -1;
1099 return next_index;
1103 * Search for the seek track and set appropriate indices. Used after shuffle
1104 * to make sure the current index is still pointing to correct track.
1106 static void find_and_set_playlist_index(struct playlist_info* playlist,
1107 unsigned int seek)
1109 int i;
1111 /* Set the index to the current song */
1112 for (i=0; i<playlist->amount; i++)
1114 if (playlist->indices[i] == seek)
1116 playlist->index = playlist->first_index = i;
1118 if (playlist->current)
1120 global_status.resume_first_index = i;
1121 status_save();
1124 break;
1130 * used to sort track indices. Sort order is as follows:
1131 * 1. Prepended tracks (in prepend order)
1132 * 2. Playlist/directory tracks (in playlist order)
1133 * 3. Inserted/Appended tracks (in insert order)
1135 static int compare(const void* p1, const void* p2)
1137 unsigned long* e1 = (unsigned long*) p1;
1138 unsigned long* e2 = (unsigned long*) p2;
1139 unsigned long flags1 = *e1 & PLAYLIST_INSERT_TYPE_MASK;
1140 unsigned long flags2 = *e2 & PLAYLIST_INSERT_TYPE_MASK;
1142 if (flags1 == flags2)
1143 return (*e1 & PLAYLIST_SEEK_MASK) - (*e2 & PLAYLIST_SEEK_MASK);
1144 else if (flags1 == PLAYLIST_INSERT_TYPE_PREPEND ||
1145 flags2 == PLAYLIST_INSERT_TYPE_APPEND)
1146 return -1;
1147 else if (flags1 == PLAYLIST_INSERT_TYPE_APPEND ||
1148 flags2 == PLAYLIST_INSERT_TYPE_PREPEND)
1149 return 1;
1150 else if (flags1 && flags2)
1151 return (*e1 & PLAYLIST_SEEK_MASK) - (*e2 & PLAYLIST_SEEK_MASK);
1152 else
1153 return *e1 - *e2;
1156 #ifdef HAVE_DIRCACHE
1158 * Thread to update filename pointers to dircache on background
1159 * without affecting playlist load up performance. This thread also flushes
1160 * any pending control commands when the disk spins up.
1162 static void playlist_thread(void)
1164 struct event ev;
1165 bool dirty_pointers = false;
1166 static char tmp[MAX_PATH+1];
1168 struct playlist_info *playlist;
1169 int index;
1170 int seek;
1171 bool control_file;
1173 int sleep_time = 5;
1175 #ifndef HAVE_FLASH_STORAGE
1176 if (global_settings.disk_spindown > 1 &&
1177 global_settings.disk_spindown <= 5)
1178 sleep_time = global_settings.disk_spindown - 1;
1179 #endif
1181 while (1)
1183 queue_wait_w_tmo(&playlist_queue, &ev, HZ*sleep_time);
1185 switch (ev.id)
1187 case PLAYLIST_LOAD_POINTERS:
1188 dirty_pointers = true;
1189 break ;
1191 /* Start the background scanning after either the disk spindown
1192 timeout or 5s, whichever is less */
1193 case SYS_TIMEOUT:
1194 playlist = &current_playlist;
1196 if (playlist->control_fd >= 0
1197 # ifndef SIMULATOR
1198 && ata_disk_is_active()
1199 # endif
1202 if (playlist->num_cached > 0)
1204 mutex_lock(&playlist->control_mutex);
1205 flush_cached_control(playlist);
1206 mutex_unlock(&playlist->control_mutex);
1209 sync_control(playlist, true);
1212 if (!dirty_pointers)
1213 break ;
1215 if (!dircache_is_enabled() || !playlist->filenames
1216 || playlist->amount <= 0)
1217 break ;
1219 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
1220 cpu_boost(true);
1221 #endif
1222 for (index = 0; index < playlist->amount
1223 && queue_empty(&playlist_queue); index++)
1225 /* Process only pointers that are not already loaded. */
1226 if (playlist->filenames[index])
1227 continue ;
1229 control_file = playlist->indices[index] & PLAYLIST_INSERT_TYPE_MASK;
1230 seek = playlist->indices[index] & PLAYLIST_SEEK_MASK;
1232 /* Load the filename from playlist file. */
1233 if (get_filename(playlist, index, seek, control_file, tmp,
1234 sizeof(tmp)) < 0)
1235 break ;
1237 /* Set the dircache entry pointer. */
1238 playlist->filenames[index] = dircache_get_entry_ptr(tmp);
1240 /* And be on background so user doesn't notice any delays. */
1241 yield();
1244 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
1245 cpu_boost(false);
1246 #endif
1247 dirty_pointers = false;
1248 break ;
1250 #ifndef SIMULATOR
1251 case SYS_USB_CONNECTED:
1252 usb_acknowledge(SYS_USB_CONNECTED_ACK);
1253 usb_wait_for_disconnect(&playlist_queue);
1254 break ;
1255 #endif
1259 #endif
1262 * gets pathname for track at seek index
1264 static int get_filename(struct playlist_info* playlist, int index, int seek,
1265 bool control_file, char *buf, int buf_length)
1267 int fd;
1268 int max = -1;
1269 char tmp_buf[MAX_PATH+1];
1270 char dir_buf[MAX_PATH+1];
1272 if (buf_length > MAX_PATH+1)
1273 buf_length = MAX_PATH+1;
1275 #ifdef HAVE_DIRCACHE
1276 if (dircache_is_enabled() && playlist->filenames)
1278 if (playlist->filenames[index] != NULL)
1280 dircache_copy_path(playlist->filenames[index], tmp_buf, sizeof(tmp_buf)-1);
1281 max = strlen(tmp_buf) + 1;
1284 #else
1285 (void)index;
1286 #endif
1288 if (playlist->in_ram && !control_file && max < 0)
1290 strncpy(tmp_buf, &playlist->buffer[seek], sizeof(tmp_buf));
1291 tmp_buf[MAX_PATH] = '\0';
1292 max = strlen(tmp_buf) + 1;
1294 else if (max < 0)
1296 mutex_lock(&playlist->control_mutex);
1298 if (control_file)
1299 fd = playlist->control_fd;
1300 else
1302 if(-1 == playlist->fd)
1303 playlist->fd = open(playlist->filename, O_RDONLY);
1305 fd = playlist->fd;
1308 if(-1 != fd)
1311 if (lseek(fd, seek, SEEK_SET) != seek)
1312 max = -1;
1313 else
1315 max = read(fd, tmp_buf, MIN((size_t) buf_length, sizeof(tmp_buf)));
1317 if ((max > 0) && !playlist->utf8)
1319 char* end;
1320 int i = 0;
1322 /* Locate EOL. */
1323 while ((tmp_buf[i] != '\n') && (tmp_buf[i] != '\r')
1324 && (i < max))
1326 i++;
1329 /* Now work back killing white space. */
1330 while ((i > 0) && isspace(tmp_buf[i - 1]))
1332 i--;
1335 /* Borrow dir_buf a little... */
1336 /* TODO: iso_decode can overflow dir_buf; it really
1337 * should take a dest size argument.
1339 end = iso_decode(tmp_buf, dir_buf, -1, i);
1340 *end = 0;
1341 strncpy(tmp_buf, dir_buf, sizeof(tmp_buf));
1342 tmp_buf[sizeof(tmp_buf) - 1] = 0;
1343 max = strlen(tmp_buf);
1348 mutex_unlock(&playlist->control_mutex);
1350 if (max < 0)
1352 if (control_file)
1353 gui_syncsplash(HZ*2, str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
1354 else
1355 gui_syncsplash(HZ*2, str(LANG_PLAYLIST_ACCESS_ERROR));
1357 return max;
1361 strncpy(dir_buf, playlist->filename, playlist->dirlen-1);
1362 dir_buf[playlist->dirlen-1] = 0;
1364 return (format_track_path(buf, tmp_buf, buf_length, max, dir_buf));
1367 static int get_next_directory(char *dir){
1368 return get_next_dir(dir,true,false);
1371 static int get_previous_directory(char *dir){
1372 return get_next_dir(dir,false,false);
1376 * search through all the directories (starting with the current) to find
1377 * one that has tracks to play
1379 static int get_next_dir(char *dir, bool is_forward, bool recursion)
1381 struct playlist_info* playlist = &current_playlist;
1382 int result = -1;
1383 int sort_dir = global_settings.sort_dir;
1384 char *start_dir = NULL;
1385 bool exit = false;
1386 struct tree_context* tc = tree_get_context();
1387 int dirfilter = *(tc->dirfilter);
1389 if (global_settings.next_folder == FOLDER_ADVANCE_RANDOM)
1391 int fd = open(ROCKBOX_DIR "/folder_advance_list.dat",O_RDONLY);
1392 char buffer[MAX_PATH];
1393 int folder_count = 0,i;
1394 srand(current_tick);
1395 *(tc->dirfilter) = SHOW_MUSIC;
1396 if (fd >= 0)
1398 read(fd,&folder_count,sizeof(int));
1399 while (!exit)
1401 i = rand()%folder_count;
1402 lseek(fd,sizeof(int) + (MAX_PATH*i),SEEK_SET);
1403 read(fd,buffer,MAX_PATH);
1404 if (check_subdir_for_music(buffer,"") ==0)
1405 exit = true;
1407 strcpy(dir,buffer);
1408 close(fd);
1409 *(tc->dirfilter) = dirfilter;
1410 reload_directory();
1411 return 0;
1414 /* not random folder advance */
1415 if (recursion){
1416 /* start with root */
1417 dir[0] = '\0';
1419 else{
1420 /* start with current directory */
1421 strncpy(dir, playlist->filename, playlist->dirlen-1);
1422 dir[playlist->dirlen-1] = '\0';
1425 /* use the tree browser dircache to load files */
1426 *(tc->dirfilter) = SHOW_ALL;
1428 /* sort in another direction if previous dir is requested */
1429 if(!is_forward){
1430 if ((global_settings.sort_dir == 0) || (global_settings.sort_dir == 3))
1431 global_settings.sort_dir = 4;
1432 else if (global_settings.sort_dir == 1)
1433 global_settings.sort_dir = 2;
1434 else if (global_settings.sort_dir == 2)
1435 global_settings.sort_dir = 1;
1436 else if (global_settings.sort_dir == 4)
1437 global_settings.sort_dir = 0;
1440 while (!exit)
1442 struct entry *files;
1443 int num_files = 0;
1444 int i;
1446 if (ft_load(tc, (dir[0]=='\0')?"/":dir) < 0)
1448 gui_syncsplash(HZ*2, str(LANG_PLAYLIST_DIRECTORY_ACCESS_ERROR));
1449 exit = true;
1450 result = -1;
1451 break;
1454 files = (struct entry*) tc->dircache;
1455 num_files = tc->filesindir;
1457 for (i=0; i<num_files; i++)
1459 /* user abort */
1460 if (action_userabort(TIMEOUT_NOBLOCK))
1462 result = -1;
1463 exit = true;
1464 break;
1467 if (files[i].attr & ATTR_DIRECTORY)
1469 if (!start_dir)
1471 result = check_subdir_for_music(dir, files[i].name);
1472 if (result != -1)
1474 exit = true;
1475 break;
1478 else if (!strcmp(start_dir, files[i].name))
1479 start_dir = NULL;
1483 if (!exit)
1485 /* move down to parent directory. current directory name is
1486 stored as the starting point for the search in parent */
1487 start_dir = strrchr(dir, '/');
1488 if (start_dir)
1490 *start_dir = '\0';
1491 start_dir++;
1493 else
1494 break;
1498 /* we've overwritten the dircache so tree browser will need to be
1499 reloaded */
1500 reload_directory();
1502 /* restore dirfilter & sort_dir */
1503 *(tc->dirfilter) = dirfilter;
1504 global_settings.sort_dir = sort_dir;
1506 /* special case if nothing found: try start searching again from root */
1507 if (result == -1 && !recursion){
1508 result = get_next_dir(dir,is_forward, true);
1511 return result;
1515 * Checks if there are any music files in the dir or any of its
1516 * subdirectories. May be called recursively.
1518 static int check_subdir_for_music(char *dir, char *subdir)
1520 int result = -1;
1521 int dirlen = strlen(dir);
1522 int num_files = 0;
1523 int i;
1524 struct entry *files;
1525 bool has_music = false;
1526 bool has_subdir = false;
1527 struct tree_context* tc = tree_get_context();
1529 snprintf(dir+dirlen, MAX_PATH-dirlen, "/%s", subdir);
1531 if (ft_load(tc, dir) < 0)
1533 gui_syncsplash(HZ*2, str(LANG_PLAYLIST_DIRECTORY_ACCESS_ERROR));
1534 return -2;
1537 files = (struct entry*) tc->dircache;
1538 num_files = tc->filesindir;
1540 for (i=0; i<num_files; i++)
1542 if (files[i].attr & ATTR_DIRECTORY)
1543 has_subdir = true;
1544 else if ((files[i].attr & FILE_ATTR_MASK) == FILE_ATTR_AUDIO)
1546 has_music = true;
1547 break;
1551 if (has_music)
1552 return 0;
1554 if (has_subdir)
1556 for (i=0; i<num_files; i++)
1558 if (action_userabort(TIMEOUT_NOBLOCK))
1560 result = -2;
1561 break;
1564 if (files[i].attr & ATTR_DIRECTORY)
1566 result = check_subdir_for_music(dir, files[i].name);
1567 if (!result)
1568 break;
1573 if (result < 0)
1575 if (dirlen)
1577 dir[dirlen] = '\0';
1579 else
1581 strcpy(dir, "/");
1584 /* we now need to reload our current directory */
1585 if(ft_load(tc, dir) < 0)
1586 gui_syncsplash(HZ*2,
1587 str(LANG_PLAYLIST_DIRECTORY_ACCESS_ERROR));
1590 return result;
1594 * Returns absolute path of track
1596 static int format_track_path(char *dest, char *src, int buf_length, int max,
1597 char *dir)
1599 int i = 0;
1600 int j;
1601 char *temp_ptr;
1603 /* Zero-terminate the file name */
1604 while((src[i] != '\n') &&
1605 (src[i] != '\r') &&
1606 (i < max))
1607 i++;
1609 /* Now work back killing white space */
1610 while((src[i-1] == ' ') ||
1611 (src[i-1] == '\t'))
1612 i--;
1614 src[i]=0;
1616 /* replace backslashes with forward slashes */
1617 for ( j=0; j<i; j++ )
1618 if ( src[j] == '\\' )
1619 src[j] = '/';
1621 if('/' == src[0])
1623 strncpy(dest, src, buf_length);
1625 else
1627 /* handle dos style drive letter */
1628 if (':' == src[1])
1629 strncpy(dest, &src[2], buf_length);
1630 else if (!strncmp(src, "../", 3))
1632 /* handle relative paths */
1633 i=3;
1634 while(!strncmp(&src[i], "../", 3))
1635 i += 3;
1636 for (j=0; j<i/3; j++) {
1637 temp_ptr = strrchr(dir, '/');
1638 if (temp_ptr)
1639 *temp_ptr = '\0';
1640 else
1641 break;
1643 snprintf(dest, buf_length, "%s/%s", dir, &src[i]);
1645 else if ( '.' == src[0] && '/' == src[1] ) {
1646 snprintf(dest, buf_length, "%s/%s", dir, &src[2]);
1648 else {
1649 snprintf(dest, buf_length, "%s/%s", dir, src);
1653 return 0;
1657 * Display splash message showing progress of playlist/directory insertion or
1658 * save.
1660 static void display_playlist_count(int count, const unsigned char *fmt)
1662 lcd_clear_display();
1664 #ifdef HAVE_LCD_BITMAP
1665 if(global_settings.statusbar)
1666 lcd_setmargins(0, STATUSBAR_HEIGHT);
1667 else
1668 lcd_setmargins(0, 0);
1669 #endif
1671 gui_syncsplash(0, fmt, count,
1672 #if CONFIG_KEYPAD == PLAYER_PAD
1673 str(LANG_STOP_ABORT)
1674 #else
1675 str(LANG_OFF_ABORT)
1676 #endif
1681 * Display buffer full message
1683 static void display_buffer_full(void)
1685 gui_syncsplash(HZ*2, str(LANG_PLAYLIST_BUFFER_FULL));
1689 * Flush any cached control commands to disk. Called when playlist is being
1690 * modified. Returns 0 on success and -1 on failure.
1692 static int flush_cached_control(struct playlist_info* playlist)
1694 int result = 0;
1695 int i;
1697 if (!playlist->num_cached)
1698 return 0;
1700 lseek(playlist->control_fd, 0, SEEK_END);
1702 for (i=0; i<playlist->num_cached; i++)
1704 struct playlist_control_cache* cache =
1705 &(playlist->control_cache[i]);
1707 switch (cache->command)
1709 case PLAYLIST_COMMAND_PLAYLIST:
1710 result = fdprintf(playlist->control_fd, "P:%d:%s:%s\n",
1711 cache->i1, cache->s1, cache->s2);
1712 break;
1713 case PLAYLIST_COMMAND_ADD:
1714 case PLAYLIST_COMMAND_QUEUE:
1715 result = fdprintf(playlist->control_fd, "%c:%d:%d:",
1716 (cache->command == PLAYLIST_COMMAND_ADD)?'A':'Q',
1717 cache->i1, cache->i2);
1718 if (result > 0)
1720 /* save the position in file where name is written */
1721 int* seek_pos = (int *)cache->data;
1722 *seek_pos = lseek(playlist->control_fd, 0, SEEK_CUR);
1723 result = fdprintf(playlist->control_fd, "%s\n",
1724 cache->s1);
1726 break;
1727 case PLAYLIST_COMMAND_DELETE:
1728 result = fdprintf(playlist->control_fd, "D:%d\n", cache->i1);
1729 break;
1730 case PLAYLIST_COMMAND_SHUFFLE:
1731 result = fdprintf(playlist->control_fd, "S:%d:%d\n",
1732 cache->i1, cache->i2);
1733 break;
1734 case PLAYLIST_COMMAND_UNSHUFFLE:
1735 result = fdprintf(playlist->control_fd, "U:%d\n", cache->i1);
1736 break;
1737 case PLAYLIST_COMMAND_RESET:
1738 result = fdprintf(playlist->control_fd, "R\n");
1739 break;
1740 default:
1741 break;
1744 if (result <= 0)
1745 break;
1748 if (result > 0)
1750 if (global_status.resume_seed >= 0)
1752 global_status.resume_seed = -1;
1753 status_save();
1756 playlist->num_cached = 0;
1757 playlist->pending_control_sync = true;
1759 result = 0;
1761 else
1763 result = -1;
1764 gui_syncsplash(HZ*2, str(LANG_PLAYLIST_CONTROL_UPDATE_ERROR));
1767 return result;
1771 * Update control data with new command. Depending on the command, it may be
1772 * cached or flushed to disk.
1774 static int update_control(struct playlist_info* playlist,
1775 enum playlist_command command, int i1, int i2,
1776 const char* s1, const char* s2, void* data)
1778 int result = 0;
1779 struct playlist_control_cache* cache;
1780 bool flush = false;
1782 mutex_lock(&playlist->control_mutex);
1784 cache = &(playlist->control_cache[playlist->num_cached++]);
1786 cache->command = command;
1787 cache->i1 = i1;
1788 cache->i2 = i2;
1789 cache->s1 = s1;
1790 cache->s2 = s2;
1791 cache->data = data;
1793 switch (command)
1795 case PLAYLIST_COMMAND_PLAYLIST:
1796 case PLAYLIST_COMMAND_ADD:
1797 case PLAYLIST_COMMAND_QUEUE:
1798 #ifndef HAVE_DIRCACHE
1799 case PLAYLIST_COMMAND_DELETE:
1800 case PLAYLIST_COMMAND_RESET:
1801 #endif
1802 flush = true;
1803 break;
1804 case PLAYLIST_COMMAND_SHUFFLE:
1805 case PLAYLIST_COMMAND_UNSHUFFLE:
1806 default:
1807 /* only flush when needed */
1808 break;
1811 if (flush || playlist->num_cached == PLAYLIST_MAX_CACHE)
1812 result = flush_cached_control(playlist);
1814 mutex_unlock(&playlist->control_mutex);
1816 return result;
1820 * sync control file to disk
1822 static void sync_control(struct playlist_info* playlist, bool force)
1824 #ifdef HAVE_DIRCACHE
1825 if (playlist->started && force)
1826 #else
1827 (void) force;
1829 if (playlist->started)
1830 #endif
1832 if (playlist->pending_control_sync)
1834 mutex_lock(&playlist->control_mutex);
1835 fsync(playlist->control_fd);
1836 playlist->pending_control_sync = false;
1837 mutex_unlock(&playlist->control_mutex);
1843 * Rotate indices such that first_index is index 0
1845 static int rotate_index(const struct playlist_info* playlist, int index)
1847 index -= playlist->first_index;
1848 if (index < 0)
1849 index += playlist->amount;
1851 return index;
1855 * Initialize playlist entries at startup
1857 void playlist_init(void)
1859 struct playlist_info* playlist = &current_playlist;
1861 playlist->current = true;
1862 snprintf(playlist->control_filename, sizeof(playlist->control_filename),
1863 "%s", PLAYLIST_CONTROL_FILE);
1864 playlist->fd = -1;
1865 playlist->control_fd = -1;
1866 playlist->max_playlist_size = global_settings.max_files_in_playlist;
1867 playlist->indices = buffer_alloc(
1868 playlist->max_playlist_size * sizeof(int));
1869 playlist->buffer_size =
1870 AVERAGE_FILENAME_LENGTH * global_settings.max_files_in_dir;
1871 playlist->buffer = buffer_alloc(playlist->buffer_size);
1872 mutex_init(&playlist->control_mutex);
1873 empty_playlist(playlist, true);
1875 #ifdef HAVE_DIRCACHE
1876 playlist->filenames = buffer_alloc(
1877 playlist->max_playlist_size * sizeof(int));
1878 memset(playlist->filenames, 0,
1879 playlist->max_playlist_size * sizeof(int));
1880 create_thread(playlist_thread, playlist_stack, sizeof(playlist_stack),
1881 playlist_thread_name IF_PRIO(, PRIORITY_BACKGROUND)
1882 IF_COP(, CPU, false));
1883 queue_init(&playlist_queue, true);
1884 #endif
1888 * Clean playlist at shutdown
1890 void playlist_shutdown(void)
1892 struct playlist_info* playlist = &current_playlist;
1894 if (playlist->control_fd >= 0)
1896 mutex_lock(&playlist->control_mutex);
1898 if (playlist->num_cached > 0)
1899 flush_cached_control(playlist);
1901 close(playlist->control_fd);
1903 mutex_unlock(&playlist->control_mutex);
1908 * Create new playlist
1910 int playlist_create(const char *dir, const char *file)
1912 struct playlist_info* playlist = &current_playlist;
1914 new_playlist(playlist, dir, file);
1916 if (file)
1917 /* load the playlist file */
1918 add_indices_to_playlist(playlist, NULL, 0);
1920 return 0;
1923 #define PLAYLIST_COMMAND_SIZE (MAX_PATH+12)
1926 * Restore the playlist state based on control file commands. Called to
1927 * resume playback after shutdown.
1929 int playlist_resume(void)
1931 struct playlist_info* playlist = &current_playlist;
1932 char *buffer;
1933 size_t buflen;
1934 int nread;
1935 int total_read = 0;
1936 int control_file_size = 0;
1937 bool first = true;
1938 bool sorted = true;
1940 /* use mp3 buffer for maximum load speed */
1941 #if CONFIG_CODEC != SWCODEC
1942 talk_buffer_steal(); /* we use the mp3 buffer, need to tell */
1943 buflen = (audiobufend - audiobuf);
1944 buffer = (char *)audiobuf;
1945 #else
1946 buffer = (char *)audio_get_buffer(false, &buflen);
1947 #endif
1949 empty_playlist(playlist, true);
1951 gui_syncsplash(0, str(LANG_WAIT));
1952 playlist->control_fd = open(playlist->control_filename, O_RDWR);
1953 if (playlist->control_fd < 0)
1955 gui_syncsplash(HZ*2, str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
1956 return -1;
1958 playlist->control_created = true;
1960 control_file_size = filesize(playlist->control_fd);
1961 if (control_file_size <= 0)
1963 gui_syncsplash(HZ*2, str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
1964 return -1;
1967 /* read a small amount first to get the header */
1968 nread = read(playlist->control_fd, buffer,
1969 PLAYLIST_COMMAND_SIZE<buflen?PLAYLIST_COMMAND_SIZE:buflen);
1970 if(nread <= 0)
1972 gui_syncsplash(HZ*2, str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
1973 return -1;
1976 playlist->started = true;
1978 while (1)
1980 int result = 0;
1981 int count;
1982 enum playlist_command current_command = PLAYLIST_COMMAND_COMMENT;
1983 int last_newline = 0;
1984 int str_count = -1;
1985 bool newline = true;
1986 bool exit_loop = false;
1987 char *p = buffer;
1988 char *str1 = NULL;
1989 char *str2 = NULL;
1990 char *str3 = NULL;
1991 unsigned long last_tick = current_tick;
1993 for(count=0; count<nread && !exit_loop; count++,p++)
1995 /* So a splash while we are loading. */
1996 if (current_tick - last_tick > HZ/4)
1998 gui_syncsplash(0, str(LANG_LOADING_PERCENT),
1999 (total_read+count)*100/control_file_size,
2000 #if CONFIG_KEYPAD == PLAYER_PAD
2001 str(LANG_STOP_ABORT)
2002 #else
2003 str(LANG_OFF_ABORT)
2004 #endif
2006 if (action_userabort(TIMEOUT_NOBLOCK))
2008 /* FIXME:
2009 * Not sure how to implement this, somebody more familiar
2010 * with the code, please fix this. */
2012 last_tick = current_tick;
2015 /* Are we on a new line? */
2016 if((*p == '\n') || (*p == '\r'))
2018 *p = '\0';
2020 /* save last_newline in case we need to load more data */
2021 last_newline = count;
2023 switch (current_command)
2025 case PLAYLIST_COMMAND_PLAYLIST:
2027 /* str1=version str2=dir str3=file */
2028 int version;
2030 if (!str1)
2032 result = -1;
2033 exit_loop = true;
2034 break;
2037 if (!str2)
2038 str2 = "";
2040 if (!str3)
2041 str3 = "";
2043 version = atoi(str1);
2045 if (version != PLAYLIST_CONTROL_FILE_VERSION)
2046 return -1;
2048 update_playlist_filename(playlist, str2, str3);
2050 if (str3[0] != '\0')
2052 /* NOTE: add_indices_to_playlist() overwrites the
2053 audiobuf so we need to reload control file
2054 data */
2055 add_indices_to_playlist(playlist, NULL, 0);
2057 else if (str2[0] != '\0')
2059 playlist->in_ram = true;
2060 resume_directory(str2);
2063 /* load the rest of the data */
2064 first = false;
2065 exit_loop = true;
2067 break;
2069 case PLAYLIST_COMMAND_ADD:
2070 case PLAYLIST_COMMAND_QUEUE:
2072 /* str1=position str2=last_position str3=file */
2073 int position, last_position;
2074 bool queue;
2076 if (!str1 || !str2 || !str3)
2078 result = -1;
2079 exit_loop = true;
2080 break;
2083 position = atoi(str1);
2084 last_position = atoi(str2);
2086 queue = (current_command == PLAYLIST_COMMAND_ADD)?
2087 false:true;
2089 /* seek position is based on str3's position in
2090 buffer */
2091 if (add_track_to_playlist(playlist, str3, position,
2092 queue, total_read+(str3-buffer)) < 0)
2093 return -1;
2095 playlist->last_insert_pos = last_position;
2097 break;
2099 case PLAYLIST_COMMAND_DELETE:
2101 /* str1=position */
2102 int position;
2104 if (!str1)
2106 result = -1;
2107 exit_loop = true;
2108 break;
2111 position = atoi(str1);
2113 if (remove_track_from_playlist(playlist, position,
2114 false) < 0)
2115 return -1;
2117 break;
2119 case PLAYLIST_COMMAND_SHUFFLE:
2121 /* str1=seed str2=first_index */
2122 int seed;
2124 if (!str1 || !str2)
2126 result = -1;
2127 exit_loop = true;
2128 break;
2131 if (!sorted)
2133 /* Always sort list before shuffling */
2134 sort_playlist(playlist, false, false);
2137 seed = atoi(str1);
2138 playlist->first_index = atoi(str2);
2140 if (randomise_playlist(playlist, seed, false,
2141 false) < 0)
2142 return -1;
2144 sorted = false;
2145 break;
2147 case PLAYLIST_COMMAND_UNSHUFFLE:
2149 /* str1=first_index */
2150 if (!str1)
2152 result = -1;
2153 exit_loop = true;
2154 break;
2157 playlist->first_index = atoi(str1);
2159 if (sort_playlist(playlist, false, false) < 0)
2160 return -1;
2162 sorted = true;
2163 break;
2165 case PLAYLIST_COMMAND_RESET:
2167 playlist->last_insert_pos = -1;
2168 break;
2170 case PLAYLIST_COMMAND_COMMENT:
2171 default:
2172 break;
2175 newline = true;
2177 /* to ignore any extra newlines */
2178 current_command = PLAYLIST_COMMAND_COMMENT;
2180 else if(newline)
2182 newline = false;
2184 /* first non-comment line must always specify playlist */
2185 if (first && *p != 'P' && *p != '#')
2187 result = -1;
2188 exit_loop = true;
2189 break;
2192 switch (*p)
2194 case 'P':
2195 /* playlist can only be specified once */
2196 if (!first)
2198 result = -1;
2199 exit_loop = true;
2200 break;
2203 current_command = PLAYLIST_COMMAND_PLAYLIST;
2204 break;
2205 case 'A':
2206 current_command = PLAYLIST_COMMAND_ADD;
2207 break;
2208 case 'Q':
2209 current_command = PLAYLIST_COMMAND_QUEUE;
2210 break;
2211 case 'D':
2212 current_command = PLAYLIST_COMMAND_DELETE;
2213 break;
2214 case 'S':
2215 current_command = PLAYLIST_COMMAND_SHUFFLE;
2216 break;
2217 case 'U':
2218 current_command = PLAYLIST_COMMAND_UNSHUFFLE;
2219 break;
2220 case 'R':
2221 current_command = PLAYLIST_COMMAND_RESET;
2222 break;
2223 case '#':
2224 current_command = PLAYLIST_COMMAND_COMMENT;
2225 break;
2226 default:
2227 result = -1;
2228 exit_loop = true;
2229 break;
2232 str_count = -1;
2233 str1 = NULL;
2234 str2 = NULL;
2235 str3 = NULL;
2237 else if(current_command != PLAYLIST_COMMAND_COMMENT)
2239 /* all control file strings are separated with a colon.
2240 Replace the colon with 0 to get proper strings that can be
2241 used by commands above */
2242 if (*p == ':')
2244 *p = '\0';
2245 str_count++;
2247 if ((count+1) < nread)
2249 switch (str_count)
2251 case 0:
2252 str1 = p+1;
2253 break;
2254 case 1:
2255 str2 = p+1;
2256 break;
2257 case 2:
2258 str3 = p+1;
2259 break;
2260 default:
2261 /* allow last string to contain colons */
2262 *p = ':';
2263 break;
2270 if (result < 0)
2272 gui_syncsplash(HZ*2, str(LANG_PLAYLIST_CONTROL_INVALID));
2273 return result;
2276 if (!newline || (exit_loop && count<nread))
2278 if ((total_read + count) >= control_file_size)
2280 /* no newline at end of control file */
2281 gui_syncsplash(HZ*2, str(LANG_PLAYLIST_CONTROL_INVALID));
2282 return -1;
2285 /* We didn't end on a newline or we exited loop prematurely.
2286 Either way, re-read the remainder. */
2287 count = last_newline;
2288 lseek(playlist->control_fd, total_read+count, SEEK_SET);
2291 total_read += count;
2293 if (first)
2294 /* still looking for header */
2295 nread = read(playlist->control_fd, buffer,
2296 PLAYLIST_COMMAND_SIZE<buflen?PLAYLIST_COMMAND_SIZE:buflen);
2297 else
2298 nread = read(playlist->control_fd, buffer, buflen);
2300 /* Terminate on EOF */
2301 if(nread <= 0)
2303 if (global_status.resume_seed >= 0)
2305 /* Apply shuffle command saved in settings */
2306 if (global_status.resume_seed == 0)
2307 sort_playlist(playlist, false, true);
2308 else
2310 if (!sorted)
2311 sort_playlist(playlist, false, false);
2313 randomise_playlist(playlist, global_status.resume_seed,
2314 false, true);
2318 playlist->first_index = global_status.resume_first_index;
2319 break;
2323 #ifdef HAVE_DIRCACHE
2324 queue_post(&playlist_queue, PLAYLIST_LOAD_POINTERS, 0);
2325 #endif
2327 return 0;
2331 * Add track to in_ram playlist. Used when playing directories.
2333 int playlist_add(const char *filename)
2335 struct playlist_info* playlist = &current_playlist;
2336 int len = strlen(filename);
2338 if((len+1 > playlist->buffer_size - playlist->buffer_end_pos) ||
2339 (playlist->amount >= playlist->max_playlist_size))
2341 display_buffer_full();
2342 return -1;
2345 playlist->indices[playlist->amount] = playlist->buffer_end_pos;
2346 #ifdef HAVE_DIRCACHE
2347 playlist->filenames[playlist->amount] = NULL;
2348 #endif
2349 playlist->amount++;
2351 strcpy(&playlist->buffer[playlist->buffer_end_pos], filename);
2352 playlist->buffer_end_pos += len;
2353 playlist->buffer[playlist->buffer_end_pos++] = '\0';
2355 return 0;
2358 /* shuffle newly created playlist using random seed. */
2359 int playlist_shuffle(int random_seed, int start_index)
2361 struct playlist_info* playlist = &current_playlist;
2363 unsigned int seek_pos = 0;
2364 bool start_current = false;
2366 if (start_index >= 0 && global_settings.play_selected)
2368 /* store the seek position before the shuffle */
2369 seek_pos = playlist->indices[start_index];
2370 playlist->index = global_status.resume_first_index =
2371 playlist->first_index = start_index;
2372 start_current = true;
2375 gui_syncsplash(0, str(LANG_PLAYLIST_SHUFFLE));
2377 randomise_playlist(playlist, random_seed, start_current, true);
2379 return playlist->index;
2382 /* start playing current playlist at specified index/offset */
2383 int playlist_start(int start_index, int offset)
2385 struct playlist_info* playlist = &current_playlist;
2387 playlist->index = start_index;
2389 #if CONFIG_CODEC != SWCODEC
2390 talk_buffer_steal(); /* will use the mp3 buffer */
2391 #endif
2393 playlist->started = true;
2394 sync_control(playlist, false);
2395 audio_play(offset);
2397 return 0;
2400 /* Returns false if 'steps' is out of bounds, else true */
2401 bool playlist_check(int steps)
2403 struct playlist_info* playlist = &current_playlist;
2405 /* always allow folder navigation */
2406 if (global_settings.next_folder && playlist->in_ram)
2407 return true;
2409 int index = get_next_index(playlist, steps, -1);
2411 if (index < 0 && steps >= 0 && global_settings.repeat_mode == REPEAT_SHUFFLE)
2412 index = get_next_index(playlist, steps, REPEAT_ALL);
2414 return (index >= 0);
2417 /* get trackname of track that is "steps" away from current playing track.
2418 NULL is used to identify end of playlist */
2419 char* playlist_peek(int steps)
2421 struct playlist_info* playlist = &current_playlist;
2422 int seek;
2423 int fd;
2424 char *temp_ptr;
2425 int index;
2426 bool control_file;
2428 index = get_next_index(playlist, steps, -1);
2429 if (index < 0)
2430 return NULL;
2432 control_file = playlist->indices[index] & PLAYLIST_INSERT_TYPE_MASK;
2433 seek = playlist->indices[index] & PLAYLIST_SEEK_MASK;
2435 if (get_filename(playlist, index, seek, control_file, now_playing,
2436 MAX_PATH+1) < 0)
2437 return NULL;
2439 temp_ptr = now_playing;
2441 if (!playlist->in_ram || control_file)
2443 /* remove bogus dirs from beginning of path
2444 (workaround for buggy playlist creation tools) */
2445 while (temp_ptr)
2447 #ifdef HAVE_DIRCACHE
2448 if (dircache_is_enabled())
2450 if (dircache_get_entry_ptr(temp_ptr))
2451 break;
2453 else
2454 #endif
2456 fd = open(temp_ptr, O_RDONLY);
2457 if (fd >= 0)
2459 close(fd);
2460 break;
2464 temp_ptr = strchr(temp_ptr+1, '/');
2467 if (!temp_ptr)
2469 /* Even though this is an invalid file, we still need to pass a
2470 file name to the caller because NULL is used to indicate end
2471 of playlist */
2472 return now_playing;
2476 return temp_ptr;
2480 * Update indices as track has changed
2482 int playlist_next(int steps)
2484 struct playlist_info* playlist = &current_playlist;
2485 int index;
2487 if ( (steps > 0)
2488 #ifdef AB_REPEAT_ENABLE
2489 && (global_settings.repeat_mode != REPEAT_AB)
2490 #endif
2491 && (global_settings.repeat_mode != REPEAT_ONE) )
2493 int i, j;
2495 /* We need to delete all the queued songs */
2496 for (i=0, j=steps; i<j; i++)
2498 index = get_next_index(playlist, i, -1);
2500 if (playlist->indices[index] & PLAYLIST_QUEUE_MASK)
2502 remove_track_from_playlist(playlist, index, true);
2503 steps--; /* one less track */
2508 index = get_next_index(playlist, steps, -1);
2510 if (index < 0)
2512 /* end of playlist... or is it */
2513 if (global_settings.repeat_mode == REPEAT_SHUFFLE &&
2514 playlist->amount > 1)
2516 /* Repeat shuffle mode. Re-shuffle playlist and resume play */
2517 playlist->first_index = global_status.resume_first_index = 0;
2518 sort_playlist(playlist, false, false);
2519 randomise_playlist(playlist, current_tick, false, true);
2520 #if CONFIG_CODEC != SWCODEC
2521 playlist_start(0, 0);
2522 #endif
2523 playlist->index = 0;
2524 index = 0;
2526 else if (playlist->in_ram && global_settings.next_folder)
2528 char dir[MAX_PATH+1];
2530 changing_dir = true;
2531 if (steps > 0)
2533 if (!get_next_directory(dir))
2535 /* start playing next directory */
2536 if (playlist_create(dir, NULL) != -1)
2538 ft_build_playlist(tree_get_context(), 0);
2539 if (global_settings.playlist_shuffle)
2540 playlist_shuffle(current_tick, -1);
2541 #if CONFIG_CODEC != SWCODEC
2542 playlist_start(0, 0);
2543 #endif
2544 playlist->index = index = 0;
2548 else
2550 if (!get_previous_directory(dir))
2552 /* start playing previous directory */
2553 if (playlist_create(dir, NULL) != -1)
2555 ft_build_playlist(tree_get_context(), 0);
2556 if (global_settings.playlist_shuffle)
2557 playlist_shuffle(current_tick, -1);
2558 #if CONFIG_CODEC != SWCODEC
2559 playlist_start(current_playlist.amount-1, 0);
2560 #endif
2561 playlist->index = index = current_playlist.amount - 1;
2565 changing_dir = false;
2568 return index;
2571 playlist->index = index;
2573 if (playlist->last_insert_pos >= 0 && steps > 0)
2575 /* check to see if we've gone beyond the last inserted track */
2576 int cur = rotate_index(playlist, index);
2577 int last_pos = rotate_index(playlist, playlist->last_insert_pos);
2579 if (cur > last_pos)
2581 /* reset last inserted track */
2582 playlist->last_insert_pos = -1;
2584 if (playlist->control_fd >= 0)
2586 int result = update_control(playlist, PLAYLIST_COMMAND_RESET,
2587 -1, -1, NULL, NULL, NULL);
2589 if (result < 0)
2590 return result;
2592 sync_control(playlist, false);
2597 return index;
2600 /* try playing next or previous folder */
2601 bool playlist_next_dir(int direction)
2603 char dir[MAX_PATH+1];
2604 bool result;
2605 int res;
2607 /* not to mess up real playlists */
2608 if(!current_playlist.in_ram)
2609 return false;
2611 if(changing_dir)
2612 return false;
2614 changing_dir = true;
2615 if(direction > 0)
2616 res = get_next_directory(dir);
2617 else
2618 res = get_previous_directory(dir);
2619 if (!res)
2621 if (playlist_create(dir, NULL) != -1)
2623 ft_build_playlist(tree_get_context(), 0);
2624 if (global_settings.playlist_shuffle)
2625 playlist_shuffle(current_tick, -1);
2626 #if (CONFIG_CODEC != SWCODEC)
2627 playlist_start(0,0);
2628 #endif
2629 result = true;
2631 else
2632 result = false;
2634 else
2635 result = false;
2637 changing_dir = false;
2639 return result;
2642 /* Get resume info for current playing song. If return value is -1 then
2643 settings shouldn't be saved. */
2644 int playlist_get_resume_info(int *resume_index)
2646 struct playlist_info* playlist = &current_playlist;
2648 *resume_index = playlist->index;
2650 return 0;
2653 /* Update resume info for current playing song. Returns -1 on error. */
2654 int playlist_update_resume_info(const struct mp3entry* id3)
2656 struct playlist_info* playlist = &current_playlist;
2658 if (id3)
2660 if (global_status.resume_index != playlist->index ||
2661 global_status.resume_offset != id3->offset)
2663 global_status.resume_index = playlist->index;
2664 global_status.resume_offset = id3->offset;
2665 status_save();
2668 else
2670 global_status.resume_index = -1;
2671 global_status.resume_offset = -1;
2672 status_save();
2675 return 0;
2678 /* Returns index of current playing track for display purposes. This value
2679 should not be used for resume purposes as it doesn't represent the actual
2680 index into the playlist */
2681 int playlist_get_display_index(void)
2683 struct playlist_info* playlist = &current_playlist;
2685 /* first_index should always be index 0 for display purposes */
2686 int index = rotate_index(playlist, playlist->index);
2688 return (index+1);
2691 /* returns number of tracks in current playlist */
2692 int playlist_amount(void)
2694 return playlist_amount_ex(NULL);
2698 * Create a new playlist If playlist is not NULL then we're loading a
2699 * playlist off disk for viewing/editing. The index_buffer is used to store
2700 * playlist indices (required for and only used if !current playlist). The
2701 * temp_buffer (if not NULL) is used as a scratchpad when loading indices.
2703 int playlist_create_ex(struct playlist_info* playlist,
2704 const char* dir, const char* file,
2705 void* index_buffer, int index_buffer_size,
2706 void* temp_buffer, int temp_buffer_size)
2708 if (!playlist)
2709 playlist = &current_playlist;
2710 else
2712 /* Initialize playlist structure */
2713 int r = rand() % 10;
2714 playlist->current = false;
2716 /* Use random name for control file */
2717 snprintf(playlist->control_filename, sizeof(playlist->control_filename),
2718 "%s.%d", PLAYLIST_CONTROL_FILE, r);
2719 playlist->fd = -1;
2720 playlist->control_fd = -1;
2722 if (index_buffer)
2724 int num_indices = index_buffer_size / sizeof(int);
2726 #ifdef HAVE_DIRCACHE
2727 num_indices /= 2;
2728 #endif
2729 if (num_indices > global_settings.max_files_in_playlist)
2730 num_indices = global_settings.max_files_in_playlist;
2732 playlist->max_playlist_size = num_indices;
2733 playlist->indices = index_buffer;
2734 #ifdef HAVE_DIRCACHE
2735 playlist->filenames = (const struct dircache_entry **)
2736 &playlist->indices[num_indices];
2737 #endif
2739 else
2741 playlist->max_playlist_size = current_playlist.max_playlist_size;
2742 playlist->indices = current_playlist.indices;
2743 #ifdef HAVE_DIRCACHE
2744 playlist->filenames = current_playlist.filenames;
2745 #endif
2748 playlist->buffer_size = 0;
2749 playlist->buffer = NULL;
2750 mutex_init(&playlist->control_mutex);
2753 new_playlist(playlist, dir, file);
2755 if (file)
2756 /* load the playlist file */
2757 add_indices_to_playlist(playlist, temp_buffer, temp_buffer_size);
2759 return 0;
2763 * Set the specified playlist as the current.
2764 * NOTE: You will get undefined behaviour if something is already playing so
2765 * remember to stop before calling this. Also, this call will
2766 * effectively close your playlist, making it unusable.
2768 int playlist_set_current(struct playlist_info* playlist)
2770 if (!playlist || (check_control(playlist) < 0))
2771 return -1;
2773 empty_playlist(&current_playlist, false);
2775 strncpy(current_playlist.filename, playlist->filename,
2776 sizeof(current_playlist.filename));
2778 current_playlist.utf8 = playlist->utf8;
2779 current_playlist.fd = playlist->fd;
2781 close(playlist->control_fd);
2782 close(current_playlist.control_fd);
2783 remove(current_playlist.control_filename);
2784 if (rename(playlist->control_filename,
2785 current_playlist.control_filename) < 0)
2786 return -1;
2787 current_playlist.control_fd = open(current_playlist.control_filename,
2788 O_RDWR);
2789 if (current_playlist.control_fd < 0)
2790 return -1;
2791 current_playlist.control_created = true;
2793 current_playlist.dirlen = playlist->dirlen;
2795 if (playlist->indices && playlist->indices != current_playlist.indices)
2797 memcpy(current_playlist.indices, playlist->indices,
2798 playlist->max_playlist_size*sizeof(int));
2799 #ifdef HAVE_DIRCACHE
2800 memcpy(current_playlist.filenames, playlist->filenames,
2801 playlist->max_playlist_size*sizeof(int));
2802 #endif
2805 current_playlist.first_index = playlist->first_index;
2806 current_playlist.amount = playlist->amount;
2807 current_playlist.last_insert_pos = playlist->last_insert_pos;
2808 current_playlist.seed = playlist->seed;
2809 current_playlist.shuffle_modified = playlist->shuffle_modified;
2810 current_playlist.deleted = playlist->deleted;
2811 current_playlist.num_inserted_tracks = playlist->num_inserted_tracks;
2813 memcpy(current_playlist.control_cache, playlist->control_cache,
2814 sizeof(current_playlist.control_cache));
2815 current_playlist.num_cached = playlist->num_cached;
2816 current_playlist.pending_control_sync = playlist->pending_control_sync;
2818 return 0;
2822 * Close files and delete control file for non-current playlist.
2824 void playlist_close(struct playlist_info* playlist)
2826 if (!playlist)
2827 return;
2829 if (playlist->fd >= 0)
2830 close(playlist->fd);
2832 if (playlist->control_fd >= 0)
2833 close(playlist->control_fd);
2835 if (playlist->control_created)
2836 remove(playlist->control_filename);
2839 void playlist_sync(struct playlist_info* playlist)
2841 if (!playlist)
2842 playlist = &current_playlist;
2844 sync_control(playlist, false);
2845 if ((audio_status() & AUDIO_STATUS_PLAY) && playlist->started)
2846 audio_flush_and_reload_tracks();
2848 #ifdef HAVE_DIRCACHE
2849 queue_post(&playlist_queue, PLAYLIST_LOAD_POINTERS, 0);
2850 #endif
2854 * Insert track into playlist at specified position (or one of the special
2855 * positions). Returns position where track was inserted or -1 if error.
2857 int playlist_insert_track(struct playlist_info* playlist, const char *filename,
2858 int position, bool queue, bool sync)
2860 int result;
2862 if (!playlist)
2863 playlist = &current_playlist;
2865 if (check_control(playlist) < 0)
2867 gui_syncsplash(HZ*2, str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
2868 return -1;
2871 result = add_track_to_playlist(playlist, filename, position, queue, -1);
2873 /* Check if we want manually sync later. For example when adding
2874 * bunch of files from tagcache, syncing after every file wouldn't be
2875 * a good thing to do. */
2876 if (sync && result >= 0)
2877 playlist_sync(playlist);
2879 return result;
2883 * Insert all tracks from specified directory into playlist.
2885 int playlist_insert_directory(struct playlist_info* playlist,
2886 const char *dirname, int position, bool queue,
2887 bool recurse)
2889 int result;
2890 unsigned char *count_str;
2891 struct directory_search_context context;
2893 if (!playlist)
2894 playlist = &current_playlist;
2896 if (check_control(playlist) < 0)
2898 gui_syncsplash(HZ*2, str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
2899 return -1;
2902 if (position == PLAYLIST_REPLACE)
2904 if (remove_all_tracks(playlist) == 0)
2905 position = PLAYLIST_INSERT_LAST;
2906 else
2907 return -1;
2910 if (queue)
2911 count_str = str(LANG_PLAYLIST_QUEUE_COUNT);
2912 else
2913 count_str = str(LANG_PLAYLIST_INSERT_COUNT);
2915 display_playlist_count(0, count_str);
2917 context.playlist = playlist;
2918 context.position = position;
2919 context.queue = queue;
2920 context.count = 0;
2922 cpu_boost(true);
2924 result = playlist_directory_tracksearch(dirname, recurse,
2925 directory_search_callback, &context);
2927 sync_control(playlist, false);
2929 cpu_boost(false);
2931 display_playlist_count(context.count, count_str);
2933 if ((audio_status() & AUDIO_STATUS_PLAY) && playlist->started)
2934 audio_flush_and_reload_tracks();
2936 #ifdef HAVE_DIRCACHE
2937 queue_post(&playlist_queue, PLAYLIST_LOAD_POINTERS, 0);
2938 #endif
2940 return result;
2944 * Insert all tracks from specified playlist into dynamic playlist.
2946 int playlist_insert_playlist(struct playlist_info* playlist, char *filename,
2947 int position, bool queue)
2949 int fd;
2950 int max;
2951 char *temp_ptr;
2952 char *dir;
2953 unsigned char *count_str;
2954 char temp_buf[MAX_PATH+1];
2955 char trackname[MAX_PATH+1];
2956 int count = 0;
2957 int result = 0;
2959 if (!playlist)
2960 playlist = &current_playlist;
2962 if (check_control(playlist) < 0)
2964 gui_syncsplash(HZ*2, str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
2965 return -1;
2968 fd = open(filename, O_RDONLY);
2969 if (fd < 0)
2971 gui_syncsplash(HZ*2, str(LANG_PLAYLIST_ACCESS_ERROR));
2972 return -1;
2975 /* we need the directory name for formatting purposes */
2976 dir = filename;
2978 temp_ptr = strrchr(filename+1,'/');
2979 if (temp_ptr)
2980 *temp_ptr = 0;
2981 else
2982 dir = "/";
2984 if (queue)
2985 count_str = str(LANG_PLAYLIST_QUEUE_COUNT);
2986 else
2987 count_str = str(LANG_PLAYLIST_INSERT_COUNT);
2989 display_playlist_count(count, count_str);
2991 if (position == PLAYLIST_REPLACE)
2993 if (remove_all_tracks(playlist) == 0)
2994 position = PLAYLIST_INSERT_LAST;
2995 else return -1;
2998 cpu_boost(true);
3000 while ((max = read_line(fd, temp_buf, sizeof(temp_buf))) > 0)
3002 /* user abort */
3003 if (action_userabort(TIMEOUT_NOBLOCK))
3004 break;
3006 if (temp_buf[0] != '#' && temp_buf[0] != '\0')
3008 int insert_pos;
3010 /* we need to format so that relative paths are correctly
3011 handled */
3012 if (format_track_path(trackname, temp_buf, sizeof(trackname), max,
3013 dir) < 0)
3015 result = -1;
3016 break;
3019 insert_pos = add_track_to_playlist(playlist, trackname, position,
3020 queue, -1);
3022 if (insert_pos < 0)
3024 result = -1;
3025 break;
3028 /* Make sure tracks are inserted in correct order if user
3029 requests INSERT_FIRST */
3030 if (position == PLAYLIST_INSERT_FIRST || position >= 0)
3031 position = insert_pos + 1;
3033 count++;
3035 if ((count%PLAYLIST_DISPLAY_COUNT) == 0)
3037 display_playlist_count(count, count_str);
3039 if (count == PLAYLIST_DISPLAY_COUNT &&
3040 (audio_status() & AUDIO_STATUS_PLAY) &&
3041 playlist->started)
3042 audio_flush_and_reload_tracks();
3046 /* let the other threads work */
3047 yield();
3050 close(fd);
3052 if (temp_ptr)
3053 *temp_ptr = '/';
3055 sync_control(playlist, false);
3057 cpu_boost(false);
3059 display_playlist_count(count, count_str);
3061 if ((audio_status() & AUDIO_STATUS_PLAY) && playlist->started)
3062 audio_flush_and_reload_tracks();
3064 #ifdef HAVE_DIRCACHE
3065 queue_post(&playlist_queue, PLAYLIST_LOAD_POINTERS, 0);
3066 #endif
3068 return result;
3072 * Delete track at specified index. If index is PLAYLIST_DELETE_CURRENT then
3073 * we want to delete the current playing track.
3075 int playlist_delete(struct playlist_info* playlist, int index)
3077 int result = 0;
3079 if (!playlist)
3080 playlist = &current_playlist;
3082 if (check_control(playlist) < 0)
3084 gui_syncsplash(HZ*2, str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
3085 return -1;
3088 if (index == PLAYLIST_DELETE_CURRENT)
3089 index = playlist->index;
3091 result = remove_track_from_playlist(playlist, index, true);
3093 if (result != -1 && (audio_status() & AUDIO_STATUS_PLAY) &&
3094 playlist->started)
3095 audio_flush_and_reload_tracks();
3097 return result;
3101 * Move track at index to new_index. Tracks between the two are shifted
3102 * appropriately. Returns 0 on success and -1 on failure.
3104 int playlist_move(struct playlist_info* playlist, int index, int new_index)
3106 int result;
3107 int seek;
3108 bool control_file;
3109 bool queue;
3110 bool current = false;
3111 int r;
3112 char filename[MAX_PATH];
3114 if (!playlist)
3115 playlist = &current_playlist;
3117 if (check_control(playlist) < 0)
3119 gui_syncsplash(HZ*2, str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
3120 return -1;
3123 if (index == new_index)
3124 return -1;
3126 if (index == playlist->index)
3127 /* Moving the current track */
3128 current = true;
3130 control_file = playlist->indices[index] & PLAYLIST_INSERT_TYPE_MASK;
3131 queue = playlist->indices[index] & PLAYLIST_QUEUE_MASK;
3132 seek = playlist->indices[index] & PLAYLIST_SEEK_MASK;
3134 if (get_filename(playlist, index, seek, control_file, filename,
3135 sizeof(filename)) < 0)
3136 return -1;
3138 /* Delete track from original position */
3139 result = remove_track_from_playlist(playlist, index, true);
3141 if (result != -1)
3143 /* We want to insert the track at the position that was specified by
3144 new_index. This may be different then new_index because of the
3145 shifting that occurred after the delete */
3146 r = rotate_index(playlist, new_index);
3148 if (r == 0)
3149 /* First index */
3150 new_index = PLAYLIST_PREPEND;
3151 else if (r == playlist->amount)
3152 /* Append */
3153 new_index = PLAYLIST_INSERT_LAST;
3154 else
3155 /* Calculate index of desired position */
3156 new_index = (r+playlist->first_index)%playlist->amount;
3158 result = add_track_to_playlist(playlist, filename, new_index, queue,
3159 -1);
3161 if (result != -1)
3163 if (current)
3165 /* Moved the current track */
3166 switch (new_index)
3168 case PLAYLIST_PREPEND:
3169 playlist->index = playlist->first_index;
3170 break;
3171 case PLAYLIST_INSERT_LAST:
3172 playlist->index = playlist->first_index - 1;
3173 if (playlist->index < 0)
3174 playlist->index += playlist->amount;
3175 break;
3176 default:
3177 playlist->index = new_index;
3178 break;
3182 if ((audio_status() & AUDIO_STATUS_PLAY) && playlist->started)
3183 audio_flush_and_reload_tracks();
3187 #ifdef HAVE_DIRCACHE
3188 queue_post(&playlist_queue, PLAYLIST_LOAD_POINTERS, 0);
3189 #endif
3191 return result;
3194 /* shuffle currently playing playlist */
3195 int playlist_randomise(struct playlist_info* playlist, unsigned int seed,
3196 bool start_current)
3198 int result;
3200 if (!playlist)
3201 playlist = &current_playlist;
3203 check_control(playlist);
3205 result = randomise_playlist(playlist, seed, start_current, true);
3207 if (result != -1 && (audio_status() & AUDIO_STATUS_PLAY) &&
3208 playlist->started)
3209 audio_flush_and_reload_tracks();
3211 return result;
3214 /* sort currently playing playlist */
3215 int playlist_sort(struct playlist_info* playlist, bool start_current)
3217 int result;
3219 if (!playlist)
3220 playlist = &current_playlist;
3222 check_control(playlist);
3224 result = sort_playlist(playlist, start_current, true);
3226 if (result != -1 && (audio_status() & AUDIO_STATUS_PLAY) &&
3227 playlist->started)
3228 audio_flush_and_reload_tracks();
3230 return result;
3233 /* returns true if playlist has been modified */
3234 bool playlist_modified(const struct playlist_info* playlist)
3236 if (!playlist)
3237 playlist = &current_playlist;
3239 if (playlist->shuffle_modified ||
3240 playlist->deleted ||
3241 playlist->num_inserted_tracks > 0)
3242 return true;
3244 return false;
3247 /* returns index of first track in playlist */
3248 int playlist_get_first_index(const struct playlist_info* playlist)
3250 if (!playlist)
3251 playlist = &current_playlist;
3253 return playlist->first_index;
3256 /* returns shuffle seed of playlist */
3257 int playlist_get_seed(const struct playlist_info* playlist)
3259 if (!playlist)
3260 playlist = &current_playlist;
3262 return playlist->seed;
3265 /* returns number of tracks in playlist (includes queued/inserted tracks) */
3266 int playlist_amount_ex(const struct playlist_info* playlist)
3268 if (!playlist)
3269 playlist = &current_playlist;
3271 return playlist->amount;
3274 /* returns full path of playlist (minus extension) */
3275 char *playlist_name(const struct playlist_info* playlist, char *buf,
3276 int buf_size)
3278 char *sep;
3280 if (!playlist)
3281 playlist = &current_playlist;
3283 snprintf(buf, buf_size, "%s", playlist->filename+playlist->dirlen);
3285 if (!buf[0])
3286 return NULL;
3288 /* Remove extension */
3289 sep = strrchr(buf, '.');
3290 if (sep)
3291 *sep = 0;
3293 return buf;
3296 /* returns the playlist filename */
3297 char *playlist_get_name(const struct playlist_info* playlist, char *buf,
3298 int buf_size)
3300 if (!playlist)
3301 playlist = &current_playlist;
3303 snprintf(buf, buf_size, "%s", playlist->filename);
3305 if (!buf[0])
3306 return NULL;
3308 return buf;
3311 /* Fills info structure with information about track at specified index.
3312 Returns 0 on success and -1 on failure */
3313 int playlist_get_track_info(struct playlist_info* playlist, int index,
3314 struct playlist_track_info* info)
3316 int seek;
3317 bool control_file;
3319 if (!playlist)
3320 playlist = &current_playlist;
3322 if (index < 0 || index >= playlist->amount)
3323 return -1;
3325 control_file = playlist->indices[index] & PLAYLIST_INSERT_TYPE_MASK;
3326 seek = playlist->indices[index] & PLAYLIST_SEEK_MASK;
3328 if (get_filename(playlist, index, seek, control_file, info->filename,
3329 sizeof(info->filename)) < 0)
3330 return -1;
3332 info->attr = 0;
3334 if (control_file)
3336 if (playlist->indices[index] & PLAYLIST_QUEUE_MASK)
3337 info->attr |= PLAYLIST_ATTR_QUEUED;
3338 else
3339 info->attr |= PLAYLIST_ATTR_INSERTED;
3343 if (playlist->indices[index] & PLAYLIST_SKIPPED)
3344 info->attr |= PLAYLIST_ATTR_SKIPPED;
3346 info->index = index;
3347 info->display_index = rotate_index(playlist, index) + 1;
3349 return 0;
3352 /* save the current dynamic playlist to specified file */
3353 int playlist_save(struct playlist_info* playlist, char *filename)
3355 int fd;
3356 int i, index;
3357 int count = 0;
3358 char path[MAX_PATH+1];
3359 char tmp_buf[MAX_PATH+1];
3360 int result = 0;
3361 bool overwrite_current = false;
3362 int* index_buf = NULL;
3364 if (!playlist)
3365 playlist = &current_playlist;
3367 if (playlist->amount <= 0)
3368 return -1;
3370 /* use current working directory as base for pathname */
3371 if (format_track_path(path, filename, sizeof(tmp_buf),
3372 strlen(filename)+1, getcwd(NULL, -1)) < 0)
3373 return -1;
3375 if (!strncmp(playlist->filename, path, strlen(path)))
3377 /* Attempting to overwrite current playlist file.*/
3379 if (playlist->buffer_size < (int)(playlist->amount * sizeof(int)))
3381 /* not enough buffer space to store updated indices */
3382 gui_syncsplash(HZ*2, str(LANG_PLAYLIST_ACCESS_ERROR));
3383 return -1;
3386 /* in_ram buffer is unused for m3u files so we'll use for storing
3387 updated indices */
3388 index_buf = (int*)playlist->buffer;
3390 /* use temporary pathname */
3391 snprintf(path, sizeof(path), "%s_temp", playlist->filename);
3392 overwrite_current = true;
3395 fd = open(path, O_CREAT|O_WRONLY|O_TRUNC);
3396 if (fd < 0)
3398 gui_syncsplash(HZ*2, str(LANG_PLAYLIST_ACCESS_ERROR));
3399 return -1;
3402 display_playlist_count(count, str(LANG_PLAYLIST_SAVE_COUNT));
3404 cpu_boost(true);
3406 index = playlist->first_index;
3407 for (i=0; i<playlist->amount; i++)
3409 bool control_file;
3410 bool queue;
3411 int seek;
3413 /* user abort */
3414 if (action_userabort(TIMEOUT_NOBLOCK))
3416 result = -1;
3417 break;
3420 control_file = playlist->indices[index] & PLAYLIST_INSERT_TYPE_MASK;
3421 queue = playlist->indices[index] & PLAYLIST_QUEUE_MASK;
3422 seek = playlist->indices[index] & PLAYLIST_SEEK_MASK;
3424 /* Don't save queued files */
3425 if (!queue)
3427 if (get_filename(playlist, index, seek, control_file, tmp_buf,
3428 MAX_PATH+1) < 0)
3430 result = -1;
3431 break;
3434 if (overwrite_current)
3435 index_buf[count] = lseek(fd, 0, SEEK_CUR);
3437 if (fdprintf(fd, "%s\n", tmp_buf) < 0)
3439 gui_syncsplash(HZ*2, str(LANG_PLAYLIST_ACCESS_ERROR));
3440 result = -1;
3441 break;
3444 count++;
3446 if ((count % PLAYLIST_DISPLAY_COUNT) == 0)
3447 display_playlist_count(count, str(LANG_PLAYLIST_SAVE_COUNT));
3449 yield();
3452 index = (index+1)%playlist->amount;
3455 display_playlist_count(count, str(LANG_PLAYLIST_SAVE_COUNT));
3457 close(fd);
3459 if (overwrite_current && result >= 0)
3461 result = -1;
3463 mutex_lock(&playlist->control_mutex);
3465 /* Replace the current playlist with the new one and update indices */
3466 close(playlist->fd);
3467 if (remove(playlist->filename) >= 0)
3469 if (rename(path, playlist->filename) >= 0)
3471 playlist->fd = open(playlist->filename, O_RDONLY);
3472 if (playlist->fd >= 0)
3474 index = playlist->first_index;
3475 for (i=0, count=0; i<playlist->amount; i++)
3477 if (!(playlist->indices[index] & PLAYLIST_QUEUE_MASK))
3479 playlist->indices[index] = index_buf[count];
3480 count++;
3482 index = (index+1)%playlist->amount;
3485 /* we need to recreate control because inserted tracks are
3486 now part of the playlist and shuffle has been
3487 invalidated */
3488 result = recreate_control(playlist);
3493 mutex_unlock(&playlist->control_mutex);
3497 cpu_boost(false);
3499 return result;
3503 * Search specified directory for tracks and notify via callback. May be
3504 * called recursively.
3506 int playlist_directory_tracksearch(const char* dirname, bool recurse,
3507 int (*callback)(char*, void*),
3508 void* context)
3510 char buf[MAX_PATH+1];
3511 int result = 0;
3512 int num_files = 0;
3513 int i;
3514 struct entry *files;
3515 struct tree_context* tc = tree_get_context();
3516 int old_dirfilter = *(tc->dirfilter);
3518 if (!callback)
3519 return -1;
3521 /* use the tree browser dircache to load files */
3522 *(tc->dirfilter) = SHOW_ALL;
3524 if (ft_load(tc, dirname) < 0)
3526 gui_syncsplash(HZ*2, str(LANG_PLAYLIST_DIRECTORY_ACCESS_ERROR));
3527 *(tc->dirfilter) = old_dirfilter;
3528 return -1;
3531 files = (struct entry*) tc->dircache;
3532 num_files = tc->filesindir;
3534 /* we've overwritten the dircache so tree browser will need to be
3535 reloaded */
3536 reload_directory();
3538 for (i=0; i<num_files; i++)
3540 /* user abort */
3541 if (action_userabort(TIMEOUT_NOBLOCK))
3543 result = -1;
3544 break;
3547 if (files[i].attr & ATTR_DIRECTORY)
3549 if (recurse)
3551 /* recursively add directories */
3552 snprintf(buf, sizeof(buf), "%s/%s", dirname, files[i].name);
3553 result = playlist_directory_tracksearch(buf, recurse,
3554 callback, context);
3555 if (result < 0)
3556 break;
3558 /* we now need to reload our current directory */
3559 if(ft_load(tc, dirname) < 0)
3561 result = -1;
3562 break;
3565 files = (struct entry*) tc->dircache;
3566 num_files = tc->filesindir;
3567 if (!num_files)
3569 result = -1;
3570 break;
3573 else
3574 continue;
3576 else if ((files[i].attr & FILE_ATTR_MASK) == FILE_ATTR_AUDIO)
3578 snprintf(buf, sizeof(buf), "%s/%s", dirname, files[i].name);
3580 if (callback(buf, context) != 0)
3582 result = -1;
3583 break;
3586 /* let the other threads work */
3587 yield();
3591 /* restore dirfilter */
3592 *(tc->dirfilter) = old_dirfilter;
3594 return result;